From 78b004a947643eafd8722bcdd7348fc12ee01bec Mon Sep 17 00:00:00 2001 From: shiny-code-bot Date: Sun, 27 Sep 2026 13:51:36 -0400 Subject: [PATCH 1/3] refactor: delete retired approval detection and routes --- contracts/agent-operator-contract.json | 59 +- control_plane/agent_operator_contract.py | 18 - .../change_impact_decision_digest.py | 150 - control_plane/change_impact_generated.py | 166 - control_plane/change_impact_matching.py | 63 - control_plane/change_impact_policy_audit.py | 125 - control_plane/change_impact_service.py | 767 - .../contracts/advisory_check_projection.py | 4 + .../contracts/engineering_review_decision.py | 36 +- .../contracts/governance_projection.py | 6 +- .../manager_preview_approval_projection.py | 118 - .../contracts/ordinary_agent_snapshot.py | 8 +- control_plane/contracts/owner_acceptance.py | 6 +- control_plane/contracts/product_owner.py | 24 + .../contracts/repository_evidence.py | 219 + ...nge_impact.py => retired_change_impact.py} | 278 +- ...udit.py => retired_change_impact_audit.py} | 0 ...ng.py => retired_change_impact_binding.py} | 0 ...py => retired_manager_preview_approval.py} | 61 +- .../engineering_review_decision_service.py | 51 +- control_plane/engineering_review_service.py | 55 +- control_plane/governance_projection.py | 28 +- control_plane/http_app.py | 253 +- control_plane/http_routes/__init__.py | 40 +- control_plane/http_routes/change_impact.py | 343 - .../engineering_review_decision.py | 12 +- .../http_routes/engineering_review_run.py | 6 +- control_plane/http_routes/generic_web.py | 37 +- .../http_routes/governance_projection.py | 18 +- control_plane/http_routes/product_owner.py | 567 - control_plane/manager_preview_approval.py | 520 - ...manager_preview_approval_github_webhook.py | 959 - .../manager_preview_approval_projection.py | 460 - control_plane/merge_admission_live.py | 22 +- control_plane/odoo_preview_apply_http.py | 3 - .../ordinary_agent_landing_evidence.py | 8 +- .../ordinary_agent_landing_reader.py | 20 +- control_plane/product_owner_service.py | 751 - control_plane/product_review_status.py | 4 +- ...mpact_github.py => repository_evidence.py} | 124 +- control_plane/service_bootstrap.py | 8 +- control_plane/storage/filesystem.py | 14 +- control_plane/storage/postgres.py | 16 +- control_plane/tenant_admission_controller.py | 4 +- .../trusted_maintenance_github_webhook.py | 83 + docs/README.md | 9 +- docs/change-impact-policy.md | 282 +- docs/engineering-review-decisions.md | 64 +- docs/engineering-review-runs.md | 4 +- docs/merge-admission.md | 4 +- docs/operations.md | 96 +- docs/product-owner-policy.md | 164 +- docs/records.md | 153 +- docs/release-review.md | 6 +- frontend/generated/openapi-canonical.json | 29394 +++++++--------- frontend/generated/openapi-ui.json | 96 +- frontend/src/generated/openapi.ts/index.ts | 2 +- .../src/generated/openapi.ts/types.gen.ts | 22 +- .../retired-approval/manager-event.json | 58 + tests/support/ordinary_agent_qualification.py | 2 +- tests/support/repository_evidence.py | 38 +- tests/support/retired_approval_history.py | 129 + tests/test_agent_operator_contract.py | 19 +- tests/test_change_impact.py | 370 - tests/test_change_impact_coverage.py | 111 - tests/test_change_impact_decision_digest.py | 353 - tests/test_change_impact_generated.py | 244 - tests/test_change_impact_http.py | 543 - tests/test_change_impact_policy_audit.py | 205 - tests/test_change_impact_policy_selection.py | 84 - tests/test_change_impact_rename.py | 119 - tests/test_change_impact_v2.py | 254 - tests/test_engineering_review_decision.py | 55 +- tests/test_governance_projection.py | 8 +- tests/test_governance_projection_http.py | 6 +- tests/test_http_app_browser_mutation.py | 1 - tests/test_http_app_driver_odoo.py | 47 +- tests/test_manager_preview_approval.py | 814 - ...manager_preview_approval_github_webhook.py | 1105 - .../test_merge_admission_evidence_failures.py | 27 +- tests/test_merge_admission_live.py | 14 +- tests/test_merge_readiness.py | 6 +- tests/test_ordinary_agent_landing_evidence.py | 8 +- tests/test_ordinary_agent_landing_storage.py | 18 +- tests/test_postgres_integration.py | 14 +- tests/test_preview_lifecycle_cleanup.py | 2 +- tests/test_product_owner_http.py | 363 - tests/test_product_owner_policy.py | 674 - tests/test_product_promotion_http.py | 29 +- ..._github.py => test_repository_evidence.py} | 60 +- ...s.py => test_retired_approval_bindings.py} | 0 tests/test_retired_approval_routes.py | 101 + tests/test_schema_migration.py | 21 - 93 files changed, 14348 insertions(+), 28364 deletions(-) delete mode 100644 control_plane/change_impact_decision_digest.py delete mode 100644 control_plane/change_impact_generated.py delete mode 100644 control_plane/change_impact_matching.py delete mode 100644 control_plane/change_impact_policy_audit.py delete mode 100644 control_plane/change_impact_service.py delete mode 100644 control_plane/contracts/manager_preview_approval_projection.py create mode 100644 control_plane/contracts/repository_evidence.py rename control_plane/contracts/{change_impact.py => retired_change_impact.py} (57%) rename control_plane/contracts/{change_impact_audit.py => retired_change_impact_audit.py} (100%) rename control_plane/contracts/{change_impact_binding.py => retired_change_impact_binding.py} (100%) rename control_plane/contracts/{manager_preview_approval.py => retired_manager_preview_approval.py} (88%) delete mode 100644 control_plane/http_routes/change_impact.py delete mode 100644 control_plane/http_routes/product_owner.py delete mode 100644 control_plane/manager_preview_approval.py delete mode 100644 control_plane/manager_preview_approval_github_webhook.py delete mode 100644 control_plane/manager_preview_approval_projection.py delete mode 100644 control_plane/product_owner_service.py rename control_plane/{change_impact_github.py => repository_evidence.py} (82%) create mode 100644 tests/fixtures/retired-approval/manager-event.json create mode 100644 tests/support/retired_approval_history.py delete mode 100644 tests/test_change_impact.py delete mode 100644 tests/test_change_impact_coverage.py delete mode 100644 tests/test_change_impact_decision_digest.py delete mode 100644 tests/test_change_impact_generated.py delete mode 100644 tests/test_change_impact_http.py delete mode 100644 tests/test_change_impact_policy_audit.py delete mode 100644 tests/test_change_impact_policy_selection.py delete mode 100644 tests/test_change_impact_rename.py delete mode 100644 tests/test_change_impact_v2.py delete mode 100644 tests/test_manager_preview_approval.py delete mode 100644 tests/test_manager_preview_approval_github_webhook.py delete mode 100644 tests/test_product_owner_http.py delete mode 100644 tests/test_product_owner_policy.py rename tests/{test_change_impact_github.py => test_repository_evidence.py} (88%) rename tests/{test_change_impact_binding_versions.py => test_retired_approval_bindings.py} (100%) create mode 100644 tests/test_retired_approval_routes.py diff --git a/contracts/agent-operator-contract.json b/contracts/agent-operator-contract.json index 4f5a332cc..346581241 100644 --- a/contracts/agent-operator-contract.json +++ b/contracts/agent-operator-contract.json @@ -319,61 +319,6 @@ "service_api" ] }, - { - "idempotency": "none", - "identity_dependencies": [ - "read_write_identity" - ], - "method": "POST", - "modes": [ - "dry-run", - "apply" - ], - "operation_id": "apply_change_impact_policy", - "path": "/v1/change-impact/policies/apply", - "purpose": "Dry-run or apply a reviewed change-impact policy revision.", - "response_statuses": [ - "202", - "403", - "409", - "503" - ], - "reviewed_evidence": [ - "reviewed_dry_run", - "expected_policy_digest" - ], - "schema_fingerprint_sha256": "92ffceffe42ea64c829e71e69eb405fa452fb34fb7e54f469c6d5606c8eb458a", - "supported_surfaces": [ - "agent_helper", - "operator_ui", - "service_api" - ] - }, - { - "idempotency": "none", - "identity_dependencies": [ - "read_identity" - ], - "method": "GET", - "modes": [ - "read" - ], - "operation_id": "read_change_impact_policy", - "path": "/v1/change-impact/policy", - "purpose": "Read active change-impact policy revision evidence.", - "response_statuses": [ - "200", - "403", - "409", - "503" - ], - "reviewed_evidence": [], - "schema_fingerprint_sha256": "7626b4b974ffe0b822553785fed15acfd2acff2c89bddb7d27c2788e6336b0c7", - "supported_surfaces": [ - "agent_helper", - "read_only_service" - ] - }, { "idempotency": "mutate", "identity_dependencies": [ @@ -614,8 +559,8 @@ }, "normalization_version": 1, "provenance": { - "source_commit_sha": "b4772244da64efd7b3864db35a6c40e1cecc7340" + "source_commit_sha": "af13626023b4937ee3dda0b2c6249186383aca33" }, "schema_version": 1, - "semantic_digest_sha256": "5e609c581e439dae63c22ef8ee2727cb717e8f9cd97ded25d03a2fa4550cbfb6" + "semantic_digest_sha256": "e51c35801697fef784121a41b032d9d6872d03917e2b09b221709c72a62f68e8" } diff --git a/control_plane/agent_operator_contract.py b/control_plane/agent_operator_contract.py index 50bf96eb6..1b01d960a 100644 --- a/control_plane/agent_operator_contract.py +++ b/control_plane/agent_operator_contract.py @@ -179,24 +179,6 @@ class OperationSpec: "apply", ("reviewed_dry_run",), ), - OperationSpec( - "POST", - "/v1/change-impact/policies/apply", - "Dry-run or apply a reviewed change-impact policy revision.", - ("agent_helper", "operator_ui", "service_api"), - ("dry-run", "apply"), - "none", - ("reviewed_dry_run", "expected_policy_digest"), - ), - OperationSpec( - "GET", - "/v1/change-impact/policy", - "Read active change-impact policy revision evidence.", - ("agent_helper", "read_only_service"), - ("read",), - "none", - (), - ), OperationSpec( "POST", "/v1/work-graph/merge-train/controller/run-once", diff --git a/control_plane/change_impact_decision_digest.py b/control_plane/change_impact_decision_digest.py deleted file mode 100644 index 21938c9e0..000000000 --- a/control_plane/change_impact_decision_digest.py +++ /dev/null @@ -1,150 +0,0 @@ -"""Canonical v2 authority projection; diagnostic provenance never licenses replay.""" - -from collections.abc import Mapping -from dataclasses import asdict -import hashlib -import json - -from control_plane.change_impact_generated import ( - GeneratedBoundary, - product_authority_mode, - rule_floors, -) -from control_plane.change_impact_matching import select_change_impact_path_rule -from control_plane.contracts.change_impact import ( - ChangeImpactEvaluation, - ChangeImpactPolicyRecord, - ChangeImpactProductScope, - ChangeImpactRepositoryEvidence, - ChangeImpactStoredEvidence, -) - - -CHANGE_IMPACT_DECISION_FIELDS = { - "schema_version", - "status", - "reason_code", - "classification_model", - "engineering_review_tier", - "required_engineering_review_count", - "owner_impact", - "affected_products", - "production_affecting_products", - "coverage", - "governance_impact", -} - - -def validate_v2_file_evidence(evidence: ChangeImpactRepositoryEvidence) -> None: - paths = {file.path for file in evidence.changed_files} - for file in evidence.changed_files: - if file.change_kind == "unknown": - raise ValueError("v2 requires known file change kinds") - if file.change_kind == "renamed" and file.previous_path not in paths: - raise ValueError("v2 requires an explicit rename origin in the changed paths") - - -def _scopes(scopes: tuple[ChangeImpactProductScope, ...]) -> list[tuple[str, str, str, str]]: - return sorted( - { - (scope.product, scope.system, scope.owner_action, scope.owner_environment) - for scope in scopes - } - ) - - -def _generated(boundary: GeneratedBoundary | None) -> object: - if boundary is None: - return None - return { - "component": boundary.component, - "affected_products": _scopes(boundary.affected_products), - "declared_none": boundary.declared_none, - "floors": asdict(boundary.floors), - "generators": [ - { - "component": leaf.component, - "affected_products": _scopes(leaf.affected_products), - "declared_none": leaf.declared_none, - "own_floors": asdict(leaf.own_floors), - "ancestor_floors": [asdict(ancestor) for ancestor in leaf.ancestor_floors], - } - for leaf in sorted(boundary.generators, key=lambda leaf: leaf.component) - ], - } - - -def build_change_impact_decision_digest( - *, - repository_evidence: ChangeImpactRepositoryEvidence, - policy: ChangeImpactPolicyRecord, - stored_evidence: tuple[ChangeImpactStoredEvidence, ...], - generated_boundaries: Mapping[str, GeneratedBoundary], - evaluation: ChangeImpactEvaluation, -) -> str: - """Hash complete successful authority, excluding policy/evidence IDs and prose. - - The covered output surface is the effective decision. Matched-evidence - decorations, order and multiplicity remain independently visible provenance. - """ - if policy.classification_model != "v2" or evaluation.status != "success": - raise ValueError("scoped identity requires successful v2 classification") - validate_v2_file_evidence(repository_evidence) - paths: list[dict[str, object]] = [] - for file in sorted(repository_evidence.changed_files, key=lambda changed: changed.path): - selection = select_change_impact_path_rule(path=file.path, rules=policy.component_rules) - if selection is None: - raise ValueError("scoped identity requires complete path authority") - rule = selection.winner.rule - paths.append( - { - "path": file.path, - "change_kind": file.change_kind, - "previous_path": file.previous_path, - "winner": { - "schema_version": rule.schema_version, - "component": rule.component, - "prefix": selection.winner.prefix, - "authority": product_authority_mode(rule).value, - "affected_products": _scopes(rule.affected_products), - "floors": asdict(rule_floors(rule)), - }, - "ancestors": [ - { - "component": match.rule.component, - "prefix": match.prefix, - "floors": asdict(rule_floors(match.rule)), - } - for match in selection.ancestors - ], - "generated": _generated(generated_boundaries.get(rule.component)), - } - ) - payload = { - "domain": "launchplane.change-impact-decision.v2", - "policy_schema_version": policy.schema_version, - "target": repository_evidence.target.model_dump(mode="json"), - "base": repository_evidence.base.model_dump(mode="json") - if repository_evidence.base is not None - else None, - "paths": paths, - "stored_evidence": sorted( - { - ( - evidence.kind, - evidence.confidence, - evidence.component, - tuple(_scopes(evidence.affected_products)), - ) - for evidence in stored_evidence - } - ), - "decision": evaluation.model_dump( - mode="json", - include=CHANGE_IMPACT_DECISION_FIELDS, - ), - } - # JSON defaults to ASCII escapes; str.encode defaults to UTF-8. - return hashlib.sha256( - json.dumps(payload, sort_keys=True, separators=(",", ":")).encode() - ).hexdigest() diff --git a/control_plane/change_impact_generated.py b/control_plane/change_impact_generated.py deleted file mode 100644 index d5ea87a18..000000000 --- a/control_plane/change_impact_generated.py +++ /dev/null @@ -1,166 +0,0 @@ -"""Bounded generator attribution; this never verifies generated byte freshness. - -Only the winning path rule expands its generators. Ancestors contribute their -own floors, never their products or generator edges. Frozen leaf contributions -preserve the authority behind an aggregate for the scoped decision digest. -""" - -from collections.abc import Mapping -from dataclasses import dataclass -from enum import StrEnum -from types import MappingProxyType - -from control_plane.contracts.change_impact import ( - ChangeImpactComponentRule, - ChangeImpactPolicyRecord, - ChangeImpactProductScope, -) - - -class ProductAuthorityMode(StrEnum): - DIRECT = "direct" - DECLARED_NONE = "declared_none" - GENERATED = "generated" - LEGACY_EMPTY = "legacy_empty" - - -def product_authority_mode(rule: ChangeImpactComponentRule) -> ProductAuthorityMode: - if rule.generated_by is not None: - return ProductAuthorityMode.GENERATED - if rule.affected_products: - return ProductAuthorityMode.DIRECT - if rule.product_impact == "declared_none": - return ProductAuthorityMode.DECLARED_NONE - return ProductAuthorityMode.LEGACY_EMPTY - - -@dataclass(frozen=True, slots=True) -class ChangeImpactFloors: - sensitive: bool = False - governance: bool = False - production: bool = False - - -def rule_floors(rule: ChangeImpactComponentRule) -> ChangeImpactFloors: - return ChangeImpactFloors( - sensitive=rule.review_tier == "sensitive" or bool(rule.governance_impact), - governance=bool(rule.governance_impact), - production=bool(rule.production_affecting), - ) - - -def join_floors(*floors: ChangeImpactFloors) -> ChangeImpactFloors: - return ChangeImpactFloors( - sensitive=any(floor.sensitive for floor in floors), - governance=any(floor.governance for floor in floors), - production=any(floor.production for floor in floors), - ) - - -@dataclass(frozen=True, slots=True) -class GeneratorAncestorFloor: - component: str - prefix: str - floors: ChangeImpactFloors - - -@dataclass(frozen=True, slots=True) -class GeneratorContribution: - component: str - affected_products: tuple[ChangeImpactProductScope, ...] - declared_none: bool - own_floors: ChangeImpactFloors - ancestor_floors: tuple[GeneratorAncestorFloor, ...] - - -@dataclass(frozen=True, slots=True) -class GeneratedBoundary: - component: str - generators: tuple[GeneratorContribution, ...] - affected_products: tuple[ChangeImpactProductScope, ...] - declared_none: bool - floors: ChangeImpactFloors - - -def validate_generated_boundaries(policy: ChangeImpactPolicyRecord) -> None: - """Reject nonterminal graphs before any v2 policy can be used.""" - rules = {rule.component: rule for rule in policy.component_rules} - edge_count = 0 - for rule in policy.component_rules: - if rule.generated_by is None: - continue - edge_count += len(rule.generated_by) - if edge_count > 400: - raise ValueError("generated boundary policy exceeds 400 edges") - for component in rule.generated_by: - if component == rule.component: - raise ValueError("generated boundary cannot reference itself") - leaf = rules.get(component) - if leaf is None: - raise ValueError("generated boundary references missing component") - mode = product_authority_mode(leaf) - if mode == ProductAuthorityMode.GENERATED: - raise ValueError("generated boundary requires terminal components") - if mode == ProductAuthorityMode.LEGACY_EMPTY: - raise ValueError("generated boundary cannot use implicit empty authority") - - -def resolve_generated_boundaries( - policy: ChangeImpactPolicyRecord, -) -> Mapping[str, GeneratedBoundary]: - """Resolve validated depth-1 mappings once, independently of changed paths.""" - if policy.classification_model != "v2": - return MappingProxyType({}) - validate_generated_boundaries(policy) - rules = {rule.component: rule for rule in policy.component_rules} - resolved: dict[str, GeneratedBoundary] = {} - for rule in policy.component_rules: - if rule.generated_by is None: - continue - contributions: list[GeneratorContribution] = [] - floors: list[ChangeImpactFloors] = [] - products: set[ChangeImpactProductScope] = set() - for component in rule.generated_by: - leaf = rules[component] - ancestors = tuple( - sorted( - { - GeneratorAncestorFloor(ancestor.component, prefix, rule_floors(ancestor)) - for ancestor in policy.component_rules - if ancestor.component != component - for prefix in ancestor.path_prefixes - if any(path.startswith(f"{prefix}/") for path in leaf.path_prefixes) - }, - key=lambda ancestor: (ancestor.component, ancestor.prefix), - ) - ) - own_floors = rule_floors(leaf) - floors.extend((own_floors, *(ancestor.floors for ancestor in ancestors))) - products.update(leaf.affected_products) - contributions.append( - GeneratorContribution( - component=component, - affected_products=leaf.affected_products, - declared_none=leaf.product_impact == "declared_none", - own_floors=own_floors, - ancestor_floors=ancestors, - ) - ) - resolved[rule.component] = GeneratedBoundary( - component=rule.component, - generators=tuple(contributions), - affected_products=tuple( - sorted( - products, - key=lambda scope: ( - scope.product, - scope.system, - scope.owner_action, - scope.owner_environment, - ), - ) - ), - declared_none=all(leaf.declared_none for leaf in contributions), - floors=join_floors(*floors), - ) - return MappingProxyType(resolved) diff --git a/control_plane/change_impact_matching.py b/control_plane/change_impact_matching.py deleted file mode 100644 index 7d0a97189..000000000 --- a/control_plane/change_impact_matching.py +++ /dev/null @@ -1,63 +0,0 @@ -"""Deterministic v2 path selection without changing legacy policy matching.""" - -from dataclasses import dataclass - -from control_plane.change_impact_generated import validate_generated_boundaries -from control_plane.contracts.change_impact import ( - ChangeImpactComponentRule, - ChangeImpactPolicyRecord, -) - - -@dataclass(frozen=True, slots=True) -class ChangeImpactRuleMatch: - rule: ChangeImpactComponentRule - prefix: str - - -@dataclass(frozen=True, slots=True) -class ChangeImpactPathSelection: - winner: ChangeImpactRuleMatch - ancestors: tuple[ChangeImpactRuleMatch, ...] - - -def validate_change_impact_v2_policy(policy: ChangeImpactPolicyRecord) -> None: - """Reject ambiguous or implicit v2 authority at apply and evaluation boundaries.""" - if policy.classification_model != "v2": - return - prefixes: set[str] = set() - for rule in policy.component_rules: - if ( - not rule.affected_products - and rule.product_impact != "declared_none" - and rule.generated_by is None - ): - raise ValueError("v2 rules require explicit products, declared_none, or generated_by") - for prefix in rule.path_prefixes: - if not prefix or "." in prefix.split("/") or "" in prefix.split("/"): - raise ValueError("v2 prefixes must be canonical non-root paths") - if prefix in prefixes: - raise ValueError("v2 policy has equal-specificity component ambiguity") - prefixes.add(prefix) - validate_generated_boundaries(policy) - - -def select_change_impact_path_rule( - *, path: str, rules: tuple[ChangeImpactComponentRule, ...] -) -> ChangeImpactPathSelection | None: - """Select one product authority and retain every distinct ancestor rule as a floor.""" - matches: list[ChangeImpactRuleMatch] = [] - for rule in rules: - prefixes = tuple( - prefix - for prefix in rule.path_prefixes - if path == prefix or path.startswith(f"{prefix}/") - ) - if prefixes: - matches.append(ChangeImpactRuleMatch(rule, max(prefixes, key=len))) - if not matches: - return None - matches.sort(key=lambda match: (len(match.prefix), match.prefix, match.rule.component)) - if len(matches) > 1 and len(matches[-1].prefix) == len(matches[-2].prefix): - raise ValueError("v2 path has equal-specificity component ambiguity") - return ChangeImpactPathSelection(winner=matches[-1], ancestors=tuple(matches[:-1])) diff --git a/control_plane/change_impact_policy_audit.py b/control_plane/change_impact_policy_audit.py deleted file mode 100644 index 8a14ebd85..000000000 --- a/control_plane/change_impact_policy_audit.py +++ /dev/null @@ -1,125 +0,0 @@ -"""Audited policy administration by the write route's existing principals.""" - -from datetime import datetime, timezone -from typing import Literal, Protocol, cast - -from control_plane.change_impact_service import ( - ChangeImpactApplyMode, - ChangeImpactPolicyApplyResult, - ChangeImpactPolicyReadStore, - _validate_policy_append, - validate_change_impact_policy_apply_request, -) -from control_plane.contracts.change_impact import ChangeImpactPolicyRecord -from control_plane.contracts.change_impact_audit import ( - ChangeImpactPolicyAuditRecord, - ChangeImpactPolicyAuditedWriteResult, - ChangeImpactPolicyWorkflowIdentity, -) -from control_plane.service_auth import ( - GitHubActionsIdentity, - LaunchplaneIdentity, - LocalAdminIdentity, - LocalOperatorIdentity, -) - - -class ChangeImpactPolicyAttributionError(ValueError): - """The authenticated principal cannot supply policy-writer provenance.""" - - -class ChangeImpactPolicyAuditedStore(ChangeImpactPolicyReadStore, Protocol): - def compare_and_write_change_impact_policy_record_with_audit( - self, - record: ChangeImpactPolicyRecord, - *, - audit: ChangeImpactPolicyAuditRecord, - expected_current_record_id: str, - expected_current_policy_digest: str, - ) -> ChangeImpactPolicyAuditedWriteResult: ... - - -def require_change_impact_policy_audited_store(store: object) -> ChangeImpactPolicyAuditedStore: - if not all( - callable(getattr(store, method, None)) - for method in ( - "list_change_impact_policy_records", - "compare_and_write_change_impact_policy_record_with_audit", - ) - ): - raise TypeError("Audited change-impact policy writes require database storage.") - return cast(ChangeImpactPolicyAuditedStore, store) - - -def derive_change_impact_policy_audit( - *, - identity: LaunchplaneIdentity, - record: ChangeImpactPolicyRecord, - trace_id: str, -) -> ChangeImpactPolicyAuditRecord: - workflow = None - kind: Literal["local_admin", "local_operator", "github_actions"] - if isinstance(identity, LocalAdminIdentity): - kind = "local_admin" - elif isinstance(identity, LocalOperatorIdentity): - kind = "local_operator" - elif isinstance(identity, GitHubActionsIdentity): - kind = "github_actions" - workflow = ChangeImpactPolicyWorkflowIdentity( - repository=identity.repository, - repository_id=identity.repository_id, - repository_owner_id=identity.repository_owner_id, - workflow_ref=identity.workflow_ref, - job_workflow_ref=identity.job_workflow_ref, - ref=identity.ref, - sha=identity.sha, - ) - else: - raise ChangeImpactPolicyAttributionError( - "Caller identity cannot be attributed to a policy administration write." - ) - return ChangeImpactPolicyAuditRecord( - record_id=record.record_id, - policy_digest=record.policy_digest, - actor_kind=kind, - actor_subject=identity.subject, - workflow_identity=workflow, - trace_id=trace_id, - recorded_at=datetime.now(timezone.utc), - ) - - -def apply_change_impact_policy_with_audit( - *, - store: ChangeImpactPolicyAuditedStore, - record: ChangeImpactPolicyRecord, - audit: ChangeImpactPolicyAuditRecord, - expected_current_record_id: str = "", - expected_current_policy_digest: str = "", - mode: ChangeImpactApplyMode = "apply", -) -> ChangeImpactPolicyApplyResult: - validate_change_impact_policy_apply_request(record=record, mode=mode) - replay = _validate_policy_append( - records=store.list_change_impact_policy_records(repository_id=record.repository_id), - record=record, - expected_current_record_id=expected_current_record_id, - expected_current_policy_digest=expected_current_policy_digest, - ) - if mode == "dry_run": - return ChangeImpactPolicyApplyResult( - status="would_replay" if replay else "would_apply", - record=record, - attribution_status="not_applied", - ) - result = store.compare_and_write_change_impact_policy_record_with_audit( - record, - audit=audit, - expected_current_record_id=expected_current_record_id, - expected_current_policy_digest=expected_current_policy_digest, - ) - return ChangeImpactPolicyApplyResult( - status="replayed" if result.status == "replayed" else "applied", - record=record, - audit=result.audit, - attribution_status="attributed" if result.audit is not None else "legacy_unattributed", - ) diff --git a/control_plane/change_impact_service.py b/control_plane/change_impact_service.py deleted file mode 100644 index 3bc8a432f..000000000 --- a/control_plane/change_impact_service.py +++ /dev/null @@ -1,767 +0,0 @@ -from __future__ import annotations - -from dataclasses import dataclass -from datetime import datetime, timezone -from typing import Literal, Protocol, TypeVar, cast - -from pydantic import BaseModel, ConfigDict, Field - -from control_plane.change_impact_decision_digest import ( - build_change_impact_decision_digest, - validate_v2_file_evidence, -) -from control_plane.change_impact_generated import ( - ChangeImpactFloors, - GeneratedBoundary, - join_floors, - resolve_generated_boundaries, - rule_floors, -) -from control_plane.change_impact_matching import ( - select_change_impact_path_rule, - validate_change_impact_v2_policy, -) -from control_plane.contracts.change_impact import ( - ChangeImpactAffectedProduct, - ChangeImpactChangedFileEvidence, - ChangeImpactComponentRule, - ChangeImpactCoverage, - ChangeImpactEvaluation, - ChangeImpactMatchedEvidence, - ChangeImpactPolicyRecord, - ChangeImpactProductScope, - ChangeImpactRepositoryEvidence, - ChangeImpactStoredEvidence, - ChangeImpactTarget, - ChangeImpactTargetReference, -) -from control_plane.contracts.change_impact_audit import ( - ChangeImpactAttributionStatus, - ChangeImpactPolicyAuditRecord, -) - - -class ChangeImpactPolicyConflictError(ValueError): - """Raised on a change-impact policy write race or conflicting replay.""" - - -class ChangeImpactPolicySequenceError(ValueError): - """Raised when change-impact policy revision history is stale or non-linear.""" - - -ChangeImpactApplyMode = Literal["dry_run", "apply"] -ChangeImpactApplyStatus = Literal["would_apply", "would_replay", "applied", "replayed"] - - -class ChangeImpactPolicyReadStore(Protocol): - def list_change_impact_policy_records( - self, - *, - repository_id: str = "", - repository: str = "", - status: str = "", - limit: int | None = None, - ) -> tuple[ChangeImpactPolicyRecord, ...]: ... - - -class ChangeImpactRepositoryEvidenceProvider(Protocol): - def resolve( - self, - target: ChangeImpactTargetReference, - ) -> ChangeImpactRepositoryEvidence: ... - - -class ChangeImpactStoredEvidenceReadStore(Protocol): - def list_change_impact_stored_evidence( - self, - *, - repository_id: str, - pull_request_number: int, - head_sha: str, - tree_sha: str, - ) -> tuple[ChangeImpactStoredEvidence, ...]: ... - - -class ChangeImpactPolicyStore(ChangeImpactPolicyReadStore, Protocol): - def compare_and_write_change_impact_policy_record( - self, - record: ChangeImpactPolicyRecord, - *, - expected_current_record_id: str, - expected_current_policy_digest: str, - ) -> Literal["written", "replayed"]: ... - - -class ChangeImpactPolicyApplyResult(BaseModel): - model_config = ConfigDict(extra="forbid", frozen=True) - - schema_version: int = Field(default=1, ge=1) - status: ChangeImpactApplyStatus - record: ChangeImpactPolicyRecord - audit: ChangeImpactPolicyAuditRecord | None = None - attribution_status: ChangeImpactAttributionStatus = "attribution_unavailable" - - -class ChangeImpactPolicyReadModel(BaseModel): - model_config = ConfigDict(extra="forbid", frozen=True) - - schema_version: int = Field(default=1, ge=1) - repository_id: str - current_policy: ChangeImpactPolicyRecord | None = None - policy_history_count: int = Field(default=0, ge=0) - audit: ChangeImpactPolicyAuditRecord | None = None - attribution_status: ChangeImpactAttributionStatus = "attribution_unavailable" - - -class ChangeImpactPolicyAuditReadStore(Protocol): - def read_change_impact_policy_audit( - self, record_id: str - ) -> ChangeImpactPolicyAuditRecord | None: ... - - -RecordT = TypeVar("RecordT", bound=ChangeImpactPolicyRecord) - - -@dataclass(frozen=True, slots=True) -class ChangeImpactTrustedCallerBinding: - repository: str - repository_id: str - repository_owner_id: str - head_sha: str - ref: str = "" - - -def validate_change_impact_policy_apply_request( - *, record: ChangeImpactPolicyRecord, mode: ChangeImpactApplyMode -) -> None: - """Share policy validation and the staged activation gate across apply services.""" - try: - validate_change_impact_v2_policy(record) - except ValueError as error: - raise ChangeImpactPolicySequenceError(str(error)) from error - if mode != "dry_run" and record.classification_model == "v2": - raise ChangeImpactPolicySequenceError( - "v2 classification apply is unavailable pending rollout qualification" - ) - - -def apply_change_impact_policy( - *, - store: ChangeImpactPolicyStore, - record: ChangeImpactPolicyRecord, - expected_current_record_id: str = "", - expected_current_policy_digest: str = "", - mode: ChangeImpactApplyMode = "apply", -) -> ChangeImpactPolicyApplyResult: - validate_change_impact_policy_apply_request(record=record, mode=mode) - replay = _validate_policy_append( - records=store.list_change_impact_policy_records(repository_id=record.repository_id), - record=record, - expected_current_record_id=expected_current_record_id, - expected_current_policy_digest=expected_current_policy_digest, - ) - if mode == "dry_run": - return ChangeImpactPolicyApplyResult( - status="would_replay" if replay else "would_apply", - record=record, - ) - write_status = store.compare_and_write_change_impact_policy_record( - record, - expected_current_record_id=expected_current_record_id, - expected_current_policy_digest=expected_current_policy_digest, - ) - return ChangeImpactPolicyApplyResult( - status="replayed" if write_status == "replayed" else "applied", - record=record, - ) - - -def get_change_impact_policy_read_model( - *, - store: ChangeImpactPolicyReadStore, - repository_id: str, - audit_store: ChangeImpactPolicyAuditReadStore | None = None, -) -> ChangeImpactPolicyReadModel: - records = store.list_change_impact_policy_records(repository_id=repository_id) - current = _safe_current_policy(records) - audit = ( - audit_store.read_change_impact_policy_audit(current.record_id) - if audit_store is not None and current is not None - else None - ) - return ChangeImpactPolicyReadModel( - repository_id=repository_id.strip(), - current_policy=current, - policy_history_count=len(records), - audit=audit, - attribution_status=( - "attributed" - if audit is not None - else "legacy_unattributed" - if audit_store is not None and current is not None - else "attribution_unavailable" - ), - ) - - -def evaluate_change_impact( - *, - repository_evidence: ChangeImpactRepositoryEvidence, - policies: tuple[ChangeImpactPolicyRecord, ...], - stored_evidence: tuple[ChangeImpactStoredEvidence, ...] = (), - trusted_caller_binding: ChangeImpactTrustedCallerBinding | None = None, - evaluated_at: str = "", -) -> ChangeImpactEvaluation: - target = repository_evidence.target - if trusted_caller_binding is not None and not _binding_matches_repository( - binding=trusted_caller_binding, - target=target, - ): - return _unknown_evaluation( - target=target, - status="unknown", - reason_code="caller_repository_mismatch", - unknown_evidence=( - "trusted caller repository identity does not match provider evidence", - ), - ) - if trusted_caller_binding is not None and not _binding_matches_target( - binding=trusted_caller_binding, - repository_evidence=repository_evidence, - ): - return _unknown_evaluation( - target=target, - status="stale_head", - reason_code="stale_head", - unknown_evidence=("trusted caller head does not match current pull request head",), - ) - - try: - policy = _current_policy( - policies, - repository_id=target.repository_id, - evaluated_at=_evaluation_timestamp(evaluated_at), - ) - except ValueError: - return _unknown_evaluation( - target=target, - status="unknown", - reason_code="policy_history_invalid", - unknown_evidence=("change-impact policy history is ambiguous",), - ) - if policy is None: - return _unknown_evaluation( - target=target, - status="unknown", - reason_code="policy_unavailable", - unknown_evidence=("no active change-impact policy for repository",), - ) - if not _policy_matches_target(policy=policy, target=target): - return _unknown_evaluation( - target=target, - status="unknown", - reason_code="policy_target_mismatch", - policy=policy, - unknown_evidence=("active change-impact policy does not match repository identity",), - ) - try: - validate_change_impact_v2_policy(policy) - generated_boundaries = resolve_generated_boundaries(policy) - except ValueError: - return _unknown_evaluation( - target=target, - status="unknown", - reason_code="policy_rules_invalid", - policy=policy, - unknown_evidence=("v2 policy rules have ambiguous or implicit authority",), - ) - - if policy.classification_model == "v2": - try: - validate_v2_file_evidence(repository_evidence) - except ValueError: - return _unknown_evaluation( - target=target, - status="unknown", - reason_code="repository_evidence_incomplete", - policy=policy, - unknown_evidence=("v2 file change kinds and rename origins must be complete",), - ) - - matched: list[ChangeImpactMatchedEvidence] = [] - affected_products: set[ChangeImpactProductScope] = set() - production_affecting_products: set[ChangeImpactProductScope] = set() - sensitive = False - unknown: list[str] = [] - unmatched_paths: set[str] = set() - production_components: set[str] = set() - governance_impact = False - - rules_by_component = {rule.component: rule for rule in policy.component_rules} - for changed_file in repository_evidence.changed_files: - path_matches = _matching_rules(changed_file=changed_file, rules=policy.component_rules) - floor_rules: tuple[ChangeImpactComponentRule, ...] = () - if policy.classification_model == "v2": - selection = select_change_impact_path_rule( - path=changed_file.path, rules=policy.component_rules - ) - path_matches = (selection.winner.rule,) if selection else () - floor_rules = tuple(match.rule for match in selection.ancestors) if selection else () - if not path_matches: - unmatched_paths.add(changed_file.path) - continue - for rule in floor_rules: - matched.append( - _file_match(changed_file=changed_file, rule=rule, review_floor_only=True) - ) - ancestor_floors = join_floors(*(rule_floors(rule) for rule in floor_rules)) - for rule in path_matches: - boundary = generated_boundaries.get(rule.component) - scopes = boundary.affected_products if boundary else rule.affected_products - floors = join_floors( - rule_floors(rule), - ancestor_floors, - boundary.floors if boundary else ChangeImpactFloors(), - ) - matched.append(_file_match(changed_file=changed_file, rule=rule, boundary=boundary)) - affected_products.update(scopes) - if floors.production: - production_affecting_products.update(scopes) - production_components.add(rule.component) - sensitive = sensitive or floors.sensitive - governance_impact = governance_impact or floors.governance - - dependency_components = { - evidence.component - for evidence in stored_evidence - if evidence.kind == "dependency" and evidence.confidence == "known" - } - matched_components = { - evidence.component - for evidence in matched - if evidence.component and not evidence.review_floor_only - } - - for evidence in stored_evidence: - if evidence.confidence != "known": - unknown.append(f"{evidence.confidence} stored evidence for {evidence.component}") - continue - if evidence.component not in rules_by_component: - unknown.append(f"stored evidence references unknown component {evidence.component}") - continue - if evidence.component not in matched_components: - unknown.append( - f"stored evidence references component not matched by changed files {evidence.component}" - ) - continue - rule = rules_by_component[evidence.component] - if ( - evidence.kind == "reviewer" - and evidence.affected_products - and evidence.component not in dependency_components - ): - unknown.append( - f"reviewer evidence lacks trusted dependency evidence for {evidence.component}" - ) - continue - affected_products.update(evidence.affected_products) - affected_products.update(rule.affected_products) - if rule.production_affecting or evidence.component in production_components: - production_affecting_products.update(evidence.affected_products) - production_affecting_products.update(rule.affected_products) - matched.append( - _stored_evidence_match( - evidence=evidence, rule=rule, boundary=generated_boundaries.get(rule.component) - ) - ) - if rule.review_tier == "sensitive": - sensitive = True - - sorted_unmatched_paths = sorted(unmatched_paths) - coverage = ChangeImpactCoverage( - state="incomplete" if unmatched_paths else "complete", - unmatched_path_count=len(unmatched_paths), - unmatched_path_samples=tuple(path[:256] for path in sorted_unmatched_paths[:20]), - truncated=len(unmatched_paths) > 20 - or any(len(path) > 256 for path in sorted_unmatched_paths[:20]), - ) - if unknown or unmatched_paths: - reason_code = "ambiguous_or_missing_evidence" if unknown else "policy_coverage_incomplete" - unknown.extend( - f"no policy rule matched changed path {path}" - for path in coverage.unmatched_path_samples - ) - if coverage.truncated: - unknown.append( - f"unmatched path diagnostics truncated; {coverage.unmatched_path_count} total paths" - ) - return _unknown_evaluation( - target=target, - status="unknown", - reason_code=reason_code, - policy=policy, - matched_evidence=tuple(matched), - affected_products=_affected_products(affected_products), - unknown_evidence=tuple(dict.fromkeys(unknown)), - coverage=coverage, - governance_impact=governance_impact if policy.classification_model == "v2" else None, - ) - - affected = _affected_products(affected_products) - review_tier: Literal["routine", "sensitive"] = "sensitive" if sensitive else "routine" - evaluation = ChangeImpactEvaluation( - status="success", - reason_code="change_impact_classified", - target=target, - policy_record_id=policy.record_id, - policy_revision=policy.policy_revision, - policy_digest=policy.policy_digest, - engineering_review_tier=review_tier, - required_engineering_review_count=2 if review_tier == "sensitive" else 1, - owner_impact="required" if affected else "not_required", - affected_products=affected, - production_affecting_products=tuple(sorted(production_affecting_products, key=_scope_key)), - matched_evidence=tuple(matched), - unknown_evidence=(), - coverage=coverage, - classification_model=policy.classification_model, - governance_impact=governance_impact if policy.classification_model == "v2" else None, - ) - if policy.classification_model == "v2": - digest = build_change_impact_decision_digest( - repository_evidence=repository_evidence, - policy=policy, - stored_evidence=stored_evidence, - generated_boundaries=generated_boundaries, - evaluation=evaluation, - ) - return evaluation.model_copy( - update={"binding_hash_version": 2, "change_impact_decision_digest": digest} - ) - return evaluation - - -def require_change_impact_policy_store(store: object) -> ChangeImpactPolicyStore: - required_methods = ( - "list_change_impact_policy_records", - "compare_and_write_change_impact_policy_record", - ) - missing_methods = [ - method_name - for method_name in required_methods - if not callable(getattr(store, method_name, None)) - ] - if missing_methods: - raise TypeError( - "Launchplane record store does not support change-impact policy writes: " - + ", ".join(missing_methods) - ) - return cast(ChangeImpactPolicyStore, store) - - -def require_change_impact_policy_read_store(store: object) -> ChangeImpactPolicyReadStore: - if not callable(getattr(store, "list_change_impact_policy_records", None)): - raise TypeError("Launchplane record store does not support change-impact policy reads.") - return cast(ChangeImpactPolicyReadStore, store) - - -def load_change_impact_stored_evidence( - *, - store: object, - target: ChangeImpactTarget, -) -> tuple[ChangeImpactStoredEvidence, ...]: - if not callable(getattr(store, "list_change_impact_stored_evidence", None)): - return () - reader = cast(ChangeImpactStoredEvidenceReadStore, store) - evidence = reader.list_change_impact_stored_evidence( - repository_id=target.repository_id, - pull_request_number=target.pull_request_number, - head_sha=target.head_sha, - tree_sha=target.tree_sha, - ) - if not isinstance(evidence, tuple) or not all( - isinstance(item, ChangeImpactStoredEvidence) for item in evidence - ): - raise TypeError("Launchplane stored change-impact evidence has an invalid contract.") - return evidence - - -def _validate_policy_append( - *, - records: tuple[ChangeImpactPolicyRecord, ...], - record: ChangeImpactPolicyRecord, - expected_current_record_id: str, - expected_current_policy_digest: str, -) -> bool: - matching = tuple( - existing for existing in records if existing.repository_id == record.repository_id - ) - same_id = tuple(existing for existing in matching if existing.record_id == record.record_id) - if same_id: - if len(same_id) != 1 or same_id[0].policy_digest != record.policy_digest: - raise ChangeImpactPolicyConflictError( - "change-impact policy record id conflicts with history" - ) - if same_id[0].status != record.status: - raise ChangeImpactPolicyConflictError( - "change-impact policy replay status conflicts with history" - ) - return True - current_records = tuple(existing for existing in matching if existing.status == "active") - if len(current_records) > 1: - raise ChangeImpactPolicySequenceError( - "change-impact policy history has multiple active revisions" - ) - current = current_records[0] if current_records else None - if record.status != "active": - raise ChangeImpactPolicySequenceError( - "incoming change-impact policy revision must be active" - ) - if record.effective_at > _evaluation_timestamp(""): - raise ChangeImpactPolicySequenceError( - "incoming change-impact policy effective_at cannot be in the future" - ) - if current is None: - if record.policy_revision != 1: - raise ChangeImpactPolicySequenceError("initial change-impact policy revision must be 1") - if expected_current_record_id.strip() or expected_current_policy_digest.strip(): - raise ChangeImpactPolicyConflictError( - "initial change-impact policy expected tip must be absent" - ) - return False - if ( - expected_current_record_id.strip() != current.record_id - or expected_current_policy_digest.strip().lower() != current.policy_digest - ): - raise ChangeImpactPolicyConflictError("expected current change-impact policy tip is stale") - if record.policy_revision != current.policy_revision + 1: - raise ChangeImpactPolicySequenceError("next change-impact policy revision is not linear") - if record.supersedes_record_id != current.record_id: - raise ChangeImpactPolicySequenceError( - "next change-impact policy must supersede current record" - ) - if record.effective_at < current.effective_at: - raise ChangeImpactPolicySequenceError( - "next change-impact policy effective_at cannot precede current revision" - ) - return False - - -def _safe_current_policy( - records: tuple[ChangeImpactPolicyRecord, ...], -) -> ChangeImpactPolicyRecord | None: - try: - return _current_policy(records, repository_id="", evaluated_at=_evaluation_timestamp("")) - except ValueError: - return None - - -def _current_policy( - records: tuple[ChangeImpactPolicyRecord, ...], - *, - repository_id: str, - evaluated_at: str, -) -> ChangeImpactPolicyRecord | None: - matching = tuple( - record - for record in records - if (not repository_id or record.repository_id == repository_id) - and record.effective_at <= evaluated_at - ) - active = tuple(record for record in matching if record.status == "active") - if len(active) > 1: - raise ValueError("multiple active change-impact policies") - if active: - return active[0] - return None - - -def _policy_matches_target( - *, - policy: ChangeImpactPolicyRecord, - target: object, -) -> bool: - return bool( - getattr(target, "repository_id") == policy.repository_id - and getattr(target, "repository_owner_id") == policy.repository_owner_id - and getattr(target, "repository") == policy.repository - ) - - -def _matching_rules( - *, - changed_file: ChangeImpactChangedFileEvidence, - rules: tuple[ChangeImpactComponentRule, ...], -) -> tuple[ChangeImpactComponentRule, ...]: - return tuple( - rule - for rule in rules - if any( - changed_file.path == prefix or changed_file.path.startswith(f"{prefix}/") - for prefix in rule.path_prefixes - ) - ) - - -def _file_match( - *, - changed_file: ChangeImpactChangedFileEvidence, - rule: ChangeImpactComponentRule, - review_floor_only: bool = False, - boundary: GeneratedBoundary | None = None, -) -> ChangeImpactMatchedEvidence: - floors = join_floors(rule_floors(rule), boundary.floors if boundary else ChangeImpactFloors()) - return ChangeImpactMatchedEvidence( - source="server_diff", - path=changed_file.path, - component=rule.component, - rule_id=rule.rule_id, - review_tier="sensitive" if floors.sensitive else "routine", - production_affecting=True if floors.production else None, - affected_products=( - () - if review_floor_only - else boundary.affected_products - if boundary - else rule.affected_products - ), - review_floor_only=review_floor_only, - governance_impact=True if floors.governance else None, - reason=rule.reason, - ) - - -def _stored_evidence_match( - *, - evidence: ChangeImpactStoredEvidence, - rule: ChangeImpactComponentRule, - boundary: GeneratedBoundary | None = None, -) -> ChangeImpactMatchedEvidence: - floors = join_floors(rule_floors(rule), boundary.floors if boundary else ChangeImpactFloors()) - return ChangeImpactMatchedEvidence( - source=( - "launchplane_dependency" if evidence.kind == "dependency" else "launchplane_reviewer" - ), - component=evidence.component, - rule_id=rule.rule_id, - review_tier="sensitive" if floors.sensitive else "routine", - governance_impact=True if floors.governance else None, - production_affecting=True if floors.production else None, - affected_products=tuple( - sorted( - set(boundary.affected_products if boundary else rule.affected_products) - | set(evidence.affected_products), - key=_scope_key, - ) - ), - reason=evidence.reason, - ) - - -def _affected_products( - scopes: set[ChangeImpactProductScope] | tuple[ChangeImpactAffectedProduct, ...], -) -> tuple[ChangeImpactAffectedProduct, ...]: - if not scopes: - return () - affected: list[ChangeImpactAffectedProduct] = [] - for scope in sorted(scopes, key=_scope_key): - if isinstance(scope, ChangeImpactAffectedProduct): - affected.append(scope) - else: - affected.append( - ChangeImpactAffectedProduct( - product=scope.product, - system=scope.system, - owner_action=scope.owner_action, - owner_environment=scope.owner_environment, - ) - ) - return tuple(affected) - - -def _scope_key( - scope: ChangeImpactProductScope | ChangeImpactAffectedProduct, -) -> tuple[str, str, str, str]: - return scope.product, scope.system, scope.owner_action, scope.owner_environment - - -def _unknown_evaluation( - *, - target: ChangeImpactTarget, - status: Literal["unknown", "stale_head", "stale_policy"], - reason_code: str, - policy: ChangeImpactPolicyRecord | None = None, - matched_evidence: tuple[ChangeImpactMatchedEvidence, ...] = (), - affected_products: tuple[ChangeImpactAffectedProduct, ...] = (), - unknown_evidence: tuple[str, ...], - coverage: ChangeImpactCoverage | None = None, - governance_impact: bool | None = None, -) -> ChangeImpactEvaluation: - return ChangeImpactEvaluation( - status=status, - reason_code=reason_code, - target=target, - policy_record_id=policy.record_id if policy is not None else "", - policy_revision=policy.policy_revision if policy is not None else None, - policy_digest=policy.policy_digest if policy is not None else "", - engineering_review_tier=policy.default_unknown_review_tier - if policy is not None - else "sensitive", - required_engineering_review_count=2, - owner_impact="unknown" if not affected_products else "required", - affected_products=affected_products, - matched_evidence=matched_evidence, - unknown_evidence=unknown_evidence, - coverage=coverage, - classification_model=policy.classification_model if policy is not None else None, - governance_impact=governance_impact, - ) - - -def _binding_matches_repository( - *, - binding: ChangeImpactTrustedCallerBinding, - target: ChangeImpactTarget, -) -> bool: - return bool( - binding.repository.strip().lower() == target.repository - and binding.repository_id.strip() == target.repository_id - and binding.repository_owner_id.strip() == target.repository_owner_id - ) - - -def _binding_matches_target( - *, - binding: ChangeImpactTrustedCallerBinding, - repository_evidence: ChangeImpactRepositoryEvidence, -) -> bool: - target = repository_evidence.target - expected_sha = target.head_sha - if binding.ref.strip() == f"refs/pull/{target.pull_request_number}/merge": - expected_sha = repository_evidence.merge_commit_sha - return bool(expected_sha and binding.head_sha.strip().lower() == expected_sha) - - -def _evaluation_timestamp(value: str) -> str: - if not value.strip(): - return ( - datetime.now(timezone.utc) - .replace(microsecond=0) - .isoformat() - .replace( - "+00:00", - "Z", - ) - ) - parsed = datetime.fromisoformat(value.strip().replace("Z", "+00:00")) - if parsed.tzinfo is None: - raise ValueError("evaluated_at must include a timezone") - return ( - parsed.astimezone(timezone.utc) - .replace(microsecond=0) - .isoformat() - .replace( - "+00:00", - "Z", - ) - ) diff --git a/control_plane/contracts/advisory_check_projection.py b/control_plane/contracts/advisory_check_projection.py index 28baf975f..2090e1414 100644 --- a/control_plane/contracts/advisory_check_projection.py +++ b/control_plane/contracts/advisory_check_projection.py @@ -94,3 +94,7 @@ class AdvisoryCheckProjectionResult(BaseModel): def is_launchplane_projected_check(name: str) -> bool: return name.strip().casefold() in LAUNCHPLANE_PROJECTED_CHECK_NAMES + + +# Compatibility only: never an approval requirement. +MANAGER_PREVIEW_APPROVAL_CHECK_NAME = "manager-preview-approval" diff --git a/control_plane/contracts/engineering_review_decision.py b/control_plane/contracts/engineering_review_decision.py index 286d2b3fc..1175e1a97 100644 --- a/control_plane/contracts/engineering_review_decision.py +++ b/control_plane/contracts/engineering_review_decision.py @@ -7,7 +7,7 @@ from pydantic import BaseModel, ConfigDict, Field, model_validator -from control_plane.contracts.change_impact_binding import ( +from control_plane.contracts.retired_change_impact_binding import ( ChangeImpactBindingHashVersion, change_impact_bound_payload, validate_change_impact_binding, @@ -16,11 +16,13 @@ from control_plane.contracts.advisory_check_projection import ( ENGINEERING_REVIEW_CHECK_NAME, ) -from control_plane.contracts.change_impact import ( +from control_plane.contracts.retired_change_impact import ( ChangeImpactDecisionStatus, ChangeImpactReviewTier, - ChangeImpactTarget, - ChangeImpactTargetReference, +) +from control_plane.contracts.repository_evidence import ( + RepositoryTarget, + RepositoryTargetReference, ) @@ -40,7 +42,7 @@ class EngineeringReviewDecisionRequest(BaseModel): model_config = ConfigDict(extra="forbid", frozen=True) schema_version: int = Field(default=1, ge=1) - target: ChangeImpactTargetReference + target: RepositoryTargetReference work_request_id: str = Field(min_length=1, max_length=128) @model_validator(mode="after") @@ -63,16 +65,16 @@ class EngineeringReviewDecisionRecord(BaseModel): enforcement_effect: Literal["none"] = "none" status: EngineeringReviewDecisionStatus reason_code: str - target: ChangeImpactTarget + target: RepositoryTarget work_request_id: str work_request_lifecycle_id: str - change_impact_status: ChangeImpactDecisionStatus + change_impact_status: ChangeImpactDecisionStatus | None = None change_impact_policy_record_id: str = "" change_impact_policy_revision: int | None = Field(default=None, ge=1) change_impact_policy_digest: str = "" binding_hash_version: ChangeImpactBindingHashVersion | None = None change_impact_decision_digest: str | None = None - engineering_review_tier: ChangeImpactReviewTier + engineering_review_tier: ChangeImpactReviewTier | None = None required_review_count: Literal[1, 2] authority_id: str = "" authority_digest: str = "" @@ -83,8 +85,24 @@ class EngineeringReviewDecisionRecord(BaseModel): @model_validator(mode="after") def _validate_record(self) -> "EngineeringReviewDecisionRecord": - if self.schema_version != 1: + if self.schema_version not in {1, 2}: raise ValueError("Unsupported engineering review decision schema version.") + if self.schema_version == 1: + if self.change_impact_status is None or self.engineering_review_tier is None: + raise ValueError("Historical engineering decisions require their classification.") + elif ( + self.change_impact_status is not None + or self.engineering_review_tier is not None + or self.change_impact_policy_record_id + or self.change_impact_policy_revision is not None + or self.change_impact_policy_digest + or self.binding_hash_version is not None + or self.change_impact_decision_digest is not None + or self.required_review_count != 2 + ): + raise ValueError( + "Current engineering decisions require two reviews without retired classification." + ) if not self.reason_code or self.reason_code.strip() != self.reason_code: raise ValueError("Engineering review decision requires canonical reason_code.") if not self.work_request_id or self.work_request_id.strip() != self.work_request_id: diff --git a/control_plane/contracts/governance_projection.py b/control_plane/contracts/governance_projection.py index 44dfba5f1..653cecafd 100644 --- a/control_plane/contracts/governance_projection.py +++ b/control_plane/contracts/governance_projection.py @@ -4,7 +4,9 @@ from pydantic import BaseModel, ConfigDict, model_validator -from control_plane.contracts.change_impact import ChangeImpactTarget +from control_plane.contracts.repository_evidence import ( + RepositoryTarget, +) from control_plane.contracts.merge_admission_record import ( MergeAdmissionRecord, MergeLandingOutcomeRecord, @@ -177,7 +179,7 @@ class GovernanceProjection(BaseModel): mode: Literal["read_only_projection"] = "read_only_projection" authoritative: Literal[False] = False authorizes: tuple[str, ...] = () - target: ChangeImpactTarget + target: RepositoryTarget owner_judgment: GovernanceOwnerJudgmentFacet | None = None merge_readiness: GovernanceMergeReadinessFacet merge_admission: GovernanceMergeAdmissionFacet diff --git a/control_plane/contracts/manager_preview_approval_projection.py b/control_plane/contracts/manager_preview_approval_projection.py deleted file mode 100644 index 031a26572..000000000 --- a/control_plane/contracts/manager_preview_approval_projection.py +++ /dev/null @@ -1,118 +0,0 @@ -from __future__ import annotations - -from typing import Literal - -from pydantic import BaseModel, ConfigDict, Field, model_validator - -from control_plane.contracts.manager_preview_approval import ( - ManagerPreviewApprovalDecision, -) - - -MANAGER_PREVIEW_APPROVAL_CHECK_NAME = "manager-preview-approval" -MANAGER_PREVIEW_APPROVAL_COMMENT_MARKER = "" - -ManagerPreviewApprovalCommandAction = Literal[ - "approved", - "changes_requested", - "revoked", -] -ManagerPreviewApprovalCheckState = Literal["pending", "success", "failure", "error"] - - -class ManagerPreviewApprovalCommand(BaseModel): - model_config = ConfigDict(extra="forbid") - - action: ManagerPreviewApprovalCommandAction - fingerprint: str - reason: str = "" - - @model_validator(mode="after") - def _validate_command(self) -> "ManagerPreviewApprovalCommand": - self.fingerprint = self.fingerprint.strip().lower() - self.reason = self.reason.strip() - if len(self.fingerprint) != 64 or any( - character not in "0123456789abcdef" for character in self.fingerprint - ): - raise ValueError("Manager preview approval command requires a SHA-256 fingerprint.") - if self.action in {"changes_requested", "revoked"} and not self.reason: - raise ValueError(f"Manager preview approval action {self.action!r} requires a reason.") - if self.action == "approved" and self.reason: - raise ValueError("Manager preview approval approve command cannot include a reason.") - return self - - -class ManagerPreviewApprovalProjection(BaseModel): - model_config = ConfigDict(extra="forbid") - - schema_version: int = Field(default=1, ge=1) - required: bool - product: str - context: str - repository: str - pr_number: int = Field(ge=1) - pr_url: str - pr_state: str - head_sha: str - preview_id: str = "" - preview_url: str = "" - serving_generation_id: str = "" - artifact_id: str = "" - artifact_image_digest: str = "" - manifest_fingerprint: str = "" - binding_sha256: str = "" - decision: ManagerPreviewApprovalDecision - check_state: ManagerPreviewApprovalCheckState - check_description: str - comment_markdown: str - - @model_validator(mode="after") - def _validate_projection(self) -> "ManagerPreviewApprovalProjection": - if self.schema_version != 1: - raise ValueError("Unsupported manager preview approval projection schema version.") - for field_name in ( - "product", - "context", - "repository", - "pr_url", - "pr_state", - "head_sha", - "check_description", - "comment_markdown", - ): - normalized = str(getattr(self, field_name)).strip() - if not normalized: - raise ValueError(f"Manager preview approval projection requires {field_name}.") - setattr(self, field_name, normalized) - for field_name in ( - "preview_id", - "preview_url", - "serving_generation_id", - "artifact_id", - "artifact_image_digest", - "manifest_fingerprint", - "binding_sha256", - ): - setattr(self, field_name, str(getattr(self, field_name)).strip()) - if len(self.check_description) > 140: - raise ValueError( - "Manager preview approval check description cannot exceed 140 characters." - ) - if self.required and not self.binding_sha256 and self.decision.status != "unavailable": - raise ValueError("Required manager preview approval projection requires a fingerprint.") - return self - - -class ManagerPreviewApprovalReconcileEnvelope(BaseModel): - model_config = ConfigDict(extra="forbid") - - schema_version: int = Field(default=1, ge=1) - repository: str - pr_number: int = Field(ge=1) - - @model_validator(mode="after") - def _validate_request(self) -> "ManagerPreviewApprovalReconcileEnvelope": - self.repository = self.repository.strip() - if not self.repository or self.repository.count("/") != 1: - raise ValueError("Manager preview approval reconciliation requires owner/repository.") - return self diff --git a/control_plane/contracts/ordinary_agent_snapshot.py b/control_plane/contracts/ordinary_agent_snapshot.py index a63e95503..cbc6bfa26 100644 --- a/control_plane/contracts/ordinary_agent_snapshot.py +++ b/control_plane/contracts/ordinary_agent_snapshot.py @@ -7,7 +7,9 @@ from pydantic import Field, field_validator, model_validator from control_plane.contracts.canonical_json import canonical_json_sha256 -from control_plane.contracts.change_impact import ChangeImpactRepositoryEvidence +from control_plane.contracts.repository_evidence import ( + RepositoryEvidence, +) from control_plane.contracts.ordinary_agent import ( OrdinaryAgentPullRequest, OrdinaryAgentTarget, @@ -216,8 +218,8 @@ class OrdinaryAgentLandingEvidence(StrictFrozenModel): repository: str = Field(min_length=3, max_length=512) base_ref: str = Field(min_length=1, max_length=255) base_identity: OrdinaryAgentCommitIdentity - repository_evidence: ChangeImpactRepositoryEvidence - candidate_entry_evidence: tuple[ChangeImpactRepositoryEvidence, ...] = Field( + repository_evidence: RepositoryEvidence + candidate_entry_evidence: tuple[RepositoryEvidence, ...] = Field( min_length=1, max_length=MAX_ORDINARY_LANDING_ENTRIES ) snapshot: MergeTrainDryRunSnapshot diff --git a/control_plane/contracts/owner_acceptance.py b/control_plane/contracts/owner_acceptance.py index 821b2b41a..bc5fa5cae 100644 --- a/control_plane/contracts/owner_acceptance.py +++ b/control_plane/contracts/owner_acceptance.py @@ -9,8 +9,10 @@ from pydantic import BaseModel, ConfigDict, Field, model_validator -from control_plane.contracts.change_impact import ChangeImpactCoverage -from control_plane.contracts.change_impact_binding import ( +from control_plane.contracts.retired_change_impact import ( + ChangeImpactCoverage, +) +from control_plane.contracts.retired_change_impact_binding import ( ChangeImpactBindingHashVersion, change_impact_bound_payload, validate_change_impact_binding, diff --git a/control_plane/contracts/product_owner.py b/control_plane/contracts/product_owner.py index e96989c74..cbe728e59 100644 --- a/control_plane/contracts/product_owner.py +++ b/control_plane/contracts/product_owner.py @@ -794,3 +794,27 @@ def product_owner_scoped_policy_fingerprint( "payload": payload, } ) + + +class ProductOwnerPolicyConflictError(ValueError): + """Raised on an Owner policy write race or conflicting immutable replay.""" + + +class ProductOwnerPolicySequenceError(ValueError): + """Raised when Owner policy revision history is stale or non-linear.""" + + +class ProductOwnerRequirementConflictError(ValueError): + """Raised on an Owner requirement write race or conflicting immutable replay.""" + + +class ProductOwnerRequirementSequenceError(ValueError): + """Raised when Owner requirement revision history is stale or non-linear.""" + + +class ProductOwnerRoutingConflictError(ValueError): + """Raised on an Owner routing write race or conflicting immutable replay.""" + + +class ProductOwnerRoutingSequenceError(ValueError): + """Raised when Owner routing revision history is stale or non-linear.""" diff --git a/control_plane/contracts/repository_evidence.py b/control_plane/contracts/repository_evidence.py new file mode 100644 index 000000000..deb7fbb25 --- /dev/null +++ b/control_plane/contracts/repository_evidence.py @@ -0,0 +1,219 @@ +"""Provider-resolved Git identities and evidence, independent of approval policy.""" + +from __future__ import annotations + +import re +from typing import Literal, Protocol +from pydantic import BaseModel, ConfigDict, Field, model_validator + +RepositoryChangeKind = Literal["added", "modified", "removed", "renamed", "unknown"] +RepositoryAuthorshipResolution = Literal["resolved", "unresolved", "conflicting"] +_GIT_SHA_PATTERN = re.compile(r"^[0-9a-f]{40}(?:[0-9a-f]{24})?$") +_REPOSITORY_PATTERN = re.compile(r"^[^/\s]+/[^/\s]+$") +_GIT_REF_PATTERN = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._\-/]{0,254}$") + + +def _required_token(value: str, field_name: str) -> str: + normalized = value.strip() + if not normalized: + raise ValueError(f"{field_name} must be non-empty") + return normalized + + +def _normalize_decimal_id(value: str, field_name: str) -> str: + normalized = _required_token(value, field_name) + if not normalized.isdecimal() or int(normalized) < 1: + raise ValueError(f"{field_name} must be a positive decimal identity") + return str(int(normalized)) + + +def _normalize_repository(value: str, field_name: str) -> str: + normalized = _required_token(value, field_name).lower() + if _REPOSITORY_PATTERN.fullmatch(normalized) is None: + raise ValueError(f"{field_name} must be owner/name") + return normalized + + +def _normalize_git_sha(value: str, field_name: str) -> str: + normalized = _required_token(value, field_name).lower() + if _GIT_SHA_PATTERN.fullmatch(normalized) is None: + raise ValueError(f"{field_name} must be a Git SHA") + return normalized + + +def _normalize_path(value: str, field_name: str) -> str: + normalized = _required_token(value, field_name).lstrip("/") + if ".." in normalized.split("/"): + raise ValueError(f"{field_name} cannot contain '..'") + return normalized + + +class RepositoryTarget(BaseModel): + model_config = ConfigDict(extra="forbid", frozen=True) + + schema_version: int = Field(default=1, ge=1) + repository_id: str + repository_owner_id: str + repository: str + pull_request_number: int = Field(ge=1) + head_sha: str + tree_sha: str + + @model_validator(mode="after") + def _validate_target(self) -> "RepositoryTarget": + if self.schema_version != 1: + raise ValueError("Unsupported repository target schema version.") + object.__setattr__( + self, + "repository_id", + _normalize_decimal_id(self.repository_id, "repository_id"), + ) + object.__setattr__( + self, + "repository_owner_id", + _normalize_decimal_id(self.repository_owner_id, "repository_owner_id"), + ) + object.__setattr__( + self, + "repository", + _normalize_repository(self.repository, "repository"), + ) + object.__setattr__(self, "head_sha", _normalize_git_sha(self.head_sha, "head_sha")) + object.__setattr__(self, "tree_sha", _normalize_git_sha(self.tree_sha, "tree_sha")) + return self + + +class RepositoryChangedFileEvidence(BaseModel): + model_config = ConfigDict(extra="forbid", frozen=True) + + schema_version: int = Field(default=1, ge=1) + path: str + change_kind: RepositoryChangeKind = "unknown" + previous_path: str | None = None + source: Literal["server_diff"] = "server_diff" + + @model_validator(mode="after") + def _validate_file(self) -> "RepositoryChangedFileEvidence": + if self.schema_version != 1: + raise ValueError("Unsupported repository file evidence schema version.") + object.__setattr__(self, "path", _normalize_path(self.path, "path")) + if self.previous_path is not None: + previous_path = _normalize_path(self.previous_path, "previous_path") + if self.change_kind != "renamed" or previous_path == self.path: + raise ValueError("previous_path requires a rename from a distinct path") + object.__setattr__(self, "previous_path", previous_path) + return self + + +class RepositoryTargetReference(BaseModel): + model_config = ConfigDict(extra="forbid", frozen=True) + + schema_version: int = Field(default=1, ge=1) + repository: str + pull_request_number: int = Field(ge=1) + + @model_validator(mode="after") + def _validate_reference(self) -> "RepositoryTargetReference": + if self.schema_version != 1: + raise ValueError("Unsupported repository target reference schema version.") + object.__setattr__( + self, + "repository", + _normalize_repository(self.repository, "repository"), + ) + return self + + +class RepositoryBaseEvidence(BaseModel): + """Server-resolved base ref and SHA the reviewed change was compared against.""" + + model_config = ConfigDict(extra="forbid", frozen=True) + + schema_version: int = Field(default=1, ge=1) + base_ref: str + base_sha: str + + @model_validator(mode="after") + def _validate_base(self) -> "RepositoryBaseEvidence": + if self.schema_version != 1: + raise ValueError("Unsupported repository base evidence schema version.") + normalized_ref = _required_token(self.base_ref, "base_ref") + if _GIT_REF_PATTERN.fullmatch(normalized_ref) is None: + raise ValueError("base_ref must be a canonical Git ref name") + object.__setattr__(self, "base_ref", normalized_ref) + object.__setattr__(self, "base_sha", _normalize_git_sha(self.base_sha, "base_sha")) + return self + + +class RepositoryAuthorshipEvidence(BaseModel): + """Server-resolved numeric GitHub contributing identities over the reviewed range. + + ``resolution`` is ``resolved`` only when every reviewed commit and the pull + request itself carry a consistent GitHub-linked numeric identity. Missing, + incomplete, or contradictory identity evidence is never repaired here; it is + reported without inferring approval requirements. + """ + + model_config = ConfigDict(extra="forbid", frozen=True) + + schema_version: int = Field(default=1, ge=1) + resolution: RepositoryAuthorshipResolution + contributor_github_ids: tuple[int, ...] = () + commit_count: int = Field(default=0, ge=0) + reason: str = Field(default="", max_length=500) + + @model_validator(mode="after") + def _validate_authorship(self) -> "RepositoryAuthorshipEvidence": + if self.schema_version != 1: + raise ValueError("Unsupported repository authorship evidence schema version.") + for github_id in self.contributor_github_ids: + if github_id < 1: + raise ValueError("contributor_github_ids must be positive numeric GitHub IDs") + object.__setattr__( + self, + "contributor_github_ids", + tuple(sorted(set(self.contributor_github_ids))), + ) + object.__setattr__(self, "reason", self.reason.strip()) + if self.resolution == "resolved" and not self.contributor_github_ids: + raise ValueError("resolved repository authorship requires contributing identities") + if self.resolution != "resolved" and not self.reason: + raise ValueError("unresolved repository authorship requires a reason") + return self + + +class RepositoryEvidence(BaseModel): + model_config = ConfigDict(extra="forbid", frozen=True) + + schema_version: int = Field(default=1, ge=1) + target: RepositoryTarget + merge_commit_sha: str = "" + changed_files: tuple[RepositoryChangedFileEvidence, ...] + base: RepositoryBaseEvidence | None = None + authorship: RepositoryAuthorshipEvidence | None = None + + @model_validator(mode="after") + def _validate_evidence(self) -> "RepositoryEvidence": + if self.schema_version != 1: + raise ValueError("Unsupported repository repository evidence schema version.") + if self.merge_commit_sha: + object.__setattr__( + self, + "merge_commit_sha", + _normalize_git_sha(self.merge_commit_sha, "merge_commit_sha"), + ) + if not self.changed_files: + raise ValueError("repository repository evidence requires changed files") + paths = tuple(file.path for file in self.changed_files) + if len(paths) != len(set(paths)): + raise ValueError("repository repository evidence paths must be unique") + object.__setattr__( + self, + "changed_files", + tuple(sorted(self.changed_files, key=lambda changed_file: changed_file.path)), + ) + return self + + +class RepositoryEvidenceProvider(Protocol): + def resolve(self, target: RepositoryTargetReference) -> RepositoryEvidence: ... diff --git a/control_plane/contracts/change_impact.py b/control_plane/contracts/retired_change_impact.py similarity index 57% rename from control_plane/contracts/change_impact.py rename to control_plane/contracts/retired_change_impact.py index 020db4959..96c8bb2a6 100644 --- a/control_plane/contracts/change_impact.py +++ b/control_plane/contracts/retired_change_impact.py @@ -1,3 +1,5 @@ +"""Historical record compatibility; this module makes no approval decision.""" + from __future__ import annotations from datetime import datetime, timezone @@ -8,15 +10,6 @@ from pydantic import BaseModel, ConfigDict, Field, model_validator -from control_plane.contracts.change_impact_binding import ( - ChangeImpactBindingHashVersion, - validate_change_impact_binding, -) - - -CHANGE_IMPACT_POLICY_READ_ACTION = "change_impact_policy.read" -CHANGE_IMPACT_POLICY_WRITE_ACTION = "change_impact_policy.write" -CHANGE_IMPACT_EVALUATION_READ_ACTION = "change_impact_evaluation.read" ChangeImpactPolicyStatus = Literal["active", "superseded"] ChangeImpactReviewTier = Literal["routine", "sensitive"] @@ -26,9 +19,9 @@ "launchplane_dependency", "launchplane_reviewer", ] -ChangeImpactChangeKind = Literal["added", "modified", "removed", "renamed", "unknown"] +RepositoryChangeKind = Literal["added", "modified", "removed", "renamed", "unknown"] ChangeImpactStoredEvidenceKind = Literal["dependency", "reviewer"] -ChangeImpactAuthorshipResolution = Literal["resolved", "unresolved", "conflicting"] +RepositoryAuthorshipResolution = Literal["resolved", "unresolved", "conflicting"] _GIT_SHA_PATTERN = re.compile(r"^[0-9a-f]{40}(?:[0-9a-f]{24})?$") _SHA256_PATTERN = re.compile(r"^[0-9a-f]{64}$") @@ -290,63 +283,6 @@ def _validate_record(self) -> "ChangeImpactPolicyRecord": return self -class ChangeImpactTarget(BaseModel): - model_config = ConfigDict(extra="forbid", frozen=True) - - schema_version: int = Field(default=1, ge=1) - repository_id: str - repository_owner_id: str - repository: str - pull_request_number: int = Field(ge=1) - head_sha: str - tree_sha: str - - @model_validator(mode="after") - def _validate_target(self) -> "ChangeImpactTarget": - if self.schema_version != 1: - raise ValueError("Unsupported change-impact target schema version.") - object.__setattr__( - self, - "repository_id", - _normalize_decimal_id(self.repository_id, "repository_id"), - ) - object.__setattr__( - self, - "repository_owner_id", - _normalize_decimal_id(self.repository_owner_id, "repository_owner_id"), - ) - object.__setattr__( - self, - "repository", - _normalize_repository(self.repository, "repository"), - ) - object.__setattr__(self, "head_sha", _normalize_git_sha(self.head_sha, "head_sha")) - object.__setattr__(self, "tree_sha", _normalize_git_sha(self.tree_sha, "tree_sha")) - return self - - -class ChangeImpactChangedFileEvidence(BaseModel): - model_config = ConfigDict(extra="forbid", frozen=True) - - schema_version: int = Field(default=1, ge=1) - path: str - change_kind: ChangeImpactChangeKind = "unknown" - previous_path: str | None = None - source: Literal["server_diff"] = "server_diff" - - @model_validator(mode="after") - def _validate_file(self) -> "ChangeImpactChangedFileEvidence": - if self.schema_version != 1: - raise ValueError("Unsupported change-impact file evidence schema version.") - object.__setattr__(self, "path", _normalize_path(self.path, "path")) - if self.previous_path is not None: - previous_path = _normalize_path(self.previous_path, "previous_path") - if self.change_kind != "renamed" or previous_path == self.path: - raise ValueError("previous_path requires a rename from a distinct path") - object.__setattr__(self, "previous_path", previous_path) - return self - - class ChangeImpactStoredEvidence(BaseModel): model_config = ConfigDict(extra="forbid", frozen=True) @@ -373,173 +309,6 @@ def _validate_evidence(self) -> "ChangeImpactStoredEvidence": return self -class ChangeImpactTargetReference(BaseModel): - model_config = ConfigDict(extra="forbid", frozen=True) - - schema_version: int = Field(default=1, ge=1) - repository: str - pull_request_number: int = Field(ge=1) - - @model_validator(mode="after") - def _validate_reference(self) -> "ChangeImpactTargetReference": - if self.schema_version != 1: - raise ValueError("Unsupported change-impact target reference schema version.") - object.__setattr__( - self, - "repository", - _normalize_repository(self.repository, "repository"), - ) - return self - - -class ChangeImpactEvaluationMetadata(BaseModel): - model_config = ConfigDict(extra="forbid", frozen=True) - - schema_version: int = Field(default=1, ge=1) - request_id: str = Field(default="", max_length=200) - reason: str = Field(default="", max_length=1000) - - @model_validator(mode="after") - def _validate_metadata(self) -> "ChangeImpactEvaluationMetadata": - if self.schema_version != 1: - raise ValueError("Unsupported change-impact evaluation metadata schema version.") - object.__setattr__(self, "request_id", self.request_id.strip()) - object.__setattr__(self, "reason", self.reason.strip()) - return self - - -class ChangeImpactEvaluationRequest(BaseModel): - model_config = ConfigDict(extra="forbid", frozen=True) - - schema_version: int = Field(default=1, ge=1) - target: ChangeImpactTargetReference - metadata: ChangeImpactEvaluationMetadata = Field(default_factory=ChangeImpactEvaluationMetadata) - - @model_validator(mode="after") - def _validate_request(self) -> "ChangeImpactEvaluationRequest": - if self.schema_version != 1: - raise ValueError("Unsupported change-impact evaluation request schema version.") - return self - - -class ChangeImpactBaseEvidence(BaseModel): - """Server-resolved base ref and SHA the reviewed change was compared against.""" - - model_config = ConfigDict(extra="forbid", frozen=True) - - schema_version: int = Field(default=1, ge=1) - base_ref: str - base_sha: str - - @model_validator(mode="after") - def _validate_base(self) -> "ChangeImpactBaseEvidence": - if self.schema_version != 1: - raise ValueError("Unsupported change-impact base evidence schema version.") - normalized_ref = _required_token(self.base_ref, "base_ref") - if _GIT_REF_PATTERN.fullmatch(normalized_ref) is None: - raise ValueError("base_ref must be a canonical Git ref name") - object.__setattr__(self, "base_ref", normalized_ref) - object.__setattr__(self, "base_sha", _normalize_git_sha(self.base_sha, "base_sha")) - return self - - -class ChangeImpactAuthorshipEvidence(BaseModel): - """Server-resolved numeric GitHub contributing identities over the reviewed range. - - ``resolution`` is ``resolved`` only when every reviewed commit and the pull - request itself carry a consistent GitHub-linked numeric identity. Missing, - incomplete, or contradictory identity evidence is never repaired here; it is - reported so downstream Owner-review admissibility can fail closed. - """ - - model_config = ConfigDict(extra="forbid", frozen=True) - - schema_version: int = Field(default=1, ge=1) - resolution: ChangeImpactAuthorshipResolution - contributor_github_ids: tuple[int, ...] = () - commit_count: int = Field(default=0, ge=0) - reason: str = Field(default="", max_length=500) - - @model_validator(mode="after") - def _validate_authorship(self) -> "ChangeImpactAuthorshipEvidence": - if self.schema_version != 1: - raise ValueError("Unsupported change-impact authorship evidence schema version.") - for github_id in self.contributor_github_ids: - if github_id < 1: - raise ValueError("contributor_github_ids must be positive numeric GitHub IDs") - object.__setattr__( - self, - "contributor_github_ids", - tuple(sorted(set(self.contributor_github_ids))), - ) - object.__setattr__(self, "reason", self.reason.strip()) - if self.resolution == "resolved" and not self.contributor_github_ids: - raise ValueError("resolved change-impact authorship requires contributing identities") - if self.resolution != "resolved" and not self.reason: - raise ValueError("unresolved change-impact authorship requires a reason") - return self - - -class ChangeImpactRepositoryEvidence(BaseModel): - model_config = ConfigDict(extra="forbid", frozen=True) - - schema_version: int = Field(default=1, ge=1) - target: ChangeImpactTarget - merge_commit_sha: str = "" - changed_files: tuple[ChangeImpactChangedFileEvidence, ...] - base: ChangeImpactBaseEvidence | None = None - authorship: ChangeImpactAuthorshipEvidence | None = None - - @model_validator(mode="after") - def _validate_evidence(self) -> "ChangeImpactRepositoryEvidence": - if self.schema_version != 1: - raise ValueError("Unsupported change-impact repository evidence schema version.") - if self.merge_commit_sha: - object.__setattr__( - self, - "merge_commit_sha", - _normalize_git_sha(self.merge_commit_sha, "merge_commit_sha"), - ) - if not self.changed_files: - raise ValueError("change-impact repository evidence requires changed files") - paths = tuple(file.path for file in self.changed_files) - if len(paths) != len(set(paths)): - raise ValueError("change-impact repository evidence paths must be unique") - object.__setattr__( - self, - "changed_files", - tuple(sorted(self.changed_files, key=lambda changed_file: changed_file.path)), - ) - return self - - -class ChangeImpactMatchedEvidence(BaseModel): - model_config = ConfigDict(extra="forbid", frozen=True) - - schema_version: int = Field(default=1, ge=1) - source: ChangeImpactEvidenceSource - path: str = "" - component: str = "" - rule_id: str = "" - review_tier: ChangeImpactReviewTier | None = None - production_affecting: bool | None = None - affected_products: tuple[ChangeImpactProductScope, ...] = () - review_floor_only: bool = False - governance_impact: bool | None = None - reason: str - - -class ChangeImpactAffectedProduct(BaseModel): - model_config = ConfigDict(extra="forbid", frozen=True) - - schema_version: int = Field(default=1, ge=1) - product: str - system: str - owner_action: str - owner_environment: str - owner_acceptance_required: Literal[True] = True - - class ChangeImpactCoverage(BaseModel): """Bounded path-coverage diagnostics, independent of classification authority.""" @@ -553,37 +322,6 @@ class ChangeImpactCoverage(BaseModel): truncated: bool = False -class ChangeImpactEvaluation(BaseModel): - model_config = ConfigDict(extra="forbid", frozen=True) - - schema_version: int = Field(default=1, ge=1) - status: ChangeImpactDecisionStatus - reason_code: str - target: ChangeImpactTarget - policy_record_id: str = "" - policy_revision: int | None = Field(default=None, ge=1) - policy_digest: str = "" - engineering_review_tier: ChangeImpactReviewTier = "sensitive" - required_engineering_review_count: Literal[1, 2] = 2 - owner_impact: Literal["required", "not_required", "unknown"] = "unknown" - affected_products: tuple[ChangeImpactAffectedProduct, ...] = () - production_affecting_products: tuple[ChangeImpactProductScope, ...] = () - matched_evidence: tuple[ChangeImpactMatchedEvidence, ...] = () - unknown_evidence: tuple[str, ...] = () - coverage: ChangeImpactCoverage | None = None - classification_model: Literal["v2"] | None = None - governance_impact: bool | None = None - binding_hash_version: ChangeImpactBindingHashVersion | None = None - change_impact_decision_digest: str | None = None - - @model_validator(mode="after") - def _validate_binding_identity(self) -> "ChangeImpactEvaluation": - validate_change_impact_binding( - self.binding_hash_version, self.change_impact_decision_digest - ) - return self - - def build_change_impact_component_rule_id(rule: ChangeImpactComponentRule) -> str: payload = rule.model_dump( mode="json", @@ -612,3 +350,11 @@ def change_impact_policy_digest(record: ChangeImpactPolicyRecord) -> str: def _product_scope_key(scope: ChangeImpactProductScope) -> tuple[str, str, str, str]: return scope.product, scope.system, scope.owner_action, scope.owner_environment + + +class ChangeImpactPolicyConflictError(ValueError): + """Raised on a change-impact policy write race or conflicting replay.""" + + +class ChangeImpactPolicySequenceError(ValueError): + """Raised when change-impact policy revision history is stale or non-linear.""" diff --git a/control_plane/contracts/change_impact_audit.py b/control_plane/contracts/retired_change_impact_audit.py similarity index 100% rename from control_plane/contracts/change_impact_audit.py rename to control_plane/contracts/retired_change_impact_audit.py diff --git a/control_plane/contracts/change_impact_binding.py b/control_plane/contracts/retired_change_impact_binding.py similarity index 100% rename from control_plane/contracts/change_impact_binding.py rename to control_plane/contracts/retired_change_impact_binding.py diff --git a/control_plane/contracts/manager_preview_approval.py b/control_plane/contracts/retired_manager_preview_approval.py similarity index 88% rename from control_plane/contracts/manager_preview_approval.py rename to control_plane/contracts/retired_manager_preview_approval.py index a8edec825..d6a26c229 100644 --- a/control_plane/contracts/manager_preview_approval.py +++ b/control_plane/contracts/retired_manager_preview_approval.py @@ -1,3 +1,5 @@ +"""Historical record compatibility; this module makes no approval decision.""" + from __future__ import annotations from datetime import datetime, timezone @@ -28,31 +30,6 @@ "superseded", "invalidated", ] -ManagerPreviewApprovalDecisionStatus = Literal[ - "pending", - "approved", - "changes_requested", - "revoked", - "stale", - "unavailable", -] -ManagerPreviewApprovalReasonCode = Literal[ - "approval_missing", - "approval_valid", - "changes_requested", - "approval_revoked", - "approval_stale", - "preview_inactive", - "serving_generation_missing", - "serving_generation_mismatch", - "generation_not_ready", - "generation_verification_failed", - "preview_identity_mismatch", - "artifact_identity_missing", - "runtime_identity_missing", - "runtime_identity_mismatch", - "policy_unavailable", -] ManagerPreviewApprovalEventWriteStatus = Literal["written", "replayed"] _GIT_SHA_PATTERN = re.compile(r"^[0-9a-f]{40}(?:[0-9a-f]{24})?$") @@ -285,36 +262,6 @@ def policy_sha256(self) -> str: return self.authorization.policy_sha256 if self.authorization is not None else "" -class ManagerPreviewApprovalDecision(BaseModel): - model_config = ConfigDict(extra="forbid") - - schema_version: int = Field(default=1, ge=1) - status: ManagerPreviewApprovalDecisionStatus - reason_code: ManagerPreviewApprovalReasonCode - reason: str - current_binding_sha256: str = "" - event_id: str = "" - approval_id: str = "" - manager_github_id: int = Field(default=0, ge=0) - manager_login: str = "" - evaluated_at: str - - @model_validator(mode="after") - def _validate_decision(self) -> "ManagerPreviewApprovalDecision": - if self.schema_version != 1: - raise ValueError("Unsupported manager preview approval decision schema version.") - self.reason = _required_token(self.reason, "reason") - self.evaluated_at = _normalize_utc_timestamp(self.evaluated_at, "evaluated_at") - for field_name in ("current_binding_sha256", "event_id", "approval_id", "manager_login"): - setattr(self, field_name, str(getattr(self, field_name)).strip()) - if ( - self.current_binding_sha256 - and _SHA256_PATTERN.fullmatch(self.current_binding_sha256) is None - ): - raise ValueError("manager preview approval decision has invalid binding SHA-256") - return self - - def runtime_identity_sha256(identity: RuntimeIdentity) -> str: return _canonical_sha256(identity.model_dump(mode="json", exclude_none=True)) @@ -402,3 +349,7 @@ def _normalize_utc_timestamp(value: str, label: str) -> str: def _canonical_sha256(payload: object) -> str: canonical_json = json.dumps(payload, sort_keys=True, separators=(",", ":")) return hashlib.sha256(canonical_json.encode("utf-8")).hexdigest() + + +class ManagerPreviewApprovalEventConflictError(RuntimeError): + pass diff --git a/control_plane/engineering_review_decision_service.py b/control_plane/engineering_review_decision_service.py index a48643d80..2c9e1fe3e 100644 --- a/control_plane/engineering_review_decision_service.py +++ b/control_plane/engineering_review_decision_service.py @@ -3,15 +3,7 @@ from datetime import UTC, datetime from typing import Literal, Protocol, cast -from control_plane.change_impact_service import ( - ChangeImpactRepositoryEvidenceProvider, - evaluate_change_impact, - load_change_impact_stored_evidence, -) -from control_plane.contracts.change_impact import ( - ChangeImpactEvaluation, - ChangeImpactPolicyRecord, -) +from control_plane.contracts.repository_evidence import RepositoryEvidenceProvider, RepositoryTarget from control_plane.contracts.engineering_review_decision import ( EngineeringReviewDecisionRecord, EngineeringReviewDecisionRequest, @@ -31,15 +23,6 @@ def read_every_code_work_request_record( self, request_id: str ) -> EveryCodeWorkRequestRecord: ... - def list_change_impact_policy_records( - self, - *, - repository_id: str = "", - repository: str = "", - status: str = "", - limit: int | None = None, - ) -> tuple[ChangeImpactPolicyRecord, ...]: ... - def list_engineering_review_run_records( self, *, @@ -86,7 +69,6 @@ def require_engineering_review_decision_store( ) -> EngineeringReviewDecisionStore: required = ( "read_every_code_work_request_record", - "list_change_impact_policy_records", "list_engineering_review_run_records", "list_engineering_review_authority_records", "write_engineering_review_decision_record_if_absent", @@ -105,7 +87,7 @@ def evaluate_engineering_review_decision( *, store: EngineeringReviewDecisionStore, request: EngineeringReviewDecisionRequest, - repository_evidence_provider: ChangeImpactRepositoryEvidenceProvider, + repository_evidence_provider: RepositoryEvidenceProvider, evaluated_at: str = "", ) -> tuple[EngineeringReviewDecisionRecord, bool]: work_request = store.read_every_code_work_request_record(request.work_request_id) @@ -138,14 +120,6 @@ def evaluate_engineering_review_decision( raise EngineeringReviewDecisionConflictError( "Server-resolved engineering review target does not match the stored work request." ) - policies = store.list_change_impact_policy_records(repository_id=target.repository_id) - stored_evidence = load_change_impact_stored_evidence(store=store, target=target) - impact = evaluate_change_impact( - repository_evidence=repository_evidence, - policies=policies, - stored_evidence=stored_evidence, - evaluated_at=evaluated_at, - ) runs = store.list_engineering_review_run_records( repository=target.repository, pr_number=target.pull_request_number, @@ -160,7 +134,7 @@ def evaluate_engineering_review_decision( ) record = _decision_record( work_request=work_request, - impact=impact, + target=target, runs=runs, active_authority=(active_authorities[0] if len(active_authorities) == 1 else None), evaluated_at=evaluated_at.strip() or _decision_timestamp(), @@ -171,12 +145,11 @@ def evaluate_engineering_review_decision( def _decision_record( *, work_request: EveryCodeWorkRequestRecord, - impact: ChangeImpactEvaluation, + target: RepositoryTarget, runs: tuple[EngineeringReviewRunRecord, ...], active_authority: EngineeringReviewAuthorityRecord | None, evaluated_at: str, ) -> EngineeringReviewDecisionRecord: - target = impact.target exact_runs = tuple( run for run in runs @@ -199,17 +172,13 @@ def _decision_record( approved = tuple(run for run in completed if run.decision == "approved") requested = tuple(run for run in completed if run.decision == "changes_requested") blocked = tuple(run for run in completed if run.decision == "blocked") - required = impact.required_engineering_review_count + required: Literal[2] = 2 qualifying = _distinct_slots(approved) families = tuple(sorted({run.model_family for run in qualifying})) status: Literal["approved", "changes_requested", "blocked", "pending", "unknown", "stale"] reason_code: str - if impact.status in {"stale_head", "stale_policy"}: - status, reason_code = "stale", f"change_impact_{impact.status}" - elif impact.status != "success": - status, reason_code = "unknown", "change_impact_unknown" - elif active_authority is None: + if active_authority is None: status, reason_code = "unknown", "review_authority_unavailable" elif exact_runs and not authority_runs: status, reason_code = "stale", "review_authority_stale" @@ -233,13 +202,7 @@ def _decision_record( target=target, work_request_id=work_request.request_id, work_request_lifecycle_id=work_request.lifecycle_id, - change_impact_status=impact.status, - change_impact_policy_record_id=impact.policy_record_id, - change_impact_policy_revision=impact.policy_revision, - change_impact_policy_digest=impact.policy_digest, - binding_hash_version=impact.binding_hash_version, - change_impact_decision_digest=impact.change_impact_decision_digest, - engineering_review_tier=impact.engineering_review_tier, + schema_version=2, required_review_count=required, authority_id=authority_id, authority_digest=authority_digest, diff --git a/control_plane/engineering_review_service.py b/control_plane/engineering_review_service.py index 276d388b3..036c301bf 100644 --- a/control_plane/engineering_review_service.py +++ b/control_plane/engineering_review_service.py @@ -6,14 +6,11 @@ from pydantic import BaseModel, ConfigDict, Field from control_plane import secrets as control_plane_secrets -from control_plane.change_impact_service import ( - ChangeImpactRepositoryEvidenceProvider, - evaluate_change_impact, - load_change_impact_stored_evidence, +from control_plane.contracts.repository_evidence import ( + RepositoryEvidenceProvider, ) -from control_plane.contracts.change_impact import ( - ChangeImpactPolicyRecord, - ChangeImpactTargetReference, +from control_plane.contracts.repository_evidence import ( + RepositoryTargetReference, ) from control_plane.contracts.engineering_review_run import ( EngineeringReviewAuthorityRecord, @@ -67,15 +64,6 @@ def list_engineering_review_authority_records( limit: int | None = None, ) -> tuple[EngineeringReviewAuthorityRecord, ...]: ... - def list_change_impact_policy_records( - self, - *, - repository_id: str = "", - repository: str = "", - status: str = "", - limit: int | None = None, - ) -> tuple[ChangeImpactPolicyRecord, ...]: ... - def create_engineering_review_run_records_if_absent( self, records: tuple[EngineeringReviewRunRecord, ...], @@ -114,7 +102,6 @@ def require_engineering_review_authority_store( ) -> EngineeringReviewAuthorityStore: required = ( "list_engineering_review_authority_records", - "list_change_impact_policy_records", "compare_and_write_engineering_review_authority_record", ) missing = [name for name in required if not callable(getattr(record_store, name, None))] @@ -178,7 +165,7 @@ def create_engineering_review_runs( store: EngineeringReviewRunCreateStore, work_request_id: str, target_resolver: EngineeringReviewTargetResolver, - repository_evidence_provider: ChangeImpactRepositoryEvidenceProvider, + repository_evidence_provider: RepositoryEvidenceProvider, created_at: str = "", ) -> EngineeringReviewRunCreateResult: work_request = store.read_every_code_work_request_record(work_request_id) @@ -202,7 +189,7 @@ def create_engineering_review_runs( "Linked pull request repository does not match the stored work request." ) repository_evidence = repository_evidence_provider.resolve( - ChangeImpactTargetReference( + RepositoryTargetReference( repository=target.repository, pull_request_number=target.pr_number, ) @@ -212,22 +199,11 @@ def create_engineering_review_runs( or repository_evidence.target.tree_sha != target.tree_sha ): raise EngineeringReviewConflictError( - "Engineering review target changed during server-side classification." + "Engineering review target changed during repository evidence resolution." ) - impact = evaluate_change_impact( - repository_evidence=repository_evidence, - policies=store.list_change_impact_policy_records( - repository_id=repository_evidence.target.repository_id - ), - stored_evidence=load_change_impact_stored_evidence( - store=store, - target=repository_evidence.target, - ), - ) - required_review_count = ( - impact.required_engineering_review_count if impact.status == "success" else 2 - ) - review_slots = _default_review_slots(authority, required_review_count) + # Engineering review uses the existing independent authority slots. Retired + # file-path classification no longer guesses which approval is required. + review_slots = _default_review_slots(authority) timestamp = created_at.strip() or utc_now_timestamp() records: list[EngineeringReviewRunRecord] = [] for model_slot in review_slots: @@ -350,17 +326,8 @@ def resolve_engineering_review_pull_request_target( def _default_review_slots( authority: EngineeringReviewAuthorityRecord, - required_review_count: int = 2, ) -> tuple[EngineeringReviewModelSlot, ...]: - if required_review_count not in {1, 2}: - raise EngineeringReviewUnavailableError( - "Server-derived review classification returned an invalid review count." - ) - if len(authority.model_slots) < required_review_count: - raise EngineeringReviewUnavailableError( - "Engineering review authority lacks the server-required model slots." - ) - return authority.model_slots[:required_review_count] + return authority.model_slots[:2] def _validate_authority_append( diff --git a/control_plane/governance_projection.py b/control_plane/governance_projection.py index 6ca0d8c80..f7ab84ac2 100644 --- a/control_plane/governance_projection.py +++ b/control_plane/governance_projection.py @@ -5,10 +5,12 @@ from typing import Literal, Protocol, cast from control_plane.contracts.merge_train_policy import MergeTrainGitHubTokenSource -from control_plane.change_impact_service import ChangeImpactRepositoryEvidenceProvider -from control_plane.contracts.change_impact import ( - ChangeImpactRepositoryEvidence, - ChangeImpactTargetReference, +from control_plane.contracts.repository_evidence import ( + RepositoryEvidenceProvider, +) +from control_plane.contracts.repository_evidence import ( + RepositoryEvidence, + RepositoryTargetReference, ) from control_plane.contracts.governance_projection import ( GovernanceAdvisoryObservation, @@ -54,7 +56,7 @@ def __call__( self, *, store: object, - repository_evidence: ChangeImpactRepositoryEvidence, + repository_evidence: RepositoryEvidence, base_branch: str, evaluated_at: str, github_token_source: MergeTrainGitHubTokenSource, @@ -76,7 +78,7 @@ def list_merge_train_stack_collapse_plan_records( class LiveGovernanceCurrentReadinessProvider: github_token: Callable[[MergeTrainGitHubTokenSource, str], str] evaluator_factory: Callable[ - [object, ChangeImpactRepositoryEvidenceProvider, str], + [object, RepositoryEvidenceProvider, str], object, ] = lambda store, provider, token: LiveMergeAdmissionEvaluator( store=store, @@ -90,7 +92,7 @@ def __call__( self, *, store: object, - repository_evidence: ChangeImpactRepositoryEvidence, + repository_evidence: RepositoryEvidence, base_branch: str, evaluated_at: str, github_token_source: MergeTrainGitHubTokenSource, @@ -223,12 +225,12 @@ def __call__( def build_governance_projection( *, store: object, - repository_evidence_provider: ChangeImpactRepositoryEvidenceProvider, + repository_evidence_provider: RepositoryEvidenceProvider, current_readiness_provider: GovernanceCurrentReadinessProvider, - target: ChangeImpactTargetReference, + target: RepositoryTargetReference, base_branch: str, generated_at: str, - repository_evidence: ChangeImpactRepositoryEvidence | None = None, + repository_evidence: RepositoryEvidence | None = None, github_token_source: MergeTrainGitHubTokenSource | None = None, ) -> GovernanceProjection: resolved_repository_evidence = repository_evidence or repository_evidence_provider.resolve( @@ -294,9 +296,9 @@ def build_governance_projection( @dataclass(frozen=True, slots=True) class _ResolvedRepositoryEvidenceProvider: - evidence: ChangeImpactRepositoryEvidence + evidence: RepositoryEvidence - def resolve(self, target: ChangeImpactTargetReference) -> ChangeImpactRepositoryEvidence: + def resolve(self, target: RepositoryTargetReference) -> RepositoryEvidence: if ( target.repository.lower() != self.evidence.target.repository.lower() or target.pull_request_number != self.evidence.target.pull_request_number @@ -308,7 +310,7 @@ def resolve(self, target: ChangeImpactTargetReference) -> ChangeImpactRepository def _admission_target_status( *, admission: MergeAdmissionRecord | None, - repository_evidence: ChangeImpactRepositoryEvidence, + repository_evidence: RepositoryEvidence, ) -> Literal["current", "historical", "none"]: if admission is None: return "none" diff --git a/control_plane/http_app.py b/control_plane/http_app.py index 0dd87e1bc..2f73ab440 100644 --- a/control_plane/http_app.py +++ b/control_plane/http_app.py @@ -81,8 +81,12 @@ from control_plane import secrets as control_plane_secrets from control_plane import service_status as control_plane_service_status from control_plane import live_target_runtime as control_plane_live_target_runtime -from control_plane.change_impact_github import GitHubChangeImpactRepositoryEvidenceProvider -from control_plane.change_impact_service import ChangeImpactRepositoryEvidenceProvider +from control_plane.repository_evidence import ( + GitHubRepositoryEvidenceProvider, +) +from control_plane.contracts.repository_evidence import ( + RepositoryEvidenceProvider, +) from control_plane.contracts.generic_web_deploy_recovery import ( GenericWebDeployRecoveryProviderEvidenceResponse, ) @@ -141,16 +145,8 @@ EngineeringReviewWorkerIdentity, EngineeringReviewWriteRouteDependencies, GenericWebWriteRouteDependencies, - ChangeImpactReadRouteDependencies, - ChangeImpactWriteRouteDependencies, - CHANGE_IMPACT_EVALUATION_ROUTE, - CHANGE_IMPACT_POLICY_APPLY_ROUTE, PRIVILEGED_OPERATION_AGENT_PLANS_ROUTE, PRIVILEGED_OPERATION_PLANS_ROUTE, - PRODUCT_OWNER_POLICY_APPLY_ROUTE, - PRODUCT_OWNER_REQUIREMENT_APPLY_ROUTE, - PRODUCT_OWNER_ROUTING_APPLY_ROUTE, - ProductOwnerWriteRouteDependencies, ProductReviewRouteDependencies, PrivilegedOperationRouteDependencies, GovernanceProjectionRouteDependencies, @@ -163,8 +159,6 @@ idempotency_scope as idempotency_scope, provider_operation_response_payload as _provider_operation_response_payload, register_agent_context_read_routes, - register_change_impact_read_routes, - register_change_impact_write_routes, register_deployment_promotion_read_routes, register_dokploy_target_inspect_read_routes, register_driver_descriptor_read_routes, @@ -188,8 +182,6 @@ register_preview_notification_attempt_read_routes, register_preview_readiness_read_routes, register_preview_record_read_routes, - register_product_owner_read_routes, - register_product_owner_write_routes, register_product_config_status_read_routes, register_product_environment_read_routes, register_product_promotion_status_read_routes, @@ -288,7 +280,6 @@ build_launchplane_idempotency_record_id, build_launchplane_mutation_reservation_id, complete_launchplane_mutation_reservation, - format_launchplane_mutation_timestamp, ) from control_plane.http_routes.release_review import ( ReleaseReviewRouteDependencies, @@ -296,18 +287,7 @@ ) from control_plane.release_review import current_release_review from control_plane.release_review_record import publish_release_decision -from control_plane.contracts.manager_preview_approval import ( - MANAGER_PREVIEW_APPROVAL_READ_ACTION, -) -from control_plane.contracts.manager_preview_approval_projection import ( - ManagerPreviewApprovalReconcileEnvelope, -) -from control_plane.manager_preview_approval_github_webhook import ( - MANAGER_PREVIEW_APPROVAL_RECONCILE_ROUTE, - MANAGER_PREVIEW_APPROVAL_WEBHOOK_ROUTE, - record_manager_preview_approval_invalidation_for_pr, - reconcile_manager_preview_approval_for_pr, -) +from control_plane.trusted_maintenance_github_webhook import TRUSTED_MAINTENANCE_WEBHOOK_ROUTE from control_plane.provider_operations import ( DurableProviderMutationAdapter, DurableProviderOperationResult, @@ -883,7 +863,7 @@ EveryCodeGitHubWebhookHandler = Callable[ [bytes, str, str, str, object, FilePath, str], tuple[int, dict[str, object]] ] -ManagerPreviewApprovalGitHubWebhookHandler = Callable[ +TrustedMaintenanceGitHubWebhookHandler = Callable[ [bytes, str, str, str, object, FilePath, str], tuple[int, dict[str, object]] ] @@ -974,18 +954,12 @@ False, True, ), - MANAGER_PREVIEW_APPROVAL_WEBHOOK_ROUTE: ( - "Manager preview approval GitHub webhook", + TRUSTED_MAINTENANCE_WEBHOOK_ROUTE: ( + "Trusted-maintenance GitHub webhook", _GITHUB_WEBHOOK_MAX_BODY_BYTES, False, True, ), - MANAGER_PREVIEW_APPROVAL_RECONCILE_ROUTE: ( - "Manager preview approval reconciliation", - _PRODUCT_HEALTH_MONITORING_MAX_BODY_BYTES, - True, - True, - ), _PRODUCT_CONFIG_APPLY_ROUTE: ( "Product config", _PRODUCT_CONFIG_MAX_BODY_BYTES, @@ -1094,36 +1068,6 @@ True, True, ), - CHANGE_IMPACT_EVALUATION_ROUTE: ( - "Change impact evaluation", - _CHANGE_IMPACT_EVALUATION_MAX_BODY_BYTES, - True, - True, - ), - CHANGE_IMPACT_POLICY_APPLY_ROUTE: ( - "Change impact policy", - _CHANGE_IMPACT_POLICY_MAX_BODY_BYTES, - True, - True, - ), - PRODUCT_OWNER_POLICY_APPLY_ROUTE: ( - "Product Owner policy", - _PRODUCT_OWNER_POLICY_MAX_BODY_BYTES, - True, - True, - ), - PRODUCT_OWNER_REQUIREMENT_APPLY_ROUTE: ( - "Product Owner requirement", - _PRODUCT_OWNER_POLICY_MAX_BODY_BYTES, - True, - True, - ), - PRODUCT_OWNER_ROUTING_APPLY_ROUTE: ( - "Product Owner routing", - _PRODUCT_OWNER_POLICY_MAX_BODY_BYTES, - True, - True, - ), } _DOKPLOY_TARGET_SETUP_ROUTE = "/v1/dokploy-targets/setup" _PUBLIC_INGRESS_MONITOR_RUN_ONCE_ROUTE = "/v1/products/public-ingress-monitor/run-once" @@ -1923,26 +1867,6 @@ def target_key(self) -> str: reconciliation_key = self.reconciliation_key() return f"dokploy-provider-target:{hashlib.sha256(reconciliation_key.encode()).hexdigest()}" - def _destroy_invalidation_records(self) -> dict[str, object]: - provenance = self._issued_plan.plan_provenance - if provenance is None: - raise ValueError("Odoo preview destroy requires issued plan provenance.") - result = record_manager_preview_approval_invalidation_for_pr( - repository=self._profile.repository, - pr_number=self._issued_plan.plan_request.pr_number, - reason="The serving preview was destroyed.", - source_event_kind="preview_destroy", - source_event_id=f"odoo-preview-destroy:{provenance.plan_id}", - record_store=cast(Any, self._record_store), - occurred_at=format_launchplane_mutation_timestamp(provenance.issued_at), - ) - return { - "manager_preview_approval_required": bool(result.get("required")), - "manager_preview_invalidation_event_status": string_value( - result.get("event_status") or "" - ), - } - def _finalize_successful_result( self, driver_result: dict[str, object], @@ -1954,11 +1878,6 @@ def _finalize_successful_result( issued_plan=self._issued_plan, driver_result=driver_result, runtime_identity=self._runtime_identity, - before_destroy=( - self._destroy_invalidation_records - if self._issued_plan.operation == "destroy" - else None - ), ) lifecycle_status = string_value( lifecycle_records.get("lifecycle_evidence_status") or "" @@ -4004,16 +3923,14 @@ def create_launchplane_fastapi_app( work_graph_planning_facts_provider: WorkGraphPlanningFactsProvider | None = None, work_graph_issue_inbox_provider: WorkGraphIssueInboxProvider | None = None, work_graph_issue_inbox_reconcile_provider: WorkGraphIssueInboxReconcileProvider | None = None, - change_impact_repository_evidence_provider: ( - ChangeImpactRepositoryEvidenceProvider | None - ) = None, + repository_evidence_provider: (RepositoryEvidenceProvider | None) = None, every_code_discord_sender: Callable[[str, dict[str, object]], object] = post_discord_webhook, preview_pr_feedback_discord_sender: Callable[ [str, dict[str, object]], object ] = post_discord_webhook, every_code_github_webhook_handler: EveryCodeGitHubWebhookHandler | None = None, - manager_preview_approval_github_webhook_handler: ( - ManagerPreviewApprovalGitHubWebhookHandler | None + trusted_maintenance_github_webhook_handler: ( + TrustedMaintenanceGitHubWebhookHandler | None ) = None, engineering_review_target_resolver: EngineeringReviewTargetResolver | None = None, owner_review_status_publisher: OwnerReviewStatusPublisher | None = None, @@ -4022,9 +3939,9 @@ def create_launchplane_fastapi_app( control_plane_root_path or FilePath(__file__).resolve().parent.parent ) resolved_state_dir = state_dir or resolved_control_plane_root / "state" - resolved_change_impact_repository_evidence_provider = ( - change_impact_repository_evidence_provider - or GitHubChangeImpactRepositoryEvidenceProvider( + resolved_repository_evidence_provider = ( + repository_evidence_provider + or GitHubRepositoryEvidenceProvider( control_plane_root=resolved_control_plane_root, github_token=resolve_launchplane_github_token, github_api=github_api_request, @@ -4808,7 +4725,7 @@ async def handle_every_code_github_webhook( ) return JSONResponse(status_code=status_code, content=payload) - async def handle_manager_preview_approval_github_webhook( + async def handle_trusted_maintenance_github_webhook( request: Request, x_github_event: Annotated[str, Header(alias="X-GitHub-Event")] = "", x_github_delivery: Annotated[str, Header(alias="X-GitHub-Delivery")] = "", @@ -4816,14 +4733,14 @@ async def handle_manager_preview_approval_github_webhook( record_store: object = Depends(get_record_store), ) -> JSONResponse: trace_id = next_trace_id() - if manager_preview_approval_github_webhook_handler is None: + if trusted_maintenance_github_webhook_handler is None: raise _launchplane_http_error( status_code=404, trace_id=trace_id, code="not_found", - message=f"No Launchplane route for {MANAGER_PREVIEW_APPROVAL_WEBHOOK_ROUTE}.", + message=f"No Launchplane route for {TRUSTED_MAINTENANCE_WEBHOOK_ROUTE}.", ) - status_code, payload = manager_preview_approval_github_webhook_handler( + status_code, payload = trusted_maintenance_github_webhook_handler( await request.body(), x_github_event, x_github_delivery, @@ -4834,68 +4751,6 @@ async def handle_manager_preview_approval_github_webhook( ) return JSONResponse(status_code=status_code, content=payload) - async def reconcile_manager_preview_approval( - reconcile_request: ManagerPreviewApprovalReconcileEnvelope, - identity: Annotated[LaunchplaneIdentity, Depends(read_browser_mutation_identity)], - record_store: Annotated[object, Depends(get_record_store)], - ) -> dict[str, object]: - trace_id = next_trace_id() - list_profiles = optional_callable_attribute(record_store, "list_product_profile_records") - if list_profiles is None: - raise _launchplane_http_error( - status_code=503, - trace_id=trace_id, - code="record_storage_unavailable", - message="Manager preview approval reconciliation requires product profile storage.", - ) - profiles = tuple( - profile - for profile in list_profiles() - if profile.repository.strip().casefold() == reconcile_request.repository.casefold() - ) - if len(profiles) != 1: - raise _launchplane_http_error( - status_code=404, - trace_id=trace_id, - code="not_found", - message="Manager preview approval product profile was not found.", - ) - profile = profiles[0] - if not resolved_authz_policy_runtime.policy.allows( - identity=identity, - action=MANAGER_PREVIEW_APPROVAL_READ_ACTION, - product=profile.product, - context=profile.preview.context, - target=AuthorizationTarget(scope="context"), - ): - raise _launchplane_http_error( - status_code=403, - trace_id=trace_id, - code="authorization_denied", - message="Caller cannot reconcile manager preview approval for this product.", - ) - try: - result = reconcile_manager_preview_approval_for_pr( - repository=reconcile_request.repository, - pr_number=reconcile_request.pr_number, - record_store=cast(Any, record_store), - control_plane_root=resolved_control_plane_root, - ) - except ( - click.ClickException, - FileNotFoundError, - LookupError, - TypeError, - ValueError, - ) as error: - raise _launchplane_http_error( - status_code=503, - trace_id=trace_id, - code="manager_preview_approval_unavailable", - message="Manager preview approval reconciliation could not complete.", - ) from error - return {"status": "ok", "trace_id": trace_id, "result": result} - def read_every_code_work_request_worker_write_identity( authorization: Annotated[str, Header(alias="Authorization")] = "", ) -> LaunchplaneIdentity | None: @@ -4956,22 +4811,7 @@ def read_write_identity( bearer_identity_config or BearerIdentityConfig() ), ) - product_owner_write_route_dependencies = ProductOwnerWriteRouteDependencies( - read_write_identity=read_write_identity, - get_record_store=get_record_store, - next_trace_id=next_trace_id, - authorization_allows=resolved_authz_policy_runtime.allows, - http_error=_launchplane_http_error, - error_response_model=LaunchplaneErrorResponse, - ) - change_impact_write_route_dependencies = ChangeImpactWriteRouteDependencies( - read_write_identity=read_write_identity, - get_record_store=get_record_store, - next_trace_id=next_trace_id, - authorization_allows=resolved_authz_policy_runtime.allows, - http_error=_launchplane_http_error, - error_response_model=LaunchplaneErrorResponse, - ) + engineering_review_write_route_dependencies = EngineeringReviewWriteRouteDependencies( read_write_identity=read_write_identity, read_worker_identity=read_engineering_review_worker_identity, @@ -4981,7 +4821,7 @@ def read_write_identity( http_error=_launchplane_http_error, error_response_model=LaunchplaneErrorResponse, target_resolver=resolved_engineering_review_target_resolver, - repository_evidence_provider=resolved_change_impact_repository_evidence_provider, + repository_evidence_provider=resolved_repository_evidence_provider, ) engineering_review_decision_route_dependencies = EngineeringReviewDecisionRouteDependencies( read_write_identity=read_write_identity, @@ -4990,7 +4830,7 @@ def read_write_identity( authorization_allows=resolved_authz_policy_runtime.allows, http_error=_launchplane_http_error, error_response_model=LaunchplaneErrorResponse, - repository_evidence_provider=resolved_change_impact_repository_evidence_provider, + repository_evidence_provider=resolved_repository_evidence_provider, github_app_token=lambda repository, repository_id: mint_repository_installation_token( identity=resolve_advisory_github_app_identity( control_plane_root=resolved_control_plane_root @@ -6134,7 +5974,7 @@ def store_controller_idempotency_before_release( admission_evaluator = LiveMergeAdmissionEvaluator( store=record_store, - repository_evidence_provider=resolved_change_impact_repository_evidence_provider, + repository_evidence_provider=resolved_repository_evidence_provider, technical_check_client=GitHubMergeTrainClient( transport=UrllibMergeTrainGitHubTransport( token=token, @@ -9955,9 +9795,7 @@ def checkpoint_landing_mutation( admission_store=admission_store, admission_evaluator=LiveMergeAdmissionEvaluator( store=record_store, - repository_evidence_provider=( - resolved_change_impact_repository_evidence_provider - ), + repository_evidence_provider=(resolved_repository_evidence_provider), technical_check_client=GitHubMergeTrainClient( transport=UrllibMergeTrainGitHubTransport( token=token, @@ -24103,12 +23941,12 @@ def preserve_renewed_session_cookie(request: Request, response: JSONResponse) -> }, ) app.add_api_route( - MANAGER_PREVIEW_APPROVAL_WEBHOOK_ROUTE, - handle_manager_preview_approval_github_webhook, + TRUSTED_MAINTENANCE_WEBHOOK_ROUTE, + handle_trusted_maintenance_github_webhook, methods=["POST"], status_code=202, - operation_id="handle_manager_preview_approval_github_webhook", - summary="Handle manager preview approval GitHub webhook", + operation_id="handle_trusted_maintenance_github_webhook", + summary="Capture signed trusted-maintenance GitHub evidence", responses={ 400: {"model": LaunchplaneErrorResponse}, 401: {"model": LaunchplaneErrorResponse}, @@ -24117,30 +23955,7 @@ def preserve_renewed_session_cookie(request: Request, response: JSONResponse) -> 503: {"model": LaunchplaneErrorResponse}, }, ) - app.add_api_route( - MANAGER_PREVIEW_APPROVAL_RECONCILE_ROUTE, - reconcile_manager_preview_approval, - methods=["POST"], - status_code=200, - operation_id="reconcile_manager_preview_approval", - summary="Reconcile manager preview approval projection", - responses={ - 403: {"model": LaunchplaneErrorResponse}, - 404: {"model": LaunchplaneErrorResponse}, - 413: {"model": LaunchplaneErrorResponse}, - 503: {"model": LaunchplaneErrorResponse}, - }, - ) - register_product_owner_read_routes(app, dependencies=read_route_dependencies) - register_change_impact_read_routes( - app, - dependencies=ChangeImpactReadRouteDependencies( - common=read_route_dependencies, - read_evaluation_identity=read_bearer_identity, - repository_evidence_provider=resolved_change_impact_repository_evidence_provider, - ), - ) register_release_review_routes( app, dependencies=ReleaseReviewRouteDependencies( @@ -24201,7 +24016,7 @@ def preserve_renewed_session_cookie(request: Request, response: JSONResponse) -> app, dependencies=GovernanceProjectionRouteDependencies( common=read_route_dependencies, - repository_evidence_provider=resolved_change_impact_repository_evidence_provider, + repository_evidence_provider=resolved_repository_evidence_provider, current_readiness_provider=LiveGovernanceCurrentReadinessProvider( github_token=lambda source, repository: resolve_merge_train_github_token( source=source, @@ -25522,14 +25337,6 @@ def preserve_renewed_session_cookie(request: Request, response: JSONResponse) -> error_response_model=LaunchplaneErrorResponse, ), ) - register_product_owner_write_routes( - app, - dependencies=product_owner_write_route_dependencies, - ) - register_change_impact_write_routes( - app, - dependencies=change_impact_write_route_dependencies, - ) register_odoo_runtime_read_routes( app, diff --git a/control_plane/http_routes/__init__.py b/control_plane/http_routes/__init__.py index 6658f5cef..f4e535593 100644 --- a/control_plane/http_routes/__init__.py +++ b/control_plane/http_routes/__init__.py @@ -61,27 +61,8 @@ register_preview_readiness_read_routes, register_preview_record_read_routes, ) -from control_plane.http_routes.change_impact import ( - CHANGE_IMPACT_EVALUATION_ROUTE, - CHANGE_IMPACT_POLICY_APPLY_ROUTE, - CHANGE_IMPACT_POLICY_READ_ROUTE, - ChangeImpactReadRouteDependencies, - ChangeImpactWriteRouteDependencies, - register_change_impact_read_routes, - register_change_impact_write_routes, -) -from control_plane.http_routes.product_owner import ( - PRODUCT_OWNER_POLICY_APPLY_ROUTE, - PRODUCT_OWNER_POLICY_READ_ROUTE, - PRODUCT_OWNER_REQUIREMENT_APPLY_ROUTE, - PRODUCT_OWNER_REQUIREMENT_READ_ROUTE, - PRODUCT_OWNER_ROUTING_APPLY_ROUTE, - PRODUCT_OWNER_ROUTING_READ_ROUTE, - PRODUCT_OWNER_AUTHORITY_EVALUATION_ROUTE, - ProductOwnerWriteRouteDependencies, - register_product_owner_read_routes, - register_product_owner_write_routes, -) + + from control_plane.http_routes.product_review import ( ProductReviewRouteDependencies, register_product_review_routes, @@ -146,10 +127,6 @@ "BackupGateEvidenceRequest", "DeploymentEvidenceRequest", "DriverReadRouteDependencies", - "CHANGE_IMPACT_EVALUATION_ROUTE", - "CHANGE_IMPACT_POLICY_APPLY_ROUTE", - "CHANGE_IMPACT_POLICY_READ_ROUTE", - "ChangeImpactReadRouteDependencies", "EVIDENCE_INGRESS_ROUTES", "EvidenceWriteRouteDependencies", "GenericWebWriteRouteDependencies", @@ -157,8 +134,6 @@ "GOVERNANCE_PROJECTION_ROUTE", "GovernanceProjectionRouteDependencies", "ProductReadRouteDependencies", - "ChangeImpactWriteRouteDependencies", - "ProductOwnerWriteRouteDependencies", "ProductReviewRouteDependencies", "PRIVILEGED_OPERATION_AGENT_PLANS_ROUTE", "PRIVILEGED_OPERATION_AGENT_SUMMARY_ROUTE", @@ -167,13 +142,6 @@ "PRIVILEGED_OPERATION_PLANS_ROUTE", "PrivilegedOperationRouteDependencies", "PromotionEvidenceRequest", - "PRODUCT_OWNER_POLICY_APPLY_ROUTE", - "PRODUCT_OWNER_POLICY_READ_ROUTE", - "PRODUCT_OWNER_REQUIREMENT_APPLY_ROUTE", - "PRODUCT_OWNER_REQUIREMENT_READ_ROUTE", - "PRODUCT_OWNER_ROUTING_APPLY_ROUTE", - "PRODUCT_OWNER_ROUTING_READ_ROUTE", - "PRODUCT_OWNER_AUTHORITY_EVALUATION_ROUTE", "PRODUCTION_BACKUP_AUTHORITY_APPLY_ROUTE", "PRODUCTION_BACKUP_AUTHORITY_LEGACY_MIGRATION_ROUTE", "PRODUCTION_BACKUP_AUTHORITY_READ_ROUTE", @@ -194,8 +162,6 @@ "idempotency_scope", "provider_operation_response_payload", "register_agent_context_read_routes", - "register_change_impact_read_routes", - "register_change_impact_write_routes", "register_deployment_promotion_read_routes", "register_dokploy_target_inspect_read_routes", "register_driver_descriptor_read_routes", @@ -219,8 +185,6 @@ "register_operation_status_read_routes", "register_preview_notification_attempt_read_routes", "register_preview_readiness_read_routes", - "register_product_owner_read_routes", - "register_product_owner_write_routes", "register_product_review_routes", "register_privileged_operation_routes", "register_preview_record_read_routes", diff --git a/control_plane/http_routes/change_impact.py b/control_plane/http_routes/change_impact.py deleted file mode 100644 index 91de53081..000000000 --- a/control_plane/http_routes/change_impact.py +++ /dev/null @@ -1,343 +0,0 @@ -from collections.abc import Callable -from dataclasses import dataclass -from typing import Annotated, Literal, cast - -from fastapi import Depends, Query -from pydantic import BaseModel, ConfigDict, Field, ValidationError - -from control_plane.change_impact_service import ( - ChangeImpactPolicyApplyResult, - ChangeImpactPolicyAuditReadStore, - ChangeImpactPolicyConflictError, - ChangeImpactPolicySequenceError, - ChangeImpactPolicyReadModel, - ChangeImpactRepositoryEvidenceProvider, - ChangeImpactTrustedCallerBinding, - evaluate_change_impact, - get_change_impact_policy_read_model, - load_change_impact_stored_evidence, - require_change_impact_policy_read_store, -) -from control_plane.change_impact_policy_audit import ( - ChangeImpactPolicyAttributionError, - apply_change_impact_policy_with_audit, - derive_change_impact_policy_audit, - require_change_impact_policy_audited_store, -) -from control_plane.change_impact_github import ( - ChangeImpactRepositoryEvidenceError, - ChangeImpactRepositoryEvidenceStaleError, -) -from control_plane.contracts.change_impact import ( - CHANGE_IMPACT_EVALUATION_READ_ACTION, - CHANGE_IMPACT_POLICY_READ_ACTION, - CHANGE_IMPACT_POLICY_WRITE_ACTION, - ChangeImpactEvaluation, - ChangeImpactEvaluationRequest, - ChangeImpactPolicyRecord, -) -from control_plane.http_routes.support import ( - ApiRouteRegistrar, - AuthorizationAllows, - HttpErrorFactory, - ReadRouteDependencies, -) -from control_plane.service_auth import ( - AuthorizationTarget, - GitHubActionsIdentity, - LaunchplaneIdentity, -) - - -CHANGE_IMPACT_POLICY_READ_ROUTE = "/v1/change-impact/policy" -CHANGE_IMPACT_EVALUATION_ROUTE = "/v1/change-impact/evaluation" -CHANGE_IMPACT_POLICY_APPLY_ROUTE = "/v1/change-impact/policies/apply" - - -@dataclass(frozen=True, slots=True) -class ChangeImpactWriteRouteDependencies: - read_write_identity: Callable[..., LaunchplaneIdentity] - get_record_store: Callable[[], object] - next_trace_id: Callable[[], str] - authorization_allows: AuthorizationAllows - http_error: HttpErrorFactory - error_response_model: type[BaseModel] - - -@dataclass(frozen=True, slots=True) -class ChangeImpactReadRouteDependencies: - common: ReadRouteDependencies - read_evaluation_identity: Callable[..., LaunchplaneIdentity] - repository_evidence_provider: ChangeImpactRepositoryEvidenceProvider - - -class ChangeImpactPolicyReadResponse(BaseModel): - model_config = ConfigDict(extra="forbid", frozen=True) - - status: Literal["ok"] = "ok" - trace_id: str - read_model: ChangeImpactPolicyReadModel - - -class ChangeImpactEvaluationResponse(BaseModel): - model_config = ConfigDict(extra="forbid", frozen=True) - - status: Literal["ok"] = "ok" - trace_id: str - evaluation: ChangeImpactEvaluation - - -class ChangeImpactPolicyApplyEnvelope(BaseModel): - model_config = ConfigDict(extra="forbid", frozen=True) - - schema_version: int = Field(default=1, ge=1) - mode: Literal["dry_run", "apply"] = "apply" - expected_current_record_id: str = "" - expected_current_policy_digest: str = "" - record: ChangeImpactPolicyRecord - - -class ChangeImpactPolicyApplyResponse(BaseModel): - model_config = ConfigDict(extra="forbid", frozen=True) - - status: Literal["ok"] = "ok" - trace_id: str - result: ChangeImpactPolicyApplyResult - - -def register_change_impact_read_routes( - app: ApiRouteRegistrar, - *, - dependencies: ChangeImpactReadRouteDependencies, -) -> None: - common = dependencies.common - - def read_policy( - repository_id: Annotated[str, Query(...)], - identity: Annotated[LaunchplaneIdentity, Depends(common.read_identity)], - record_store: Annotated[object, Depends(common.get_record_store)], - ) -> ChangeImpactPolicyReadResponse: - trace_id = common.next_trace_id() - if not common.authorization_allows( - identity=identity, - action=CHANGE_IMPACT_POLICY_READ_ACTION, - product="launchplane", - context="change-impact", - target=AuthorizationTarget(scope="context"), - ): - raise common.http_error( - status_code=403, - trace_id=trace_id, - code="authorization_denied", - message="Caller cannot read change-impact policy records.", - ) - try: - store = require_change_impact_policy_read_store(record_store) - read_model = get_change_impact_policy_read_model( - store=store, - repository_id=repository_id, - audit_store=( - cast(ChangeImpactPolicyAuditReadStore, record_store) - if callable(getattr(record_store, "read_change_impact_policy_audit", None)) - else None - ), - ) - except TypeError as error: - raise common.http_error( - status_code=503, - trace_id=trace_id, - code="database_storage_required", - message=str(error), - ) from error - except ValueError as error: - raise common.http_error( - status_code=400, - trace_id=trace_id, - code="invalid_request", - message=str(error), - ) from error - return ChangeImpactPolicyReadResponse(trace_id=trace_id, read_model=read_model) - - def evaluate( - envelope: ChangeImpactEvaluationRequest, - identity: Annotated[ - LaunchplaneIdentity, - Depends(dependencies.read_evaluation_identity), - ], - record_store: Annotated[object, Depends(common.get_record_store)], - ) -> ChangeImpactEvaluationResponse: - trace_id = common.next_trace_id() - if not common.authorization_allows( - identity=identity, - action=CHANGE_IMPACT_EVALUATION_READ_ACTION, - product="launchplane", - context="change-impact", - target=AuthorizationTarget(scope="context"), - ): - raise common.http_error( - status_code=403, - trace_id=trace_id, - code="authorization_denied", - message="Caller cannot evaluate change impact.", - ) - if isinstance(identity, GitHubActionsIdentity) and ( - identity.repository.strip().lower() != envelope.target.repository - ): - raise common.http_error( - status_code=403, - trace_id=trace_id, - code="authorization_denied", - message="GitHub Actions callers can evaluate only their OIDC repository.", - ) - try: - repository_evidence = dependencies.repository_evidence_provider.resolve(envelope.target) - store = require_change_impact_policy_read_store(record_store) - policies = store.list_change_impact_policy_records( - repository_id=repository_evidence.target.repository_id, - ) - stored_evidence = load_change_impact_stored_evidence( - store=record_store, - target=repository_evidence.target, - ) - trusted_caller_binding = ( - ChangeImpactTrustedCallerBinding( - repository=identity.repository, - repository_id=identity.repository_id, - repository_owner_id=identity.repository_owner_id, - head_sha=identity.sha, - ref=identity.ref, - ) - if isinstance(identity, GitHubActionsIdentity) - else None - ) - evaluation = evaluate_change_impact( - repository_evidence=repository_evidence, - policies=policies, - stored_evidence=stored_evidence, - trusted_caller_binding=trusted_caller_binding, - ) - except ChangeImpactRepositoryEvidenceStaleError as error: - raise common.http_error( - status_code=409, - trace_id=trace_id, - code="change_impact_target_stale", - message=str(error), - ) from error - except ChangeImpactRepositoryEvidenceError as error: - raise common.http_error( - status_code=503, - trace_id=trace_id, - code="change_impact_repository_evidence_unavailable", - message=str(error), - ) from error - except TypeError as error: - raise common.http_error( - status_code=503, - trace_id=trace_id, - code="database_storage_required", - message=str(error), - ) from error - return ChangeImpactEvaluationResponse(trace_id=trace_id, evaluation=evaluation) - - app.add_api_route( - CHANGE_IMPACT_POLICY_READ_ROUTE, - read_policy, - methods=["GET"], - response_model=ChangeImpactPolicyReadResponse, - tags=["change-impact"], - operation_id="read_change_impact_policy", - responses={ - 403: {"model": common.error_response_model}, - 503: {"model": common.error_response_model}, - }, - ) - app.add_api_route( - CHANGE_IMPACT_EVALUATION_ROUTE, - evaluate, - methods=["POST"], - response_model=ChangeImpactEvaluationResponse, - tags=["change-impact"], - operation_id="evaluate_change_impact", - responses={ - 403: {"model": common.error_response_model}, - 409: {"model": common.error_response_model}, - 503: {"model": common.error_response_model}, - }, - ) - - -def register_change_impact_write_routes( - app: ApiRouteRegistrar, - *, - dependencies: ChangeImpactWriteRouteDependencies, -) -> None: - def apply_policy( - envelope: ChangeImpactPolicyApplyEnvelope, - identity: Annotated[LaunchplaneIdentity, Depends(dependencies.read_write_identity)], - record_store: Annotated[object, Depends(dependencies.get_record_store)], - ) -> ChangeImpactPolicyApplyResponse: - trace_id = dependencies.next_trace_id() - if not dependencies.authorization_allows( - identity=identity, - action=CHANGE_IMPACT_POLICY_WRITE_ACTION, - product="launchplane", - context="change-impact", - target=AuthorizationTarget(scope="context"), - ): - raise dependencies.http_error( - status_code=403, - trace_id=trace_id, - code="authorization_denied", - message="Caller cannot write change-impact policy records.", - ) - try: - audit = derive_change_impact_policy_audit( - identity=identity, record=envelope.record, trace_id=trace_id - ) - except (ChangeImpactPolicyAttributionError, ValidationError) as error: - raise dependencies.http_error( - status_code=403, - trace_id=trace_id, - code="policy_attribution_unavailable", - message="Caller cannot provide policy administration attribution.", - ) from error - try: - store = require_change_impact_policy_audited_store(record_store) - result = apply_change_impact_policy_with_audit( - store=store, - record=envelope.record, - audit=audit, - expected_current_record_id=envelope.expected_current_record_id, - expected_current_policy_digest=envelope.expected_current_policy_digest, - mode=envelope.mode, - ) - except TypeError as error: - raise dependencies.http_error( - status_code=503, - trace_id=trace_id, - code="database_storage_required", - message=str(error), - ) from error - except (ChangeImpactPolicyConflictError, ChangeImpactPolicySequenceError) as error: - raise dependencies.http_error( - status_code=409, - trace_id=trace_id, - code="change_impact_policy_conflict", - message=str(error), - ) from error - return ChangeImpactPolicyApplyResponse(trace_id=trace_id, result=result) - - app.add_api_route( - CHANGE_IMPACT_POLICY_APPLY_ROUTE, - apply_policy, - methods=["POST"], - response_model=ChangeImpactPolicyApplyResponse, - status_code=202, - tags=["change-impact"], - operation_id="apply_change_impact_policy", - responses={ - 403: {"model": dependencies.error_response_model}, - 409: {"model": dependencies.error_response_model}, - 503: {"model": dependencies.error_response_model}, - }, - ) diff --git a/control_plane/http_routes/engineering_review_decision.py b/control_plane/http_routes/engineering_review_decision.py index 4d246cf4c..b3940ce62 100644 --- a/control_plane/http_routes/engineering_review_decision.py +++ b/control_plane/http_routes/engineering_review_decision.py @@ -5,8 +5,12 @@ from fastapi import Depends, Path from pydantic import BaseModel, ConfigDict -from control_plane.change_impact_service import ChangeImpactRepositoryEvidenceProvider -from control_plane.contracts.change_impact import ChangeImpactTargetReference +from control_plane.contracts.repository_evidence import ( + RepositoryEvidenceProvider, +) +from control_plane.contracts.repository_evidence import ( + RepositoryTargetReference, +) from control_plane.contracts.engineering_review_decision import ( ENGINEERING_REVIEW_DECISION_CREATE_ACTION, ENGINEERING_REVIEW_DECISION_PROJECT_ACTION, @@ -57,7 +61,7 @@ class EngineeringReviewDecisionRouteDependencies: authorization_allows: Callable[..., bool] http_error: Callable[..., Exception] error_response_model: type[BaseModel] - repository_evidence_provider: ChangeImpactRepositoryEvidenceProvider + repository_evidence_provider: RepositoryEvidenceProvider github_app_token: Callable[[str, str], GitHubAppInstallationToken] github_api: Callable[..., object] @@ -173,7 +177,7 @@ async def project( "Engineering review projection requires the latest persisted decision." ) current_evidence = write_dependencies.repository_evidence_provider.resolve( - ChangeImpactTargetReference( + RepositoryTargetReference( repository=decision.target.repository, pull_request_number=decision.target.pull_request_number, ) diff --git a/control_plane/http_routes/engineering_review_run.py b/control_plane/http_routes/engineering_review_run.py index 236a0d330..17bff6266 100644 --- a/control_plane/http_routes/engineering_review_run.py +++ b/control_plane/http_routes/engineering_review_run.py @@ -35,7 +35,9 @@ require_engineering_review_authority_store, require_engineering_review_run_create_store, ) -from control_plane.change_impact_service import ChangeImpactRepositoryEvidenceProvider +from control_plane.contracts.repository_evidence import ( + RepositoryEvidenceProvider, +) from control_plane.http_routes.support import ApiRouteRegistrar, ReadRouteDependencies from control_plane.service_auth import LaunchplaneIdentity @@ -170,7 +172,7 @@ class EngineeringReviewWriteRouteDependencies: http_error: Callable[..., Exception] error_response_model: type[BaseModel] target_resolver: EngineeringReviewTargetResolver - repository_evidence_provider: ChangeImpactRepositoryEvidenceProvider + repository_evidence_provider: RepositoryEvidenceProvider def _store(record_store: object) -> EngineeringReviewStore: diff --git a/control_plane/http_routes/generic_web.py b/control_plane/http_routes/generic_web.py index e02f6d4e3..c32bd8b2f 100644 --- a/control_plane/http_routes/generic_web.py +++ b/control_plane/http_routes/generic_web.py @@ -115,9 +115,7 @@ AuthorizationAllows, HttpErrorFactory, ) -from control_plane.manager_preview_approval_github_webhook import ( - invalidate_manager_preview_approval_for_pr_best_effort, -) + from control_plane.product_promotion_http import ( build_product_promotion_status, product_promotion_intent_matches, @@ -151,7 +149,6 @@ from control_plane.workflows.generic_web_preview import ( GenericWebPreviewProfileStore, discover_generic_web_preview_desired_state, - preview_pr_number_from_slug, resolve_generic_web_preview_slug, ) from control_plane.workflows.ship import utc_now_timestamp @@ -368,19 +365,6 @@ def build_generic_web_write_route_handlers( ) ) - def manager_preview_pr_number( - *, - profile: LaunchplaneProductProfileRecord, - anchor_pr_number: int | None, - preview_slug: str, - ) -> int | None: - if anchor_pr_number is not None: - return anchor_pr_number - return preview_pr_number_from_slug( - preview_slug=preview_slug, - slug_template=profile.preview.slug_template, - ) - def require_product_promotion_intent( *, record_store: object, @@ -1229,24 +1213,7 @@ async def apply_generic_web_preview_destroy( code="invalid_request", message="Request could not be completed.", ) from error - pr_number = manager_preview_pr_number( - profile=profile, - anchor_pr_number=destroy_request.destroy.anchor_pr_number, - preview_slug=destroy_request.destroy.preview_slug, - ) - if pr_number is not None and result.get("destroy_status") == "pass": - invalidate_manager_preview_approval_for_pr_best_effort( - repository=profile.repository, - pr_number=pr_number, - reason="The serving preview was destroyed.", - source_event_kind="preview_destroy", - source_event_id=( - f"{profile.product}:{profile.preview.context}:{pr_number}:" - f"{str(result.get('destroy_finished_at') or '').strip()}" - ), - record_store=record_store, - control_plane_root=dependencies.control_plane_root, - ) + response = accepted_evidence_response( trace_id=trace_id, records=records, diff --git a/control_plane/http_routes/governance_projection.py b/control_plane/http_routes/governance_projection.py index 460adae1e..8f9107315 100644 --- a/control_plane/http_routes/governance_projection.py +++ b/control_plane/http_routes/governance_projection.py @@ -4,9 +4,15 @@ from fastapi import Depends, Query -from control_plane.change_impact_github import ChangeImpactRepositoryEvidenceError -from control_plane.change_impact_service import ChangeImpactRepositoryEvidenceProvider -from control_plane.contracts.change_impact import ChangeImpactTargetReference +from control_plane.repository_evidence import ( + RepositoryEvidenceError, +) +from control_plane.contracts.repository_evidence import ( + RepositoryEvidenceProvider, +) +from control_plane.contracts.repository_evidence import ( + RepositoryTargetReference, +) from control_plane.contracts.engineering_review_decision import ( ENGINEERING_REVIEW_DECISION_READ_ACTION, ) @@ -34,7 +40,7 @@ @dataclass(frozen=True, slots=True) class GovernanceProjectionRouteDependencies: common: ReadRouteDependencies - repository_evidence_provider: ChangeImpactRepositoryEvidenceProvider + repository_evidence_provider: RepositoryEvidenceProvider current_readiness_provider: GovernanceCurrentReadinessProvider now: Callable[[], str] @@ -85,7 +91,7 @@ def read_governance_projection( code="authorization_denied", message="Caller cannot read every governance evidence facet.", ) - target = ChangeImpactTargetReference( + target = RepositoryTargetReference( repository=repository, pull_request_number=pull_request_number, ) @@ -130,7 +136,7 @@ def read_governance_projection( ) try: repository_evidence = dependencies.repository_evidence_provider.resolve(target) - except (ChangeImpactRepositoryEvidenceError, LookupError, TypeError, ValueError): + except (RepositoryEvidenceError, LookupError, TypeError, ValueError): raise common.http_error( status_code=503, trace_id=trace_id, diff --git a/control_plane/http_routes/product_owner.py b/control_plane/http_routes/product_owner.py deleted file mode 100644 index 0acc8f687..000000000 --- a/control_plane/http_routes/product_owner.py +++ /dev/null @@ -1,567 +0,0 @@ -from collections.abc import Callable -from dataclasses import dataclass -from typing import Annotated, Literal - -from fastapi import Depends, Query -from pydantic import BaseModel, ConfigDict, Field - -from control_plane.contracts.product_owner import ( - PRODUCT_OWNER_POLICY_READ_ACTION, - PRODUCT_OWNER_POLICY_WRITE_ACTION, - PRODUCT_OWNER_REQUIREMENT_READ_ACTION, - PRODUCT_OWNER_REQUIREMENT_WRITE_ACTION, - PRODUCT_OWNER_ROUTING_READ_ACTION, - PRODUCT_OWNER_ROUTING_WRITE_ACTION, - ProductOwnerActionContext, - ProductOwnerActorIdentity, - ProductOwnerPolicyRecord, - ProductOwnerRequirementRecord, - ProductOwnerRoutingRecord, - ProductOwnerAuthorityEvaluation, -) -from control_plane.contracts.owner_acceptance import OWNER_ACCEPTANCE_READ_ACTION -from control_plane.http_routes.support import ( - ApiRouteRegistrar, - AuthorizationAllows, - HttpErrorFactory, - ReadRouteDependencies, -) -from control_plane.product_owner_service import ( - ProductOwnerPolicyApplyResult, - ProductOwnerPolicyConflictError, - ProductOwnerPolicySequenceError, - ProductOwnerReadModel, - ProductOwnerRequirementApplyResult, - ProductOwnerRequirementConflictError, - ProductOwnerRequirementSequenceError, - ProductOwnerRoutingApplyResult, - ProductOwnerRoutingConflictError, - ProductOwnerRoutingSequenceError, - apply_product_owner_policy, - apply_product_owner_requirement, - apply_product_owner_routing, - evaluate_product_owner_authority, - get_product_owner_read_model, - require_product_owner_policy_read_store, - require_product_owner_policy_store, - require_product_owner_requirement_read_store, - require_product_owner_requirement_store, - require_product_owner_routing_read_store, - require_product_owner_routing_store, -) -from control_plane.service_auth import AuthorizationTarget, GitHubHumanIdentity, LaunchplaneIdentity - - -PRODUCT_OWNER_POLICY_READ_ROUTE = "/v1/product-owner/policy" -PRODUCT_OWNER_REQUIREMENT_READ_ROUTE = "/v1/product-owner/requirement" -PRODUCT_OWNER_ROUTING_READ_ROUTE = "/v1/product-owner/routing" -PRODUCT_OWNER_AUTHORITY_EVALUATION_ROUTE = "/v1/product-owner/evaluation" -PRODUCT_OWNER_POLICY_APPLY_ROUTE = "/v1/product-owner/policies/apply" -PRODUCT_OWNER_REQUIREMENT_APPLY_ROUTE = "/v1/product-owner/requirements/apply" -PRODUCT_OWNER_ROUTING_APPLY_ROUTE = "/v1/product-owner/routing/apply" - - -@dataclass(frozen=True, slots=True) -class ProductOwnerWriteRouteDependencies: - read_write_identity: Callable[..., LaunchplaneIdentity] - get_record_store: Callable[[], object] - next_trace_id: Callable[[], str] - authorization_allows: AuthorizationAllows - http_error: HttpErrorFactory - error_response_model: type[BaseModel] - - -class ProductOwnerReadResponse(BaseModel): - model_config = ConfigDict(extra="forbid") - - status: Literal["ok"] = "ok" - trace_id: str - read_model: ProductOwnerReadModel - - -class ProductOwnerAuthorityEvaluationResponse(BaseModel): - model_config = ConfigDict(extra="forbid") - - status: Literal["ok"] = "ok" - trace_id: str - evaluation: ProductOwnerAuthorityEvaluation - - -class ProductOwnerPolicyApplyEnvelope(BaseModel): - model_config = ConfigDict(extra="forbid") - - schema_version: int = Field(default=1, ge=1) - mode: Literal["dry_run", "apply"] = "apply" - expected_current_record_id: str = "" - expected_current_policy_digest: str = "" - record: ProductOwnerPolicyRecord - - -class ProductOwnerRequirementApplyEnvelope(BaseModel): - model_config = ConfigDict(extra="forbid") - - schema_version: int = Field(default=1, ge=1) - mode: Literal["dry_run", "apply"] = "apply" - expected_current_record_id: str = "" - expected_current_requirement_digest: str = "" - record: ProductOwnerRequirementRecord - - -class ProductOwnerRoutingApplyEnvelope(BaseModel): - model_config = ConfigDict(extra="forbid") - - schema_version: int = Field(default=1, ge=1) - mode: Literal["dry_run", "apply"] = "apply" - expected_current_record_id: str = "" - expected_current_routing_digest: str = "" - record: ProductOwnerRoutingRecord - - -class ProductOwnerPolicyApplyResponse(BaseModel): - model_config = ConfigDict(extra="forbid") - - status: Literal["ok"] = "ok" - trace_id: str - result: ProductOwnerPolicyApplyResult - - -class ProductOwnerRequirementApplyResponse(BaseModel): - model_config = ConfigDict(extra="forbid") - - status: Literal["ok"] = "ok" - trace_id: str - result: ProductOwnerRequirementApplyResult - - -class ProductOwnerRoutingApplyResponse(BaseModel): - model_config = ConfigDict(extra="forbid") - - status: Literal["ok"] = "ok" - trace_id: str - result: ProductOwnerRoutingApplyResult - - -def register_product_owner_read_routes( - app: ApiRouteRegistrar, - *, - dependencies: ReadRouteDependencies, -) -> None: - def read_product_owner( - action: str, - product: str, - system: str, - identity: LaunchplaneIdentity, - record_store: object, - ) -> ProductOwnerReadResponse: - trace_id = dependencies.next_trace_id() - try: - normalized_product, normalized_system = _normalize_scope( - product=product, - system=system, - ) - except ValueError as error: - raise dependencies.http_error( - status_code=400, - trace_id=trace_id, - code="invalid_request", - message=str(error), - ) from error - if not dependencies.authorization_allows( - identity=identity, - action=action, - product=normalized_product, - context=normalized_system, - target=AuthorizationTarget(scope="context"), - ): - raise dependencies.http_error( - status_code=403, - trace_id=trace_id, - code="authorization_denied", - message="Caller cannot read product Owner policy records.", - ) - try: - read_model = get_product_owner_read_model( - store=record_store, - product=normalized_product, - system=normalized_system, - ) - except TypeError as error: - raise dependencies.http_error( - status_code=503, - trace_id=trace_id, - code="database_storage_required", - message=str(error), - ) from error - return ProductOwnerReadResponse(trace_id=trace_id, read_model=read_model) - - def read_policy( - product: Annotated[str, Query(...)], - system: Annotated[str, Query(...)], - identity: Annotated[LaunchplaneIdentity, Depends(dependencies.read_identity)], - record_store: Annotated[object, Depends(dependencies.get_record_store)], - ) -> ProductOwnerReadResponse: - return read_product_owner( - PRODUCT_OWNER_POLICY_READ_ACTION, - product, - system, - identity, - record_store, - ) - - def read_requirement( - product: Annotated[str, Query(...)], - system: Annotated[str, Query(...)], - identity: Annotated[LaunchplaneIdentity, Depends(dependencies.read_identity)], - record_store: Annotated[object, Depends(dependencies.get_record_store)], - ) -> ProductOwnerReadResponse: - return read_product_owner( - PRODUCT_OWNER_REQUIREMENT_READ_ACTION, - product, - system, - identity, - record_store, - ) - - def read_routing( - product: Annotated[str, Query(...)], - system: Annotated[str, Query(...)], - identity: Annotated[LaunchplaneIdentity, Depends(dependencies.read_identity)], - record_store: Annotated[object, Depends(dependencies.get_record_store)], - ) -> ProductOwnerReadResponse: - return read_product_owner( - PRODUCT_OWNER_ROUTING_READ_ACTION, - product, - system, - identity, - record_store, - ) - - def read_authority_evaluation( - product: Annotated[str, Query(...)], - system: Annotated[str, Query(...)], - repository_id: Annotated[str, Query(...)], - environment: Annotated[str, Query(...)], - action: Annotated[str, Query(...)], - identity: Annotated[LaunchplaneIdentity, Depends(dependencies.read_identity)], - record_store: Annotated[object, Depends(dependencies.get_record_store)], - claimed_policy_revision: Annotated[int | None, Query(ge=1)] = None, - claimed_policy_digest: Annotated[str, Query()] = "", - claimed_requirement_revision: Annotated[int | None, Query(ge=1)] = None, - claimed_requirement_digest: Annotated[str, Query()] = "", - ) -> ProductOwnerAuthorityEvaluationResponse: - trace_id = dependencies.next_trace_id() - try: - context = ProductOwnerActionContext( - product=product, - system=system, - repository_id=repository_id, - environment=environment, - action=action, - ) - except ValueError as error: - raise dependencies.http_error( - status_code=400, - trace_id=trace_id, - code="invalid_request", - message=str(error), - ) from error - if not dependencies.authorization_allows( - identity=identity, - action=OWNER_ACCEPTANCE_READ_ACTION, - product=context.product, - context=context.system, - target=AuthorizationTarget(scope="context"), - ): - raise dependencies.http_error( - status_code=403, - trace_id=trace_id, - code="authorization_denied", - message="Caller cannot read product Owner authority evaluation.", - ) - try: - actor = _actor_identity(identity) - except (TypeError, ValueError) as error: - raise dependencies.http_error( - status_code=403, - trace_id=trace_id, - code="product_owner_actor_identity_required", - message="Product Owner authority evaluation requires a human GitHub identity.", - ) from error - try: - policies = require_product_owner_policy_read_store( - record_store - ).list_product_owner_policy_records( - product=context.product, - system=context.system, - ) - requirements = require_product_owner_requirement_read_store( - record_store - ).list_product_owner_requirement_records( - product=context.product, - system=context.system, - ) - routings = require_product_owner_routing_read_store( - record_store - ).list_product_owner_routing_records( - product=context.product, - system=context.system, - ) - except TypeError as error: - raise dependencies.http_error( - status_code=503, - trace_id=trace_id, - code="database_storage_required", - message="Product Owner authority storage is unavailable.", - ) from error - evaluation = evaluate_product_owner_authority( - context=context, - actor=actor, - policies=policies, - requirements=requirements, - routings=routings, - claimed_policy_revision=claimed_policy_revision, - claimed_policy_digest=claimed_policy_digest, - claimed_requirement_revision=claimed_requirement_revision, - claimed_requirement_digest=claimed_requirement_digest, - ) - return ProductOwnerAuthorityEvaluationResponse(trace_id=trace_id, evaluation=evaluation) - - errors = { - 400: {"model": dependencies.error_response_model}, - 401: {"model": dependencies.error_response_model}, - 403: {"model": dependencies.error_response_model}, - 503: {"model": dependencies.error_response_model}, - } - for path, endpoint, operation_id, summary in ( - ( - PRODUCT_OWNER_POLICY_READ_ROUTE, - read_policy, - "read_product_owner_policy", - "Read the product Owner policy bundle", - ), - ( - PRODUCT_OWNER_REQUIREMENT_READ_ROUTE, - read_requirement, - "read_product_owner_requirement", - "Read the product Owner requirement bundle", - ), - ( - PRODUCT_OWNER_ROUTING_READ_ROUTE, - read_routing, - "read_product_owner_routing", - "Read non-authoritative product Owner routing", - ), - ): - app.add_api_route( - path, - endpoint, - methods=["GET"], - response_model=ProductOwnerReadResponse, - operation_id=operation_id, - summary=summary, - responses=errors, - ) - app.add_api_route( - PRODUCT_OWNER_AUTHORITY_EVALUATION_ROUTE, - read_authority_evaluation, - methods=["GET"], - response_model=ProductOwnerAuthorityEvaluationResponse, - operation_id="read_product_owner_authority_evaluation", - summary="Evaluate current product Owner authority", - responses=errors, - ) - - -def register_product_owner_write_routes( - app: ApiRouteRegistrar, - *, - dependencies: ProductOwnerWriteRouteDependencies, -) -> None: - async def apply_policy( - envelope: ProductOwnerPolicyApplyEnvelope, - identity: Annotated[LaunchplaneIdentity, Depends(dependencies.read_write_identity)], - record_store: Annotated[object, Depends(dependencies.get_record_store)], - ) -> ProductOwnerPolicyApplyResponse: - trace_id = dependencies.next_trace_id() - _require_write_authorization( - dependencies=dependencies, - identity=identity, - action=PRODUCT_OWNER_POLICY_WRITE_ACTION, - product=envelope.record.product, - system=envelope.record.system, - trace_id=trace_id, - ) - try: - result = apply_product_owner_policy( - store=require_product_owner_policy_store(record_store), - record=envelope.record, - expected_current_record_id=envelope.expected_current_record_id, - expected_current_policy_digest=envelope.expected_current_policy_digest, - mode=envelope.mode, - ) - except (ProductOwnerPolicyConflictError, ProductOwnerPolicySequenceError) as error: - raise _apply_error(dependencies, trace_id, error) from error - return ProductOwnerPolicyApplyResponse(trace_id=trace_id, result=result) - - async def apply_requirement( - envelope: ProductOwnerRequirementApplyEnvelope, - identity: Annotated[LaunchplaneIdentity, Depends(dependencies.read_write_identity)], - record_store: Annotated[object, Depends(dependencies.get_record_store)], - ) -> ProductOwnerRequirementApplyResponse: - trace_id = dependencies.next_trace_id() - _require_write_authorization( - dependencies=dependencies, - identity=identity, - action=PRODUCT_OWNER_REQUIREMENT_WRITE_ACTION, - product=envelope.record.product, - system=envelope.record.system, - trace_id=trace_id, - ) - try: - result = apply_product_owner_requirement( - store=require_product_owner_requirement_store(record_store), - record=envelope.record, - expected_current_record_id=envelope.expected_current_record_id, - expected_current_requirement_digest=(envelope.expected_current_requirement_digest), - mode=envelope.mode, - ) - except ( - ProductOwnerRequirementConflictError, - ProductOwnerRequirementSequenceError, - ) as error: - raise _apply_error(dependencies, trace_id, error) from error - return ProductOwnerRequirementApplyResponse(trace_id=trace_id, result=result) - - async def apply_routing( - envelope: ProductOwnerRoutingApplyEnvelope, - identity: Annotated[LaunchplaneIdentity, Depends(dependencies.read_write_identity)], - record_store: Annotated[object, Depends(dependencies.get_record_store)], - ) -> ProductOwnerRoutingApplyResponse: - trace_id = dependencies.next_trace_id() - _require_write_authorization( - dependencies=dependencies, - identity=identity, - action=PRODUCT_OWNER_ROUTING_WRITE_ACTION, - product=envelope.record.product, - system=envelope.record.system, - trace_id=trace_id, - ) - try: - result = apply_product_owner_routing( - store=require_product_owner_routing_store(record_store), - record=envelope.record, - expected_current_record_id=envelope.expected_current_record_id, - expected_current_routing_digest=envelope.expected_current_routing_digest, - mode=envelope.mode, - ) - except (ProductOwnerRoutingConflictError, ProductOwnerRoutingSequenceError) as error: - raise _apply_error(dependencies, trace_id, error) from error - return ProductOwnerRoutingApplyResponse(trace_id=trace_id, result=result) - - errors = { - 400: {"model": dependencies.error_response_model}, - 401: {"model": dependencies.error_response_model}, - 403: {"model": dependencies.error_response_model}, - 409: {"model": dependencies.error_response_model}, - 503: {"model": dependencies.error_response_model}, - } - for path, endpoint, response_model, operation_id, summary in ( - ( - PRODUCT_OWNER_POLICY_APPLY_ROUTE, - apply_policy, - ProductOwnerPolicyApplyResponse, - "apply_product_owner_policy", - "Apply or dry-run a product Owner policy revision", - ), - ( - PRODUCT_OWNER_REQUIREMENT_APPLY_ROUTE, - apply_requirement, - ProductOwnerRequirementApplyResponse, - "apply_product_owner_requirement", - "Apply or dry-run a product Owner requirement revision", - ), - ( - PRODUCT_OWNER_ROUTING_APPLY_ROUTE, - apply_routing, - ProductOwnerRoutingApplyResponse, - "apply_product_owner_routing", - "Apply or dry-run non-authoritative product Owner routing", - ), - ): - app.add_api_route( - path, - endpoint, - methods=["POST"], - status_code=202, - response_model=response_model, - operation_id=operation_id, - summary=summary, - responses=errors, - ) - - -def _normalize_scope(*, product: str, system: str) -> tuple[str, str]: - normalized_product = product.strip() - normalized_system = system.strip() - if not normalized_product or not normalized_system: - raise ValueError("Product Owner scope requires product and system.") - return normalized_product, normalized_system - - -def _actor_identity(identity: LaunchplaneIdentity) -> ProductOwnerActorIdentity: - if not isinstance(identity, GitHubHumanIdentity): - raise TypeError("Product Owner actors must be human GitHub identities.") - return ProductOwnerActorIdentity( - provider="github", - provider_subject_id=str(identity.github_id), - ) - - -def _require_write_authorization( - *, - dependencies: ProductOwnerWriteRouteDependencies, - identity: LaunchplaneIdentity, - action: str, - product: str, - system: str, - trace_id: str, -) -> None: - if dependencies.authorization_allows( - identity=identity, - action=action, - product=product, - context=system, - target=AuthorizationTarget(scope="context"), - ): - return - raise dependencies.http_error( - status_code=403, - trace_id=trace_id, - code="authorization_denied", - message="Caller cannot administer product Owner records.", - ) - - -def _apply_error( - dependencies: ProductOwnerWriteRouteDependencies, - trace_id: str, - error: ValueError, -) -> Exception: - is_sequence_error = isinstance( - error, - ( - ProductOwnerPolicySequenceError, - ProductOwnerRequirementSequenceError, - ProductOwnerRoutingSequenceError, - ), - ) - return dependencies.http_error( - status_code=409, - trace_id=trace_id, - code=( - "product_owner_revision_sequence_error" - if is_sequence_error - else "product_owner_revision_conflict" - ), - message=str(error), - ) diff --git a/control_plane/manager_preview_approval.py b/control_plane/manager_preview_approval.py deleted file mode 100644 index 7076bc227..000000000 --- a/control_plane/manager_preview_approval.py +++ /dev/null @@ -1,520 +0,0 @@ -from __future__ import annotations - -from datetime import datetime, timezone -from typing import NamedTuple, Protocol, cast - -from control_plane.contracts.authz_policy_record import LaunchplaneAuthzPolicyRecord -from control_plane.contracts.manager_preview_approval import ( - MANAGER_PREVIEW_APPROVAL_WRITE_ACTION, - ManagerPreviewApprovalAction, - ManagerPreviewApprovalAuthorization, - ManagerPreviewApprovalBinding, - ManagerPreviewApprovalDecision, - ManagerPreviewApprovalDecisionStatus, - ManagerPreviewApprovalEventRecord, - ManagerPreviewApprovalEventWriteStatus, - ManagerPreviewApprovalReasonCode, -) -from control_plane.contracts.repository_human_admission import ( - RepositoryHumanRolePolicyRecord, -) -from control_plane.contracts.preview_generation_record import PreviewGenerationRecord -from control_plane.contracts.preview_record import PreviewRecord -from control_plane.repository_human_admission import ( - RepositoryHumanRolePolicyError, - manager_authority_current, - manager_role_policy_provenance, -) -from control_plane.preview_serving_evidence import ( - PreviewServingEvidenceError, - verify_serving_preview, -) -from control_plane.service_auth import AuthorizationTarget, GitHubHumanIdentity -from control_plane.service_auth import matching_github_human_policy_rules - - -_TERMINAL_APPROVAL_ACTIONS = frozenset({"invalidated", "superseded"}) - - -class ManagerPreviewApprovalEvidenceError(ValueError): - def __init__(self, *, code: ManagerPreviewApprovalReasonCode, message: str) -> None: - super().__init__(message) - self.code = code - - -class ManagerPreviewApprovalAuthorizationError(PermissionError): - pass - - -class ManagerPreviewApprovalEventConflictError(RuntimeError): - pass - - -class ManagerPreviewApprovalEventStore(Protocol): - def write_manager_preview_approval_event_record( - self, record: ManagerPreviewApprovalEventRecord - ) -> ManagerPreviewApprovalEventWriteStatus: - raise NotImplementedError - - def list_manager_preview_approval_event_records( - self, - *, - product: str = "", - context: str = "", - repository: str = "", - pr_number: int | None = None, - preview_id: str = "", - action: str = "", - limit: int | None = None, - ) -> tuple[ManagerPreviewApprovalEventRecord, ...]: - raise NotImplementedError - - -class ManagerPreviewApprovalWriteResult(NamedTuple): - status: ManagerPreviewApprovalEventWriteStatus - record: ManagerPreviewApprovalEventRecord - - -def manager_preview_approval_required( - *, - policy_record: LaunchplaneAuthzPolicyRecord, - product: str, - context: str, -) -> bool: - if policy_record.status != "active" or policy_record.policy.schema_version not in (2, 3): - return False - return any( - rule.managed_set_id is not None - and rule.managed_rule_id is not None - and bool(rule.github_ids) - and MANAGER_PREVIEW_APPROVAL_WRITE_ACTION in rule.actions - and (not rule.products or product in rule.products) - and (not rule.contexts or context in rule.contexts) - for rule in policy_record.policy.github_humans - ) - - -def build_current_manager_preview_approval_binding( - *, - product: str, - preview: PreviewRecord, - generation: PreviewGenerationRecord, -) -> ManagerPreviewApprovalBinding: - try: - evidence = verify_serving_preview( - product=product, - preview=preview, - generation=generation, - ) - return ManagerPreviewApprovalBinding( - product=product, - context=evidence.context, - repository=evidence.anchor_repo, - pr_number=evidence.anchor_pr_number, - pr_url=evidence.anchor_pr_url, - head_sha=evidence.head_sha, - preview_id=evidence.preview_id, - serving_generation_id=evidence.serving_generation_id, - artifact_id=evidence.artifact_id, - artifact_image_digest=evidence.artifact_image_digest, - manifest_fingerprint=evidence.manifest_fingerprint, - preview_url=evidence.preview_url, - runtime_identity=evidence.runtime_identity, - ) - except PreviewServingEvidenceError as error: - raise ManagerPreviewApprovalEvidenceError( - code=error.code, - message=str(error), - ) from error - except ValueError as error: - raise ManagerPreviewApprovalEvidenceError( - code="runtime_identity_mismatch", - message="The serving preview runtime identity is incomplete or inconsistent.", - ) from error - - -def capture_manager_preview_approval_authorization( - *, - identity: GitHubHumanIdentity, - product: str, - context: str, - policy_record: LaunchplaneAuthzPolicyRecord, - authorized_at: str, - role_policy_record: RepositoryHumanRolePolicyRecord | None = None, - repository_id: str = "", - repository_owner_id: str = "", - repository: str = "", -) -> ManagerPreviewApprovalAuthorization: - if identity.github_id < 1: - raise ManagerPreviewApprovalAuthorizationError( - "Manager preview approval requires a stable GitHub numeric identity." - ) - if policy_record.status != "active" or policy_record.policy.schema_version not in (2, 3): - raise ManagerPreviewApprovalAuthorizationError( - "Manager preview approval requires one active schema-v2 or schema-v3 authorization policy." - ) - role_policy_provenance = None - if role_policy_record is not None: - if not repository_id.strip() or not repository_owner_id.strip() or not repository.strip(): - raise ManagerPreviewApprovalAuthorizationError( - "Role-aware manager preview approval requires repository numeric identity." - ) - try: - role_policy_provenance = manager_role_policy_provenance( - role_policy_record=role_policy_record, - repository_id=repository_id, - repository_owner_id=repository_owner_id, - repository=repository, - product=product, - context=context, - github_id=identity.github_id, - evaluated_at=authorized_at, - ) - except RepositoryHumanRolePolicyError as error: - raise ManagerPreviewApprovalAuthorizationError(str(error)) from error - target = AuthorizationTarget(scope="context") - matching_rules = tuple( - rule - for rule in matching_github_human_policy_rules( - policy=policy_record.policy, - identity=identity, - action=MANAGER_PREVIEW_APPROVAL_WRITE_ACTION, - product=product, - context=context, - target=target, - managed_only=True, - ) - if identity.github_id in rule.github_ids - ) - if len(matching_rules) != 1: - raise ManagerPreviewApprovalAuthorizationError( - "Manager preview approval requires exactly one managed policy rule for the actor." - ) - matching_rule = matching_rules[0] - return ManagerPreviewApprovalAuthorization( - manager_github_id=identity.github_id, - manager_login=identity.login, - managed_set_id=cast(str, matching_rule.managed_set_id), - managed_rule_id=cast(str, matching_rule.managed_rule_id), - policy_record_id=policy_record.record_id, - policy_revision=policy_record.revision, - policy_sha256=policy_record.policy_sha256, - policy_source=policy_record.source, - policy_schema_version=policy_record.policy.schema_version, - role_policy_provenance=role_policy_provenance, - authorized_at=authorized_at, - ) - - -def record_manager_preview_approval_event( - *, - record_store: ManagerPreviewApprovalEventStore, - identity: GitHubHumanIdentity, - policy_record: LaunchplaneAuthzPolicyRecord, - product: str, - preview: PreviewRecord, - generation: PreviewGenerationRecord, - action: ManagerPreviewApprovalAction, - occurred_at: str, - source_event_kind: str, - source_event_id: str, - reason: str = "", - role_policy_record: RepositoryHumanRolePolicyRecord | None = None, - repository_id: str = "", - repository_owner_id: str = "", - recorded_at: str | None = None, -) -> ManagerPreviewApprovalWriteResult: - if action not in {"approved", "changes_requested", "revoked"}: - raise ValueError("Manager-authored preview approval events require a manager action.") - normalized_occurred_at = _normalize_utc_timestamp(occurred_at) - normalized_recorded_at = _normalize_utc_timestamp(recorded_at or occurred_at) - if normalized_occurred_at > normalized_recorded_at: - raise ValueError("Manager preview approval cannot be recorded before it occurred.") - binding = build_current_manager_preview_approval_binding( - product=product, - preview=preview, - generation=generation, - ) - authorization = capture_manager_preview_approval_authorization( - identity=identity, - product=binding.product, - context=binding.context, - policy_record=policy_record, - authorized_at=normalized_occurred_at, - role_policy_record=role_policy_record, - repository_id=repository_id, - repository_owner_id=repository_owner_id, - repository=binding.repository, - ) - record = ManagerPreviewApprovalEventRecord( - binding=binding, - action=action, - occurred_at=normalized_occurred_at, - source_event_kind=source_event_kind, - source_event_id=source_event_id, - reason=reason, - authorization=authorization, - ) - status = record_store.write_manager_preview_approval_event_record(record) - return ManagerPreviewApprovalWriteResult(status=status, record=record) - - -def build_manager_preview_approval_system_event( - *, - binding: ManagerPreviewApprovalBinding, - action: ManagerPreviewApprovalAction, - occurred_at: str, - source_event_kind: str, - source_event_id: str, - reason: str, -) -> ManagerPreviewApprovalEventRecord: - if action not in {"superseded", "invalidated"}: - raise ValueError("System preview approval events require superseded or invalidated action.") - return ManagerPreviewApprovalEventRecord( - binding=binding, - action=action, - occurred_at=occurred_at, - source_event_kind=source_event_kind, - source_event_id=source_event_id, - reason=reason, - ) - - -def evaluate_manager_preview_approval( - *, - product: str, - preview: PreviewRecord, - generation: PreviewGenerationRecord, - policy_record: LaunchplaneAuthzPolicyRecord | None, - events: tuple[ManagerPreviewApprovalEventRecord, ...], - evaluated_at: str, - role_policy_record: RepositoryHumanRolePolicyRecord | None = None, -) -> ManagerPreviewApprovalDecision: - """Evaluate current evidence against the complete approval history for the PR.""" - normalized_evaluated_at = _normalize_utc_timestamp(evaluated_at) - try: - binding = build_current_manager_preview_approval_binding( - product=product, - preview=preview, - generation=generation, - ) - except ManagerPreviewApprovalEvidenceError as error: - return _decision( - status="unavailable", - reason_code=error.code, - reason=str(error), - evaluated_at=normalized_evaluated_at, - ) - if ( - policy_record is None - or policy_record.status != "active" - or policy_record.policy.schema_version not in (2, 3) - ): - return _decision( - status="unavailable", - reason_code="policy_unavailable", - reason="The active manager authorization policy is unavailable.", - binding=binding, - evaluated_at=normalized_evaluated_at, - ) - - subject_events = tuple( - event - for event in events - if event.occurred_at <= normalized_evaluated_at - and event.binding.product == binding.product - and event.binding.context == binding.context - and event.binding.repository == binding.repository - and event.binding.pr_number == binding.pr_number - ) - exact_events = tuple( - event for event in subject_events if event.binding.binding_sha256 == binding.binding_sha256 - ) - if not exact_events: - if subject_events: - latest_subject_event = max( - subject_events, - key=lambda event: (event.occurred_at, event.event_id), - ) - latest_binding_events = tuple( - event - for event in subject_events - if event.binding.binding_sha256 == latest_subject_event.binding.binding_sha256 - ) - if not any( - event.action in _TERMINAL_APPROVAL_ACTIONS for event in latest_binding_events - ): - return _decision( - status="stale", - reason_code="approval_stale", - reason="Prior approval evidence does not match the current preview.", - binding=binding, - evaluated_at=normalized_evaluated_at, - ) - return _decision( - status="pending", - reason_code="approval_missing", - reason="The current preview has not been approved by the manager.", - binding=binding, - evaluated_at=normalized_evaluated_at, - ) - - terminal_events = tuple( - event for event in exact_events if event.action in _TERMINAL_APPROVAL_ACTIONS - ) - if terminal_events: - latest_terminal_event = max( - terminal_events, - key=lambda event: (event.occurred_at, event.event_id), - ) - return _decision( - status="stale", - reason_code="approval_stale", - reason="The current preview approval was superseded or invalidated.", - binding=binding, - event=latest_terminal_event, - evaluated_at=normalized_evaluated_at, - ) - - latest_event = max( - exact_events, - key=lambda event: ( - event.occurred_at, - {"approved": 0, "changes_requested": 1, "revoked": 2}.get(event.action, 3), - event.event_id, - ), - ) - if latest_event.action == "approved": - if not _approval_authorization_matches_policy( - event=latest_event, - policy_record=policy_record, - role_policy_record=role_policy_record, - evaluated_at=normalized_evaluated_at, - ): - return _decision( - status="stale", - reason_code="approval_stale", - reason="The approval was recorded under a different authorization policy.", - binding=binding, - event=latest_event, - evaluated_at=normalized_evaluated_at, - ) - return _decision( - status="approved", - reason_code="approval_valid", - reason="The manager approved the exact current preview.", - binding=binding, - event=latest_event, - evaluated_at=normalized_evaluated_at, - ) - if latest_event.action == "changes_requested": - return _decision( - status="changes_requested", - reason_code="changes_requested", - reason="The manager requested changes to the current preview.", - binding=binding, - event=latest_event, - evaluated_at=normalized_evaluated_at, - ) - if latest_event.action == "revoked": - return _decision( - status="revoked", - reason_code="approval_revoked", - reason="The manager revoked approval for the current preview.", - binding=binding, - event=latest_event, - evaluated_at=normalized_evaluated_at, - ) - return _decision( - status="stale", - reason_code="approval_stale", - reason="The current preview approval was superseded or invalidated.", - binding=binding, - event=latest_event, - evaluated_at=normalized_evaluated_at, - ) - - -def _approval_authorization_matches_policy( - *, - event: ManagerPreviewApprovalEventRecord, - policy_record: LaunchplaneAuthzPolicyRecord, - role_policy_record: RepositoryHumanRolePolicyRecord | None, - evaluated_at: str, -) -> bool: - authorization = event.authorization - if authorization is None: - return False - if ( - authorization.policy_schema_version != policy_record.policy.schema_version - or authorization.policy_record_id != policy_record.record_id - or authorization.policy_revision != policy_record.revision - or authorization.policy_sha256 != policy_record.policy_sha256 - ): - return False - matching_rules = tuple( - rule - for rule in policy_record.policy.github_humans - if rule.managed_set_id == authorization.managed_set_id - and rule.managed_rule_id == authorization.managed_rule_id - ) - if len(matching_rules) != 1: - return False - rule = matching_rules[0] - authz_current = ( - authorization.manager_github_id in rule.github_ids - and MANAGER_PREVIEW_APPROVAL_WRITE_ACTION in rule.actions - and (not rule.products or event.binding.product in rule.products) - and (not rule.contexts or event.binding.context in rule.contexts) - ) - if not authz_current: - return False - if role_policy_record is not None and ( - role_policy_record.repository != event.binding.repository - or role_policy_record.product != event.binding.product - or role_policy_record.context != event.binding.context - ): - return False - return manager_authority_current( - provenance=authorization.role_policy_provenance, - role_policy_record=role_policy_record, - manager_github_id=authorization.manager_github_id, - evaluated_at=evaluated_at, - ) - - -def _decision( - *, - status: ManagerPreviewApprovalDecisionStatus, - reason_code: ManagerPreviewApprovalReasonCode, - reason: str, - evaluated_at: str, - binding: ManagerPreviewApprovalBinding | None = None, - event: ManagerPreviewApprovalEventRecord | None = None, -) -> ManagerPreviewApprovalDecision: - return ManagerPreviewApprovalDecision( - status=status, - reason_code=reason_code, - reason=reason, - current_binding_sha256=binding.binding_sha256 if binding is not None else "", - event_id=event.event_id if event is not None else "", - approval_id=event.approval_id if event is not None else "", - manager_github_id=event.manager_github_id if event is not None else 0, - manager_login=event.manager_login if event is not None else "", - evaluated_at=evaluated_at, - ) - - -def _normalize_utc_timestamp(value: str) -> str: - normalized = value.strip() - if not normalized: - raise ValueError("manager preview approval requires evaluated_at") - try: - parsed = datetime.fromisoformat(normalized.replace("Z", "+00:00")) - except ValueError as error: - raise ValueError( - "manager preview approval evaluated_at must be an ISO-8601 timestamp" - ) from error - if parsed.tzinfo is None or parsed.utcoffset() is None: - raise ValueError("manager preview approval evaluated_at requires a timezone") - return parsed.astimezone(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z") diff --git a/control_plane/manager_preview_approval_github_webhook.py b/control_plane/manager_preview_approval_github_webhook.py deleted file mode 100644 index 9b3571a57..000000000 --- a/control_plane/manager_preview_approval_github_webhook.py +++ /dev/null @@ -1,959 +0,0 @@ -from __future__ import annotations - -from collections.abc import Callable -from dataclasses import dataclass -from datetime import datetime, timezone -import hashlib -import json -import logging -import os -from pathlib import Path -import re -from typing import Protocol, cast - -import click - -from control_plane.contracts.manager_preview_approval import ManagerPreviewApprovalBinding -from control_plane.contracts.manager_preview_approval_projection import ( - ManagerPreviewApprovalCommand, - ManagerPreviewApprovalCommandAction, - ManagerPreviewApprovalProjection, -) -from control_plane.contracts.preview_generation_record import PreviewGenerationRecord -from control_plane.contracts.preview_record import PreviewRecord -from control_plane.contracts.product_profile_record import LaunchplaneProductProfileRecord -from control_plane.durable_operation_authorization import read_active_authz_policy_record -from control_plane.manager_preview_approval import ( - ManagerPreviewApprovalAuthorizationError, - ManagerPreviewApprovalEvidenceError, - ManagerPreviewApprovalEventConflictError, - ManagerPreviewApprovalEventStore, - build_current_manager_preview_approval_binding, - build_manager_preview_approval_system_event, - manager_preview_approval_required, - record_manager_preview_approval_event, -) -from control_plane.manager_preview_approval_projection import ( - GitHubApiRequest, - ManagerPreviewApprovalProjectionStore, - build_manager_preview_approval_projection, - write_manager_preview_approval_projection, -) -from control_plane.service_auth import GitHubHumanIdentity -from control_plane.trusted_maintenance_github_webhook import ( - TrustedMaintenanceGitHubWebhookDependencies, - TrustedMaintenanceGitHubWebhookResult, - handle_trusted_maintenance_github_webhook, -) -from control_plane.workflows.launchplane import ( - github_api_request, - resolve_launchplane_github_token, - verify_github_webhook_signature, -) - - -MANAGER_PREVIEW_APPROVAL_WEBHOOK_ROUTE = "/v1/manager-preview-approval/github-webhook" -MANAGER_PREVIEW_APPROVAL_RECONCILE_ROUTE = "/v1/manager-preview-approval/reconcile" -_WEBHOOK_SECRET_ENV_KEY = "LAUNCHPLANE_MANAGER_PREVIEW_GITHUB_WEBHOOK_SECRET" -_COMMAND_PATTERN = re.compile( - r"^/preview\s+(?Papprove|changes|revoke)\s+" - r"(?P[0-9a-fA-F]{64})(?:\s+(?P.+))?$" -) -_LOGGER = logging.getLogger(__name__) - - -class ManagerPreviewApprovalGitHubStore( - ManagerPreviewApprovalProjectionStore, - ManagerPreviewApprovalEventStore, - Protocol, -): - pass - - -class _WebhookSignatureVerifier(Protocol): - def __call__( - self, - *, - payload_bytes: bytes, - signature_header: str, - secret: str, - ) -> None: ... - - -class _GitHubTokenResolver(Protocol): - def __call__(self, *, control_plane_root: Path, context_name: str) -> str: ... - - -def _utc_now_timestamp() -> str: - return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z") - - -def _webhook_secret() -> str: - return os.environ.get(_WEBHOOK_SECRET_ENV_KEY, "").strip() - - -@dataclass(frozen=True) -class ManagerPreviewApprovalGitHubDependencies: - webhook_secret: Callable[[], str] = _webhook_secret - verify_signature: _WebhookSignatureVerifier = verify_github_webhook_signature - now_timestamp: Callable[[], str] = _utc_now_timestamp - github_api: GitHubApiRequest = github_api_request - github_token: _GitHubTokenResolver = resolve_launchplane_github_token - trusted_maintenance: TrustedMaintenanceGitHubWebhookDependencies | None = None - - -def parse_manager_preview_approval_command( - body: str, -) -> ManagerPreviewApprovalCommand | None: - normalized = body.strip() - if "\n" in normalized or "\r" in normalized: - return None - match = _COMMAND_PATTERN.fullmatch(normalized) - if match is None: - return None - command = match.group("command").lower() - action = cast( - ManagerPreviewApprovalCommandAction, - { - "approve": "approved", - "changes": "changes_requested", - "revoke": "revoked", - }[command], - ) - reason = (match.group("reason") or "").strip() - try: - return ManagerPreviewApprovalCommand( - action=action, - fingerprint=match.group("fingerprint"), - reason=reason, - ) - except ValueError: - return None - - -def handle_manager_preview_approval_github_webhook_request( - payload_bytes: bytes, - event_name: str, - delivery_id: str, - signature_header: str, - record_store: object, - control_plane_root: Path, - trace_id: str, - *, - dependencies: ManagerPreviewApprovalGitHubDependencies | None = None, -) -> tuple[int, dict[str, object]]: - resolved_dependencies = dependencies or ManagerPreviewApprovalGitHubDependencies() - normalized_delivery_id = delivery_id.strip() - normalized_event_name = event_name.strip().lower() - if not normalized_delivery_id: - return _error_response(trace_id, 400, "invalid_payload", "GitHub delivery id is required.") - try: - resolved_dependencies.verify_signature( - payload_bytes=payload_bytes, - signature_header=signature_header, - secret=resolved_dependencies.webhook_secret(), - ) - except click.ClickException: - _LOGGER.warning( - "Manager preview approval webhook signature verification failed", - exc_info=True, - extra={"trace_id": trace_id}, - ) - return _error_response( - trace_id, - 401, - "invalid_signature", - "GitHub webhook signature is invalid.", - ) - try: - decoded = json.loads(payload_bytes.decode("utf-8")) - except (UnicodeDecodeError, json.JSONDecodeError): - return _error_response( - trace_id, 400, "invalid_payload", "GitHub webhook body is invalid JSON." - ) - if not isinstance(decoded, dict): - return _error_response( - trace_id, 400, "invalid_payload", "GitHub webhook body must be an object." - ) - payload = cast(dict[str, object], decoded) - try: - store = cast(ManagerPreviewApprovalGitHubStore, record_store) - if normalized_event_name == "issue_comment": - return _handle_issue_comment( - payload=payload, - delivery_id=normalized_delivery_id, - trace_id=trace_id, - record_store=store, - control_plane_root=control_plane_root, - dependencies=resolved_dependencies, - ) - if normalized_event_name == "pull_request": - trusted_maintenance_result = handle_trusted_maintenance_github_webhook( - event_name=normalized_event_name, - delivery_id=normalized_delivery_id, - signed_payload_sha256=hashlib.sha256(payload_bytes).hexdigest(), - payload=payload, - record_store=record_store, - control_plane_root=control_plane_root, - dependencies=_trusted_maintenance_dependencies(resolved_dependencies), - ) - terminal_response = _trusted_maintenance_terminal_response( - trace_id=trace_id, - result=trusted_maintenance_result, - ) - if terminal_response is not None: - return terminal_response - status_code, response_payload = _handle_pull_request( - payload=payload, - delivery_id=normalized_delivery_id, - trace_id=trace_id, - record_store=store, - control_plane_root=control_plane_root, - dependencies=resolved_dependencies, - ) - return status_code, _merge_trusted_maintenance_response( - response_payload, - trusted_maintenance_result, - ) - return 202, _accepted(trace_id, skipped=True, reason="unsupported_event") - except ManagerPreviewApprovalEventConflictError: - _LOGGER.warning( - "Manager preview approval webhook event conflicted with stored evidence", - exc_info=True, - extra={"trace_id": trace_id}, - ) - return _error_response( - trace_id, - 409, - "manager_preview_approval_conflict", - "Manager preview approval evidence conflicts with an existing event.", - ) - except (click.ClickException, ValueError, LookupError, FileNotFoundError): - _LOGGER.warning( - "Manager preview approval webhook could not evaluate current evidence", - exc_info=True, - extra={"trace_id": trace_id}, - ) - return _error_response( - trace_id, - 503, - "manager_preview_approval_unavailable", - "Manager preview approval is temporarily unavailable.", - ) - - -def reconcile_manager_preview_approval_for_pr( - *, - repository: str, - pr_number: int, - record_store: ManagerPreviewApprovalGitHubStore, - control_plane_root: Path, - evaluated_at: str = "", - dependencies: ManagerPreviewApprovalGitHubDependencies | None = None, -) -> dict[str, object]: - resolved_dependencies = dependencies or ManagerPreviewApprovalGitHubDependencies() - resolved_evaluated_at = evaluated_at.strip() or resolved_dependencies.now_timestamp() - profile = _product_profile(record_store, repository) - token = resolved_dependencies.github_token( - control_plane_root=control_plane_root, - context_name=profile.preview.context, - ) - if not token.strip(): - raise click.ClickException("Launchplane GitHub projection credentials are unavailable.") - pull_request = _github_pull_request( - api_request=resolved_dependencies.github_api, - token=token, - repository=repository, - pr_number=pr_number, - ) - projection = build_manager_preview_approval_projection( - record_store=record_store, - repository=repository, - pr_number=pr_number, - pr_url=_string(pull_request, "html_url"), - pr_state=_string(pull_request, "state"), - current_head_sha=_nested_string(pull_request, "head", "sha"), - evaluated_at=resolved_evaluated_at, - ) - result = write_manager_preview_approval_projection( - projection=projection, - token=token, - api_request=resolved_dependencies.github_api, - ) - return { - "required": projection.required, - "status": projection.decision.status, - "fingerprint": projection.binding_sha256, - **result, - } - - -def _trusted_maintenance_dependencies( - dependencies: ManagerPreviewApprovalGitHubDependencies, -) -> TrustedMaintenanceGitHubWebhookDependencies: - return dependencies.trusted_maintenance or TrustedMaintenanceGitHubWebhookDependencies( - github_token=dependencies.github_token, - github_api=dependencies.github_api, - ) - - -def _trusted_maintenance_terminal_response( - *, - trace_id: str, - result: TrustedMaintenanceGitHubWebhookResult, -) -> tuple[int, dict[str, object]] | None: - if result.status == "conflict": - return _error_response( - trace_id, - 409, - "trusted_maintenance_evidence_conflict", - "Trusted-maintenance evidence conflicts with an existing signed delivery.", - ) - if result.status == "retryable_error": - return _error_response( - trace_id, - 503, - "trusted_maintenance_unavailable", - "Trusted-maintenance evidence capture is temporarily unavailable.", - ) - return None - - -def _merge_trusted_maintenance_response( - payload: dict[str, object], - result: TrustedMaintenanceGitHubWebhookResult, -) -> dict[str, object]: - if result.status not in {"captured", "replayed"}: - return payload - merged_payload = dict(payload) - result_payload = merged_payload.get("result") - if not isinstance(result_payload, dict): - result_payload = {} - merged_payload["result"] = dict(result_payload) | { - "trusted_maintenance": { - "status": result.status, - "evidence_status": result.evidence_status, - } - } - return merged_payload - - -def invalidate_manager_preview_approval_for_pr( - *, - repository: str, - pr_number: int, - reason: str, - source_event_kind: str, - source_event_id: str, - record_store: ManagerPreviewApprovalGitHubStore, - control_plane_root: Path, - occurred_at: str = "", - binding: ManagerPreviewApprovalBinding | None = None, - dependencies: ManagerPreviewApprovalGitHubDependencies | None = None, -) -> dict[str, object]: - resolved_dependencies = dependencies or ManagerPreviewApprovalGitHubDependencies() - resolved_occurred_at = occurred_at.strip() or resolved_dependencies.now_timestamp() - event_result = record_manager_preview_approval_invalidation_for_pr( - repository=repository, - pr_number=pr_number, - reason=reason, - source_event_kind=source_event_kind, - source_event_id=source_event_id, - record_store=record_store, - occurred_at=resolved_occurred_at, - binding=binding, - ) - # The invalidation stays a stored event for promotion; nothing is posted to the - # pull request any more. - del control_plane_root - return event_result - - -def record_manager_preview_approval_invalidation_for_pr( - *, - repository: str, - pr_number: int, - reason: str, - source_event_kind: str, - source_event_id: str, - record_store: ManagerPreviewApprovalGitHubStore, - occurred_at: str, - binding: ManagerPreviewApprovalBinding | None = None, -) -> dict[str, object]: - try: - profile = _product_profile(record_store, repository) - policy_record = read_active_authz_policy_record(record_store) - except (FileNotFoundError, LookupError, ValueError): - return {"required": False, "event_status": "unavailable"} - if not manager_preview_approval_required( - policy_record=policy_record, - product=profile.product, - context=profile.preview.context, - ): - return {"required": False, "event_status": "not_required"} - try: - resolved_binding = binding or current_manager_preview_approval_binding_for_pr( - repository=repository, - pr_number=pr_number, - record_store=record_store, - ) - except ( - FileNotFoundError, - LookupError, - ManagerPreviewApprovalEvidenceError, - ValueError, - ): - return {"required": True, "event_status": "unavailable"} - event_status = record_store.write_manager_preview_approval_event_record( - build_manager_preview_approval_system_event( - binding=resolved_binding, - action="invalidated", - occurred_at=occurred_at, - source_event_kind=source_event_kind, - source_event_id=source_event_id, - reason=reason, - ) - ) - return {"required": True, "event_status": event_status} - - -def current_manager_preview_approval_binding_for_pr( - *, - repository: str, - pr_number: int, - record_store: object, -) -> ManagerPreviewApprovalBinding: - store = cast(ManagerPreviewApprovalGitHubStore, record_store) - profile = _product_profile(store, repository) - preview, generation = _preview_and_generation( - record_store=store, - profile=profile, - repository=repository, - pr_number=pr_number, - ) - return build_current_manager_preview_approval_binding( - product=profile.product, - preview=preview, - generation=generation, - ) - - -def reconcile_manager_preview_approval_for_pr_best_effort( - *, - repository: str, - pr_number: int, - record_store: object, - control_plane_root: Path, - dependencies: ManagerPreviewApprovalGitHubDependencies | None = None, - force: bool = False, -) -> bool: - store = cast(ManagerPreviewApprovalGitHubStore, record_store) - if not force: - try: - profile = _product_profile(store, repository) - policy_record = read_active_authz_policy_record(store) - except (AttributeError, FileNotFoundError, LookupError, TypeError, ValueError): - return True - if not manager_preview_approval_required( - policy_record=policy_record, - product=profile.product, - context=profile.preview.context, - ): - return True - try: - reconcile_manager_preview_approval_for_pr( - repository=repository, - pr_number=pr_number, - record_store=store, - control_plane_root=control_plane_root, - dependencies=dependencies, - ) - except Exception: - _LOGGER.warning( - "Manager preview approval reconciliation failed", - exc_info=True, - extra={"repository": repository, "pr_number": pr_number}, - ) - return False - return True - - -def invalidate_manager_preview_approval_for_pr_best_effort( - *, - repository: str, - pr_number: int, - reason: str, - source_event_kind: str, - source_event_id: str, - record_store: object, - control_plane_root: Path, - occurred_at: str = "", - binding: ManagerPreviewApprovalBinding | None = None, - dependencies: ManagerPreviewApprovalGitHubDependencies | None = None, -) -> bool: - try: - invalidate_manager_preview_approval_for_pr( - repository=repository, - pr_number=pr_number, - reason=reason, - source_event_kind=source_event_kind, - source_event_id=source_event_id, - record_store=cast(ManagerPreviewApprovalGitHubStore, record_store), - control_plane_root=control_plane_root, - occurred_at=occurred_at, - binding=binding, - dependencies=dependencies, - ) - except Exception: - _LOGGER.warning( - "Manager preview approval invalidation failed", - exc_info=True, - extra={"repository": repository, "pr_number": pr_number}, - ) - return False - return True - - -def reconcile_all_manager_preview_approvals_best_effort( - *, - record_store: object, - control_plane_root: Path, - dependencies: ManagerPreviewApprovalGitHubDependencies | None = None, -) -> tuple[int, int]: - store = cast(ManagerPreviewApprovalGitHubStore, record_store) - attempted = 0 - succeeded = 0 - seen: set[tuple[str, int]] = set() - try: - profiles = tuple( - profile for profile in store.list_product_profile_records() if profile.is_active - ) - except (AttributeError, TypeError, ValueError): - return attempted, succeeded - for profile in profiles: - repository = profile.repository.strip() - if not _valid_repository(repository) or not profile.preview.context.strip(): - continue - anchor_repo = repository.split("/", 1)[1] - try: - previews = store.list_preview_records( - context_name=profile.preview.context, - anchor_repo=anchor_repo, - limit=200, - ) - except (AttributeError, TypeError, ValueError): - continue - for preview in previews: - target = (repository, preview.anchor_pr_number) - if target in seen: - continue - seen.add(target) - attempted += 1 - if reconcile_manager_preview_approval_for_pr_best_effort( - repository=repository, - pr_number=preview.anchor_pr_number, - record_store=store, - control_plane_root=control_plane_root, - dependencies=dependencies, - force=True, - ): - succeeded += 1 - return attempted, succeeded - - -def _handle_issue_comment( - *, - payload: dict[str, object], - delivery_id: str, - trace_id: str, - record_store: ManagerPreviewApprovalGitHubStore, - control_plane_root: Path, - dependencies: ManagerPreviewApprovalGitHubDependencies, -) -> tuple[int, dict[str, object]]: - if payload.get("action") != "created": - return 202, _accepted(trace_id, skipped=True, reason="unsupported_action") - repository = _nested_string(payload, "repository", "full_name") - pr_number = _positive_int(_mapping(payload, "issue"), "number") - comment_id = _positive_int(_mapping(payload, "comment"), "id") - if not _valid_repository(repository) or pr_number is None or comment_id is None: - return _error_response( - trace_id, 400, "invalid_payload", "GitHub comment payload is incomplete." - ) - if _mapping(_mapping(payload, "issue"), "pull_request") is None: - return 202, _accepted(trace_id, skipped=True, reason="comment_is_not_on_pull_request") - profile = _product_profile(record_store, repository) - token = dependencies.github_token( - control_plane_root=control_plane_root, - context_name=profile.preview.context, - ) - if not token.strip(): - raise click.ClickException("Launchplane GitHub projection credentials are unavailable.") - comment = _github_comment( - api_request=dependencies.github_api, - token=token, - repository=repository, - comment_id=comment_id, - ) - command = parse_manager_preview_approval_command(_string(comment, "body")) - if command is None: - return 202, _accepted(trace_id, skipped=True, reason="command_not_matched") - pull_request = _github_pull_request( - api_request=dependencies.github_api, - token=token, - repository=repository, - pr_number=pr_number, - ) - evaluated_at = dependencies.now_timestamp() - projection_before = build_manager_preview_approval_projection( - record_store=record_store, - repository=repository, - pr_number=pr_number, - pr_url=_string(pull_request, "html_url"), - pr_state=_string(pull_request, "state"), - current_head_sha=_nested_string(pull_request, "head", "sha"), - evaluated_at=evaluated_at, - ) - if not projection_before.required: - return 202, _accepted(trace_id, skipped=True, reason="approval_not_required") - if ( - not projection_before.binding_sha256 - or command.fingerprint != projection_before.binding_sha256 - ): - return 202, _accepted(trace_id, skipped=True, reason="stale_fingerprint") - if projection_before.decision.status == "unavailable" or projection_before.pr_state != "open": - return 202, _accepted(trace_id, skipped=True, reason="preview_evidence_not_current") - preview, generation = _preview_and_generation( - record_store=record_store, - profile=profile, - repository=repository, - pr_number=pr_number, - ) - if generation.anchor_summary.head_sha != _nested_string(pull_request, "head", "sha"): - return 202, _accepted(trace_id, skipped=True, reason="stale_head") - current_binding = build_current_manager_preview_approval_binding( - product=profile.product, - preview=preview, - generation=generation, - ) - if current_binding.binding_sha256 != command.fingerprint: - return 202, _accepted(trace_id, skipped=True, reason="stale_fingerprint") - current_events = record_store.list_manager_preview_approval_event_records( - product=profile.product, - context=preview.context, - repository=preview.anchor_repo, - pr_number=pr_number, - limit=200, - ) - if any( - event.binding.binding_sha256 == current_binding.binding_sha256 - and event.action in {"superseded", "invalidated"} - for event in current_events - ): - return 202, _accepted(trace_id, skipped=True, reason="preview_evidence_not_current") - user = _mapping(comment, "user") - login = _string(user, "login") - github_id = _positive_int(user, "id") - if github_id is None or not login or _string(user, "type").casefold() == "bot": - return 202, _accepted(trace_id, skipped=True, reason="unauthorized_actor") - actor = _github_actor( - api_request=dependencies.github_api, - token=token, - login=login, - ) - if _positive_int(actor, "id") != github_id: - return 202, _accepted(trace_id, skipped=True, reason="actor_identity_mismatch") - policy_record = read_active_authz_policy_record(record_store) - try: - result = record_manager_preview_approval_event( - record_store=record_store, - identity=GitHubHumanIdentity( - login=login, - github_id=github_id, - name=_string(actor, "name"), - email="", - organizations=frozenset(), - teams=frozenset(), - role="read_only", - ), - policy_record=policy_record, - product=profile.product, - preview=preview, - generation=generation, - action=command.action, - occurred_at=_string(comment, "created_at"), - recorded_at=evaluated_at, - source_event_kind="github_issue_comment", - source_event_id=f"{repository}#{comment_id}", - reason=command.reason, - ) - except ManagerPreviewApprovalAuthorizationError: - return 202, _accepted(trace_id, skipped=True, reason="unauthorized_actor") - projection_after = build_manager_preview_approval_projection( - record_store=record_store, - repository=repository, - pr_number=pr_number, - pr_url=_string(pull_request, "html_url"), - pr_state=_string(pull_request, "state"), - current_head_sha=_nested_string(pull_request, "head", "sha"), - evaluated_at=evaluated_at, - ) - return 202, _accepted( - trace_id, - result={ - "event_id": result.record.event_id, - "event_status": result.status, - "approval_status": projection_after.decision.status, - }, - github_delivery_id=delivery_id, - ) - - -def _handle_pull_request( - *, - payload: dict[str, object], - delivery_id: str, - trace_id: str, - record_store: ManagerPreviewApprovalGitHubStore, - control_plane_root: Path, - dependencies: ManagerPreviewApprovalGitHubDependencies, -) -> tuple[int, dict[str, object]]: - action = str(payload.get("action") or "").strip().lower() - if action not in {"synchronize", "reopened", "closed", "unlabeled"}: - return 202, _accepted(trace_id, skipped=True, reason="unsupported_action") - repository = _nested_string(payload, "repository", "full_name") - pr_number = _positive_int(_mapping(payload, "number"), "value") - if pr_number is None: - raw_number = payload.get("number") - pr_number = raw_number if isinstance(raw_number, int) and raw_number > 0 else None - if not _valid_repository(repository) or pr_number is None: - return _error_response( - trace_id, 400, "invalid_payload", "GitHub pull request payload is incomplete." - ) - profile = _product_profile(record_store, repository) - if ( - action == "unlabeled" - and _nested_string(payload, "label", "name") != profile.preview.enable_label - ): - return 202, _accepted(trace_id, skipped=True, reason="unrelated_label") - token = dependencies.github_token( - control_plane_root=control_plane_root, - context_name=profile.preview.context, - ) - pull_request = _github_pull_request( - api_request=dependencies.github_api, - token=token, - repository=repository, - pr_number=pr_number, - ) - evaluated_at = dependencies.now_timestamp() - if action in {"closed", "unlabeled"}: - try: - preview, generation = _preview_and_generation( - record_store=record_store, - profile=profile, - repository=repository, - pr_number=pr_number, - ) - binding = build_current_manager_preview_approval_binding( - product=profile.product, - preview=preview, - generation=generation, - ) - record_store.write_manager_preview_approval_event_record( - build_manager_preview_approval_system_event( - binding=binding, - action="invalidated", - occurred_at=evaluated_at, - source_event_kind=( - "github_pull_request_closed" - if action == "closed" - else "github_preview_label_removed" - ), - source_event_id=f"{repository}#{pr_number}:{delivery_id}", - reason=( - "The pull request was closed." - if action == "closed" - else "The preview enablement label was removed." - ), - ) - ) - except (ValueError, LookupError, FileNotFoundError): - pass - projection = build_manager_preview_approval_projection( - record_store=record_store, - repository=repository, - pr_number=pr_number, - pr_url=_string(pull_request, "html_url"), - pr_state=_string(pull_request, "state"), - current_head_sha=_nested_string(pull_request, "head", "sha"), - evaluated_at=evaluated_at, - ) - if not projection.required: - return 202, _accepted(trace_id, skipped=True, reason="approval_not_required") - return 202, _accepted( - trace_id, - result={"approval_status": projection.decision.status}, - github_delivery_id=delivery_id, - ) - - -def _product_profile( - record_store: ManagerPreviewApprovalGitHubStore, - repository: str, -) -> LaunchplaneProductProfileRecord: - matches = tuple( - profile - for profile in record_store.list_product_profile_records() - if profile.is_active - and profile.repository.strip().casefold() == repository.strip().casefold() - ) - if len(matches) != 1: - raise LookupError("Launchplane requires one product profile for the GitHub repository.") - return matches[0] - - -def _preview_and_generation( - *, - record_store: ManagerPreviewApprovalGitHubStore, - profile: LaunchplaneProductProfileRecord, - repository: str, - pr_number: int, -) -> tuple[PreviewRecord, PreviewGenerationRecord]: - anchor_repo = repository.split("/", 1)[1] - previews = record_store.list_preview_records( - context_name=profile.preview.context, - anchor_repo=anchor_repo, - anchor_pr_number=pr_number, - limit=10, - ) - if not previews or not previews[0].serving_generation_id.strip(): - raise LookupError("The pull request does not have a serving Launchplane preview.") - preview = previews[0] - return preview, record_store.read_preview_generation_record(preview.serving_generation_id) - - -def _github_comment( - *, - api_request: GitHubApiRequest, - token: str, - repository: str, - comment_id: int, -) -> dict[str, object]: - payload = api_request( - path=f"/repos/{repository}/issues/comments/{comment_id}", - token=token, - ) - if not isinstance(payload, dict): - raise click.ClickException("GitHub comment response must be an object.") - return cast(dict[str, object], payload) - - -def _github_pull_request( - *, - api_request: GitHubApiRequest, - token: str, - repository: str, - pr_number: int, -) -> dict[str, object]: - payload = api_request( - path=f"/repos/{repository}/pulls/{pr_number}", - token=token, - ) - if not isinstance(payload, dict): - raise click.ClickException("GitHub pull request response must be an object.") - return cast(dict[str, object], payload) - - -def _github_actor( - *, - api_request: GitHubApiRequest, - token: str, - login: str, -) -> dict[str, object]: - payload = api_request(path=f"/users/{login}", token=token) - if not isinstance(payload, dict): - raise click.ClickException("GitHub actor response must be an object.") - return cast(dict[str, object], payload) - - -def _write_projection_best_effort( - *, - projection: ManagerPreviewApprovalProjection, - token: str, - dependencies: ManagerPreviewApprovalGitHubDependencies, -) -> None: - try: - write_manager_preview_approval_projection( - projection=projection, - token=token, - api_request=dependencies.github_api, - ) - except (ValueError, click.ClickException): - return - - -def _mapping(mapping: dict[str, object] | None, key: str) -> dict[str, object] | None: - if mapping is None: - return None - value = mapping.get(key) - return cast(dict[str, object], value) if isinstance(value, dict) else None - - -def _string(mapping: dict[str, object] | None, key: str) -> str: - if mapping is None: - return "" - value = mapping.get(key) - return value.strip() if isinstance(value, str) else "" - - -def _nested_string(mapping: dict[str, object], parent: str, key: str) -> str: - return _string(_mapping(mapping, parent), key) - - -def _positive_int(mapping: dict[str, object] | None, key: str) -> int | None: - if mapping is None: - return None - value = mapping.get(key) - if isinstance(value, int) and not isinstance(value, bool) and value > 0: - return value - return None - - -def _valid_repository(repository: str) -> bool: - owner, separator, repo = repository.strip().partition("/") - return bool(separator and owner and repo and "/" not in repo) - - -def _accepted( - trace_id: str, - *, - skipped: bool = False, - reason: str = "", - result: dict[str, object] | None = None, - github_delivery_id: str = "", -) -> dict[str, object]: - payload: dict[str, object] = {"status": "accepted", "trace_id": trace_id} - if skipped: - payload["skipped"] = True - if reason: - payload["reason"] = reason - if result is not None: - payload["result"] = result - if github_delivery_id: - payload["github_delivery_id"] = github_delivery_id - return payload - - -def _error_response( - trace_id: str, - status_code: int, - code: str, - message: str, -) -> tuple[int, dict[str, object]]: - return status_code, { - "error": { - "code": code, - "message": message, - "trace_id": trace_id, - } - } diff --git a/control_plane/manager_preview_approval_projection.py b/control_plane/manager_preview_approval_projection.py deleted file mode 100644 index 9845c32cf..000000000 --- a/control_plane/manager_preview_approval_projection.py +++ /dev/null @@ -1,460 +0,0 @@ -from __future__ import annotations - -from collections.abc import Callable -from typing import Protocol, cast -from urllib.parse import quote - -from control_plane.contracts.authz_policy_record import LaunchplaneAuthzPolicyRecord -from control_plane.contracts.manager_preview_approval import ( - ManagerPreviewApprovalBinding, - ManagerPreviewApprovalDecision, - ManagerPreviewApprovalEventRecord, -) -from control_plane.contracts.manager_preview_approval_projection import ( - MANAGER_PREVIEW_APPROVAL_CHECK_NAME, - MANAGER_PREVIEW_APPROVAL_COMMENT_MARKER, - ManagerPreviewApprovalCheckState, - ManagerPreviewApprovalProjection, -) -from control_plane.contracts.preview_generation_record import PreviewGenerationRecord -from control_plane.contracts.preview_record import PreviewRecord -from control_plane.contracts.product_profile_record import LaunchplaneProductProfileRecord -from control_plane.durable_operation_authorization import read_active_authz_policy_record -from control_plane.manager_preview_approval import ( - build_current_manager_preview_approval_binding, - evaluate_manager_preview_approval, - manager_preview_approval_required, -) -from control_plane.workflows.launchplane import github_api_request - - -GitHubApiRequest = Callable[..., object] - - -class ManagerPreviewApprovalProjectionStore(Protocol): - def list_product_profile_records( - self, *, driver_id: str = "" - ) -> tuple[LaunchplaneProductProfileRecord, ...]: ... - - def list_preview_records( - self, - *, - context_name: str = "", - anchor_repo: str = "", - anchor_pr_number: int | None = None, - limit: int | None = None, - ) -> tuple[PreviewRecord, ...]: ... - - def read_preview_generation_record(self, generation_id: str) -> PreviewGenerationRecord: ... - - def list_manager_preview_approval_event_records( - self, - *, - product: str = "", - context: str = "", - repository: str = "", - pr_number: int | None = None, - preview_id: str = "", - action: str = "", - limit: int | None = None, - ) -> tuple[ManagerPreviewApprovalEventRecord, ...]: ... - - def list_authz_policy_records( - self, - *, - status: str = "", - limit: int | None = None, - ) -> tuple[LaunchplaneAuthzPolicyRecord, ...]: ... - - -def build_manager_preview_approval_projection( - *, - record_store: ManagerPreviewApprovalProjectionStore, - repository: str, - pr_number: int, - pr_url: str, - pr_state: str, - current_head_sha: str, - evaluated_at: str, -) -> ManagerPreviewApprovalProjection: - normalized_repository = repository.strip() - normalized_pr_url = pr_url.strip() - normalized_pr_state = pr_state.strip().lower() or "unknown" - normalized_head_sha = current_head_sha.strip().lower() - profiles = tuple( - profile - for profile in record_store.list_product_profile_records() - if profile.is_active - and profile.repository.strip().casefold() == normalized_repository.casefold() - ) - if len(profiles) != 1: - return _unavailable_projection( - required=False, - product="unknown", - context="unknown", - repository=normalized_repository, - pr_number=pr_number, - pr_url=normalized_pr_url, - pr_state=normalized_pr_state, - head_sha=normalized_head_sha, - reason="Launchplane could not resolve one product profile for this pull request.", - evaluated_at=evaluated_at, - ) - profile = profiles[0] - context = profile.preview.context.strip() - try: - policy_record = read_active_authz_policy_record(record_store) - except (LookupError, TypeError, ValueError): - return _unavailable_projection( - required=True, - product=profile.product, - context=context or "unknown", - repository=normalized_repository, - pr_number=pr_number, - pr_url=normalized_pr_url, - pr_state=normalized_pr_state, - head_sha=normalized_head_sha, - reason="The active manager authorization policy is unavailable.", - evaluated_at=evaluated_at, - ) - required = manager_preview_approval_required( - policy_record=policy_record, - product=profile.product, - context=context, - ) - if not required: - return _unavailable_projection( - required=False, - product=profile.product, - context=context or "unknown", - repository=normalized_repository, - pr_number=pr_number, - pr_url=normalized_pr_url, - pr_state=normalized_pr_state, - head_sha=normalized_head_sha, - reason="Manager preview approval is not required by the active product policy.", - evaluated_at=evaluated_at, - check_state="success", - ) - - anchor_repo = normalized_repository.split("/", 1)[1] - previews = record_store.list_preview_records( - context_name=context, - anchor_repo=anchor_repo, - anchor_pr_number=pr_number, - limit=10, - ) - preview = previews[0] if previews else None - if preview is None or not preview.serving_generation_id.strip(): - return _unavailable_projection( - required=True, - product=profile.product, - context=context, - repository=normalized_repository, - pr_number=pr_number, - pr_url=normalized_pr_url, - pr_state=normalized_pr_state, - head_sha=normalized_head_sha, - reason="The current pull request does not have a serving Launchplane preview.", - evaluated_at=evaluated_at, - ) - try: - generation = record_store.read_preview_generation_record(preview.serving_generation_id) - except (FileNotFoundError, LookupError, ValueError): - return _unavailable_projection( - required=True, - product=profile.product, - context=context, - repository=normalized_repository, - pr_number=pr_number, - pr_url=normalized_pr_url, - pr_state=normalized_pr_state, - head_sha=normalized_head_sha, - reason="The serving Launchplane preview generation is unavailable.", - evaluated_at=evaluated_at, - preview=preview, - ) - events = record_store.list_manager_preview_approval_event_records( - product=profile.product, - context=context, - repository=preview.anchor_repo, - pr_number=pr_number, - limit=200, - ) - decision = evaluate_manager_preview_approval( - product=profile.product, - preview=preview, - generation=generation, - policy_record=policy_record, - events=events, - evaluated_at=evaluated_at, - ) - binding: ManagerPreviewApprovalBinding | None = None - if decision.current_binding_sha256: - binding = build_current_manager_preview_approval_binding( - product=profile.product, - preview=preview, - generation=generation, - ) - if normalized_pr_state != "open": - decision = _stale_decision( - binding=binding, - evaluated_at=evaluated_at, - reason="The pull request is not open.", - ) - elif binding is not None and normalized_head_sha != binding.head_sha: - decision = _stale_decision( - binding=binding, - evaluated_at=evaluated_at, - reason="The current pull request head does not match the serving preview.", - ) - check_state = _check_state(decision) - return ManagerPreviewApprovalProjection( - required=True, - product=profile.product, - context=context, - repository=normalized_repository, - pr_number=pr_number, - pr_url=normalized_pr_url, - pr_state=normalized_pr_state, - head_sha=normalized_head_sha, - preview_id=preview.preview_id, - preview_url=preview.canonical_url, - serving_generation_id=generation.generation_id, - artifact_id=generation.artifact_id, - artifact_image_digest=binding.artifact_image_digest if binding is not None else "", - manifest_fingerprint=generation.resolved_manifest_fingerprint, - binding_sha256=binding.binding_sha256 if binding is not None else "", - decision=decision, - check_state=check_state, - check_description=_check_description(decision), - comment_markdown=_render_comment( - preview=preview, - generation=generation, - binding=binding, - decision=decision, - ), - ) - - -def write_manager_preview_approval_projection( - *, - projection: ManagerPreviewApprovalProjection, - token: str, - api_request: GitHubApiRequest = github_api_request, -) -> dict[str, object]: - owner, repo = projection.repository.split("/", 1) - authenticated_user = api_request(path="/user", token=token) - if not isinstance(authenticated_user, dict): - raise ValueError("GitHub authenticated user response must be an object.") - raw_authenticated_user_id = authenticated_user.get("id") - authenticated_user_id = ( - raw_authenticated_user_id - if isinstance(raw_authenticated_user_id, int) - and not isinstance(raw_authenticated_user_id, bool) - else 0 - ) - if authenticated_user_id < 1: - raise ValueError("GitHub projection credential is missing stable actor identity.") - comments_payload = api_request( - path=( - f"/repos/{quote(owner)}/{quote(repo)}/issues/{projection.pr_number}/comments" - "?per_page=100" - ), - token=token, - ) - if not isinstance(comments_payload, list): - raise ValueError("GitHub issue comments response must be a list.") - existing_comment = next( - ( - cast(dict[str, object], item) - for item in comments_payload - if isinstance(item, dict) - and MANAGER_PREVIEW_APPROVAL_COMMENT_MARKER in str(item.get("body") or "") - and _github_actor_id(item.get("user")) == authenticated_user_id - ), - None, - ) - if existing_comment is None: - comment_payload = api_request( - path=f"/repos/{quote(owner)}/{quote(repo)}/issues/{projection.pr_number}/comments", - token=token, - method="POST", - body={"body": projection.comment_markdown}, - ) - else: - raw_comment_id = existing_comment.get("id") - comment_id = ( - raw_comment_id - if isinstance(raw_comment_id, int) and not isinstance(raw_comment_id, bool) - else 0 - ) - if comment_id < 1: - raise ValueError("GitHub manager preview approval comment is missing its id.") - comment_payload = api_request( - path=f"/repos/{quote(owner)}/{quote(repo)}/issues/comments/{comment_id}", - token=token, - method="PATCH", - body={"body": projection.comment_markdown}, - ) - if not isinstance(comment_payload, dict): - raise ValueError("GitHub manager preview approval comment response must be an object.") - target_url = str(comment_payload.get("html_url") or projection.preview_url or projection.pr_url) - status_payload = api_request( - path=( - f"/repos/{quote(owner)}/{quote(repo)}/statuses/{quote(projection.head_sha, safe='')}" - ), - token=token, - method="POST", - body={ - "state": projection.check_state, - "target_url": target_url, - "description": projection.check_description, - "context": MANAGER_PREVIEW_APPROVAL_CHECK_NAME, - }, - ) - if not isinstance(status_payload, dict): - raise ValueError("GitHub manager preview approval status response must be an object.") - return { - "comment_url": target_url, - "check_state": projection.check_state, - "check_context": MANAGER_PREVIEW_APPROVAL_CHECK_NAME, - } - - -def _github_actor_id(value: object) -> int: - if not isinstance(value, dict): - return 0 - raw_id = value.get("id") - if isinstance(raw_id, int) and not isinstance(raw_id, bool) and raw_id > 0: - return raw_id - return 0 - - -def _unavailable_projection( - *, - required: bool, - product: str, - context: str, - repository: str, - pr_number: int, - pr_url: str, - pr_state: str, - head_sha: str, - reason: str, - evaluated_at: str, - check_state: ManagerPreviewApprovalCheckState = "error", - preview: PreviewRecord | None = None, -) -> ManagerPreviewApprovalProjection: - decision = ManagerPreviewApprovalDecision( - status="unavailable", - reason_code="policy_unavailable" if "policy" in reason.lower() else "preview_inactive", - reason=reason, - evaluated_at=evaluated_at, - ) - return ManagerPreviewApprovalProjection( - required=required, - product=product, - context=context, - repository=repository, - pr_number=pr_number, - pr_url=pr_url, - pr_state=pr_state, - head_sha=head_sha, - preview_id=preview.preview_id if preview is not None else "", - preview_url=preview.canonical_url if preview is not None else "", - decision=decision, - check_state=check_state, - check_description=( - "Manager preview approval is not required." - if not required and check_state == "success" - else "Manager preview approval is unavailable." - ), - comment_markdown=_render_unavailable_comment(reason=reason), - ) - - -def _stale_decision( - *, - binding: ManagerPreviewApprovalBinding | None, - evaluated_at: str, - reason: str, -) -> ManagerPreviewApprovalDecision: - return ManagerPreviewApprovalDecision( - status="stale", - reason_code="approval_stale", - reason=reason, - current_binding_sha256=binding.binding_sha256 if binding is not None else "", - evaluated_at=evaluated_at, - ) - - -def _check_state(decision: ManagerPreviewApprovalDecision) -> ManagerPreviewApprovalCheckState: - if decision.status == "approved": - return "success" - if decision.status == "pending": - return "pending" - if decision.status == "unavailable": - return "error" - return "failure" - - -def _check_description(decision: ManagerPreviewApprovalDecision) -> str: - descriptions = { - "approved": "The manager approved the exact current preview.", - "pending": "Waiting for the manager to approve the exact current preview.", - "changes_requested": "The manager requested changes to the current preview.", - "revoked": "The manager revoked approval for the current preview.", - "stale": "Manager approval is stale for the current preview.", - "unavailable": "Manager preview approval is unavailable.", - } - return descriptions[decision.status] - - -def _render_comment( - *, - preview: PreviewRecord, - generation: PreviewGenerationRecord, - binding: ManagerPreviewApprovalBinding | None, - decision: ManagerPreviewApprovalDecision, -) -> str: - lines = [ - MANAGER_PREVIEW_APPROVAL_COMMENT_MARKER, - "## Manager preview approval", - "", - f"- State: **{decision.status.replace('_', ' ')}**", - f"- Preview URL: {preview.canonical_url}", - f"- PR head: `{generation.anchor_summary.head_sha}`", - f"- Serving generation: `{generation.generation_id}`", - f"- Artifact: `{generation.artifact_id}`", - f"- Manifest: `{generation.resolved_manifest_fingerprint}`", - f"- Reason: {decision.reason}", - ] - if binding is not None: - lines.extend( - [ - f"- Immutable image digest: `{binding.artifact_image_digest}`", - f"- Exact fingerprint: `{binding.binding_sha256}`", - "", - "The authorized manager can use one exact command:", - "", - f"- `/preview approve {binding.binding_sha256}`", - f"- `/preview changes {binding.binding_sha256} `", - f"- `/preview revoke {binding.binding_sha256} `", - ] - ) - return "\n".join(lines) - - -def _render_unavailable_comment(*, reason: str) -> str: - return "\n".join( - [ - MANAGER_PREVIEW_APPROVAL_COMMENT_MARKER, - "## Manager preview approval", - "", - "- State: **unavailable**", - f"- Reason: {reason}", - "", - "No manager command is valid until Launchplane can resolve exact preview evidence.", - ] - ) diff --git a/control_plane/merge_admission_live.py b/control_plane/merge_admission_live.py index abe274a65..39aba0eca 100644 --- a/control_plane/merge_admission_live.py +++ b/control_plane/merge_admission_live.py @@ -6,15 +6,15 @@ import json from typing import Protocol -from control_plane.change_impact_github import ( - ChangeImpactRepositoryEvidenceError, - ChangeImpactRepositoryEvidenceStaleError, +from control_plane.repository_evidence import ( + RepositoryEvidenceError, + RepositoryEvidenceStaleError, ) -from control_plane.change_impact_service import ( - ChangeImpactRepositoryEvidenceProvider, +from control_plane.contracts.repository_evidence import ( + RepositoryEvidenceProvider, ) -from control_plane.contracts.change_impact import ( - ChangeImpactTargetReference, +from control_plane.contracts.repository_evidence import ( + RepositoryTargetReference, ) from control_plane.contracts.merge_readiness import ( MERGE_READINESS_POLICY_DIMENSIONS, @@ -195,7 +195,7 @@ def _ordinary_no_op_lifecycle_queue_matches( @dataclass(frozen=True) class LiveMergeAdmissionEvaluator: store: object - repository_evidence_provider: ChangeImpactRepositoryEvidenceProvider + repository_evidence_provider: RepositoryEvidenceProvider technical_check_client: MergeAdmissionTechnicalCheckReader policy_record_provider: Callable[[], MergeTrainPolicyRecord] | None = None snapshot_reader: MergeTrainSnapshotReader | None = None @@ -293,8 +293,8 @@ def evaluate( ) for candidate_entry in candidate_record.candidate.entries ) - except ChangeImpactRepositoryEvidenceError as error: - if isinstance(error, ChangeImpactRepositoryEvidenceStaleError): + except RepositoryEvidenceError as error: + if isinstance(error, RepositoryEvidenceStaleError): message = "Authoritative repository evidence changed during merge admission." reason_code = "repository_evidence_stale" else: @@ -395,7 +395,7 @@ def _entry_evidence( pull_request_number: int, position: int, ) -> MergeTrainStructuralEntryObservation: - target_reference = ChangeImpactTargetReference( + target_reference = RepositoryTargetReference( repository=repository, pull_request_number=pull_request_number, ) diff --git a/control_plane/odoo_preview_apply_http.py b/control_plane/odoo_preview_apply_http.py index a327f55f5..5d3b69d87 100644 --- a/control_plane/odoo_preview_apply_http.py +++ b/control_plane/odoo_preview_apply_http.py @@ -425,7 +425,6 @@ def apply_odoo_preview_lifecycle_evidence( issued_plan: OdooPreviewApplyInputsResult, driver_result: dict[str, object], runtime_identity: RuntimeIdentity | None = None, - before_destroy: Callable[[], dict[str, object]] | None = None, ) -> dict[str, object]: result = OdooPreviewDokployApplyResult.model_validate(driver_result) if result.status != "pass": @@ -479,7 +478,6 @@ def apply_odoo_preview_lifecycle_evidence( status="stale", transition=existing_preview.state, ) - destroy_records = before_destroy() if before_destroy is not None else {} preview_request = PreviewMutationRequest( context=profile.preview.context, anchor_repo=anchor_repo, @@ -503,7 +501,6 @@ def apply_odoo_preview_lifecycle_evidence( ) preview_path = typed_record_store.write_preview_record(transitioned_preview) return { - **destroy_records, **_odoo_preview_lifecycle_records( preview=transitioned_preview, status="applied", diff --git a/control_plane/ordinary_agent_landing_evidence.py b/control_plane/ordinary_agent_landing_evidence.py index c9c4c4c29..e4ae6a75b 100644 --- a/control_plane/ordinary_agent_landing_evidence.py +++ b/control_plane/ordinary_agent_landing_evidence.py @@ -5,9 +5,9 @@ from dataclasses import dataclass from datetime import datetime -from control_plane.contracts.change_impact import ( - ChangeImpactRepositoryEvidence, - ChangeImpactTargetReference, +from control_plane.contracts.repository_evidence import ( + RepositoryEvidence, + RepositoryTargetReference, ) from control_plane.contracts.ordinary_agent_snapshot import OrdinaryAgentLandingEvidence from control_plane.contracts.ordinary_agent_effect import LANDING_EVIDENCE_MAX_AGE_SECONDS @@ -38,7 +38,7 @@ def read_merge_train_snapshot( class OrdinaryAgentLandingRepositoryEvidenceProvider: evidence: OrdinaryAgentLandingEvidence - def resolve(self, target: ChangeImpactTargetReference) -> ChangeImpactRepositoryEvidence: + def resolve(self, target: RepositoryTargetReference) -> RepositoryEvidence: if target.repository == self.evidence.repository: for entry in self.evidence.candidate_entry_evidence: if entry.target.pull_request_number == target.pull_request_number: diff --git a/control_plane/ordinary_agent_landing_reader.py b/control_plane/ordinary_agent_landing_reader.py index beef70a91..5121e9b4f 100644 --- a/control_plane/ordinary_agent_landing_reader.py +++ b/control_plane/ordinary_agent_landing_reader.py @@ -8,16 +8,16 @@ from urllib.parse import quote from control_plane.ordinary_agent_repository_roles import OrdinaryRepositoryAdminObservation -from control_plane.change_impact_github import ( - ChangeImpactRepositoryEvidenceError, +from control_plane.repository_evidence import ( + RepositoryEvidenceError, read_github_authorship, read_github_changed_files, ) from control_plane.contracts.canonical_json import canonical_json_sha256 -from control_plane.contracts.change_impact import ( - ChangeImpactBaseEvidence, - ChangeImpactRepositoryEvidence, - ChangeImpactTarget, +from control_plane.contracts.repository_evidence import ( + RepositoryBaseEvidence, + RepositoryEvidence, + RepositoryTarget, ) from control_plane.contracts.merge_train_batch import ( MergeTrainBatchCandidateRecord, @@ -155,8 +155,8 @@ def entry_request(path: str) -> object: ) merge_commit = pr["mergeCommit"] entries.append( - ChangeImpactRepositoryEvidence( - target=ChangeImpactTarget( + RepositoryEvidence( + target=RepositoryTarget( repository_id=str(target.repository_id), repository_owner_id=str(repository_owner_id), repository=candidate.repository, @@ -169,7 +169,7 @@ def entry_request(path: str) -> object: else _text(_object(merge_commit).get("oid")), changed_files=changed_files, authorship=authorship, - base=ChangeImpactBaseEvidence( + base=RepositoryBaseEvidence( base_ref=_text(pr.get("baseRefName")), base_sha=_text(pr.get("baseRefOid")), ), @@ -183,7 +183,7 @@ def entry_request(path: str) -> object: repository_policy=repository_policy, ) ) - except (ChangeImpactRepositoryEvidenceError, ValueError, TypeError) as error: + except (RepositoryEvidenceError, ValueError, TypeError) as error: raise OrdinaryAgentProviderEvidenceError("landing_entry_evidence_malformed") from error confirm_landing_graphql( transport=transport, diff --git a/control_plane/product_owner_service.py b/control_plane/product_owner_service.py deleted file mode 100644 index 126cedd53..000000000 --- a/control_plane/product_owner_service.py +++ /dev/null @@ -1,751 +0,0 @@ -from __future__ import annotations - -from datetime import datetime, timezone -from typing import Literal, Protocol, TypeVar, cast - -from pydantic import BaseModel, ConfigDict, Field - -from control_plane.contracts.product_owner import ( - ProductOwnerActionContext, - ProductOwnerActorIdentity, - ProductOwnerPolicyRecord, - ProductOwnerRequirementRecord, - ProductOwnerRoutingRecord, - ProductOwnerAuthorityEvaluation, -) - - -class ProductOwnerPolicyConflictError(ValueError): - """Raised on an Owner policy write race or conflicting immutable replay.""" - - -class ProductOwnerPolicySequenceError(ValueError): - """Raised when Owner policy revision history is stale or non-linear.""" - - -class ProductOwnerRequirementConflictError(ValueError): - """Raised on an Owner requirement write race or conflicting immutable replay.""" - - -class ProductOwnerRequirementSequenceError(ValueError): - """Raised when Owner requirement revision history is stale or non-linear.""" - - -class ProductOwnerRoutingConflictError(ValueError): - """Raised on an Owner routing write race or conflicting immutable replay.""" - - -class ProductOwnerRoutingSequenceError(ValueError): - """Raised when Owner routing revision history is stale or non-linear.""" - - -ProductOwnerApplyMode = Literal["dry_run", "apply"] -ProductOwnerApplyStatus = Literal["would_apply", "would_replay", "applied", "replayed"] - - -class ProductOwnerPolicyReadStore(Protocol): - def list_product_owner_policy_records( - self, - *, - product: str = "", - system: str = "", - status: str = "", - limit: int | None = None, - ) -> tuple[ProductOwnerPolicyRecord, ...]: ... - - -class ProductOwnerPolicyStore(ProductOwnerPolicyReadStore, Protocol): - def compare_and_write_product_owner_policy_record( - self, - record: ProductOwnerPolicyRecord, - *, - expected_current_record_id: str, - expected_current_policy_digest: str, - ) -> Literal["written", "replayed"]: ... - - -class ProductOwnerRequirementReadStore(Protocol): - def list_product_owner_requirement_records( - self, - *, - product: str = "", - system: str = "", - status: str = "", - limit: int | None = None, - ) -> tuple[ProductOwnerRequirementRecord, ...]: ... - - -class ProductOwnerRequirementStore(ProductOwnerRequirementReadStore, Protocol): - def compare_and_write_product_owner_requirement_record( - self, - record: ProductOwnerRequirementRecord, - *, - expected_current_record_id: str, - expected_current_requirement_digest: str, - ) -> Literal["written", "replayed"]: ... - - -class ProductOwnerRoutingReadStore(Protocol): - def list_product_owner_routing_records( - self, - *, - product: str = "", - system: str = "", - status: str = "", - limit: int | None = None, - ) -> tuple[ProductOwnerRoutingRecord, ...]: ... - - -class ProductOwnerRoutingStore(ProductOwnerRoutingReadStore, Protocol): - def compare_and_write_product_owner_routing_record( - self, - record: ProductOwnerRoutingRecord, - *, - expected_current_record_id: str, - expected_current_routing_digest: str, - ) -> Literal["written", "replayed"]: ... - - -class ProductOwnerPolicyApplyResult(BaseModel): - model_config = ConfigDict(extra="forbid") - - schema_version: int = Field(default=1, ge=1) - status: ProductOwnerApplyStatus - record: ProductOwnerPolicyRecord - - -class ProductOwnerRequirementApplyResult(BaseModel): - model_config = ConfigDict(extra="forbid") - - schema_version: int = Field(default=1, ge=1) - status: ProductOwnerApplyStatus - record: ProductOwnerRequirementRecord - - -class ProductOwnerRoutingApplyResult(BaseModel): - model_config = ConfigDict(extra="forbid") - - schema_version: int = Field(default=1, ge=1) - status: ProductOwnerApplyStatus - record: ProductOwnerRoutingRecord - - -class ProductOwnerReadModel(BaseModel): - model_config = ConfigDict(extra="forbid") - - schema_version: int = Field(default=1, ge=1) - product: str - system: str - current_policy: ProductOwnerPolicyRecord | None = None - current_requirement: ProductOwnerRequirementRecord | None = None - current_routing: ProductOwnerRoutingRecord | None = None - policy_history_count: int = Field(default=0, ge=0) - requirement_history_count: int = Field(default=0, ge=0) - routing_history_count: int = Field(default=0, ge=0) - - -RecordT = TypeVar( - "RecordT", - ProductOwnerPolicyRecord, - ProductOwnerRequirementRecord, - ProductOwnerRoutingRecord, -) - - -def apply_product_owner_policy( - *, - store: ProductOwnerPolicyStore, - record: ProductOwnerPolicyRecord, - expected_current_record_id: str = "", - expected_current_policy_digest: str = "", - mode: ProductOwnerApplyMode = "apply", -) -> ProductOwnerPolicyApplyResult: - replay = _validate_revision_append( - records=store.list_product_owner_policy_records( - product=record.product, - system=record.system, - ), - record=record, - revision_field="policy_revision", - digest_field="policy_digest", - expected_current_record_id=expected_current_record_id, - expected_current_digest=expected_current_policy_digest, - sequence_error=ProductOwnerPolicySequenceError, - conflict_error=ProductOwnerPolicyConflictError, - label="product Owner policy", - ) - if mode == "dry_run": - return ProductOwnerPolicyApplyResult( - status="would_replay" if replay else "would_apply", - record=record, - ) - write_status = store.compare_and_write_product_owner_policy_record( - record, - expected_current_record_id=expected_current_record_id, - expected_current_policy_digest=expected_current_policy_digest, - ) - return ProductOwnerPolicyApplyResult( - status="replayed" if write_status == "replayed" else "applied", - record=record, - ) - - -def apply_product_owner_requirement( - *, - store: ProductOwnerRequirementStore, - record: ProductOwnerRequirementRecord, - expected_current_record_id: str = "", - expected_current_requirement_digest: str = "", - mode: ProductOwnerApplyMode = "apply", -) -> ProductOwnerRequirementApplyResult: - replay = _validate_revision_append( - records=store.list_product_owner_requirement_records( - product=record.product, - system=record.system, - ), - record=record, - revision_field="requirement_revision", - digest_field="requirement_digest", - expected_current_record_id=expected_current_record_id, - expected_current_digest=expected_current_requirement_digest, - sequence_error=ProductOwnerRequirementSequenceError, - conflict_error=ProductOwnerRequirementConflictError, - label="product Owner requirement", - ) - if mode == "dry_run": - return ProductOwnerRequirementApplyResult( - status="would_replay" if replay else "would_apply", - record=record, - ) - write_status = store.compare_and_write_product_owner_requirement_record( - record, - expected_current_record_id=expected_current_record_id, - expected_current_requirement_digest=expected_current_requirement_digest, - ) - return ProductOwnerRequirementApplyResult( - status="replayed" if write_status == "replayed" else "applied", - record=record, - ) - - -def apply_product_owner_routing( - *, - store: ProductOwnerRoutingStore, - record: ProductOwnerRoutingRecord, - expected_current_record_id: str = "", - expected_current_routing_digest: str = "", - mode: ProductOwnerApplyMode = "apply", -) -> ProductOwnerRoutingApplyResult: - replay = _validate_revision_append( - records=store.list_product_owner_routing_records( - product=record.product, - system=record.system, - ), - record=record, - revision_field="routing_revision", - digest_field="routing_digest", - expected_current_record_id=expected_current_record_id, - expected_current_digest=expected_current_routing_digest, - sequence_error=ProductOwnerRoutingSequenceError, - conflict_error=ProductOwnerRoutingConflictError, - label="product Owner routing", - ) - if mode == "dry_run": - return ProductOwnerRoutingApplyResult( - status="would_replay" if replay else "would_apply", - record=record, - ) - write_status = store.compare_and_write_product_owner_routing_record( - record, - expected_current_record_id=expected_current_record_id, - expected_current_routing_digest=expected_current_routing_digest, - ) - return ProductOwnerRoutingApplyResult( - status="replayed" if write_status == "replayed" else "applied", - record=record, - ) - - -def get_product_owner_read_model( - *, - store: object, - product: str, - system: str, -) -> ProductOwnerReadModel: - policy_store = require_product_owner_policy_read_store(store) - requirement_store = require_product_owner_requirement_read_store(store) - routing_store = require_product_owner_routing_read_store(store) - policies = policy_store.list_product_owner_policy_records(product=product, system=system) - requirements = requirement_store.list_product_owner_requirement_records( - product=product, - system=system, - ) - routings = routing_store.list_product_owner_routing_records(product=product, system=system) - evaluated_at = _evaluation_timestamp("") - return ProductOwnerReadModel( - product=product.strip(), - system=system.strip(), - current_policy=select_current_product_owner_policy( - policies=policies, - product=product, - system=system, - evaluated_at=evaluated_at, - ), - current_requirement=_safe_current_scoped_record( - requirements, - product=product, - system=system, - revision_field="requirement_revision", - evaluated_at=evaluated_at, - label="product Owner requirement", - ), - current_routing=_safe_current_scoped_record( - routings, - product=product, - system=system, - revision_field="routing_revision", - evaluated_at=evaluated_at, - label="product Owner routing", - ), - policy_history_count=len(policies), - requirement_history_count=len(requirements), - routing_history_count=len(routings), - ) - - -def select_current_product_owner_policy( - *, - policies: tuple[ProductOwnerPolicyRecord, ...], - product: str, - system: str, - evaluated_at: str, -) -> ProductOwnerPolicyRecord | None: - """Select a valid current policy from an already-loaded policy history.""" - - if not evaluated_at.strip(): - raise ValueError("Product Owner policy selection requires evaluated_at.") - return _safe_current_scoped_record( - policies, - product=product, - system=system, - revision_field="policy_revision", - evaluated_at=_evaluation_timestamp(evaluated_at), - label="product Owner policy", - ) - - -def evaluate_product_owner_authority( - *, - context: ProductOwnerActionContext, - actor: ProductOwnerActorIdentity, - policies: tuple[ProductOwnerPolicyRecord, ...], - requirements: tuple[ProductOwnerRequirementRecord, ...], - routings: tuple[ProductOwnerRoutingRecord, ...], - claimed_policy_revision: int | None = None, - claimed_policy_digest: str = "", - claimed_requirement_revision: int | None = None, - claimed_requirement_digest: str = "", - evaluated_at: str = "", -) -> ProductOwnerAuthorityEvaluation: - normalized_evaluated_at = _evaluation_timestamp(evaluated_at) - try: - current_requirement = _current_scoped_record( - requirements, - product=context.product, - system=context.system, - revision_field="requirement_revision", - evaluated_at=normalized_evaluated_at, - label="product Owner requirement", - ) - except ValueError: - return _evaluation( - decision="unavailable", - reason_code="requirement_history_invalid", - context=context, - actor=actor, - ) - if current_requirement is None: - return _evaluation( - decision="not_required", - reason_code="owner_requirement_not_configured", - context=context, - actor=actor, - ) - - matching_requirements = tuple( - requirement - for requirement in current_requirement.requirements - if requirement.action == context.action - and context.repository_id in requirement.repository_ids - and context.environment in requirement.environments - ) - if not matching_requirements: - return _evaluation( - decision="not_required", - reason_code="owner_action_not_required", - context=context, - actor=actor, - requirement=current_requirement, - ) - - try: - current_policy = _current_scoped_record( - policies, - product=context.product, - system=context.system, - revision_field="policy_revision", - evaluated_at=normalized_evaluated_at, - label="product Owner policy", - ) - except ValueError: - current_policy = None - try: - current_routing = _current_scoped_record( - routings, - product=context.product, - system=context.system, - revision_field="routing_revision", - evaluated_at=normalized_evaluated_at, - label="product Owner routing", - ) - except ValueError: - current_routing = None - if current_policy is None: - return _evaluation( - decision="unavailable", - reason_code="owner_policy_unavailable", - context=context, - actor=actor, - requirement=current_requirement, - routing=current_routing, - ) - - scoped_owners = tuple( - owner - for owner in current_policy.owners - if context.repository_id in owner.repository_ids - and context.environment in owner.environments - ) - if not scoped_owners: - return _evaluation( - decision="unavailable", - reason_code="policy_scope_not_covered", - context=context, - actor=actor, - requirement=current_requirement, - policy=current_policy, - routing=current_routing, - ) - notify_identity_ids = tuple(owner.identity.identity_id for owner in scoped_owners) - preferred_identity_ids = ( - set(current_routing.preferred_owner_identity_ids) if current_routing is not None else set() - ) - actor_is_preferred = actor.identity_id in preferred_identity_ids - - if actor.identity_id not in notify_identity_ids: - return _evaluation( - decision="denied", - reason_code="actor_not_current_owner", - context=context, - actor=actor, - requirement=current_requirement, - policy=current_policy, - routing=current_routing, - actor_is_preferred=actor_is_preferred, - notify_owner_identity_ids=notify_identity_ids, - ) - - if ( - claimed_policy_revision is None - or not claimed_policy_digest.strip() - or claimed_requirement_revision is None - or not claimed_requirement_digest.strip() - ): - return _evaluation( - decision="denied", - reason_code="missing_authority_provenance", - context=context, - actor=actor, - requirement=current_requirement, - policy=current_policy, - routing=current_routing, - actor_is_preferred=actor_is_preferred, - notify_owner_identity_ids=notify_identity_ids, - ) - if ( - claimed_policy_revision != current_policy.policy_revision - or claimed_policy_digest.strip().lower() != current_policy.policy_digest - ): - return _evaluation( - decision="denied", - reason_code="stale_policy", - context=context, - actor=actor, - requirement=current_requirement, - policy=current_policy, - routing=current_routing, - actor_is_preferred=actor_is_preferred, - notify_owner_identity_ids=notify_identity_ids, - ) - if ( - claimed_requirement_revision != current_requirement.requirement_revision - or claimed_requirement_digest.strip().lower() != current_requirement.requirement_digest - ): - return _evaluation( - decision="denied", - reason_code="stale_requirement", - context=context, - actor=actor, - requirement=current_requirement, - policy=current_policy, - routing=current_routing, - actor_is_preferred=actor_is_preferred, - notify_owner_identity_ids=notify_identity_ids, - ) - - return _evaluation( - decision="authorized", - reason_code="current_owner_quorum_satisfied", - context=context, - actor=actor, - requirement=current_requirement, - policy=current_policy, - routing=current_routing, - actor_is_preferred=actor_is_preferred, - satisfying_owner_identity_ids=(actor.identity_id,), - notify_owner_identity_ids=notify_identity_ids, - ) - - -def require_product_owner_policy_read_store(store: object) -> ProductOwnerPolicyReadStore: - if not callable(getattr(store, "list_product_owner_policy_records", None)): - raise TypeError("Product Owner policy storage is unavailable.") - return cast(ProductOwnerPolicyReadStore, store) - - -def require_product_owner_requirement_read_store(store: object) -> ProductOwnerRequirementReadStore: - if not callable(getattr(store, "list_product_owner_requirement_records", None)): - raise TypeError("Product Owner requirement storage is unavailable.") - return cast(ProductOwnerRequirementReadStore, store) - - -def require_product_owner_routing_read_store(store: object) -> ProductOwnerRoutingReadStore: - if not callable(getattr(store, "list_product_owner_routing_records", None)): - raise TypeError("Product Owner routing storage is unavailable.") - return cast(ProductOwnerRoutingReadStore, store) - - -def require_product_owner_policy_store(store: object) -> ProductOwnerPolicyStore: - read_store = require_product_owner_policy_read_store(store) - if not callable(getattr(store, "compare_and_write_product_owner_policy_record", None)): - raise TypeError("Product Owner policy mutation storage is unavailable.") - return cast(ProductOwnerPolicyStore, read_store) - - -def require_product_owner_requirement_store(store: object) -> ProductOwnerRequirementStore: - read_store = require_product_owner_requirement_read_store(store) - if not callable(getattr(store, "compare_and_write_product_owner_requirement_record", None)): - raise TypeError("Product Owner requirement mutation storage is unavailable.") - return cast(ProductOwnerRequirementStore, read_store) - - -def require_product_owner_routing_store(store: object) -> ProductOwnerRoutingStore: - read_store = require_product_owner_routing_read_store(store) - if not callable(getattr(store, "compare_and_write_product_owner_routing_record", None)): - raise TypeError("Product Owner routing mutation storage is unavailable.") - return cast(ProductOwnerRoutingStore, read_store) - - -def _validate_revision_append( - *, - records: tuple[RecordT, ...], - record: RecordT, - revision_field: str, - digest_field: str, - expected_current_record_id: str, - expected_current_digest: str, - sequence_error: type[ValueError], - conflict_error: type[ValueError], - label: str, -) -> bool: - _assert_record_integrity( - record, - label=label, - error_type=conflict_error, - ) - for existing in records: - _assert_record_integrity( - existing, - label=label, - error_type=sequence_error, - ) - if record.status != "active": - raise sequence_error(f"Incoming {label} revision must have active status.") - if record.effective_at > _evaluation_timestamp(""): - raise sequence_error(f"Incoming active {label} revision cannot take effect in the future.") - same_id = tuple(existing for existing in records if existing.record_id == record.record_id) - if same_id: - if len(same_id) != 1 or getattr(same_id[0], digest_field) != getattr( - record, - digest_field, - ): - raise conflict_error(f"{label} record id conflicts with existing history.") - return True - - current = _current_record(records) - revision = int(getattr(record, revision_field)) - if current is None: - if revision != 1: - raise sequence_error(f"Initial {label} revision must be 1, got {revision}.") - if expected_current_record_id.strip() or expected_current_digest.strip(): - raise conflict_error(f"Initial {label} apply requires an absent expected current tip.") - return False - - current_revision = int(getattr(current, revision_field)) - current_digest = str(getattr(current, digest_field)) - if ( - expected_current_record_id.strip() != current.record_id - or expected_current_digest.strip().lower() != current_digest - ): - raise conflict_error(f"Expected current {label} tip is stale.") - if revision != current_revision + 1: - raise sequence_error( - f"Next {label} revision must be {current_revision + 1}, got {revision}." - ) - if record.supersedes_record_id != current.record_id: - raise sequence_error(f"Next {label} record must supersede the current record.") - if record.effective_at < current.effective_at: - raise sequence_error(f"Next {label} effective_at cannot precede the current revision.") - return False - - -def _current_record(records: tuple[RecordT, ...]) -> RecordT | None: - active = tuple(record for record in records if record.status == "active") - if len(active) > 1: - raise ValueError("Product Owner record history has multiple active revisions.") - return active[0] if active else None - - -def _current_scoped_record( - records: tuple[RecordT, ...], - *, - product: str, - system: str, - revision_field: str, - evaluated_at: str, - label: str, -) -> RecordT | None: - scoped = tuple( - record for record in records if record.product == product and record.system == system - ) - if not scoped: - return None - for record in scoped: - _assert_record_integrity( - record, - label=label, - error_type=ValueError, - ) - by_revision = {int(getattr(record, revision_field)): record for record in scoped} - if len(by_revision) != len(scoped): - raise ValueError(f"{label} history contains duplicate revisions.") - current = _current_record(scoped) - if current is None: - raise ValueError(f"{label} history has no active revision.") - current_revision = int(getattr(current, revision_field)) - expected_revisions = tuple(range(1, current_revision + 1)) - if tuple(sorted(by_revision)) != expected_revisions: - raise ValueError(f"{label} history is not contiguous through the active revision.") - for revision in expected_revisions: - record = by_revision[revision] - if revision == 1: - if record.supersedes_record_id is not None: - raise ValueError(f"Initial {label} revision cannot supersede another record.") - elif record.supersedes_record_id != by_revision[revision - 1].record_id: - raise ValueError(f"{label} history has a broken predecessor chain.") - if revision > 1 and record.effective_at < by_revision[revision - 1].effective_at: - raise ValueError(f"{label} history has a non-monotonic effective_at sequence.") - expected_status = "active" if revision == current_revision else "superseded" - if record.status != expected_status: - raise ValueError(f"{label} history has an invalid revision status.") - if current.effective_at > evaluated_at: - raise ValueError(f"Current {label} revision is not yet effective.") - return current - - -def _assert_record_integrity( - record: RecordT, - *, - label: str, - error_type: type[ValueError], -) -> None: - try: - type(record).model_validate(record.model_dump(mode="python")) - except ValueError as error: - raise error_type(f"{label} payload does not match its derived identifiers.") from error - - -def _evaluation_timestamp(value: str) -> str: - if not value.strip(): - return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z") - parsed = datetime.fromisoformat(value.strip().replace("Z", "+00:00")) - if parsed.tzinfo is None: - raise ValueError("Product Owner evaluated_at must include a timezone.") - return parsed.astimezone(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z") - - -def _safe_current_scoped_record( - records: tuple[RecordT, ...], - *, - product: str, - system: str, - revision_field: str, - evaluated_at: str, - label: str, -) -> RecordT | None: - try: - return _current_scoped_record( - records, - product=product, - system=system, - revision_field=revision_field, - evaluated_at=evaluated_at, - label=label, - ) - except ValueError: - return None - - -def _evaluation( - *, - decision: Literal["authorized", "denied", "not_required", "unavailable"], - reason_code: str, - context: ProductOwnerActionContext, - actor: ProductOwnerActorIdentity, - requirement: ProductOwnerRequirementRecord | None = None, - policy: ProductOwnerPolicyRecord | None = None, - routing: ProductOwnerRoutingRecord | None = None, - actor_is_preferred: bool = False, - satisfying_owner_identity_ids: tuple[str, ...] = (), - notify_owner_identity_ids: tuple[str, ...] = (), -) -> ProductOwnerAuthorityEvaluation: - return ProductOwnerAuthorityEvaluation( - decision=decision, - reason_code=reason_code, - context=context, - actor_identity_id=actor.identity_id, - requirement_record_id=requirement.record_id if requirement is not None else "", - requirement_revision=( - requirement.requirement_revision if requirement is not None else None - ), - requirement_digest=(requirement.requirement_digest if requirement is not None else ""), - policy_record_id=policy.record_id if policy is not None else "", - policy_revision=policy.policy_revision if policy is not None else None, - policy_digest=policy.policy_digest if policy is not None else "", - routing_record_id=routing.record_id if routing is not None else "", - routing_revision=routing.routing_revision if routing is not None else None, - routing_digest=routing.routing_digest if routing is not None else "", - actor_is_preferred=actor_is_preferred, - satisfying_owner_identity_ids=satisfying_owner_identity_ids, - notify_owner_identity_ids=notify_owner_identity_ids, - ) diff --git a/control_plane/product_review_status.py b/control_plane/product_review_status.py index 6af13d12d..7ba3533ab 100644 --- a/control_plane/product_review_status.py +++ b/control_plane/product_review_status.py @@ -13,9 +13,7 @@ from urllib.parse import quote, urlencode, urlsplit from control_plane.contracts.advisory_check_projection import OWNER_ACCEPTANCE_CHECK_NAME -from control_plane.contracts.manager_preview_approval_projection import ( - MANAGER_PREVIEW_APPROVAL_CHECK_NAME, -) +from control_plane.contracts.advisory_check_projection import MANAGER_PREVIEW_APPROVAL_CHECK_NAME from control_plane.contracts.product_profile_record import ( LaunchplaneProductProfileRecord, ProductOwnerProfile, diff --git a/control_plane/change_impact_github.py b/control_plane/repository_evidence.py similarity index 82% rename from control_plane/change_impact_github.py rename to control_plane/repository_evidence.py index 2877cfb5c..9fc6caa76 100644 --- a/control_plane/change_impact_github.py +++ b/control_plane/repository_evidence.py @@ -6,22 +6,22 @@ from pydantic import BaseModel, ConfigDict, Field, model_validator -from control_plane.contracts.change_impact import ( - ChangeImpactAuthorshipEvidence, - ChangeImpactBaseEvidence, - ChangeImpactChangeKind, - ChangeImpactChangedFileEvidence, - ChangeImpactRepositoryEvidence, - ChangeImpactTarget, - ChangeImpactTargetReference, +from control_plane.contracts.repository_evidence import ( + RepositoryAuthorshipEvidence, + RepositoryBaseEvidence, + RepositoryChangeKind, + RepositoryChangedFileEvidence, + RepositoryEvidence, + RepositoryTarget, + RepositoryTargetReference, ) -class ChangeImpactRepositoryEvidenceError(RuntimeError): +class RepositoryEvidenceError(RuntimeError): """Raised when authoritative repository evidence cannot be resolved.""" -class ChangeImpactRepositoryEvidenceStaleError(ChangeImpactRepositoryEvidenceError): +class RepositoryEvidenceStaleError(RepositoryEvidenceError): """Raised when the pull request changes while evidence is being resolved.""" @@ -45,7 +45,7 @@ def _normalize(self) -> "GitHubOpenPullRequest": return self -class GitHubChangeImpactRepositoryEvidenceProvider: +class GitHubRepositoryEvidenceProvider: def __init__( self, *, @@ -57,9 +57,9 @@ def __init__( max_commit_pages: int = 10, ) -> None: if max_file_pages < 1: - raise ValueError("change-impact GitHub provider requires at least one file page") + raise ValueError("GitHub evidence provider requires at least one file page") if max_commit_pages < 1: - raise ValueError("change-impact GitHub provider requires at least one commit page") + raise ValueError("GitHub evidence provider requires at least one commit page") self._control_plane_root = control_plane_root self._github_token = github_token self._github_api = github_api @@ -67,7 +67,7 @@ def __init__( self._max_file_pages = max_file_pages self._max_commit_pages = max_commit_pages if not self._token_context: - raise ValueError("change-impact GitHub provider requires a token context") + raise ValueError("GitHub evidence provider requires a token context") def list_open_pull_requests( self, @@ -76,7 +76,7 @@ def list_open_pull_requests( limit: int, ) -> tuple[GitHubOpenPullRequest, ...]: if limit < 1 or limit > 100: - raise ValueError("change-impact GitHub open pull request limit must be 1 through 100") + raise ValueError("GitHub open pull request limit must be 1 through 100") try: token = self._token() repository_path = _repository_path(repository) @@ -100,31 +100,31 @@ def list_open_pull_requests( ) for pull_request in pull_requests ) - except ChangeImpactRepositoryEvidenceError: + except RepositoryEvidenceError: raise except Exception as error: - raise ChangeImpactRepositoryEvidenceError( + raise RepositoryEvidenceError( "Launchplane could not enumerate GitHub open pull requests." ) from error def resolve( self, - target: ChangeImpactTargetReference, - ) -> ChangeImpactRepositoryEvidence: + target: RepositoryTargetReference, + ) -> RepositoryEvidence: return self._resolve(target, max_file_pages=self._max_file_pages) def resolve_current_item( self, - target: ChangeImpactTargetReference, - ) -> ChangeImpactRepositoryEvidence: + target: RepositoryTargetReference, + ) -> RepositoryEvidence: return self._resolve(target, max_file_pages=min(self._max_file_pages, 5)) def _resolve( self, - target: ChangeImpactTargetReference, + target: RepositoryTargetReference, *, max_file_pages: int, - ) -> ChangeImpactRepositoryEvidence: + ) -> RepositoryEvidence: try: token = self._token() repository_path = _repository_path(target.repository) @@ -134,7 +134,7 @@ def _resolve( ) canonical_repository = _required_string(repository, "full_name").lower() if canonical_repository != target.repository: - raise ChangeImpactRepositoryEvidenceError( + raise RepositoryEvidenceError( "GitHub repository identity does not match the requested target." ) repository_id = _positive_decimal(repository, "id") @@ -189,12 +189,12 @@ def _resolve( or confirmed_merge_commit_sha != merge_commit_sha or confirmed_updated_at != updated_at ): - raise ChangeImpactRepositoryEvidenceStaleError( + raise RepositoryEvidenceStaleError( "GitHub pull request changed while resolving repository evidence." ) - return ChangeImpactRepositoryEvidence( - target=ChangeImpactTarget( + return RepositoryEvidence( + target=RepositoryTarget( repository_id=repository_id, repository_owner_id=repository_owner_id, repository=canonical_repository, @@ -204,13 +204,13 @@ def _resolve( ), merge_commit_sha=merge_commit_sha, changed_files=changed_files, - base=ChangeImpactBaseEvidence(base_ref=base_ref, base_sha=base_sha), + base=RepositoryBaseEvidence(base_ref=base_ref, base_sha=base_sha), authorship=authorship, ) - except ChangeImpactRepositoryEvidenceError: + except RepositoryEvidenceError: raise except Exception as error: - raise ChangeImpactRepositoryEvidenceError( + raise RepositoryEvidenceError( "Launchplane could not resolve authoritative GitHub repository evidence." ) from error @@ -220,7 +220,7 @@ def _token(self) -> str: context_name=self._token_context, ).strip() if not token: - raise ChangeImpactRepositoryEvidenceError( + raise RepositoryEvidenceError( "Launchplane GitHub repository evidence credentials are unavailable." ) return token @@ -232,7 +232,7 @@ def _authorship( pull_request: dict[str, object], pull_request_number: int, token: str, - ) -> ChangeImpactAuthorshipEvidence: + ) -> RepositoryAuthorshipEvidence: return read_github_authorship( request=lambda path: self._github_api(path=path, token=token), repository_path=repository_path, @@ -248,7 +248,7 @@ def _changed_files( pull_request_number: int, token: str, max_file_pages: int, - ) -> tuple[ChangeImpactChangedFileEvidence, ...]: + ) -> tuple[RepositoryChangedFileEvidence, ...]: return read_github_changed_files( request=lambda path: self._github_api(path=path, token=token), repository_path=repository_path, @@ -264,13 +264,13 @@ def _repository_path(repository: str) -> str: def _object_payload(payload: object, label: str) -> dict[str, object]: if not isinstance(payload, dict): - raise ChangeImpactRepositoryEvidenceError(f"{label} response must be an object.") + raise RepositoryEvidenceError(f"{label} response must be an object.") return {str(key): value for key, value in payload.items()} def _list_payload(payload: object, label: str) -> tuple[dict[str, object], ...]: if not isinstance(payload, list) or not all(isinstance(item, dict) for item in payload): - raise ChangeImpactRepositoryEvidenceError(f"{label} response must be a list of objects.") + raise RepositoryEvidenceError(f"{label} response must be a list of objects.") return tuple({str(key): value for key, value in item.items()} for item in payload) @@ -281,18 +281,14 @@ def _object_field(payload: dict[str, object], field_name: str) -> dict[str, obje def _required_string(payload: dict[str, object], field_name: str) -> str: value = str(payload.get(field_name, "")).strip() if not value: - raise ChangeImpactRepositoryEvidenceError( - f"GitHub response is missing required {field_name}." - ) + raise RepositoryEvidenceError(f"GitHub response is missing required {field_name}.") return value def _positive_decimal(payload: dict[str, object], field_name: str) -> str: value = str(payload.get(field_name, "")).strip() if not value.isdecimal() or int(value) < 1: - raise ChangeImpactRepositoryEvidenceError( - f"GitHub response is missing positive numeric {field_name}." - ) + raise RepositoryEvidenceError(f"GitHub response is missing positive numeric {field_name}.") return str(int(value)) @@ -305,11 +301,11 @@ def _validate_pull_request_repository( base = _object_field(pull_request, "base") base_repository = _object_field(base, "repo") if _required_string(base_repository, "full_name").lower() != repository: - raise ChangeImpactRepositoryEvidenceError( + raise RepositoryEvidenceError( "GitHub pull request base repository does not match the requested target." ) if _positive_decimal(base_repository, "id") != repository_id: - raise ChangeImpactRepositoryEvidenceError( + raise RepositoryEvidenceError( "GitHub pull request base repository identity is inconsistent." ) @@ -331,16 +327,14 @@ def _pull_request_merge_commit_sha(pull_request: dict[str, object]) -> str: if value is None: return "" if not isinstance(value, str): - raise ChangeImpactRepositoryEvidenceError( + raise RepositoryEvidenceError( "GitHub pull request merge_commit_sha must be a string or null." ) normalized = value.strip().lower() if normalized and ( len(normalized) != 40 or any(ch not in "0123456789abcdef" for ch in normalized) ): - raise ChangeImpactRepositoryEvidenceError( - "GitHub pull request merge_commit_sha must be a Git SHA." - ) + raise RepositoryEvidenceError("GitHub pull request merge_commit_sha must be a Git SHA.") return normalized @@ -349,7 +343,7 @@ def _git_commit_tree_sha(payload: object) -> str: return _required_string(_object_field(commit, "tree"), "sha").lower() -def _change_kind(status: str) -> ChangeImpactChangeKind: +def _change_kind(status: str) -> RepositoryChangeKind: if status == "added": return "added" if status == "modified": @@ -368,7 +362,7 @@ def read_github_authorship( pull_request: dict[str, object], pull_request_number: int, max_commit_pages: int, -) -> ChangeImpactAuthorshipEvidence: +) -> RepositoryAuthorshipEvidence: """Resolve numeric GitHub contributing identities over the reviewed range. Bot or agent work pushed under a human GitHub identity resolves to that @@ -399,7 +393,7 @@ def record(actor: object, label: str) -> bool: return True if not record(pull_request.get("user"), "pull request author"): - return ChangeImpactAuthorshipEvidence( + return RepositoryAuthorshipEvidence( resolution="unresolved", reason="pull request author has no linked numeric GitHub identity", ) @@ -419,7 +413,7 @@ def record(actor: object, label: str) -> bool: linked = record(commit.get("author"), f"commit {commit_sha} author") linked = record(commit.get("committer"), f"commit {commit_sha} committer") or linked if not linked: - return ChangeImpactAuthorshipEvidence( + return RepositoryAuthorshipEvidence( resolution="unresolved", commit_count=commit_count, reason=f"commit {commit_sha} has no linked numeric GitHub identity", @@ -427,29 +421,29 @@ def record(actor: object, label: str) -> bool: if len(commits) < 100: break else: - return ChangeImpactAuthorshipEvidence( + return RepositoryAuthorshipEvidence( resolution="unresolved", commit_count=commit_count, reason="reviewed commit range exceeded the provider page bound", ) if conflicts: - return ChangeImpactAuthorshipEvidence( + return RepositoryAuthorshipEvidence( resolution="conflicting", commit_count=commit_count, reason="; ".join(sorted(set(conflicts)))[:500], ) if not commit_count: - return ChangeImpactAuthorshipEvidence( + return RepositoryAuthorshipEvidence( resolution="unresolved", reason="pull request returned no commit authorship evidence", ) if not contributor_ids: - return ChangeImpactAuthorshipEvidence( + return RepositoryAuthorshipEvidence( resolution="unresolved", commit_count=commit_count, reason="reviewed range has no human GitHub contributing identity", ) - return ChangeImpactAuthorshipEvidence( + return RepositoryAuthorshipEvidence( resolution="resolved", contributor_github_ids=tuple(sorted(contributor_ids)), commit_count=commit_count, @@ -462,8 +456,8 @@ def read_github_changed_files( repository_path: str, pull_request_number: int, max_file_pages: int, -) -> tuple[ChangeImpactChangedFileEvidence, ...]: - evidence_by_path: dict[str, ChangeImpactChangedFileEvidence] = {} +) -> tuple[RepositoryChangedFileEvidence, ...]: + evidence_by_path: dict[str, RepositoryChangedFileEvidence] = {} for page in range(1, max_file_pages + 1): payload = request( f"/repos/{repository_path}/pulls/{pull_request_number}/files?per_page=100&page={page}" @@ -477,33 +471,31 @@ def read_github_changed_files( if status == "renamed": origin = file_payload.get("previous_filename") if not isinstance(origin, str) or not origin.strip(): - raise ChangeImpactRepositoryEvidenceError( + raise RepositoryEvidenceError( "GitHub renamed file is missing a valid previous_filename: " + filename[:256] ) previous_filename = origin.strip() - evidence = ChangeImpactChangedFileEvidence( + evidence = RepositoryChangedFileEvidence( path=filename, change_kind=change_kind, previous_path=previous_filename ) if evidence.path in evidence_by_path: - raise ChangeImpactRepositoryEvidenceError( + raise RepositoryEvidenceError( "GitHub changed-file evidence repeats a path: " + evidence.path[:256] ) evidence_by_path[evidence.path] = evidence if len(files) < 100: break else: - raise ChangeImpactRepositoryEvidenceError( + raise RepositoryEvidenceError( "GitHub pull request file evidence exceeded the complete provider page bound." ) if not evidence_by_path: - raise ChangeImpactRepositoryEvidenceError( - "GitHub pull request did not return changed-file evidence." - ) + raise RepositoryEvidenceError("GitHub pull request did not return changed-file evidence.") # Real entries retain their change kind when a rename recreates or swaps a path. for evidence in tuple(evidence_by_path.values()): if evidence.previous_path is not None and evidence.previous_path not in evidence_by_path: - evidence_by_path[evidence.previous_path] = ChangeImpactChangedFileEvidence( + evidence_by_path[evidence.previous_path] = RepositoryChangedFileEvidence( path=evidence.previous_path, change_kind="removed" ) return tuple(evidence_by_path.values()) diff --git a/control_plane/service_bootstrap.py b/control_plane/service_bootstrap.py index db90e3cde..33acd3a64 100644 --- a/control_plane/service_bootstrap.py +++ b/control_plane/service_bootstrap.py @@ -11,8 +11,8 @@ from control_plane.drivers import native_routes from control_plane.every_code_github_webhook import handle_every_code_github_webhook_request -from control_plane.manager_preview_approval_github_webhook import ( - handle_manager_preview_approval_github_webhook_request, +from control_plane.trusted_maintenance_github_webhook import ( + handle_trusted_maintenance_github_webhook_request, ) from control_plane.http_app import ( LaunchplaneAuthzPolicyRuntime, @@ -207,8 +207,8 @@ def create_launchplane_service_application( work_graph_issue_inbox_provider=work_graph_issue_inbox_provider, work_graph_issue_inbox_reconcile_provider=work_graph_issue_inbox_reconcile_provider, every_code_github_webhook_handler=handle_every_code_github_webhook_request, - manager_preview_approval_github_webhook_handler=( - handle_manager_preview_approval_github_webhook_request + trusted_maintenance_github_webhook_handler=( + handle_trusted_maintenance_github_webhook_request ), ) diff --git a/control_plane/storage/filesystem.py b/control_plane/storage/filesystem.py index f4f8c06f7..26a592f27 100644 --- a/control_plane/storage/filesystem.py +++ b/control_plane/storage/filesystem.py @@ -26,7 +26,9 @@ ) from control_plane.contracts.authz_policy_record import LaunchplaneAuthzPolicyRecord from control_plane.contracts.backup_gate_record import BackupGateRecord -from control_plane.contracts.change_impact import ChangeImpactPolicyRecord +from control_plane.contracts.retired_change_impact import ( + ChangeImpactPolicyRecord, +) from control_plane.contracts.deployment_record import DeploymentRecord from control_plane.contracts.durable_operation_authorization import DurableOperationAuthorization from control_plane.contracts.edge_endpoint_record import EdgeEndpointRecord @@ -51,7 +53,7 @@ from control_plane.contracts.idempotency_record import LaunchplaneIdempotencyRecord from control_plane.contracts.ingress_canary_route_record import IngressCanaryRouteRecord from control_plane.contracts.ingress_route_audit_record import IngressRouteAuditRecord -from control_plane.contracts.manager_preview_approval import ( +from control_plane.contracts.retired_manager_preview_approval import ( ManagerPreviewApprovalEventRecord, ManagerPreviewApprovalEventWriteStatus, ) @@ -199,7 +201,9 @@ from control_plane.contracts.promotion_record import PromotionRecord from control_plane.contracts.release_tuple_record import ReleaseTupleRecord from control_plane.contracts.deploy_target import ProviderTargetRecord -from control_plane.manager_preview_approval import ManagerPreviewApprovalEventConflictError +from control_plane.contracts.retired_manager_preview_approval import ( + ManagerPreviewApprovalEventConflictError, +) from control_plane.contracts.dokploy_target_id_record import DokployTargetIdRecord from control_plane.contracts.dokploy_target_record import DokployTargetRecord from control_plane.contracts.runtime_environment_record import ( @@ -254,7 +258,7 @@ plan_repository_human_role_policy_append, plan_tenant_technical_human_waiver_event_append, ) -from control_plane.product_owner_service import ( +from control_plane.contracts.product_owner import ( ProductOwnerPolicyConflictError, ProductOwnerPolicySequenceError, ProductOwnerRequirementConflictError, @@ -262,7 +266,7 @@ ProductOwnerRoutingConflictError, ProductOwnerRoutingSequenceError, ) -from control_plane.change_impact_service import ( +from control_plane.contracts.retired_change_impact import ( ChangeImpactPolicyConflictError, ChangeImpactPolicySequenceError, ) diff --git a/control_plane/storage/postgres.py b/control_plane/storage/postgres.py index e2f6c39d5..d302d8960 100644 --- a/control_plane/storage/postgres.py +++ b/control_plane/storage/postgres.py @@ -141,8 +141,10 @@ from control_plane.provider_delivery_inspection_profile import ( ResolvedProviderDeliveryInspectionProfile, ) -from control_plane.contracts.change_impact import ChangeImpactPolicyRecord -from control_plane.contracts.change_impact_audit import ( +from control_plane.contracts.retired_change_impact import ( + ChangeImpactPolicyRecord, +) +from control_plane.contracts.retired_change_impact_audit import ( ChangeImpactPolicyAuditRecord, ChangeImpactPolicyAuditedWriteResult, ) @@ -234,7 +236,7 @@ from control_plane.contracts.ingress_canary_route_record import IngressCanaryRouteRecord from control_plane.contracts.ingress_route_audit_record import IngressRouteAuditRecord from control_plane.contracts.lane_summary import LaunchplaneLaneSummary -from control_plane.contracts.manager_preview_approval import ( +from control_plane.contracts.retired_manager_preview_approval import ( ManagerPreviewApprovalEventRecord, ManagerPreviewApprovalEventWriteStatus, ) @@ -402,7 +404,9 @@ PrivateHealthEndpointRecord, private_health_endpoint_record_sha256, ) -from control_plane.manager_preview_approval import ManagerPreviewApprovalEventConflictError +from control_plane.contracts.retired_manager_preview_approval import ( + ManagerPreviewApprovalEventConflictError, +) from control_plane.contracts.route_binding_record import ( EnvironmentRouteBindingRecord, route_binding_record_sha256, @@ -631,7 +635,7 @@ plan_trusted_maintenance_policy_append, trusted_maintenance_current_authority, ) -from control_plane.product_owner_service import ( +from control_plane.contracts.product_owner import ( ProductOwnerPolicyConflictError, ProductOwnerPolicySequenceError, ProductOwnerRequirementConflictError, @@ -639,7 +643,7 @@ ProductOwnerRoutingConflictError, ProductOwnerRoutingSequenceError, ) -from control_plane.change_impact_service import ( +from control_plane.contracts.retired_change_impact import ( ChangeImpactPolicyConflictError, ChangeImpactPolicySequenceError, ) diff --git a/control_plane/tenant_admission_controller.py b/control_plane/tenant_admission_controller.py index 9a4ee5118..85fddd9dd 100644 --- a/control_plane/tenant_admission_controller.py +++ b/control_plane/tenant_admission_controller.py @@ -14,9 +14,7 @@ OWNER_ACCEPTANCE_CHECK_NAME, is_launchplane_projected_check, ) -from control_plane.contracts.manager_preview_approval_projection import ( - MANAGER_PREVIEW_APPROVAL_CHECK_NAME, -) +from control_plane.contracts.advisory_check_projection import MANAGER_PREVIEW_APPROVAL_CHECK_NAME from control_plane.contracts.merge_train_policy import MergeTrainMergeMethod from control_plane.contracts.tenant_merge_eligibility import ( TenantMergeCandidate, diff --git a/control_plane/trusted_maintenance_github_webhook.py b/control_plane/trusted_maintenance_github_webhook.py index 053294374..74e3f2073 100644 --- a/control_plane/trusted_maintenance_github_webhook.py +++ b/control_plane/trusted_maintenance_github_webhook.py @@ -1,6 +1,10 @@ from __future__ import annotations +from collections.abc import Callable from dataclasses import dataclass +import hashlib +import json +import os from pathlib import Path from typing import Literal, Protocol, cast @@ -25,10 +29,14 @@ from control_plane.workflows.launchplane import ( github_api_request, resolve_launchplane_github_token, + verify_github_webhook_signature, ) TRUSTED_MAINTENANCE_GITHUB_WEBHOOK_SOURCE = "github-webhook" +# Preserve the configured transport and bootstrap secret for signed maintenance +# deliveries. This receiver no longer accepts manager approval commands. +TRUSTED_MAINTENANCE_WEBHOOK_ROUTE = "/v1/manager-preview-approval/github-webhook" TrustedMaintenanceWebhookStatus = Literal[ "captured", @@ -58,6 +66,10 @@ def __call__( class TrustedMaintenanceGitHubWebhookDependencies: github_token: _GitHubTokenResolver = resolve_launchplane_github_token github_api: _GitHubApiRequest = github_api_request + webhook_secret: Callable[[], str] = lambda: os.environ.get( + "LAUNCHPLANE_MANAGER_PREVIEW_GITHUB_WEBHOOK_SECRET", "" + ).strip() + verify_signature: Callable[..., None] = verify_github_webhook_signature @dataclass(frozen=True) @@ -67,6 +79,77 @@ class TrustedMaintenanceGitHubWebhookResult: evidence_status: Literal["written", "replayed", ""] = "" +def handle_trusted_maintenance_github_webhook_request( + payload_bytes: bytes, + event_name: str, + delivery_id: str, + signature_header: str, + record_store: object, + control_plane_root: Path, + trace_id: str, + *, + dependencies: TrustedMaintenanceGitHubWebhookDependencies | None = None, +) -> tuple[int, dict[str, object]]: + dependencies = dependencies or TrustedMaintenanceGitHubWebhookDependencies() + + def error(status: int, code: str, message: str) -> tuple[int, dict[str, object]]: + return status, { + "status": "error", + "trace_id": trace_id, + "error": {"code": code, "message": message}, + } + + if not delivery_id.strip(): + return error(400, "invalid_payload", "GitHub delivery id is required.") + try: + dependencies.verify_signature( + payload_bytes=payload_bytes, + signature_header=signature_header, + secret=dependencies.webhook_secret(), + ) + except click.ClickException: + return error(401, "invalid_signature", "GitHub webhook signature is invalid.") + try: + payload = json.loads(payload_bytes.decode("utf-8")) + except (UnicodeDecodeError, json.JSONDecodeError): + return error(400, "invalid_payload", "GitHub webhook body is invalid JSON.") + if not isinstance(payload, dict): + return error(400, "invalid_payload", "GitHub webhook body must be an object.") + result = handle_trusted_maintenance_github_webhook( + event_name=event_name, + delivery_id=delivery_id, + signed_payload_sha256=hashlib.sha256(payload_bytes).hexdigest(), + payload=payload, + record_store=record_store, + control_plane_root=control_plane_root, + dependencies=dependencies, + ) + if result.status == "conflict": + return error( + 409, + "trusted_maintenance_evidence_conflict", + "Signed delivery conflicts with existing maintenance evidence.", + ) + if result.status == "retryable_error": + return error( + 503, + "trusted_maintenance_unavailable", + "Trusted-maintenance evidence capture is temporarily unavailable.", + ) + return 202, { + "status": "accepted", + "trace_id": trace_id, + "result": { + "skipped": result.status == "skipped", + "reason": result.reason, + "trusted_maintenance": { + "status": result.status, + "evidence_status": result.evidence_status, + }, + }, + } + + def handle_trusted_maintenance_github_webhook( *, event_name: str, diff --git a/docs/README.md b/docs/README.md index abdfa9a15..5a83a6d20 100644 --- a/docs/README.md +++ b/docs/README.md @@ -67,8 +67,8 @@ not describe runtime authority until it is implemented, deployed, and activated. run records, dispatch binding, credential boundary, and worker lifecycle. - [engineering-review-decisions.md](engineering-review-decisions.md) — exact-head classification plus independent-run evaluation and shadow GitHub projection. -- [product-owner-policy.md](product-owner-policy.md) — authoritative - product/system Owner membership, requirement, routing, and evaluation contract. +- [product-owner-policy.md](product-owner-policy.md) — retired Owner policy + history and the current product-profile Owner boundary. - [owner-acceptance.md](owner-acceptance.md) — current Owner review and the compatibility boundary for retired acceptance history. - [release-review.md](release-review.md) — Owner checklist decisions for production @@ -77,9 +77,8 @@ not describe runtime authority until it is implemented, deployed, and activated. challenge payloads, cross-host conformance artifact, and deferred runtime boundary. - [privileged-operations.md](privileged-operations.md) — typed human-governed planning, managed-rule authorization, redaction, and future execution boundary. -- [change-impact-policy.md](change-impact-policy.md) — authoritative - affected-product, Owner-impact, and engineering-review classification - contract. +- [change-impact-policy.md](change-impact-policy.md) — retired classifier history and the + independent repository-evidence boundary. - [operations.md](operations.md) — operator workflows and runtime boundary rules. - [production-backup-provider.md](production-backup-provider.md) — typed Proxmox/PBS capture, host boundary, evidence and rollout prerequisites. diff --git a/docs/change-impact-policy.md b/docs/change-impact-policy.md index 3d29f4de2..a3d6e78e5 100644 --- a/docs/change-impact-policy.md +++ b/docs/change-impact-policy.md @@ -1,268 +1,20 @@ --- -title: Change Impact Policy +title: Retired Change-Impact Records --- -## Purpose - -Launchplane has an authoritative change-impact classifier for pull -requests. It derives affected products, Owner acceptance impact, and engineering -review tier from Launchplane policy plus trusted repository and change evidence. -Public callers submit only a repository/pull-request target reference and -request metadata. They cannot submit changed files, dependency -or reviewer facts, head/tree expectations, product impact, sensitive areas, or -review tiers. - -The classifier is intentionally deterministic for obvious policy matches. It -does not dispatch an extra LLM classification run for routine changes. Missing, -unknown, stale, mixed, ambiguous, or contradictory evidence returns a non-success -classification and fails closed to the stricter engineering-review requirement. - -## Policy Records - -`ChangeImpactPolicyRecord` is scoped to immutable GitHub repository identity: -numeric repository ID, numeric owner ID, and owner/name. Each revision contains -component rules that bind path prefixes to Launchplane components, affected -product/system scopes, and an engineering review tier of `routine` or -`sensitive`. - -Policy records are revisioned, digested, active/superseded records. A decision -must bind the exact repository, pull request number, head SHA, tree SHA, policy -revision, and policy digest. Stale head/tree or stale policy claims return -`stale_head` or `stale_policy` and never produce a success result. - -A component rule may additionally declare `production_affecting`. It marks the -affected product scopes whose changes reach a production surface, which raises -their Owner review class to `production_affecting` regardless of review tier. The -flag is normalized so that only an explicit `true` is stored, keeping existing -rule IDs and policy digests byte-identical. - -### Explicit V2 Classification - -Policies without `classification_model` retain the original cumulative rule -matching and policy-declared engineering-only behavior. Their rule IDs and -policy digests are unchanged. `classification_model: "v2"` opts into explicit -product authority: every rule must name affected products or -`product_impact: "declared_none"`, never both. A missing match is still unknown; -it cannot imply engineering-only impact. - -For each path, v2 selects the longest matching prefix as the product authority. -Equal-specificity rules from different components and noncanonical/root -prefixes are rejected. An explicit narrower rule may define an engineering-only -carveout or a different product scope. Those declarations belong to a reviewed -DB policy revision, not the changed repository content. - -Sensitive review, `governance_impact: true`, and `production_affecting` remain -floors across every matching ancestor and the selected rule. Governance impact -requires the existing sensitive two-review requirement without manufacturing a -product Owner subject. An inherited production floor applies to the selected -products and their trusted storage extensions. Both provider-supplied sides of -a rename classify independently and their impact is combined. - -Ancestor matches use `review_floor_only: true` with empty products in matched -evidence. They explain inherited requirements but cannot license stored product -extensions; only selected components can do that. Complete path coverage still -does not establish the validity of stored dependency or reviewer evidence. - -V2 policy dry-runs validate these rules. V2 apply is currently unavailable and -returns a policy conflict without writing records while rollout qualification -continues. Deploying this code does not activate v2, migrate policy, add a grant, -or reuse historical Owner -acceptance under different semantics. Policy activation remains a separately -reviewed CAS operation through existing policy-administrator authority. - -### Scoped Decision Identity and Total Fallback - -Successful v2 evaluations produce `binding_hash_version: 2` and a canonical -`change_impact_decision_digest`. The digest binds target/base identity, sorted -paths and change kinds, explicit rename pairs, selected prefixes and component -authority, complete product subject tuples, all effective ancestor floors, -generator contributions, trusted stored-evidence semantics, and policy/evaluation -schema versions. It also binds -the resulting product, review, governance, production, and coverage decision. - -Full policy IDs, revisions, and digests remain provenance. Unused policy -mappings, reasons, and evidence record IDs do not invalidate an otherwise -identical decision. Evidence identity is excluded because every authority -predicate is covered by kind, confidence, component, and full product scopes. -Matched-evidence prose, ordering, and multiplicity remain visible diagnostic -provenance rather than approval authority. Existing v1 hashes are unchanged; -no historical records are backfilled or upgraded to v2. - -This scoped digest identifies classifier semantics only. It does not by itself -authorize Owner-acceptance carry-forward. The issue `#2240` pilot keeps Owner -binding invalidation conservative until complete accepted-delivery evidence, -including relevant backend, configuration, interaction, artifact, and observed -runtime inputs, is proven unchanged. - -Unknown or stale evaluations emit no scoped binding pair. V2 requires known -change kinds and explicit rename origins represented among the changed paths, -including real recreated or swapped origins. The existing sensitive-only -`default_unknown_review_tier` is the total typed fallback for incomplete -coverage: two engineering reviews, unknown classification, and no invented -product subjects. Any known affected products remain visible. Root-prefix -rules cannot replace that fail-closed default. - -## Evidence Authority - -The GitHub provider retains each rename destination with an explicit -`previous_path` and evidence for its origin path. Missing or invalid -rename origins and repeated real provider paths fail evidence resolution for every -policy version; they cannot silently erase a classification boundary. Valid -legacy changes keep their existing product and review classification. Recreated -origins and rename swaps preserve real change kinds; synthetic removed origins -are added only where the provider supplied no real entry for that path. - -Launchplane resolves evaluation evidence through two server-owned boundaries: - -- A server-authenticated GitHub provider resolves immutable repository identity, - the current pull-request head and tree, and the complete changed-file set. A - second pull-request read rejects a head that changes during collection, and - renamed files preserve both old and new paths for policy matching. -- The same provider resolves the reviewed base ref and base SHA, plus numeric - GitHub contributing identities over the reviewed commit range from pull-request - authorship and GitHub-linked commit author/committer evidence. Bot or agent - work pushed under a human identity resolves to that human. A commit with no - linked numeric identity, a login that maps to two different numeric IDs, or a - range beyond the provider page bound resolves as `unresolved` or `conflicting` - and is never repaired. -- The active DB-backed component policy is authoritative for the affected - products it declares directly. Launchplane storage is the only source for - additional dependency or reviewer evidence. Stored reviewer evidence may add - affected products only when trusted dependency evidence exists for the same - matched component, and it cannot downgrade a deterministic sensitive match. - -GitHub Actions callers are additionally bound to the OIDC repository ID, owner -ID, repository name, and workflow `sha`. A repository or head mismatch is a -non-success result. Human and operator callers still receive current provider -facts rather than caller assertions. - -Missing dependency records do not invalidate a product scope declared directly -by the active component policy. They are required only when stored evidence is -used to extend that declared scope. Reviewer-only product claims remain -insufficient and return `unknown`. Provider unavailability and incomplete -pagination fail closed; no caller-controlled evidence fallback is available. - -## Generated boundaries in v2 - -A v2 component can declare `generated_by` instead of direct products or -`product_impact: declared_none`. It names 1–20 distinct terminal component -identities in the same DB-backed policy, with at most 400 edges per policy. -Missing, self-referential, implicit-empty, and generated targets are rejected; -multi-hop generator chains are unsupported. An operator must explicitly flatten -a composed generator into terminal authority components in a reviewed CAS input. - -The winning path rule inherits the union of those generators' products. It has -declared-none authority only when every generator explicitly declares none. -Review, governance, and production floors join upward from the artifact rule, -its path ancestors, its generators, and ancestors of every generator prefix. -Ancestor products never enter the union. A losing generated ancestor contributes -only its own floors; its generator edges are not expanded. - -For v2, file and stored-evidence rows report the component's derived review, -governance, and production flags, including its generator boundary. Path-ancestor -floors remain separate floor-only evidence and are joined into the aggregate -decision; per-row flags do not replace that aggregate authority. - -Resolution is pure and bounded, computed once per evaluation into immutable -per-generator contributions used by scoped decision binding. Mapping changes -remain distinguishable even when aggregate products and floors are identical. -Generator attribution does not prove generated bytes are current: existing -schema generation and drift checks remain independent requirements. These -fields do not alter v1 policy hashes when absent, and the v2 live-apply guard -remains pending explicit rollout qualification. - -## Output - -Every evaluation returns: - -- affected product/system scopes and per-product Owner acceptance requirement; -- engineering review tier and the resulting one- or two-review requirement; -- matched rules and trusted evidence; -- exact repository/PR/head/tree and policy provenance; -- explicit unknown evidence when classification fails closed. - -After policy and target validation, `coverage` independently reports whether -every provider-supplied changed path matched a policy rule. It contains the -total distinct unmatched-path count, at most 20 lexicographically sorted path -samples, and `truncated`. Each sample is capped at 256 characters; truncation -means either samples were omitted or a displayed path was shortened. The -unmatched-path entries in `unknown_evidence` use the same bounded samples and -include a truncation summary when needed. Renames retain the provider's old -and new paths, so either side can contribute an uncovered path. - -Pure coverage gaps return `policy_coverage_incomplete` with unknown status, -sensitive engineering review, and the existing fail-closed Owner result. -Contradictory or missing stored evidence retains -`ambiguous_or_missing_evidence`, even when coverage gaps also exist; `coverage` -still reports those gaps separately. Complete path coverage does not validate -stored evidence or authorize admission. `coverage` is null when evaluation -stops before path matching, such as missing policy or a stale caller target. - -These diagnostics do not change rule accumulation, product scope, review -requirements, policy selection, or acceptance-binding identities. Full policy -provenance remains authoritative; coverage is not a replacement for it. - -This retired evaluation no longer participates in Launchplane merge readiness. -Its remaining API and storage are pending deletion under #2446. Current site Owner -decisions use product review and the release checklist. - -## HTTP API - -Reads: - -- `GET /v1/change-impact/policy` -- `POST /v1/change-impact/evaluation` - -CAS apply/dry-run endpoint: - -- `POST /v1/change-impact/policies/apply` - -Policy writes are `policy_admin`. Evaluation reads are server-derived authority inputs. -Generated OpenAPI is the client contract source. - -Policy apply derives original-writer attribution from the authenticated local -admin/operator or GitHub Actions identity after the existing authorization check. -Other identity kinds are refused by the audited apply handler. -The audit contains the verified subject, bounded workflow identity fields where -applicable, server trace ID, and server timestamp. Client `source`, reasons, and -metadata do not identify the actor. No credentials or raw token claims are stored. -The additive `audit` readback sits outside the v1 policy payload and digest. - -Apply requires database storage so policy, predecessor supersession, and audit -commit or roll back together. Replays return the original audit, including -`legacy_unattributed` for records created without one; they never assign a new -actor to historical work. Dry runs return `not_applied` and write nothing. -Filesystem rehearsal APIs remain available without attribution and their policy -readback reports `attribution_unavailable`; the audited HTTP apply path refuses -that storage capability. - -The evaluation request schema contains only `target.repository`, -`target.pull_request_number`, and optional non-authoritative metadata. Extra -fields are rejected. The production route uses Launchplane-managed GitHub -credentials and an injectable repository-evidence provider protocol for tests. - -## Persistence - -Fresh evaluation and current-policy reads require an effective `active` policy. -They never fall back to a `superseded` revision when active authority is absent. -A supplied evaluation timestamp does not turn the fresh-evidence API into a -historical reconstruction API. Immutable historical policy and review records -remain intact, with unchanged digests. - -This intentionally changes broken superseded-only histories from implicit reuse -to `policy_unavailable` and sensitive, unknown classification. Operators can -detect that state with the existing policy read: `current_policy` is null while -`policy_history_count` is nonzero. Repair requires an explicitly reviewed policy -operation under existing authorization; this correction performs no backfill, -policy activation, or grant change. Normal atomic policy CAS leaves one active -revision, so correctly maintained histories retain their existing behavior. - -Filesystem rehearsal records live under `launchplane_change_impact_policies/`. -PostgreSQL uses `launchplane_change_impact_policies`. Migration -`d2e4f6a8b0c2` creates an empty table and indexes only; it does not infer or -backfill product inventory from repository text. - -Migration `d1f3a5b7c9e2` adds nullable original-writer audit JSON beside policy -rows. Existing rows remain unattributed. Superseding a policy preserves its -original audit. This migration does not grant policy administration authority or -change runtime credentials. +Path-based approval detection is retired under [DIRECTION.md](../DIRECTION.md). +The classifier, generated-boundary inference, policy administration, and +evaluation endpoints have been deleted. Do not create or repair an impact policy +to unblock a merge. The agent marks a change for Owner review using judgment; +the production release checklist supplies the Owner approval boundary. + +Merge admission reads current Git identities through `repository_evidence.py`. +Missing, malformed, incomplete, or changing provider evidence still refuses +admission. Engineering review uses the existing two independent authority slots, +without consulting path rules or affected-product detection. + +`contracts/retired_change_impact*.py` retains only historical payload and digest +compatibility needed by persisted policies, audits, and review records. Existing +tables, migrations, history, and storage import behavior are preserved. There is +no service route for applying or evaluating these retired policies. This change +does not delete records or mutate runtime grants. diff --git a/docs/engineering-review-decisions.md b/docs/engineering-review-decisions.md index b2d0eeff6..da71c2f97 100644 --- a/docs/engineering-review-decisions.md +++ b/docs/engineering-review-decisions.md @@ -2,53 +2,31 @@ title: Engineering Review Decisions --- -Engineering review decisions combine Launchplane's server-derived change-impact -classification with Launchplane-created engineering-review runs for one exact -repository, pull request, head, tree, and Every Code work-request lifecycle. - -The caller supplies only the repository/pull-request reference and stored work -request id. Launchplane resolves current GitHub evidence, the active -change-impact policy, and persisted review runs. Repository identity, head, -tree, review tier, required count, authority, reviewer slot, model family, and -decision binding are not accepted from the caller. - -Routine classifications require one approved completed run. Sensitive and -unknown classifications require two distinct slots; approval additionally -requires two model families. Stale targets, conflicting authorities, -nonterminal runs, failed/expired runs, insufficient reviews, and missing policy -evidence cannot produce approval. +Engineering review decisions combine server-resolved Git identities with +Launchplane-created review runs for one exact repository, pull request, head, +tree, and stored work-request lifecycle. The caller supplies only the PR +reference and work-request ID. The active authority binds reviewer slots, +model families, and policy revision. + +New schema-version-2 decisions require two completed, approved runs from distinct +slots and model families. Creation uses the first two configured authority +slots, without classifying paths or reading retired approval policies. Missing +authority, stale targets or authority, blocked/requested changes, and incomplete +review evidence cannot produce approval. Decision records are immutable and append-only. Their deterministic binding -excludes evaluation time so retrying identical evidence replays the same record. -The `launchplane/engineering-review-shadow` GitHub status is a projection only: -it is never read as authority and is not a required merge check. Projection -retry loads a persisted decision, re-verifies the current GitHub target, and -does not mutate review evidence. +excludes evaluation time, so identical evidence replays the original record. +Schema-version-1 history retains its original fields and exact digests, including +the optional legacy versioned impact identity. New decisions reject those retired +classification fields. No historical record is rewritten or deleted. -Routes: +The GitHub engineering-review check is a projection, never authority. Projection +retry reloads the persisted decision and verifies the current Git target. +Repository merge-train policy retains its existing `advisory`/`required` choice; +this change does not enable enforcement or change runtime policy. + +Routes remain: - `POST /v1/engineering-review-decisions/evaluate` - `GET /v1/engineering-review-decisions/{decision_id}` - `POST /v1/engineering-review-decisions/project` - -The rollout remains shadow-only until routine and sensitive canaries prove the -exact-head behavior and a later policy change deliberately enables enforcement. - -## Versioned Impact Identity - -Optional `binding_hash_version` and `change_impact_decision_digest` fields -preserve the legacy decision ID and digest when omitted. Version `2` requires a -scoped decision digest and a distinct hash domain. It excludes only the -change-impact policy record ID, revision, and full digest from semantic -identity; those fields remain stored as original provenance. Re-evaluating -identical v2 semantics returns the original immutable decision even when the -current full-policy provenance differs. Exact target, lifecycle, authority, -review outcome, required count, and qualifying runs remain bound. - -Admission requires the newest decision to match the current impact hash version and -compares versioned scoped impact identity for v2. Mixed or missing identities -fail closed. Legacy admission continues comparing full-policy digests, and all -other authority dimensions and exact-head checks remain unchanged. Successful -v2 evaluations derive scoped identity from current server-owned classification -inputs; unknown and stale results cannot publish it. V2 policy activation -remains unavailable pending rollout qualification. diff --git a/docs/engineering-review-runs.md b/docs/engineering-review-runs.md index 0425dfc5a..afa881617 100644 --- a/docs/engineering-review-runs.md +++ b/docs/engineering-review-runs.md @@ -26,9 +26,7 @@ checked-in defaults. Run creation accepts only `work_request_id`. Launchplane loads the stored `EveryCodeWorkRequestRecord`, requires its completed linked PR, resolves the exact current GitHub head and tree through authenticated server evidence, loads -the one active authority, and creates deterministic pending assignments. Until -integration issue #2001 consumes the server-derived classification foundation, -creation always selects the first two contiguous, model-family-diverse slots. +the one active authority, and creates deterministic pending assignments. Creation selects the first two contiguous, model-family-diverse slots. ## Worker lifecycle diff --git a/docs/merge-admission.md b/docs/merge-admission.md index 64d91a330..d3d4f358d 100644 --- a/docs/merge-admission.md +++ b/docs/merge-admission.md @@ -16,7 +16,9 @@ after the effect boundary. New admissions record algorithm `merge-admission-v2`, which removes retired Owner/change-impact merge gates. Existing v1 admissions and landing outcomes remain immutable and readable. Site Owner review is recorded separately by the -product-review and release-checklist paths. +product-review and release-checklist paths. Repository evidence now comes from +the independent Git evidence reader; admission never imports or calls the retired +approval classifiers. Historical payload readers preserve prior record digests. Each batch entry is re-evaluated under the current repository/base controller lease immediately before its provider merge. The live adapter resolves current diff --git a/docs/operations.md b/docs/operations.md index 56bf6a032..54e9ff077 100644 --- a/docs/operations.md +++ b/docs/operations.md @@ -38,72 +38,18 @@ provider effect was attempted for the blocked entry and the controller lease is released cleanly. Earlier entries in a multi-PR batch may already be merged; the returned landing plan identifies their persisted status. -## Change-Impact Policy Recovery - -Use the deployed service's `GET /v1/change-impact/policy` and existing -`POST /v1/change-impact/policies/apply` policy-administrator operation; do not repair -shared records through a local checkout or by first landing the blocked -repository change. Fresh evaluation requires an effective active policy and -never revives a superseded revision. A null `current_policy` with a nonzero -`policy_history_count` signals unavailable current authority; it can also -reflect invalid multiple-active history or no active record effective at the -evaluation timestamp. Inspect the reason and timestamp before choosing a repair; -passing an earlier evaluation time does not reconstruct historical authority. -Normal apply rejects future-effective incoming records. The correction performs -no backfill. - -For a valid replacement, use the active tip's record ID and full digest as CAS -preconditions in both dry-run and apply. Only an initial revision for a -repository without history uses empty expected-tip fields. Existing history -without a valid active tip needs a reviewed repair investigation; do not guess -a superseded tip or treat it as a new repository. Dry-run validates the same -preconditions without writing or activating anything. A stale precondition -requires a fresh read and reviewed replacement, not an unconditional overwrite. The -existing service authorization is required independently of repository CI or -merge readiness; an authorization denial is not permission to add a grant. -Follow [authorization authority](authorization-authority.md). - -The audited database write records the authenticated original writer in the -same transaction as the policy CAS. The request body cannot supply that actor. -Replay returns the original attribution; it does not credit a later caller. -Superseding a revision preserves its original audit, and pre-existing records -remain `legacy_unattributed` instead of receiving invented provenance. Policy -content digests remain unchanged by the separate audit record. See -[change-impact policy](change-impact-policy.md) for the audit and read contracts. -Audited apply requires database storage. `attribution_unavailable` means a read -cannot supply audit evidence; it is distinct from a database-backed historical -record known to be `legacy_unattributed`. - -The shared Codex Launchplane helper exposes only the audit's record ID, digest, -actor kind and timestamp. It omits writer subjects, workflow identity and the -audit trace ID; its displayed timestamp is normalized to whole-second UTC without -altering the stored original. See the maintained -[helper response contract](https://github.com/cbusillo/codex-skills/blob/main/launchplane/references/write-action-helper-contract.md). -When these API responses change, update the helper's explicit projection and -response fixtures in the same workstream. Unknown response fields fail closed, -including newly added optional fields; they are not silently passed through. - -`policy_coverage_incomplete` identifies pure unmatched-path gaps. Contradictory -evidence retains `ambiguous_or_missing_evidence` even when coverage gaps also -exist; the separate coverage data still reports those gaps. Samples are bounded -and may be truncated by count or by shortening individual paths. Use the total -count and truncation marker, and never copy a shortened sample as a rule prefix. -Missing coverage means evaluation stopped before coverage was known, -not that every path matched. Inspect the actual changed paths, selected rules -and generator boundaries before proposing a policy revision; do not add a broad -repository default merely to clear a diagnostic. Artifact drift checks remain -independent of generated-component attribution. - -V2 classification remains unavailable for live apply while rollout -qualification is pending. Dry-run validation and landed compatibility code do -not migrate policies, upgrade historical reviews, or resume Owner-governance -lifecycle work. Milestone 2 is owner-paused in -[#2164](https://github.com/cbusillo/launchplane/issues/2164); resuming requires a -new dated owner decision and the existing compatibility prerequisites. Lifecycle -work in [#2162](https://github.com/cbusillo/launchplane/issues/2162) remains blocked -by that pause. Required product reviews need fresh v2 human acceptance when that -version is deliberately activated; engineering-only changes need no synthetic -Owner action. +## Retired Approval Policies + +Change-impact and product-Owner policy endpoints are removed. Do not repair +retired policies to unblock delivery. Merge admission reads current Git and +technical evidence; Owner approval belongs to the product review and release +checklist. Historical policy/audit readers retain old payloads without making +approval decisions. See [retired impact records](change-impact-policy.md). + +The legacy manager webhook URL remains a signed trusted-maintenance transport. +Manager commands and the manager reconcile endpoint are removed. No manager +event authorizes a release, and preview destruction no longer writes such an +event. Runtime grant contraction is separate from this code deletion. ## Repository Inventory @@ -941,19 +887,11 @@ Owner identity directly. Do not introduce new GitHub-secret/workflow grants; use the native reviewed policy path for any separately authorized contraction. See [owner-acceptance.md](owner-acceptance.md). -The manager-preview set above is current compatibility authority, not the issue -`#2240` target. Preserve it only until Owner-acceptance replacement coverage and -rollback are proved; do not extend it for the delegated-delivery pilot or treat -it as merge, deploy, configuration, secret, or policy authority. - -`LAUNCHPLANE_AUTHZ_PRODUCT_OWNER_POLICY_ADMIN_MANAGED_SET_JSON` owns the -dedicated local-operator policy administration boundary and must declare the -exact `operator.product-owner-policy-admin` managed-set identity. Each rule -binds one exact operator subject and token label to one exact product/system -scope and exactly the Product Owner policy and requirement read/write actions. -It cannot grant routing, deployment, production, secret, or unrelated policy -authority. Use this set only to apply DB-backed Owner membership and requirement -records through the deployed service with dry-run and compare-and-swap evidence. +The manager-preview and Product Owner policy-admin grant sets are retained +runtime history, not active approval paths: their service evaluators and routes +are removed. This code retirement creates no grant and performs no runtime +contraction. Do not extend these sets or use them as delivery authority. + Generic-web preview caller grants are no longer sourced from a per-product repository secret. The typed product-onboarding planner derives repository identity, caller workflow diff --git a/docs/product-owner-policy.md b/docs/product-owner-policy.md index 10a917477..a750011db 100644 --- a/docs/product-owner-policy.md +++ b/docs/product-owner-policy.md @@ -1,155 +1,15 @@ --- -title: Product Owner Policy +title: Retired Product Owner Policy Records --- -## Purpose - -Launchplane owns the authoritative product/system Owner policy used by exact-change -Owner acceptance. The policy remains independent from production authorization, -promotion, technical checks, engineering review, and provider landing effects. -An Owner may view product change and observed runtime status and may accept, -request changes, revoke acceptance, or give feedback. Owner membership never -grants source, configuration, deploy, merge, secret, access-administration, or -other operational authority. - -The contract has one human `Owner` class. Membership and evaluated actors are -bound only to an immutable, positive numeric GitHub user ID. GitHub Actions, -terminal agents, local operators, and local administrators are not eligible -Owner identities. Identity and grant records are immutable after validation so -their derived IDs cannot become stale. A current policy may contain multiple -Owners; the quorum is fixed at one. - -## Separate Authorities - -Three independently revisioned record streams prevent accidental authority: - -- `ProductOwnerPolicyRecord` grants Owner membership. It does not make any - action require an Owner. -- `ProductOwnerRequirementRecord` lists the actions, repositories, and - environments that require an Owner. It contains no identities. -- `ProductOwnerRoutingRecord` records preferred Owner routing. It is persisted - with `authoritative=false` and never participates in membership evaluation. - -Launchplane authz grants control who may invoke the read and policy-admin APIs. -They never satisfy an Owner requirement. Administrative roles are not part of -Owner actor identity and cannot influence evaluation. A GitHub human who also -has Launchplane administration satisfies an Owner action only when that -human's immutable GitHub ID appears in the current product/system policy and -matches the action's repository and environment scope. - -## Scoped Review Policy - -The bullets below describe current runtime policy. The reconciled target in -issue `#2240` permits the actual current product Owner to accept their own -product change even when the same human contributed engineering work, while -independent technical review remains separate. Implementing that target requires -an explicit policy/schema migration and activation; this paragraph does not -change current self-review evaluation. - -`ProductOwnerPolicyRecord` also carries three scoped policies that shape Owner -product-review admissibility. They are fail-closed by default, so a policy -written before they existed behaves exactly as if it declared the defaults: - -- `review_age` declares the finite Owner-review evidence age. Migration defaults - are 30 days for routine changes and 7 days for sensitive, unknown, and - production-affecting changes. A policy may only choose shorter values, and the - elevated value can never exceed the routine value. -- `self_review` denies self-review by default. A routine-only exception requires - an explicit positive `exception_revision` and a reason, and it never applies to - sensitive, unknown, or production-affecting changes. -- `preview_trust` declares the minimum provable preview isolation class. The - default is `synthetic_seeded`; `unknown` and `not_applicable` cannot be chosen - as a minimum, and `unknown` observed isolation never satisfies any minimum. - -These three policies are deliberately excluded from `policy_digest`, which -remains the Owner *membership* fingerprint. Existing records therefore keep their -exact digest, and every dimension is instead bound through separate -`product_owner_scoped_policy_fingerprint` values. - -Each fingerprint is explicitly versioned by -`PRODUCT_OWNER_POLICY_FINGERPRINT_VERSION` and scoped to one exact product, -system, repository ID, environment, and action, alongside the source record ID -and revision. Two products, or two actions on one product, can never share a -fingerprint. Owner acceptance binds the membership, self-review, review-age, -requirement, and preview-trust fingerprints; changing any of them changes the -exact binding and stales prior evidence rather than silently reinterpreting it. - -## Current-Policy Evaluation - -Authority evaluation resolves only active records for the exact product/system -scope. When an explicit requirement matches, evidence is checked against the -current policy revision and digest. Stale revisions, removed Owners, another -product's Owners, and identities present only in preferred routing do not -satisfy the required action. - -If a current policy exists but no current Owner grant covers the requested -repository and environment, evaluation returns `unavailable` with -`policy_scope_not_covered`. This is distinct from `actor_not_current_owner`, -which means the policy scope is covered but the evaluated GitHub human is not -one of its current Owners. - -When one current Owner would satisfy the quorum, the read model returns every -current Owner in scope as the notification audience. Preferred routing only -marks which Owner is preferred; another current Owner remains able to satisfy -the quorum. - -## Persistence - -Filesystem rehearsal records live under: - -- `launchplane_product_owner_policies/` -- `launchplane_product_owner_requirements/` -- `launchplane_product_owner_routing/` - -PostgreSQL uses tables with the same names. Each stream has one active-record -partial unique index, a unique scope/revision index, a current-history index, -and a record-id primary key. Owner requirements are authoritative by definition; -preferred routing remains non-authoritative and is enforced as such at the database layer. - -Successor revisions must have a non-decreasing `effective_at`. An incoming -active revision must already be effective when applied; future scheduling -cannot supersede the current authority into an unavailable gap. Invalid -revision sequences and optimistic write conflicts are reported separately. - -Migration `c1d2e3f4a5b6` creates empty tables only. It performs no inference, -owner mapping, or backfill. Real identities remain operator-supplied runtime -records. - -Migration `b2d4f6a8c0e2` writes the fail-closed `review_age`, `self_review`, and -`preview_trust` defaults into existing stored policy payloads so the records are -self-describing. Because those fields are outside `policy_digest`, the backfill -cannot change any persisted digest. - -Migration `f0a2c4e6b8d1` removes the legacy requirement `enforcement_mode` column -without promoting old requirements into authority. It archives every exact -pre-cutover row, replaces each product/system scope with an empty successor -revision-1 baseline, and requires an operator to create the first explicit -authoritative requirement revision. Existing Owner acceptance events remain -immutable and become stale against the empty baseline until that deliberate -cutover occurs. - -## HTTP API - -Reads: - -- `GET /v1/product-owner/policy` -- `GET /v1/product-owner/requirement` -- `GET /v1/product-owner/routing` -- `GET /v1/product-owner/evaluation` - -CAS apply/dry-run endpoints: - -- `POST /v1/product-owner/policies/apply` -- `POST /v1/product-owner/requirements/apply` -- `POST /v1/product-owner/routing/apply` - -The three write actions are classified as `policy_admin`. Authority evaluation -remains a read action; only a browser-authenticated current Owner can write an -acceptance event. Generated OpenAPI is the contract source for clients. - -## Unconfigured Repositories - -Repositories without active change-impact, Owner policy, and Owner requirement -records do not expose an interactive Owner action. Their evaluation is either -`not_required` or fail-closed `unavailable`; Launchplane never falls back to a -non-authoritative approval path. +Product Owner membership, requirement, and routing policies are retired under +[DIRECTION.md](../DIRECTION.md). Their evaluators and `/v1/product-owner/*` +administration/read routes have been deleted. Existing payload contracts, +tables, migrations, and storage history remain readable; they do not determine +current approval requirements. + +The product profile's immutable Owner GitHub identity controls current +[Owner review](owner-acceptance.md) and [release review](release-review.md). +The supported product-profile Owner setting operation remains available under +its existing authorization. This retirement changes no Owner identity, grant, +record, or production deployment. diff --git a/docs/records.md b/docs/records.md index 1b231007e..ac3f63ef0 100644 --- a/docs/records.md +++ b/docs/records.md @@ -334,39 +334,15 @@ operation or reconciliation key before invoking the provider, and complete only after durable local evidence is ready. A crash or timeout after key binding is an unknown outcome, not permission to retry the provider mutation. -## Product Owner Policy Records - -Product/system Owner state is split across three independently revisioned -records: - -- `ProductOwnerPolicyRecord` stores one or more human Owner grants. Each grant - binds an immutable provider subject identity to explicit repository and - environment sets. The only Owner class is `owner`, and quorum is one. -- `ProductOwnerRequirementRecord` stores explicit action, repository, and - environment requirements. An Owner grant never implies that an action is - required. -- `ProductOwnerRoutingRecord` stores preferred immutable identity IDs. The - record carries `authoritative=false`; preferred routing cannot grant or deny - Owner authority. - -All three streams use deterministic record IDs, canonical SHA-256 payload -digests, active/superseded history, exact-next revision sequencing, predecessor -links, and compare-and-swap expected-tip writes. Authority evaluation reports the -current policy, requirement, and routing provenance. Matching requirements govern -the exact Owner acceptance binding; preferred routing never grants authority. - -The authority-cutover migration archives exact pre-cutover requirement rows in -`launchplane_product_owner_requirement_authority_migrations`. Runtime readers -and writers do not consume that table. Each migrated scope receives an empty -revision-1 baseline, intentionally resetting the executable revision stream -while preserving the prior chain in the archive. The next supported write can -append revision 2, so Owner actions cannot govern a repository until an operator -supplies an explicit authoritative requirement. - -The PostgreSQL tables are `launchplane_product_owner_policies`, -`launchplane_product_owner_requirements`, and -`launchplane_product_owner_routing`. Migration `c1d2e3f4a5b6` creates these -tables without inserting or inferring any owner data. +## Retired Product Owner Policy Records + +Historical product/system Owner grants, requirements, and routing remain in +`launchplane_product_owner_policies`, `launchplane_product_owner_requirements`, +and `launchplane_product_owner_routing`. Their immutable payloads, digests, +migrations, and archived authority-cutover rows remain readable. The retired +evaluators and service routes are removed; these records cannot decide a merge +or release. Current Owner identity comes from the product profile. + ## Retired Owner Acceptance Event Records @@ -382,30 +358,14 @@ indexes. No row, event id, binding digest, replay digest, or sequence is rewritt or deleted. Filesystem-to-PostgreSQL import rejects archives containing retired Owner events before writing anything. See [owner-acceptance.md](owner-acceptance.md). -## Change Impact Policy Records - -`ChangeImpactPolicyRecord` stores repository-scoped component/path impact rules -for authoritative pull-request classification. Each active revision binds the exact -numeric GitHub repository ID, numeric owner ID, owner/name, component rules, -affected product/system scopes, engineering review tier, source, reason, -effective timestamp, predecessor, and canonical policy digest. - -Evaluations are derived responses, not durable authority records in this slice. -They bind the exact repository, pull request number, head SHA, tree SHA, policy -revision, and policy digest. Unknown paths, missing dependency evidence, -ambiguous stored evidence, stale provider or OIDC head binding, provider -failure, or invalid policy history fail closed to non-success output with the -stricter two-review engineering requirement. The active component policy may -declare affected products directly. Additional dependency and reviewer evidence -is read only from exact-target Launchplane records, and reviewer product claims -require trusted same-component dependency evidence; missing extension evidence -cannot be replaced by a caller assertion. - -Filesystem rehearsal records live under `launchplane_change_impact_policies/`. -PostgreSQL uses `launchplane_change_impact_policies` with one active policy per -repository, unique repository/revision history, and JSONB payload storage. -Migration `d2e4f6a8b0c2` creates the empty table and indexes without inferring -runtime product inventory from checked-in repository files. +## Retired Change Impact Policy Records + +Historical repository-scoped impact policies and audits retain their exact +payloads and digests in `launchplane_change_impact_policies` and its audit store. +Record contracts and storage compatibility remain; the classifier and policy +service endpoints are deleted. Active merge admission reads Git identities +independently and current engineering decisions use two authority-bound reviews. + ## Transactional Outbox @@ -2334,70 +2294,21 @@ run` is the foreground loop intended for an external process supervisor, and Launchplane API payloads, not as the final integration boundary external products are expected to couple to forever. -## Manager Preview Approval Event Record - -These records describe current compatibility behavior and must remain readable -as historical evidence through migration and rollback. The reconciled target in -issue `#2240` uses authoritative Owner-acceptance evidence from the trusted -Launchplane Owner surface. New delivery admission must consume that Owner -evidence through a separately authorized Launchplane job; a manager event cannot -confer merge, deploy, configuration, secret, or policy authority. Retire current -manager admission only after replacement coverage is proved, without deleting -the append-only ledger. - -- One append-only event per manager decision or lifecycle invalidation for an - exact rendered preview identity. Events use deterministic ids derived from - the exact binding, action, and source event so delivery retries replay without - overwriting history; a conflicting replay is rejected. -- The binding captures product, context, repository, pull request, head SHA, - preview and serving-generation ids, artifact id and immutable image digest, - resolved manifest fingerprint, preview URL, and the full checked runtime - identity plus canonical runtime/binding digests. -- Manager-authored `approved`, `changes_requested`, and `revoked` events require - a stable GitHub numeric identity and exactly one schema-v2 managed - authorization rule granting `manager_preview_approval.write` for the product - and context. The event stores the display login only as audit presentation and - records the managed rule ids plus authorization policy record id, revision, - source, and digest. -- Lifecycle-authored `superseded` and `invalidated` events preserve teardown, - PR-close, generation-replacement, and related history without impersonating a - manager. Preview destroy and cleanup remain available independently of - approval and never consult approval as an admission gate. -- The decision projection is computed from the append-only ledger and current - preview/generation/policy evidence. It returns `pending`, `approved`, - `changes_requested`, `revoked`, `stale`, or `unavailable` with a public-safe - reason. Any head, serving generation, artifact digest, manifest, runtime - identity, verification, preview state, or policy mismatch fails closed. -- People-based manager resolution is private agent routing for communication and - planning only. It is not persisted in this record and is never runtime - authorization. Launchplane's active managed policy is the authority; GitHub - interaction and promotion-check projection are separate downstream adapters, - and tenant repositories own only their thin workflow integration. -- Signed `issue_comment.created` delivery is the manager interaction adapter. - Launchplane re-fetches the comment actor and current pull-request head, then - accepts only an exact `/preview approve|changes|revoke ` - command. Delivery replay returns the existing append-only event, while actor, - head, fingerprint, serving-generation, and policy mismatches write nothing. -- `manager-preview-approval` is a GitHub status projection of this record, not - authority. Only the explicit operator reconcile route still writes it; preview - lifecycle routes and the webhook do not. That route updates only a marker - comment owned by the authenticated Launchplane credential and projects - `pending`, `success`, `failure`, or `error` on the current head. GitHub write failure never rewrites or deletes - approval evidence. -- Preview refresh and verification routes reconcile the projection after their - durable record changes. Pull-request synchronize, reopen, close, preview-label - removal, isolated destroy, and managed-policy updates also re-project current - evidence. Destroy and cleanup proceed even when GitHub is unavailable; an - authenticated reconciliation request can retry the projection later. -- Policy-scoped live promotion joins the testing artifact digest and source SHA - to exactly one active serving preview, includes the approval decision in the - promotion evidence fingerprint, and denies before provider mutation unless - the decision is `approved`. Removing the managed approval rule disables this - admission requirement without deleting event history. - -The preceding promotion join is a current-runtime fact, not the target contract. -Its replacement must bind the full accepted delivery evidence conservatively and -use current Owner acceptance plus independent delivery authority. +## Retired Manager Preview Approval Event Record + +Historical manager events remain append-only in their existing filesystem and +PostgreSQL stores. Payloads, event IDs, binding digests, and migrations are +unchanged. Their evaluator, command parser, reconcile endpoint, and projection +writer are deleted. Preview refresh/destroy no longer creates manager events. +Current approval uses product review and the release checklist; manager history +cannot satisfy either gate. + +The configured `/v1/manager-preview-approval/github-webhook` URL and its existing +bootstrap secret remain solely as a transport compatibility boundary for signed +trusted-maintenance evidence. The receiver verifies the signature and ignores +`issue_comment` commands; it cannot record approval or merge. No webhook, +credential, runtime grant, or stored record is changed by this code retirement. + ## Launchplane Preview Enablement Record diff --git a/docs/release-review.md b/docs/release-review.md index bb76715e6..5c921fef6 100644 --- a/docs/release-review.md +++ b/docs/release-review.md @@ -109,6 +109,6 @@ live activation. The CM baseline must be verified from deployed service records. Release decisions are persisted in `launchplane_release_review_decisions`, with file storage reserved for tests and rehearsal. Deployments must migrate the -database before serving the new routes. This change does not retire the remaining -merge-admission Owner/change-impact machinery; that is the following slice of -the Owner-approval replacement. +database before serving the new routes. The retired change-impact, product-Owner-policy, and manager-preview evaluators +and administration routes are deleted. Their historical records remain readable +and cannot satisfy this release gate. diff --git a/frontend/generated/openapi-canonical.json b/frontend/generated/openapi-canonical.json index d4f8e2560..90cd243ba 100644 --- a/frontend/generated/openapi-canonical.json +++ b/frontend/generated/openapi-canonical.json @@ -3282,150 +3282,6 @@ "title": "BootstrapEvidence", "type": "object" }, - "ChangeImpactAffectedProduct": { - "additionalProperties": false, - "properties": { - "owner_acceptance_required": { - "const": true, - "default": true, - "title": "Owner Acceptance Required", - "type": "boolean" - }, - "owner_action": { - "title": "Owner Action", - "type": "string" - }, - "owner_environment": { - "title": "Owner Environment", - "type": "string" - }, - "product": { - "title": "Product", - "type": "string" - }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" - }, - "system": { - "title": "System", - "type": "string" - } - }, - "required": [ - "product", - "system", - "owner_action", - "owner_environment" - ], - "title": "ChangeImpactAffectedProduct", - "type": "object" - }, - "ChangeImpactComponentRule": { - "additionalProperties": false, - "properties": { - "affected_products": { - "default": [], - "items": { - "$ref": "#/components/schemas/ChangeImpactProductScope" - }, - "title": "Affected Products", - "type": "array" - }, - "component": { - "title": "Component", - "type": "string" - }, - "generated_by": { - "anyOf": [ - { - "items": { - "type": "string" - }, - "type": "array" - }, - { - "type": "null" - } - ], - "title": "Generated By" - }, - "governance_impact": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Governance Impact" - }, - "path_prefixes": { - "items": { - "type": "string" - }, - "title": "Path Prefixes", - "type": "array" - }, - "product_impact": { - "anyOf": [ - { - "const": "declared_none", - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Product Impact" - }, - "production_affecting": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Production Affecting" - }, - "reason": { - "title": "Reason", - "type": "string" - }, - "review_tier": { - "default": "routine", - "enum": [ - "routine", - "sensitive" - ], - "title": "Review Tier", - "type": "string" - }, - "rule_id": { - "default": "", - "title": "Rule Id", - "type": "string" - }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" - } - }, - "required": [ - "component", - "path_prefixes", - "reason" - ], - "title": "ChangeImpactComponentRule", - "type": "object" - }, "ChangeImpactCoverage": { "additionalProperties": false, "description": "Bounded path-coverage diagnostics, independent of classification authority.", @@ -3466,308 +3322,207 @@ "title": "ChangeImpactCoverage", "type": "object" }, - "ChangeImpactEvaluation": { + "DataProvenance": { "additionalProperties": false, "properties": { - "affected_products": { - "default": [], - "items": { - "$ref": "#/components/schemas/ChangeImpactAffectedProduct" - }, - "title": "Affected Products", - "type": "array" - }, - "binding_hash_version": { - "anyOf": [ - { - "const": 2, - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Binding Hash Version" - }, - "change_impact_decision_digest": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Change Impact Decision Digest" - }, - "classification_model": { - "anyOf": [ - { - "const": "v2", - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Classification Model" - }, - "coverage": { - "anyOf": [ - { - "$ref": "#/components/schemas/ChangeImpactCoverage" - }, - { - "type": "null" - } - ] - }, - "engineering_review_tier": { - "default": "sensitive", - "enum": [ - "routine", - "sensitive" - ], - "title": "Engineering Review Tier", + "detail": { + "default": "", + "title": "Detail", "type": "string" }, - "governance_impact": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Governance Impact" - }, - "matched_evidence": { - "default": [], - "items": { - "$ref": "#/components/schemas/ChangeImpactMatchedEvidence" - }, - "title": "Matched Evidence", - "type": "array" - }, - "owner_impact": { - "default": "unknown", + "freshness_status": { "enum": [ - "required", - "not_required", - "unknown" + "verified", + "recorded", + "stale", + "missing", + "unsupported" ], - "title": "Owner Impact", + "title": "Freshness Status", "type": "string" }, - "policy_digest": { + "recorded_at": { "default": "", - "title": "Policy Digest", + "title": "Recorded At", "type": "string" }, - "policy_record_id": { + "refreshed_at": { "default": "", - "title": "Policy Record Id", - "type": "string" - }, - "policy_revision": { - "anyOf": [ - { - "minimum": 1.0, - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Policy Revision" - }, - "production_affecting_products": { - "default": [], - "items": { - "$ref": "#/components/schemas/ChangeImpactProductScope" - }, - "title": "Production Affecting Products", - "type": "array" - }, - "reason_code": { - "title": "Reason Code", + "title": "Refreshed At", "type": "string" }, - "required_engineering_review_count": { - "default": 2, - "enum": [ - 1, - 2 - ], - "title": "Required Engineering Review Count", - "type": "integer" - }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" - }, - "status": { + "source_kind": { "enum": [ - "success", - "unknown", - "stale_head", - "stale_policy" + "record", + "provider", + "descriptor", + "unsupported" ], - "title": "Status", + "title": "Source Kind", "type": "string" }, - "target": { - "$ref": "#/components/schemas/ChangeImpactTarget" + "source_record_id": { + "default": "", + "title": "Source Record Id", + "type": "string" }, - "unknown_evidence": { - "default": [], - "items": { - "type": "string" - }, - "title": "Unknown Evidence", - "type": "array" + "stale_after": { + "default": "", + "title": "Stale After", + "type": "string" } }, "required": [ - "status", - "reason_code", - "target" + "source_kind", + "freshness_status" ], - "title": "ChangeImpactEvaluation", + "title": "DataProvenance", "type": "object" }, - "ChangeImpactEvaluationMetadata": { + "DeployTargetContractReference": { "additionalProperties": false, "properties": { - "reason": { - "default": "", - "maxLength": 1000, - "title": "Reason", + "provider_id": { + "default": "dokploy", + "title": "Provider Id", "type": "string" }, - "request_id": { + "provider_target_type": { "default": "", - "maxLength": 200, - "title": "Request Id", + "title": "Provider Target Type", "type": "string" }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" - } - }, - "title": "ChangeImpactEvaluationMetadata", - "type": "object" - }, - "ChangeImpactEvaluationRequest": { - "additionalProperties": false, - "properties": { - "metadata": { - "$ref": "#/components/schemas/ChangeImpactEvaluationMetadata" - }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" + "target_category": { + "default": "unknown", + "enum": [ + "application", + "compose", + "container", + "service", + "static", + "unknown" + ], + "title": "Target Category", + "type": "string" }, - "target": { - "$ref": "#/components/schemas/ChangeImpactTargetReference" + "target_name": { + "title": "Target Name", + "type": "string" } }, "required": [ - "target" + "target_name" ], - "title": "ChangeImpactEvaluationRequest", + "title": "DeployTargetContractReference", "type": "object" }, - "ChangeImpactEvaluationResponse": { + "DeployedTargetReference": { "additionalProperties": false, "properties": { - "evaluation": { - "$ref": "#/components/schemas/ChangeImpactEvaluation" + "display_name": { + "title": "Display Name", + "type": "string" }, - "status": { - "const": "ok", - "default": "ok", - "title": "Status", + "provider_evidence": { + "additionalProperties": { + "type": "string" + }, + "title": "Provider Evidence", + "type": "object" + }, + "provider_id": { + "title": "Provider Id", "type": "string" }, - "trace_id": { - "title": "Trace Id", + "provider_target_type": { + "default": "", + "title": "Provider Target Type", + "type": "string" + }, + "target_category": { + "default": "unknown", + "enum": [ + "application", + "compose", + "container", + "service", + "static", + "unknown" + ], + "title": "Target Category", + "type": "string" + }, + "target_id": { + "title": "Target Id", "type": "string" } }, "required": [ - "trace_id", - "evaluation" + "provider_id", + "target_id", + "display_name" ], - "title": "ChangeImpactEvaluationResponse", + "title": "DeployedTargetReference", "type": "object" }, - "ChangeImpactMatchedEvidence": { + "DeploymentEvidence-Input": { "additionalProperties": false, "properties": { - "affected_products": { - "default": [], - "items": { - "$ref": "#/components/schemas/ChangeImpactProductScope" - }, - "title": "Affected Products", - "type": "array" + "deploy_mode": { + "title": "Deploy Mode", + "type": "string" }, - "component": { + "deployment_id": { "default": "", - "title": "Component", + "title": "Deployment Id", "type": "string" }, - "governance_impact": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Governance Impact" + "finished_at": { + "default": "", + "title": "Finished At", + "type": "string" }, - "path": { + "provider_deploy_mode": { "default": "", - "title": "Path", + "title": "Provider Deploy Mode", "type": "string" }, - "production_affecting": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Production Affecting" + "provider_id": { + "default": "dokploy", + "title": "Provider Id", + "type": "string" }, - "reason": { - "title": "Reason", + "provider_target_type": { + "default": "", + "title": "Provider Target Type", "type": "string" }, - "review_floor_only": { - "default": false, - "title": "Review Floor Only", - "type": "boolean" + "started_at": { + "default": "", + "title": "Started At", + "type": "string" + }, + "status": { + "default": "pending", + "enum": [ + "pending", + "pass", + "fail", + "skipped" + ], + "title": "Status", + "type": "string" }, - "review_tier": { + "target_category": { "anyOf": [ { "enum": [ - "routine", - "sensitive" + "application", + "compose", + "container", + "service", + "static", + "unknown" ], "type": "string" }, @@ -3775,279 +3530,386 @@ "type": "null" } ], - "title": "Review Tier" + "title": "Target Category" }, - "rule_id": { + "target_name": { "default": "", - "title": "Rule Id", + "title": "Target Name", "type": "string" }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" + "target_reference": { + "anyOf": [ + { + "$ref": "#/components/schemas/DeployTargetContractReference" + }, + { + "type": "null" + } + ] }, - "source": { + "target_type": { + "default": "", "enum": [ - "server_diff", - "launchplane_dependency", - "launchplane_reviewer" + "application", + "compose" ], - "title": "Source", + "title": "Target Type", "type": "string" } }, "required": [ - "source", - "reason" + "deploy_mode" ], - "title": "ChangeImpactMatchedEvidence", + "title": "DeploymentEvidence", "type": "object" }, - "ChangeImpactPolicyApplyEnvelope": { + "DeploymentEvidence-Output": { "additionalProperties": false, "properties": { - "expected_current_policy_digest": { + "deploy_mode": { + "title": "Deploy Mode", + "type": "string" + }, + "deployment_id": { "default": "", - "title": "Expected Current Policy Digest", + "title": "Deployment Id", "type": "string" }, - "expected_current_record_id": { + "finished_at": { "default": "", - "title": "Expected Current Record Id", + "title": "Finished At", "type": "string" }, - "mode": { - "default": "apply", - "enum": [ - "dry_run", - "apply" - ], - "title": "Mode", + "provider_deploy_mode": { + "default": "", + "title": "Provider Deploy Mode", "type": "string" }, - "record": { - "$ref": "#/components/schemas/ChangeImpactPolicyRecord" + "provider_id": { + "default": "dokploy", + "title": "Provider Id", + "type": "string" }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" - } - }, - "required": [ - "record" - ], - "title": "ChangeImpactPolicyApplyEnvelope", - "type": "object" - }, - "ChangeImpactPolicyApplyResponse": { - "additionalProperties": false, - "properties": { - "result": { - "$ref": "#/components/schemas/ChangeImpactPolicyApplyResult" + "provider_target_type": { + "default": "", + "title": "Provider Target Type", + "type": "string" }, - "status": { - "const": "ok", - "default": "ok", - "title": "Status", + "started_at": { + "default": "", + "title": "Started At", "type": "string" }, - "trace_id": { - "title": "Trace Id", - "type": "string" - } - }, - "required": [ - "trace_id", - "result" - ], - "title": "ChangeImpactPolicyApplyResponse", - "type": "object" - }, - "ChangeImpactPolicyApplyResult": { - "additionalProperties": false, - "properties": { - "attribution_status": { - "default": "attribution_unavailable", + "status": { + "default": "pending", "enum": [ - "attributed", - "legacy_unattributed", - "attribution_unavailable", - "not_applied" + "pending", + "pass", + "fail", + "skipped" ], - "title": "Attribution Status", + "title": "Status", "type": "string" }, - "audit": { + "target_category": { "anyOf": [ { - "$ref": "#/components/schemas/ChangeImpactPolicyAuditRecord" + "enum": [ + "application", + "compose", + "container", + "service", + "static", + "unknown" + ], + "type": "string" }, { "type": "null" } - ] + ], + "title": "Target Category" }, - "record": { - "$ref": "#/components/schemas/ChangeImpactPolicyRecord" + "target_name": { + "default": "", + "title": "Target Name", + "type": "string" + }, + "target_type": { + "default": "", + "enum": [ + "application", + "compose" + ], + "title": "Target Type", + "type": "string" + } + }, + "required": [ + "deploy_mode" + ], + "title": "DeploymentEvidence", + "type": "object" + }, + "DeploymentEvidenceRequest": { + "additionalProperties": false, + "properties": { + "deployment": { + "$ref": "#/components/schemas/DeploymentRecord-Input" + }, + "product": { + "title": "Product", + "type": "string" }, "schema_version": { "default": 1, "minimum": 1.0, "title": "Schema Version", "type": "integer" - }, - "status": { - "enum": [ - "would_apply", - "would_replay", - "applied", - "replayed" - ], - "title": "Status", - "type": "string" } }, "required": [ - "status", - "record" + "product", + "deployment" ], - "title": "ChangeImpactPolicyApplyResult", + "title": "DeploymentEvidenceRequest", "type": "object" }, - "ChangeImpactPolicyAuditRecord": { + "DeploymentRecord-Input": { "additionalProperties": false, "properties": { - "actor_kind": { - "enum": [ - "local_admin", - "local_operator", - "github_actions" - ], - "title": "Actor Kind", - "type": "string" + "artifact_identity": { + "anyOf": [ + { + "$ref": "#/components/schemas/ArtifactIdentityReference" + }, + { + "type": "null" + } + ] }, - "actor_subject": { - "maxLength": 512, - "minLength": 1, - "title": "Actor Subject", - "type": "string" + "bootstrap": { + "$ref": "#/components/schemas/BootstrapEvidence" }, - "policy_digest": { - "pattern": "^[0-9a-f]{64}$", - "title": "Policy Digest", + "context": { + "title": "Context", "type": "string" }, - "record_id": { - "maxLength": 256, - "minLength": 1, - "title": "Record Id", + "delegated_executor": { + "default": "control-plane.dokploy", + "title": "Delegated Executor", "type": "string" }, - "recorded_at": { - "format": "date-time", - "title": "Recorded At", + "deploy": { + "$ref": "#/components/schemas/DeploymentEvidence-Input" + }, + "deployed_target": { + "anyOf": [ + { + "$ref": "#/components/schemas/DeployedTargetReference" + }, + { + "type": "null" + } + ] + }, + "destination_health": { + "$ref": "#/components/schemas/HealthcheckEvidence" + }, + "instance": { + "title": "Instance", "type": "string" }, - "schema_version": { - "const": 1, - "default": 1, - "title": "Schema Version", - "type": "integer" + "no_cache": { + "default": false, + "title": "No Cache", + "type": "boolean" }, - "trace_id": { - "maxLength": 256, - "minLength": 1, - "title": "Trace Id", + "post_deploy_update": { + "$ref": "#/components/schemas/PostDeployUpdateEvidence" + }, + "record_id": { + "title": "Record Id", "type": "string" }, - "workflow_identity": { + "resolved_target": { + "anyOf": [ + { + "$ref": "#/components/schemas/ResolvedTargetEvidence" + }, + { + "type": "null" + } + ] + }, + "runtime_identity": { "anyOf": [ { - "$ref": "#/components/schemas/ChangeImpactPolicyWorkflowIdentity" + "$ref": "#/components/schemas/RuntimeIdentity" }, { "type": "null" } ] + }, + "runtime_source": { + "additionalProperties": { + "type": "string" + }, + "title": "Runtime Source", + "type": "object" + }, + "schema_version": { + "default": 1, + "minimum": 1.0, + "title": "Schema Version", + "type": "integer" + }, + "source_git_ref": { + "title": "Source Git Ref", + "type": "string" + }, + "verify_destination_health": { + "default": true, + "title": "Verify Destination Health", + "type": "boolean" + }, + "wait_for_completion": { + "default": true, + "title": "Wait For Completion", + "type": "boolean" } }, "required": [ "record_id", - "policy_digest", - "actor_kind", - "actor_subject", - "trace_id", - "recorded_at" + "context", + "instance", + "source_git_ref", + "deploy" ], - "title": "ChangeImpactPolicyAuditRecord", + "title": "DeploymentRecord", "type": "object" }, - "ChangeImpactPolicyReadModel": { + "DeploymentRecord-Output": { "additionalProperties": false, "properties": { - "attribution_status": { - "default": "attribution_unavailable", - "enum": [ - "attributed", - "legacy_unattributed", - "attribution_unavailable", - "not_applied" - ], - "title": "Attribution Status", + "artifact_identity": { + "anyOf": [ + { + "$ref": "#/components/schemas/ArtifactIdentityReference" + }, + { + "type": "null" + } + ] + }, + "bootstrap": { + "$ref": "#/components/schemas/BootstrapEvidence" + }, + "context": { + "title": "Context", "type": "string" }, - "audit": { + "delegated_executor": { + "default": "control-plane.dokploy", + "title": "Delegated Executor", + "type": "string" + }, + "deploy": { + "$ref": "#/components/schemas/DeploymentEvidence-Output" + }, + "deployed_target": { "anyOf": [ { - "$ref": "#/components/schemas/ChangeImpactPolicyAuditRecord" + "$ref": "#/components/schemas/DeployedTargetReference" }, { "type": "null" } ] }, - "current_policy": { + "destination_health": { + "$ref": "#/components/schemas/HealthcheckEvidence" + }, + "instance": { + "title": "Instance", + "type": "string" + }, + "no_cache": { + "default": false, + "title": "No Cache", + "type": "boolean" + }, + "post_deploy_update": { + "$ref": "#/components/schemas/PostDeployUpdateEvidence" + }, + "record_id": { + "title": "Record Id", + "type": "string" + }, + "resolved_target": { "anyOf": [ { - "$ref": "#/components/schemas/ChangeImpactPolicyRecord" + "$ref": "#/components/schemas/ResolvedTargetEvidence" }, { "type": "null" } ] }, - "policy_history_count": { - "default": 0, - "minimum": 0.0, - "title": "Policy History Count", - "type": "integer" + "runtime_identity": { + "anyOf": [ + { + "$ref": "#/components/schemas/RuntimeIdentity" + }, + { + "type": "null" + } + ] }, - "repository_id": { - "title": "Repository Id", - "type": "string" + "runtime_source": { + "additionalProperties": { + "type": "string" + }, + "title": "Runtime Source", + "type": "object" }, "schema_version": { "default": 1, "minimum": 1.0, "title": "Schema Version", "type": "integer" + }, + "source_git_ref": { + "title": "Source Git Ref", + "type": "string" + }, + "verify_destination_health": { + "default": true, + "title": "Verify Destination Health", + "type": "boolean" + }, + "wait_for_completion": { + "default": true, + "title": "Wait For Completion", + "type": "boolean" } }, "required": [ - "repository_id" + "record_id", + "context", + "instance", + "source_git_ref", + "deploy" ], - "title": "ChangeImpactPolicyReadModel", + "title": "DeploymentRecord", "type": "object" }, - "ChangeImpactPolicyReadResponse": { + "DeploymentRecordResponse": { "additionalProperties": false, "properties": { - "read_model": { - "$ref": "#/components/schemas/ChangeImpactPolicyReadModel" + "record": { + "$ref": "#/components/schemas/DeploymentRecord-Output" }, "status": { "const": "ok", @@ -4062,585 +3924,494 @@ }, "required": [ "trace_id", - "read_model" + "record" ], - "title": "ChangeImpactPolicyReadResponse", + "title": "DeploymentRecordResponse", "type": "object" }, - "ChangeImpactPolicyRecord": { + "DetachedApplicationRetirementRequest": { "additionalProperties": false, "properties": { - "classification_model": { - "anyOf": [ - { - "const": "v2", - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Classification Model" + "application_name": { + "title": "Application Name", + "type": "string" }, - "component_rules": { - "items": { - "$ref": "#/components/schemas/ChangeImpactComponentRule" - }, - "title": "Component Rules", - "type": "array" + "candidate_target_sha256": { + "title": "Candidate Target Sha256", + "type": "string" }, - "default_unknown_review_tier": { - "const": "sensitive", - "default": "sensitive", - "title": "Default Unknown Review Tier", + "confirmation": { + "default": "", + "title": "Confirmation", "type": "string" }, - "effective_at": { - "title": "Effective At", + "environment_name": { + "title": "Environment Name", "type": "string" }, - "policy_digest": { - "default": "", - "title": "Policy Digest", + "expected_protected_target_sha256": { + "items": { + "type": "string" + }, + "title": "Expected Protected Target Sha256", + "type": "array" + }, + "mode": { + "default": "plan", + "enum": [ + "plan", + "apply" + ], + "title": "Mode", "type": "string" }, - "policy_revision": { - "minimum": 1.0, - "title": "Policy Revision", - "type": "integer" + "project_name": { + "title": "Project Name", + "type": "string" }, "reason": { "title": "Reason", "type": "string" }, - "record_id": { - "default": "", - "title": "Record Id", - "type": "string" - }, - "repository": { - "title": "Repository", + "related_issue": { + "title": "Related Issue", "type": "string" }, - "repository_id": { - "title": "Repository Id", + "reviewed_plan_record_id": { + "default": "", + "title": "Reviewed Plan Record Id", "type": "string" }, - "repository_owner_id": { - "title": "Repository Owner Id", + "reviewed_plan_sha256": { + "default": "", + "title": "Reviewed Plan Sha256", "type": "string" }, "schema_version": { + "const": 1, "default": 1, - "minimum": 1.0, "title": "Schema Version", "type": "integer" - }, - "source": { - "title": "Source", - "type": "string" - }, - "status": { - "default": "active", - "enum": [ - "active", - "superseded" - ], - "title": "Status", - "type": "string" - }, - "supersedes_record_id": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Supersedes Record Id" } }, "required": [ - "repository_id", - "repository_owner_id", - "repository", - "policy_revision", - "component_rules", - "effective_at", - "source", - "reason" + "project_name", + "environment_name", + "application_name", + "candidate_target_sha256", + "expected_protected_target_sha256", + "reason", + "related_issue" ], - "title": "ChangeImpactPolicyRecord", + "title": "DetachedApplicationRetirementRequest", "type": "object" }, - "ChangeImpactPolicyWorkflowIdentity": { + "DokployTargetIdRecord": { "additionalProperties": false, "properties": { - "job_workflow_ref": { - "maxLength": 512, - "title": "Job Workflow Ref", - "type": "string" - }, - "ref": { - "maxLength": 512, - "title": "Ref", + "context": { + "title": "Context", "type": "string" }, - "repository": { - "maxLength": 256, - "minLength": 1, - "title": "Repository", + "instance": { + "title": "Instance", "type": "string" }, - "repository_id": { - "default": "", - "maxLength": 64, - "title": "Repository Id", - "type": "string" + "schema_version": { + "default": 1, + "minimum": 1.0, + "title": "Schema Version", + "type": "integer" }, - "repository_owner_id": { + "source_label": { "default": "", - "maxLength": 64, - "title": "Repository Owner Id", + "title": "Source Label", "type": "string" }, - "sha": { - "maxLength": 64, - "minLength": 1, - "title": "Sha", + "target_id": { + "title": "Target Id", "type": "string" }, - "workflow_ref": { - "maxLength": 512, - "title": "Workflow Ref", + "updated_at": { + "title": "Updated At", "type": "string" } }, "required": [ - "repository", - "workflow_ref", - "job_workflow_ref", - "ref", - "sha" + "context", + "instance", + "target_id", + "updated_at" ], - "title": "ChangeImpactPolicyWorkflowIdentity", + "title": "DokployTargetIdRecord", "type": "object" }, - "ChangeImpactProductScope": { + "DokployTargetInspectResponse": { "additionalProperties": false, "properties": { - "owner_action": { - "default": "pull_request.owner_acceptance", - "title": "Owner Action", - "type": "string" - }, - "owner_environment": { - "default": "pull_request", - "title": "Owner Environment", - "type": "string" + "inspect": { + "additionalProperties": true, + "title": "Inspect", + "type": "object" }, - "product": { - "title": "Product", + "status": { + "const": "ok", + "default": "ok", + "title": "Status", "type": "string" }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" - }, - "system": { - "title": "System", + "trace_id": { + "title": "Trace Id", "type": "string" } }, "required": [ - "product", - "system" + "trace_id", + "inspect" ], - "title": "ChangeImpactProductScope", + "title": "DokployTargetInspectResponse", + "type": "object" + }, + "DokployTargetPolicies": { + "additionalProperties": false, + "properties": { + "shopify": { + "$ref": "#/components/schemas/DokployTargetShopifyPolicy" + } + }, + "title": "DokployTargetPolicies", "type": "object" }, - "ChangeImpactTarget": { + "DokployTargetRecord": { "additionalProperties": false, "properties": { - "head_sha": { - "title": "Head Sha", + "compose_path": { + "default": "", + "title": "Compose Path", "type": "string" }, - "pull_request_number": { - "minimum": 1.0, - "title": "Pull Request Number", - "type": "integer" + "context": { + "title": "Context", + "type": "string" }, - "repository": { - "title": "Repository", + "custom_git_branch": { + "default": "", + "title": "Custom Git Branch", "type": "string" }, - "repository_id": { - "title": "Repository Id", + "custom_git_url": { + "default": "", + "title": "Custom Git Url", "type": "string" }, - "repository_owner_id": { - "title": "Repository Owner Id", - "type": "string" + "deploy_timeout_seconds": { + "anyOf": [ + { + "minimum": 1.0, + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Deploy Timeout Seconds" }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" + "domains": { + "default": [], + "items": { + "type": "string" + }, + "title": "Domains", + "type": "array" }, - "tree_sha": { - "title": "Tree Sha", - "type": "string" - } - }, - "required": [ - "repository_id", - "repository_owner_id", - "repository", - "pull_request_number", - "head_sha", - "tree_sha" - ], - "title": "ChangeImpactTarget", - "type": "object" - }, - "ChangeImpactTargetReference": { - "additionalProperties": false, - "properties": { - "pull_request_number": { - "minimum": 1.0, - "title": "Pull Request Number", - "type": "integer" + "enable_submodules": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "title": "Enable Submodules" }, - "repository": { - "title": "Repository", - "type": "string" + "env": { + "additionalProperties": { + "type": "string" + }, + "title": "Env", + "type": "object" }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" - } - }, - "required": [ - "repository", - "pull_request_number" - ], - "title": "ChangeImpactTargetReference", - "type": "object" - }, - "DataProvenance": { - "additionalProperties": false, - "properties": { - "detail": { + "git_branch": { "default": "", - "title": "Detail", + "title": "Git Branch", "type": "string" }, - "freshness_status": { - "enum": [ - "verified", - "recorded", - "stale", - "missing", - "unsupported" - ], - "title": "Freshness Status", + "healthcheck_enabled": { + "default": true, + "title": "Healthcheck Enabled", + "type": "boolean" + }, + "healthcheck_path": { + "default": "/web/health", + "title": "Healthcheck Path", "type": "string" }, - "recorded_at": { - "default": "", - "title": "Recorded At", + "healthcheck_timeout_seconds": { + "anyOf": [ + { + "minimum": 1.0, + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Healthcheck Timeout Seconds" + }, + "instance": { + "title": "Instance", "type": "string" }, - "refreshed_at": { + "policies": { + "$ref": "#/components/schemas/DokployTargetPolicies" + }, + "project_name": { "default": "", - "title": "Refreshed At", + "title": "Project Name", "type": "string" }, - "source_kind": { - "enum": [ - "record", - "provider", - "descriptor", - "unsupported" - ], - "title": "Source Kind", + "require_prod_gate": { + "default": false, + "title": "Require Prod Gate", + "type": "boolean" + }, + "require_test_gate": { + "default": false, + "title": "Require Test Gate", + "type": "boolean" + }, + "schema_version": { + "default": 1, + "minimum": 1.0, + "title": "Schema Version", + "type": "integer" + }, + "source_git_ref": { + "default": "origin/main", + "title": "Source Git Ref", "type": "string" }, - "source_record_id": { + "source_label": { "default": "", - "title": "Source Record Id", + "title": "Source Label", "type": "string" }, - "stale_after": { + "source_type": { "default": "", - "title": "Stale After", - "type": "string" - } - }, - "required": [ - "source_kind", - "freshness_status" - ], - "title": "DataProvenance", - "type": "object" - }, - "DeployTargetContractReference": { - "additionalProperties": false, - "properties": { - "provider_id": { - "default": "dokploy", - "title": "Provider Id", + "title": "Source Type", "type": "string" }, - "provider_target_type": { + "target_name": { "default": "", - "title": "Provider Target Type", + "title": "Target Name", "type": "string" }, - "target_category": { - "default": "unknown", + "target_type": { + "default": "compose", "enum": [ - "application", "compose", - "container", - "service", - "static", - "unknown" + "application" ], - "title": "Target Category", + "title": "Target Type", "type": "string" }, - "target_name": { - "title": "Target Name", + "updated_at": { + "title": "Updated At", "type": "string" + }, + "watch_paths": { + "default": [], + "items": { + "type": "string" + }, + "title": "Watch Paths", + "type": "array" } }, "required": [ - "target_name" + "context", + "instance", + "updated_at" ], - "title": "DeployTargetContractReference", + "title": "DokployTargetRecord", "type": "object" }, - "DeployedTargetReference": { + "DokployTargetSetupEnvelope": { "additionalProperties": false, "properties": { - "display_name": { - "title": "Display Name", + "app_name": { + "default": "", + "title": "App Name", "type": "string" }, - "provider_evidence": { - "additionalProperties": { - "type": "string" - }, - "title": "Provider Evidence", - "type": "object" - }, - "provider_id": { - "title": "Provider Id", + "compose_path": { + "default": "docker-compose.yml", + "title": "Compose Path", "type": "string" }, - "provider_target_type": { + "confirmation": { "default": "", - "title": "Provider Target Type", + "title": "Confirmation", "type": "string" }, - "target_category": { - "default": "unknown", - "enum": [ - "application", - "compose", - "container", - "service", - "static", - "unknown" - ], - "title": "Target Category", + "context": { + "title": "Context", "type": "string" }, - "target_id": { - "title": "Target Id", - "type": "string" - } - }, - "required": [ - "provider_id", - "target_id", - "display_name" - ], - "title": "DeployedTargetReference", - "type": "object" - }, - "DeploymentEvidence-Input": { - "additionalProperties": false, - "properties": { - "deploy_mode": { - "title": "Deploy Mode", - "type": "string" + "deploy_timeout_seconds": { + "anyOf": [ + { + "minimum": 1.0, + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Deploy Timeout Seconds" }, - "deployment_id": { + "description": { "default": "", - "title": "Deployment Id", + "title": "Description", "type": "string" }, - "finished_at": { - "default": "", - "title": "Finished At", - "type": "string" + "domains": { + "default": [], + "items": { + "type": "string" + }, + "title": "Domains", + "type": "array" }, - "provider_deploy_mode": { + "environment_description": { "default": "", - "title": "Provider Deploy Mode", - "type": "string" - }, - "provider_id": { - "default": "dokploy", - "title": "Provider Id", + "title": "Environment Description", "type": "string" }, - "provider_target_type": { + "environment_id": { "default": "", - "title": "Provider Target Type", + "title": "Environment Id", "type": "string" }, - "started_at": { + "environment_name": { "default": "", - "title": "Started At", - "type": "string" - }, - "status": { - "default": "pending", - "enum": [ - "pending", - "pass", - "fail", - "skipped" - ], - "title": "Status", + "title": "Environment Name", "type": "string" }, - "target_category": { + "expected_current_provider_target": { "anyOf": [ { - "enum": [ - "application", - "compose", - "container", - "service", - "static", - "unknown" - ], - "type": "string" + "$ref": "#/components/schemas/DeployedTargetReference" }, { "type": "null" } - ], - "title": "Target Category" + ] }, - "target_name": { + "healthcheck_path": { "default": "", - "title": "Target Name", + "title": "Healthcheck Path", "type": "string" }, - "target_reference": { - "anyOf": [ - { - "$ref": "#/components/schemas/DeployTargetContractReference" - }, - { - "type": "null" - } - ] + "instance": { + "title": "Instance", + "type": "string" }, - "target_type": { - "default": "", + "mode": { + "default": "dry-run", "enum": [ - "application", - "compose" + "dry-run", + "apply" ], - "title": "Target Type", - "type": "string" - } - }, - "required": [ - "deploy_mode" - ], - "title": "DeploymentEvidence", - "type": "object" - }, - "DeploymentEvidence-Output": { - "additionalProperties": false, - "properties": { - "deploy_mode": { - "title": "Deploy Mode", + "title": "Mode", "type": "string" }, - "deployment_id": { - "default": "", - "title": "Deployment Id", + "operation": { + "enum": [ + "adopt", + "create-application", + "create-compose", + "prune-compose-domain", + "reconcile-compose-domain", + "repair-domain-authority" + ], + "title": "Operation", "type": "string" }, - "finished_at": { - "default": "", - "title": "Finished At", + "product": { + "default": "launchplane", + "title": "Product", "type": "string" }, - "provider_deploy_mode": { + "project_description": { "default": "", - "title": "Provider Deploy Mode", - "type": "string" - }, - "provider_id": { - "default": "dokploy", - "title": "Provider Id", + "title": "Project Description", "type": "string" }, - "provider_target_type": { + "project_id": { "default": "", - "title": "Provider Target Type", + "title": "Project Id", "type": "string" }, - "started_at": { + "project_name": { "default": "", - "title": "Started At", + "title": "Project Name", "type": "string" }, - "status": { - "default": "pending", - "enum": [ - "pending", - "pass", - "fail", - "skipped" - ], - "title": "Status", + "reason": { + "default": "", + "title": "Reason", "type": "string" }, - "target_category": { + "runtime_port": { "anyOf": [ { - "enum": [ - "application", - "compose", - "container", - "service", - "static", - "unknown" - ], - "type": "string" + "maximum": 65535.0, + "minimum": 1.0, + "type": "integer" }, { "type": "null" } ], - "title": "Target Category" + "title": "Runtime Port" + }, + "schema_version": { + "default": 1, + "minimum": 1.0, + "title": "Schema Version", + "type": "integer" + }, + "server_id": { + "default": "", + "title": "Server Id", + "type": "string" + }, + "source_git_ref": { + "default": "origin/main", + "title": "Source Git Ref", + "type": "string" + }, + "source_type": { + "default": "raw", + "title": "Source Type", + "type": "string" + }, + "target_id": { + "default": "", + "title": "Target Id", + "type": "string" }, "target_name": { "default": "", @@ -4648,7 +4419,7 @@ "type": "string" }, "target_type": { - "default": "", + "default": "compose", "enum": [ "application", "compose" @@ -4658,271 +4429,234 @@ } }, "required": [ - "deploy_mode" + "operation", + "context", + "instance" ], - "title": "DeploymentEvidence", + "title": "DokployTargetSetupEnvelope", "type": "object" }, - "DeploymentEvidenceRequest": { + "DokployTargetShopifyPolicy": { "additionalProperties": false, "properties": { - "deployment": { - "$ref": "#/components/schemas/DeploymentRecord-Input" - }, - "product": { - "title": "Product", - "type": "string" - }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" + "protected_store_keys": { + "default": [], + "items": { + "type": "string" + }, + "title": "Protected Store Keys", + "type": "array" } }, - "required": [ - "product", - "deployment" - ], - "title": "DeploymentEvidenceRequest", + "title": "DokployTargetShopifyPolicy", "type": "object" }, - "DeploymentRecord-Input": { + "DriverActionDescriptor": { "additionalProperties": false, "properties": { - "artifact_identity": { - "anyOf": [ - { - "$ref": "#/components/schemas/ArtifactIdentityReference" - }, - { - "type": "null" - } - ] + "action_id": { + "title": "Action Id", + "type": "string" }, - "bootstrap": { - "$ref": "#/components/schemas/BootstrapEvidence" + "alternate_authz_actions": { + "default": [], + "items": { + "type": "string" + }, + "title": "Alternate Authz Actions", + "type": "array" }, - "context": { - "title": "Context", + "authz_action": { + "default": "", + "title": "Authz Action", "type": "string" }, - "delegated_executor": { - "default": "control-plane.dokploy", - "title": "Delegated Executor", + "description": { + "title": "Description", "type": "string" }, - "deploy": { - "$ref": "#/components/schemas/DeploymentEvidence-Input" - }, - "deployed_target": { - "anyOf": [ - { - "$ref": "#/components/schemas/DeployedTargetReference" - }, - { - "type": "null" - } - ] + "input_schema": { + "additionalProperties": true, + "title": "Input Schema", + "type": "object" }, - "destination_health": { - "$ref": "#/components/schemas/HealthcheckEvidence" + "label": { + "title": "Label", + "type": "string" }, - "instance": { - "title": "Instance", + "method": { + "enum": [ + "GET", + "POST" + ], + "title": "Method", "type": "string" }, - "no_cache": { - "default": false, - "title": "No Cache", + "operator_visible": { + "default": true, + "title": "Operator Visible", "type": "boolean" }, - "post_deploy_update": { - "$ref": "#/components/schemas/PostDeployUpdateEvidence" + "output_schema": { + "additionalProperties": true, + "title": "Output Schema", + "type": "object" }, - "record_id": { - "title": "Record Id", + "readiness_requirements": { + "default": [], + "items": { + "enum": [ + "provider_target", + "route_binding", + "runtime_environment", + "managed_secrets", + "artifact", + "deployment", + "topology" + ], + "type": "string" + }, + "title": "Readiness Requirements", + "type": "array" + }, + "requires_production_backup_policy": { + "default": false, + "title": "Requires Production Backup Policy", + "type": "boolean" + }, + "route_path": { + "title": "Route Path", "type": "string" }, - "resolved_target": { - "anyOf": [ - { - "$ref": "#/components/schemas/ResolvedTargetEvidence" - }, - { - "type": "null" - } - ] + "safety": { + "enum": [ + "read", + "safe_write", + "mutation", + "destructive" + ], + "title": "Safety", + "type": "string" }, - "runtime_identity": { - "anyOf": [ - { - "$ref": "#/components/schemas/RuntimeIdentity" - }, - { - "type": "null" - } - ] + "scope": { + "enum": [ + "global", + "context", + "instance", + "preview" + ], + "title": "Scope", + "type": "string" }, - "runtime_source": { - "additionalProperties": { + "writes_records": { + "default": [], + "items": { "type": "string" }, - "title": "Runtime Source", - "type": "object" + "title": "Writes Records", + "type": "array" + } + }, + "required": [ + "action_id", + "label", + "description", + "safety", + "scope", + "method", + "route_path" + ], + "title": "DriverActionDescriptor", + "type": "object" + }, + "DriverCapabilityDescriptor": { + "additionalProperties": false, + "properties": { + "actions": { + "default": [], + "items": { + "type": "string" + }, + "title": "Actions", + "type": "array" }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" + "capability_id": { + "title": "Capability Id", + "type": "string" }, - "source_git_ref": { - "title": "Source Git Ref", + "description": { + "title": "Description", "type": "string" }, - "verify_destination_health": { - "default": true, - "title": "Verify Destination Health", - "type": "boolean" + "label": { + "title": "Label", + "type": "string" }, - "wait_for_completion": { - "default": true, - "title": "Wait For Completion", - "type": "boolean" + "panels": { + "default": [], + "items": { + "enum": [ + "summary", + "lane_health", + "artifact_evidence", + "deployment_evidence", + "promotion_evidence", + "backup_evidence", + "preview_inventory", + "settings", + "secret_bindings", + "audit" + ], + "type": "string" + }, + "title": "Panels", + "type": "array" } }, "required": [ - "record_id", - "context", - "instance", - "source_git_ref", - "deploy" + "capability_id", + "label", + "description" ], - "title": "DeploymentRecord", + "title": "DriverCapabilityDescriptor", "type": "object" }, - "DeploymentRecord-Output": { + "DriverContextView": { "additionalProperties": false, "properties": { - "artifact_identity": { - "anyOf": [ - { - "$ref": "#/components/schemas/ArtifactIdentityReference" - }, - { - "type": "null" - } - ] - }, - "bootstrap": { - "$ref": "#/components/schemas/BootstrapEvidence" - }, "context": { "title": "Context", "type": "string" }, - "delegated_executor": { - "default": "control-plane.dokploy", - "title": "Delegated Executor", - "type": "string" - }, - "deploy": { - "$ref": "#/components/schemas/DeploymentEvidence-Output" - }, - "deployed_target": { - "anyOf": [ - { - "$ref": "#/components/schemas/DeployedTargetReference" - }, - { - "type": "null" - } - ] - }, - "destination_health": { - "$ref": "#/components/schemas/HealthcheckEvidence" + "drivers": { + "default": [], + "items": { + "$ref": "#/components/schemas/DriverView" + }, + "title": "Drivers", + "type": "array" }, "instance": { + "default": "", "title": "Instance", "type": "string" }, - "no_cache": { - "default": false, - "title": "No Cache", - "type": "boolean" - }, - "post_deploy_update": { - "$ref": "#/components/schemas/PostDeployUpdateEvidence" - }, - "record_id": { - "title": "Record Id", - "type": "string" - }, - "resolved_target": { - "anyOf": [ - { - "$ref": "#/components/schemas/ResolvedTargetEvidence" - }, - { - "type": "null" - } - ] - }, - "runtime_identity": { - "anyOf": [ - { - "$ref": "#/components/schemas/RuntimeIdentity" - }, - { - "type": "null" - } - ] - }, - "runtime_source": { - "additionalProperties": { - "type": "string" - }, - "title": "Runtime Source", - "type": "object" - }, "schema_version": { "default": 1, "minimum": 1.0, "title": "Schema Version", "type": "integer" - }, - "source_git_ref": { - "title": "Source Git Ref", - "type": "string" - }, - "verify_destination_health": { - "default": true, - "title": "Verify Destination Health", - "type": "boolean" - }, - "wait_for_completion": { - "default": true, - "title": "Wait For Completion", - "type": "boolean" } }, "required": [ - "record_id", - "context", - "instance", - "source_git_ref", - "deploy" + "context" ], - "title": "DeploymentRecord", + "title": "DriverContextView", "type": "object" }, - "DeploymentRecordResponse": { + "DriverContextViewResponse": { "additionalProperties": false, "properties": { - "record": { - "$ref": "#/components/schemas/DeploymentRecord-Output" - }, "status": { "const": "ok", "default": "ok", @@ -4932,139 +4666,128 @@ "trace_id": { "title": "Trace Id", "type": "string" + }, + "view": { + "$ref": "#/components/schemas/DriverContextView" } }, "required": [ "trace_id", - "record" + "view" ], - "title": "DeploymentRecordResponse", + "title": "DriverContextViewResponse", "type": "object" }, - "DetachedApplicationRetirementRequest": { + "DriverDescriptor": { "additionalProperties": false, "properties": { - "application_name": { - "title": "Application Name", - "type": "string" - }, - "candidate_target_sha256": { - "title": "Candidate Target Sha256", - "type": "string" + "actions": { + "default": [], + "items": { + "$ref": "#/components/schemas/DriverActionDescriptor" + }, + "title": "Actions", + "type": "array" }, - "confirmation": { + "base_driver_id": { "default": "", - "title": "Confirmation", + "title": "Base Driver Id", "type": "string" }, - "environment_name": { - "title": "Environment Name", - "type": "string" + "capabilities": { + "default": [], + "items": { + "$ref": "#/components/schemas/DriverCapabilityDescriptor" + }, + "title": "Capabilities", + "type": "array" }, - "expected_protected_target_sha256": { + "context_patterns": { + "default": [], "items": { "type": "string" }, - "title": "Expected Protected Target Sha256", + "title": "Context Patterns", "type": "array" }, - "mode": { - "default": "plan", - "enum": [ - "plan", - "apply" - ], - "title": "Mode", - "type": "string" - }, - "project_name": { - "title": "Project Name", + "description": { + "title": "Description", "type": "string" }, - "reason": { - "title": "Reason", + "driver_id": { + "title": "Driver Id", "type": "string" }, - "related_issue": { - "title": "Related Issue", + "label": { + "title": "Label", "type": "string" }, - "reviewed_plan_record_id": { - "default": "", - "title": "Reviewed Plan Record Id", + "product": { + "title": "Product", "type": "string" }, - "reviewed_plan_sha256": { - "default": "", - "title": "Reviewed Plan Sha256", + "provider_boundary": { + "title": "Provider Boundary", "type": "string" }, "schema_version": { - "const": 1, "default": 1, + "minimum": 1.0, "title": "Schema Version", "type": "integer" + }, + "setting_groups": { + "default": [], + "items": { + "$ref": "#/components/schemas/DriverSettingGroupDescriptor" + }, + "title": "Setting Groups", + "type": "array" } }, "required": [ - "project_name", - "environment_name", - "application_name", - "candidate_target_sha256", - "expected_protected_target_sha256", - "reason", - "related_issue" + "driver_id", + "label", + "product", + "description", + "provider_boundary" ], - "title": "DetachedApplicationRetirementRequest", + "title": "DriverDescriptor", "type": "object" }, - "DokployTargetIdRecord": { + "DriverDescriptorResponse": { "additionalProperties": false, "properties": { - "context": { - "title": "Context", - "type": "string" - }, - "instance": { - "title": "Instance", - "type": "string" - }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" - }, - "source_label": { - "default": "", - "title": "Source Label", - "type": "string" + "driver": { + "$ref": "#/components/schemas/DriverDescriptor" }, - "target_id": { - "title": "Target Id", + "status": { + "const": "ok", + "default": "ok", + "title": "Status", "type": "string" }, - "updated_at": { - "title": "Updated At", + "trace_id": { + "title": "Trace Id", "type": "string" } }, "required": [ - "context", - "instance", - "target_id", - "updated_at" + "trace_id", + "driver" ], - "title": "DokployTargetIdRecord", + "title": "DriverDescriptorResponse", "type": "object" }, - "DokployTargetInspectResponse": { + "DriverDescriptorsResponse": { "additionalProperties": false, "properties": { - "inspect": { - "additionalProperties": true, - "title": "Inspect", - "type": "object" + "drivers": { + "items": { + "$ref": "#/components/schemas/DriverDescriptor" + }, + "title": "Drivers", + "type": "array" }, "status": { "const": "ok", @@ -5079,266 +4802,220 @@ }, "required": [ "trace_id", - "inspect" + "drivers" ], - "title": "DokployTargetInspectResponse", - "type": "object" - }, - "DokployTargetPolicies": { - "additionalProperties": false, - "properties": { - "shopify": { - "$ref": "#/components/schemas/DokployTargetShopifyPolicy" - } - }, - "title": "DokployTargetPolicies", + "title": "DriverDescriptorsResponse", "type": "object" }, - "DokployTargetRecord": { + "DriverSettingGroupDescriptor": { "additionalProperties": false, "properties": { - "compose_path": { - "default": "", - "title": "Compose Path", + "description": { + "title": "Description", "type": "string" }, - "context": { - "title": "Context", - "type": "string" + "fields": { + "default": [], + "items": { + "type": "string" + }, + "title": "Fields", + "type": "array" }, - "custom_git_branch": { - "default": "", - "title": "Custom Git Branch", + "group_id": { + "title": "Group Id", "type": "string" }, - "custom_git_url": { - "default": "", - "title": "Custom Git Url", + "label": { + "title": "Label", "type": "string" }, - "deploy_timeout_seconds": { - "anyOf": [ - { - "minimum": 1.0, - "type": "integer" - }, - { - "type": "null" - } + "scope": { + "enum": [ + "global", + "context", + "instance", + "preview" ], - "title": "Deploy Timeout Seconds" + "title": "Scope", + "type": "string" }, - "domains": { + "secret_bindings": { "default": [], "items": { "type": "string" }, - "title": "Domains", + "title": "Secret Bindings", + "type": "array" + } + }, + "required": [ + "group_id", + "label", + "description", + "scope" + ], + "title": "DriverSettingGroupDescriptor", + "type": "object" + }, + "DriverView": { + "additionalProperties": false, + "properties": { + "available_actions": { + "default": [], + "items": { + "$ref": "#/components/schemas/DriverActionDescriptor" + }, + "title": "Available Actions", "type": "array" }, - "enable_submodules": { + "descriptor": { + "$ref": "#/components/schemas/DriverDescriptor" + }, + "driver_id": { + "title": "Driver Id", + "type": "string" + }, + "lane_summary": { "anyOf": [ { - "type": "boolean" + "$ref": "#/components/schemas/LaunchplaneLaneSummary" }, { "type": "null" } - ], - "title": "Enable Submodules" - }, - "env": { - "additionalProperties": { - "type": "string" - }, - "title": "Env", - "type": "object" - }, - "git_branch": { - "default": "", - "title": "Git Branch", - "type": "string" - }, - "healthcheck_enabled": { - "default": true, - "title": "Healthcheck Enabled", - "type": "boolean" - }, - "healthcheck_path": { - "default": "/web/health", - "title": "Healthcheck Path", - "type": "string" + ] }, - "healthcheck_timeout_seconds": { + "preview_inventory_provenance": { "anyOf": [ { - "minimum": 1.0, - "type": "integer" + "$ref": "#/components/schemas/DataProvenance" }, { "type": "null" } - ], - "title": "Healthcheck Timeout Seconds" - }, - "instance": { - "title": "Instance", - "type": "string" - }, - "policies": { - "$ref": "#/components/schemas/DokployTargetPolicies" - }, - "project_name": { - "default": "", - "title": "Project Name", - "type": "string" - }, - "require_prod_gate": { - "default": false, - "title": "Require Prod Gate", - "type": "boolean" - }, - "require_test_gate": { - "default": false, - "title": "Require Test Gate", - "type": "boolean" - }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" - }, - "source_git_ref": { - "default": "origin/main", - "title": "Source Git Ref", - "type": "string" - }, - "source_label": { - "default": "", - "title": "Source Label", - "type": "string" - }, - "source_type": { - "default": "", - "title": "Source Type", - "type": "string" - }, - "target_name": { - "default": "", - "title": "Target Name", - "type": "string" - }, - "target_type": { - "default": "compose", - "enum": [ - "compose", - "application" - ], - "title": "Target Type", - "type": "string" + ] }, - "updated_at": { - "title": "Updated At", - "type": "string" + "preview_summaries": { + "default": [], + "items": { + "$ref": "#/components/schemas/LaunchplanePreviewSummary" + }, + "title": "Preview Summaries", + "type": "array" }, - "watch_paths": { + "production_backup_authorities": { "default": [], "items": { - "type": "string" + "$ref": "#/components/schemas/ProductionBackupAuthorityStatus" }, - "title": "Watch Paths", + "title": "Production Backup Authorities", "type": "array" } }, "required": [ - "context", - "instance", - "updated_at" + "driver_id", + "descriptor" ], - "title": "DokployTargetRecord", + "title": "DriverView", "type": "object" }, - "DokployTargetSetupEnvelope": { + "DurableOperationCancellationRequest": { "additionalProperties": false, "properties": { - "app_name": { - "default": "", - "title": "App Name", - "type": "string" - }, - "compose_path": { - "default": "docker-compose.yml", - "title": "Compose Path", - "type": "string" - }, - "confirmation": { - "default": "", - "title": "Confirmation", - "type": "string" - }, - "context": { - "title": "Context", + "reason": { + "title": "Reason", "type": "string" }, - "deploy_timeout_seconds": { + "reconciliation_attestation": { "anyOf": [ { - "minimum": 1.0, - "type": "integer" + "$ref": "#/components/schemas/DurableOperationReconciliationAttestation" }, { "type": "null" } - ], - "title": "Deploy Timeout Seconds" + ] + } + }, + "required": [ + "reason" + ], + "title": "DurableOperationCancellationRequest", + "type": "object" + }, + "DurableOperationCancellationResponse": { + "additionalProperties": false, + "properties": { + "operation": { + "additionalProperties": true, + "title": "Operation", + "type": "object" }, - "description": { - "default": "", - "title": "Description", + "status": { + "const": "ok", + "default": "ok", + "title": "Status", "type": "string" }, - "domains": { - "default": [], - "items": { - "type": "string" - }, - "title": "Domains", - "type": "array" - }, - "environment_description": { - "default": "", - "title": "Environment Description", + "trace_id": { + "title": "Trace Id", + "type": "string" + } + }, + "required": [ + "trace_id", + "operation" + ], + "title": "DurableOperationCancellationResponse", + "type": "object" + }, + "DurableOperationReconciliationAttestation": { + "additionalProperties": false, + "properties": { + "evidence_reference": { + "title": "Evidence Reference", "type": "string" }, - "environment_id": { - "default": "", - "title": "Environment Id", + "provider_inspected_at": { + "title": "Provider Inspected At", "type": "string" }, - "environment_name": { - "default": "", - "title": "Environment Name", + "provider_state": { + "title": "Provider State", "type": "string" }, - "expected_current_provider_target": { - "anyOf": [ - { - "$ref": "#/components/schemas/DeployedTargetReference" - }, - { - "type": "null" - } - ] + "safe_to_release": { + "const": true, + "title": "Safe To Release", + "type": "boolean" }, - "healthcheck_path": { + "schema_version": { + "default": 1, + "minimum": 1.0, + "title": "Schema Version", + "type": "integer" + } + }, + "required": [ + "provider_inspected_at", + "provider_state", + "evidence_reference", + "safe_to_release" + ], + "title": "DurableOperationReconciliationAttestation", + "type": "object" + }, + "EdgeEndpointApplyEnvelope": { + "additionalProperties": false, + "properties": { + "confirmation": { "default": "", - "title": "Healthcheck Path", + "title": "Confirmation", "type": "string" }, - "instance": { - "title": "Instance", - "type": "string" + "endpoint": { + "$ref": "#/components/schemas/EdgeEndpointRecord" }, "mode": { "default": "dry-run", @@ -5349,326 +5026,395 @@ "title": "Mode", "type": "string" }, - "operation": { - "enum": [ - "adopt", - "create-application", - "create-compose", - "prune-compose-domain", - "reconcile-compose-domain", - "repair-domain-authority" - ], - "title": "Operation", - "type": "string" - }, - "product": { - "default": "launchplane", - "title": "Product", - "type": "string" - }, - "project_description": { - "default": "", - "title": "Project Description", - "type": "string" - }, - "project_id": { + "reason": { "default": "", - "title": "Project Id", + "title": "Reason", "type": "string" }, - "project_name": { - "default": "", - "title": "Project Name", + "schema_version": { + "default": 1, + "minimum": 1.0, + "title": "Schema Version", + "type": "integer" + } + }, + "required": [ + "endpoint" + ], + "title": "EdgeEndpointApplyEnvelope", + "type": "object" + }, + "EdgeEndpointRecord": { + "additionalProperties": false, + "properties": { + "endpoint_key": { + "title": "Endpoint Key", "type": "string" }, - "reason": { - "default": "", - "title": "Reason", + "provider": { + "const": "dokploy", + "default": "dokploy", + "title": "Provider", "type": "string" }, - "runtime_port": { - "anyOf": [ - { - "maximum": 65535.0, - "minimum": 1.0, - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Runtime Port" - }, "schema_version": { "default": 1, "minimum": 1.0, "title": "Schema Version", "type": "integer" }, - "server_id": { + "server_name": { + "title": "Server Name", + "type": "string" + }, + "source_label": { "default": "", - "title": "Server Id", + "title": "Source Label", "type": "string" }, - "source_git_ref": { - "default": "origin/main", - "title": "Source Git Ref", + "status": { + "default": "active", + "enum": [ + "active", + "disabled" + ], + "title": "Status", "type": "string" }, - "source_type": { - "default": "raw", - "title": "Source Type", + "updated_at": { + "title": "Updated At", "type": "string" }, - "target_id": { - "default": "", - "title": "Target Id", + "upstream_host": { + "title": "Upstream Host", "type": "string" }, - "target_name": { - "default": "", - "title": "Target Name", + "upstream_host_kind": { + "const": "ip", + "default": "ip", + "title": "Upstream Host Kind", "type": "string" }, - "target_type": { - "default": "compose", + "upstream_port": { + "maximum": 65535.0, + "minimum": 1.0, + "title": "Upstream Port", + "type": "integer" + }, + "upstream_scheme": { + "default": "https", "enum": [ - "application", - "compose" + "http", + "https" ], - "title": "Target Type", + "title": "Upstream Scheme", "type": "string" } }, "required": [ - "operation", - "context", - "instance" + "endpoint_key", + "server_name", + "upstream_host", + "upstream_port", + "updated_at" ], - "title": "DokployTargetSetupEnvelope", + "title": "EdgeEndpointRecord", "type": "object" }, - "DokployTargetShopifyPolicy": { + "EdgeEndpointRecordResponse": { "additionalProperties": false, "properties": { - "protected_store_keys": { - "default": [], - "items": { - "type": "string" - }, - "title": "Protected Store Keys", - "type": "array" + "record": { + "$ref": "#/components/schemas/EdgeEndpointRecord" + }, + "status": { + "const": "ok", + "default": "ok", + "title": "Status", + "type": "string" + }, + "trace_id": { + "title": "Trace Id", + "type": "string" } }, - "title": "DokployTargetShopifyPolicy", + "required": [ + "trace_id", + "record" + ], + "title": "EdgeEndpointRecordResponse", "type": "object" }, - "DriverActionDescriptor": { + "EdgeEndpointRecordsResponse": { "additionalProperties": false, "properties": { - "action_id": { - "title": "Action Id", - "type": "string" + "count": { + "title": "Count", + "type": "integer" }, - "alternate_authz_actions": { - "default": [], + "limit": { + "title": "Limit", + "type": "integer" + }, + "records": { "items": { - "type": "string" + "$ref": "#/components/schemas/EdgeEndpointRecord" }, - "title": "Alternate Authz Actions", + "title": "Records", "type": "array" }, - "authz_action": { - "default": "", - "title": "Authz Action", - "type": "string" - }, - "description": { - "title": "Description", + "status": { + "const": "ok", + "default": "ok", + "title": "Status", "type": "string" }, - "input_schema": { - "additionalProperties": true, - "title": "Input Schema", - "type": "object" - }, - "label": { - "title": "Label", + "trace_id": { + "title": "Trace Id", "type": "string" - }, - "method": { + } + }, + "required": [ + "trace_id", + "limit", + "count", + "records" + ], + "title": "EdgeEndpointRecordsResponse", + "type": "object" + }, + "EffectiveAccessDecision": { + "additionalProperties": false, + "properties": { + "decision": { "enum": [ - "GET", - "POST" + "allowed", + "denied" ], - "title": "Method", + "title": "Decision", "type": "string" }, - "operator_visible": { - "default": true, - "title": "Operator Visible", - "type": "boolean" - }, - "output_schema": { - "additionalProperties": true, - "title": "Output Schema", - "type": "object" - }, - "readiness_requirements": { - "default": [], - "items": { - "enum": [ - "provider_target", - "route_binding", - "runtime_environment", - "managed_secrets", - "artifact", - "deployment", - "topology" - ], - "type": "string" - }, - "title": "Readiness Requirements", - "type": "array" + "reason_code": { + "enum": [ + "allowed", + "authz_policy_schema_incompatible", + "instance_scope_required", + "principal_role_restricted", + "principal_binding_invalid", + "no_matching_grant" + ], + "title": "Reason Code", + "type": "string" + } + }, + "required": [ + "decision", + "reason_code" + ], + "title": "EffectiveAccessDecision", + "type": "object" + }, + "EffectiveAccessEvaluateRequest": { + "additionalProperties": false, + "properties": { + "action": { + "title": "Action", + "type": "string" }, - "requires_production_backup_policy": { - "default": false, - "title": "Requires Production Backup Policy", - "type": "boolean" + "context": { + "title": "Context", + "type": "string" }, - "route_path": { - "title": "Route Path", + "instance": { + "default": "", + "title": "Instance", "type": "string" }, - "safety": { - "enum": [ - "read", - "safe_write", - "mutation", - "destructive" + "principal": { + "discriminator": { + "mapping": { + "github_actions": "#/components/schemas/GitHubActionsAccessPrincipal", + "github_human": "#/components/schemas/GitHubHumanAccessPrincipal", + "local_admin": "#/components/schemas/LocalAdminAccessPrincipal", + "local_operator": "#/components/schemas/LocalOperatorAccessPrincipal", + "terminal_agent": "#/components/schemas/TerminalAgentAccessPrincipal" + }, + "propertyName": "principal_type" + }, + "oneOf": [ + { + "$ref": "#/components/schemas/GitHubActionsAccessPrincipal" + }, + { + "$ref": "#/components/schemas/GitHubHumanAccessPrincipal" + }, + { + "$ref": "#/components/schemas/TerminalAgentAccessPrincipal" + }, + { + "$ref": "#/components/schemas/LocalOperatorAccessPrincipal" + }, + { + "$ref": "#/components/schemas/LocalAdminAccessPrincipal" + } ], - "title": "Safety", + "title": "Principal" + }, + "product": { + "title": "Product", "type": "string" }, - "scope": { + "target_scope": { "enum": [ - "global", "context", - "instance", - "preview" + "instance" ], - "title": "Scope", + "title": "Target Scope", "type": "string" - }, - "writes_records": { - "default": [], - "items": { - "type": "string" - }, - "title": "Writes Records", - "type": "array" } }, "required": [ - "action_id", - "label", - "description", - "safety", - "scope", - "method", - "route_path" + "principal", + "action", + "product", + "context", + "target_scope" ], - "title": "DriverActionDescriptor", + "title": "EffectiveAccessEvaluateRequest", "type": "object" }, - "DriverCapabilityDescriptor": { + "EffectiveAccessEvaluateResponse": { "additionalProperties": false, "properties": { - "actions": { - "default": [], - "items": { - "type": "string" - }, - "title": "Actions", - "type": "array" + "evaluation": { + "$ref": "#/components/schemas/EffectiveAccessDecision" }, - "capability_id": { - "title": "Capability Id", + "policy_record_id": { + "title": "Policy Record Id", "type": "string" }, - "description": { - "title": "Description", + "policy_revision": { + "minimum": 1.0, + "title": "Policy Revision", + "type": "integer" + }, + "policy_sha256": { + "title": "Policy Sha256", "type": "string" }, - "label": { - "title": "Label", + "request": { + "$ref": "#/components/schemas/EffectiveAccessRequestSummary" + }, + "status": { + "const": "ok", + "default": "ok", + "title": "Status", "type": "string" }, - "panels": { - "default": [], - "items": { - "enum": [ - "summary", - "lane_health", - "artifact_evidence", - "deployment_evidence", - "promotion_evidence", - "backup_evidence", - "preview_inventory", - "settings", - "secret_bindings", - "audit" - ], - "type": "string" - }, - "title": "Panels", - "type": "array" + "trace_id": { + "title": "Trace Id", + "type": "string" } }, "required": [ - "capability_id", - "label", - "description" + "trace_id", + "policy_record_id", + "policy_revision", + "policy_sha256", + "request", + "evaluation" ], - "title": "DriverCapabilityDescriptor", + "title": "EffectiveAccessEvaluateResponse", "type": "object" }, - "DriverContextView": { + "EffectiveAccessRequestSummary": { "additionalProperties": false, "properties": { + "action": { + "title": "Action", + "type": "string" + }, "context": { "title": "Context", "type": "string" }, - "drivers": { - "default": [], - "items": { - "$ref": "#/components/schemas/DriverView" - }, - "title": "Drivers", - "type": "array" - }, "instance": { "default": "", "title": "Instance", "type": "string" }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" + "principal_type": { + "enum": [ + "github_actions", + "github_human", + "terminal_agent", + "local_operator", + "local_admin" + ], + "title": "Principal Type", + "type": "string" + }, + "product": { + "title": "Product", + "type": "string" + }, + "target_scope": { + "enum": [ + "context", + "instance" + ], + "title": "Target Scope", + "type": "string" } }, "required": [ - "context" + "principal_type", + "action", + "product", + "context", + "target_scope" ], - "title": "DriverContextView", + "title": "EffectiveAccessRequestSummary", "type": "object" }, - "DriverContextViewResponse": { + "EngineeringReviewAuthorityApplyEnvelope": { + "additionalProperties": false, + "properties": { + "expected_current_authority_digest": { + "default": "", + "title": "Expected Current Authority Digest", + "type": "string" + }, + "expected_current_authority_id": { + "default": "", + "title": "Expected Current Authority Id", + "type": "string" + }, + "mode": { + "default": "apply", + "enum": [ + "dry_run", + "apply" + ], + "title": "Mode", + "type": "string" + }, + "record": { + "$ref": "#/components/schemas/EngineeringReviewAuthorityRecord" + } + }, + "required": [ + "record" + ], + "title": "EngineeringReviewAuthorityApplyEnvelope", + "type": "object" + }, + "EngineeringReviewAuthorityApplyResponse": { "additionalProperties": false, "properties": { + "result": { + "$ref": "#/components/schemas/EngineeringReviewAuthorityApplyResult" + }, "status": { "const": "ok", "default": "ok", @@ -5678,68 +5424,115 @@ "trace_id": { "title": "Trace Id", "type": "string" - }, - "view": { - "$ref": "#/components/schemas/DriverContextView" } }, "required": [ "trace_id", - "view" + "result" ], - "title": "DriverContextViewResponse", + "title": "EngineeringReviewAuthorityApplyResponse", "type": "object" }, - "DriverDescriptor": { + "EngineeringReviewAuthorityApplyResult": { "additionalProperties": false, "properties": { - "actions": { - "default": [], - "items": { - "$ref": "#/components/schemas/DriverActionDescriptor" - }, - "title": "Actions", - "type": "array" + "record": { + "$ref": "#/components/schemas/EngineeringReviewAuthorityRecord" }, - "base_driver_id": { + "schema_version": { + "default": 1, + "minimum": 1.0, + "title": "Schema Version", + "type": "integer" + }, + "status": { + "enum": [ + "would_apply", + "would_replay", + "applied", + "replayed" + ], + "title": "Status", + "type": "string" + } + }, + "required": [ + "status", + "record" + ], + "title": "EngineeringReviewAuthorityApplyResult", + "type": "object" + }, + "EngineeringReviewAuthorityRecord": { + "additionalProperties": false, + "description": "DB-backed shadow review policy and controlled worker runtime identity.", + "properties": { + "authoritative": { + "const": false, + "default": false, + "title": "Authoritative", + "type": "boolean" + }, + "authority_digest": { "default": "", - "title": "Base Driver Id", + "title": "Authority Digest", "type": "string" }, - "capabilities": { - "default": [], - "items": { - "$ref": "#/components/schemas/DriverCapabilityDescriptor" - }, - "title": "Capabilities", - "type": "array" + "authority_id": { + "default": "", + "title": "Authority Id", + "type": "string" }, - "context_patterns": { - "default": [], + "binary_sha256": { + "title": "Binary Sha256", + "type": "string" + }, + "enforcement_effect": { + "const": "none", + "default": "none", + "title": "Enforcement Effect", + "type": "string" + }, + "executable_path": { + "title": "Executable Path", + "type": "string" + }, + "lease_seconds": { + "maximum": 7200.0, + "minimum": 300.0, + "title": "Lease Seconds", + "type": "integer" + }, + "model_slots": { "items": { - "type": "string" + "$ref": "#/components/schemas/EngineeringReviewModelSlot" }, - "title": "Context Patterns", + "maxItems": 4, + "minItems": 2, + "title": "Model Slots", "type": "array" }, - "description": { - "title": "Description", - "type": "string" + "policy_revision": { + "minimum": 1.0, + "title": "Policy Revision", + "type": "integer" }, - "driver_id": { - "title": "Driver Id", + "reason": { + "title": "Reason", "type": "string" }, - "label": { - "title": "Label", + "recorded_at": { + "title": "Recorded At", "type": "string" }, - "product": { - "title": "Product", + "repository": { + "title": "Repository", "type": "string" }, - "provider_boundary": { - "title": "Provider Boundary", + "rollout_mode": { + "const": "shadow", + "default": "shadow", + "title": "Rollout Mode", "type": "string" }, "schema_version": { @@ -5748,30 +5541,60 @@ "title": "Schema Version", "type": "integer" }, - "setting_groups": { - "default": [], - "items": { - "$ref": "#/components/schemas/DriverSettingGroupDescriptor" - }, - "title": "Setting Groups", - "type": "array" + "source": { + "title": "Source", + "type": "string" + }, + "status": { + "enum": [ + "active", + "retired" + ], + "title": "Status", + "type": "string" + }, + "supersedes_authority_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Supersedes Authority Id" + }, + "worker_host": { + "title": "Worker Host", + "type": "string" + }, + "worker_runtime_id": { + "title": "Worker Runtime Id", + "type": "string" } }, "required": [ - "driver_id", - "label", - "product", - "description", - "provider_boundary" + "repository", + "status", + "policy_revision", + "model_slots", + "worker_runtime_id", + "worker_host", + "executable_path", + "binary_sha256", + "lease_seconds", + "recorded_at", + "source", + "reason" ], - "title": "DriverDescriptor", + "title": "EngineeringReviewAuthorityRecord", "type": "object" }, - "DriverDescriptorResponse": { + "EngineeringReviewAuthorityResponse": { "additionalProperties": false, "properties": { - "driver": { - "$ref": "#/components/schemas/DriverDescriptor" + "authority": { + "$ref": "#/components/schemas/EngineeringReviewAuthorityRecord" }, "status": { "const": "ok", @@ -5786,20 +5609,38 @@ }, "required": [ "trace_id", - "driver" + "authority" ], - "title": "DriverDescriptorResponse", + "title": "EngineeringReviewAuthorityResponse", "type": "object" }, - "DriverDescriptorsResponse": { + "EngineeringReviewDecisionProjectionRequest": { "additionalProperties": false, "properties": { - "drivers": { - "items": { - "$ref": "#/components/schemas/DriverDescriptor" - }, - "title": "Drivers", - "type": "array" + "decision_id": { + "maxLength": 128, + "minLength": 1, + "title": "Decision Id", + "type": "string" + }, + "schema_version": { + "default": 1, + "minimum": 1.0, + "title": "Schema Version", + "type": "integer" + } + }, + "required": [ + "decision_id" + ], + "title": "EngineeringReviewDecisionProjectionRequest", + "type": "object" + }, + "EngineeringReviewDecisionProjectionResponse": { + "additionalProperties": false, + "properties": { + "result": { + "$ref": "#/components/schemas/EngineeringReviewDecisionProjectionResult" }, "status": { "const": "ok", @@ -5814,155 +5655,328 @@ }, "required": [ "trace_id", - "drivers" + "result" ], - "title": "DriverDescriptorsResponse", + "title": "EngineeringReviewDecisionProjectionResponse", "type": "object" }, - "DriverSettingGroupDescriptor": { + "EngineeringReviewDecisionProjectionResult": { "additionalProperties": false, "properties": { - "description": { - "title": "Description", - "type": "string" - }, - "fields": { - "default": [], - "items": { - "type": "string" - }, - "title": "Fields", - "type": "array" + "app_id": { + "minimum": 1.0, + "title": "App Id", + "type": "integer" }, - "group_id": { - "title": "Group Id", + "check_run_id": { + "minimum": 1.0, + "title": "Check Run Id", + "type": "integer" + }, + "conclusion": { + "const": "neutral", + "default": "neutral", + "title": "Conclusion", "type": "string" }, - "label": { - "title": "Label", + "context": { + "const": "launchplane/engineering-review", + "default": "launchplane/engineering-review", + "title": "Context", "type": "string" }, - "scope": { + "external_id": { + "pattern": "^[0-9a-f]{64}$", + "title": "External Id", + "type": "string" + }, + "head_sha": { + "title": "Head Sha", + "type": "string" + }, + "installation_id": { + "minimum": 1.0, + "title": "Installation Id", + "type": "integer" + }, + "schema_version": { + "default": 1, + "minimum": 1.0, + "title": "Schema Version", + "type": "integer" + }, + "state": { "enum": [ - "global", - "context", - "instance", - "preview" + "pending", + "success", + "failure", + "error" ], - "title": "Scope", + "title": "State", "type": "string" }, - "secret_bindings": { - "default": [], - "items": { - "type": "string" - }, - "title": "Secret Bindings", - "type": "array" + "status": { + "enum": [ + "projected", + "updated", + "replayed" + ], + "title": "Status", + "type": "string" } }, "required": [ - "group_id", - "label", - "description", - "scope" + "status", + "state", + "head_sha", + "external_id", + "app_id", + "installation_id", + "check_run_id" ], - "title": "DriverSettingGroupDescriptor", + "title": "EngineeringReviewDecisionProjectionResult", "type": "object" }, - "DriverView": { + "EngineeringReviewDecisionRecord": { "additionalProperties": false, "properties": { - "available_actions": { - "default": [], - "items": { - "$ref": "#/components/schemas/DriverActionDescriptor" - }, - "title": "Available Actions", - "type": "array" + "authoritative": { + "const": false, + "default": false, + "title": "Authoritative", + "type": "boolean" }, - "descriptor": { - "$ref": "#/components/schemas/DriverDescriptor" + "authority_digest": { + "default": "", + "title": "Authority Digest", + "type": "string" }, - "driver_id": { - "title": "Driver Id", + "authority_id": { + "default": "", + "title": "Authority Id", "type": "string" }, - "lane_summary": { + "authority_policy_revision": { "anyOf": [ { - "$ref": "#/components/schemas/LaunchplaneLaneSummary" + "minimum": 1.0, + "type": "integer" }, { "type": "null" } - ] + ], + "title": "Authority Policy Revision" }, - "preview_inventory_provenance": { + "binding_hash_version": { "anyOf": [ { - "$ref": "#/components/schemas/DataProvenance" + "const": 2, + "type": "integer" }, { "type": "null" } - ] + ], + "title": "Binding Hash Version" }, - "preview_summaries": { + "change_impact_decision_digest": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Change Impact Decision Digest" + }, + "change_impact_policy_digest": { + "default": "", + "title": "Change Impact Policy Digest", + "type": "string" + }, + "change_impact_policy_record_id": { + "default": "", + "title": "Change Impact Policy Record Id", + "type": "string" + }, + "change_impact_policy_revision": { + "anyOf": [ + { + "minimum": 1.0, + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Change Impact Policy Revision" + }, + "change_impact_status": { + "anyOf": [ + { + "enum": [ + "success", + "unknown", + "stale_head", + "stale_policy" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Change Impact Status" + }, + "decision_binding_sha256": { + "default": "", + "title": "Decision Binding Sha256", + "type": "string" + }, + "decision_id": { + "default": "", + "title": "Decision Id", + "type": "string" + }, + "enforcement_effect": { + "const": "none", + "default": "none", + "title": "Enforcement Effect", + "type": "string" + }, + "engineering_review_tier": { + "anyOf": [ + { + "enum": [ + "routine", + "sensitive" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Engineering Review Tier" + }, + "evaluated_at": { + "title": "Evaluated At", + "type": "string" + }, + "mode": { + "const": "shadow", + "default": "shadow", + "title": "Mode", + "type": "string" + }, + "qualifying_model_families": { "default": [], "items": { - "$ref": "#/components/schemas/LaunchplanePreviewSummary" + "type": "string" }, - "title": "Preview Summaries", + "title": "Qualifying Model Families", "type": "array" }, - "production_backup_authorities": { + "qualifying_run_ids": { "default": [], "items": { - "$ref": "#/components/schemas/ProductionBackupAuthorityStatus" + "type": "string" }, - "title": "Production Backup Authorities", + "title": "Qualifying Run Ids", "type": "array" + }, + "reason_code": { + "title": "Reason Code", + "type": "string" + }, + "required_review_count": { + "enum": [ + 1, + 2 + ], + "title": "Required Review Count", + "type": "integer" + }, + "schema_version": { + "default": 1, + "minimum": 1.0, + "title": "Schema Version", + "type": "integer" + }, + "status": { + "enum": [ + "approved", + "changes_requested", + "blocked", + "pending", + "unknown", + "stale" + ], + "title": "Status", + "type": "string" + }, + "target": { + "$ref": "#/components/schemas/RepositoryTarget" + }, + "work_request_id": { + "title": "Work Request Id", + "type": "string" + }, + "work_request_lifecycle_id": { + "title": "Work Request Lifecycle Id", + "type": "string" } }, "required": [ - "driver_id", - "descriptor" + "status", + "reason_code", + "target", + "work_request_id", + "work_request_lifecycle_id", + "required_review_count", + "evaluated_at" ], - "title": "DriverView", + "title": "EngineeringReviewDecisionRecord", "type": "object" }, - "DurableOperationCancellationRequest": { + "EngineeringReviewDecisionRequest": { "additionalProperties": false, "properties": { - "reason": { - "title": "Reason", - "type": "string" + "schema_version": { + "default": 1, + "minimum": 1.0, + "title": "Schema Version", + "type": "integer" }, - "reconciliation_attestation": { - "anyOf": [ - { - "$ref": "#/components/schemas/DurableOperationReconciliationAttestation" - }, - { - "type": "null" - } - ] + "target": { + "$ref": "#/components/schemas/RepositoryTargetReference" + }, + "work_request_id": { + "maxLength": 128, + "minLength": 1, + "title": "Work Request Id", + "type": "string" } }, "required": [ - "reason" + "target", + "work_request_id" ], - "title": "DurableOperationCancellationRequest", + "title": "EngineeringReviewDecisionRequest", "type": "object" }, - "DurableOperationCancellationResponse": { + "EngineeringReviewDecisionResponse": { "additionalProperties": false, "properties": { - "operation": { - "additionalProperties": true, - "title": "Operation", - "type": "object" + "created": { + "default": false, + "title": "Created", + "type": "boolean" + }, + "decision": { + "$ref": "#/components/schemas/EngineeringReviewDecisionRecord" }, "status": { "const": "ok", @@ -5977,167 +5991,125 @@ }, "required": [ "trace_id", - "operation" + "decision" ], - "title": "DurableOperationCancellationResponse", + "title": "EngineeringReviewDecisionResponse", "type": "object" }, - "DurableOperationReconciliationAttestation": { + "EngineeringReviewFinding": { "additionalProperties": false, "properties": { - "evidence_reference": { - "title": "Evidence Reference", + "code": { + "maxLength": 100, + "minLength": 1, + "title": "Code", "type": "string" }, - "provider_inspected_at": { - "title": "Provider Inspected At", - "type": "string" + "line": { + "anyOf": [ + { + "minimum": 1.0, + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Line" }, - "provider_state": { - "title": "Provider State", + "message": { + "maxLength": 1000, + "minLength": 1, + "title": "Message", "type": "string" }, - "safe_to_release": { - "const": true, - "title": "Safe To Release", - "type": "boolean" - }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" + "path": { + "default": "", + "maxLength": 500, + "title": "Path", + "type": "string" } }, "required": [ - "provider_inspected_at", - "provider_state", - "evidence_reference", - "safe_to_release" + "code", + "message" ], - "title": "DurableOperationReconciliationAttestation", + "title": "EngineeringReviewFinding", "type": "object" }, - "EdgeEndpointApplyEnvelope": { + "EngineeringReviewModelSlot": { "additionalProperties": false, "properties": { - "confirmation": { - "default": "", - "title": "Confirmation", - "type": "string" - }, - "endpoint": { - "$ref": "#/components/schemas/EdgeEndpointRecord" - }, - "mode": { - "default": "dry-run", - "enum": [ - "dry-run", - "apply" - ], - "title": "Mode", + "model_family": { + "title": "Model Family", "type": "string" }, - "reason": { - "default": "", - "title": "Reason", + "model_id": { + "title": "Model Id", "type": "string" }, - "schema_version": { - "default": 1, + "review_slot": { + "maximum": 4.0, "minimum": 1.0, - "title": "Schema Version", + "title": "Review Slot", "type": "integer" } }, "required": [ - "endpoint" + "review_slot", + "model_id", + "model_family" ], - "title": "EdgeEndpointApplyEnvelope", + "title": "EngineeringReviewModelSlot", "type": "object" }, - "EdgeEndpointRecord": { + "EngineeringReviewRunAssignment": { "additionalProperties": false, "properties": { - "endpoint_key": { - "title": "Endpoint Key", - "type": "string" - }, - "provider": { - "const": "dokploy", - "default": "dokploy", - "title": "Provider", - "type": "string" - }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" - }, - "server_name": { - "title": "Server Name", - "type": "string" - }, - "source_label": { - "default": "", - "title": "Source Label", - "type": "string" - }, - "status": { - "default": "active", - "enum": [ - "active", - "disabled" - ], - "title": "Status", + "credential": { + "title": "Credential", "type": "string" }, - "updated_at": { - "title": "Updated At", + "executable_path": { + "title": "Executable Path", "type": "string" }, - "upstream_host": { - "title": "Upstream Host", - "type": "string" + "run": { + "$ref": "#/components/schemas/EngineeringReviewRunView" }, - "upstream_host_kind": { - "const": "ip", - "default": "ip", - "title": "Upstream Host Kind", + "worker_host": { + "title": "Worker Host", "type": "string" - }, - "upstream_port": { - "maximum": 65535.0, - "minimum": 1.0, - "title": "Upstream Port", - "type": "integer" - }, - "upstream_scheme": { - "default": "https", - "enum": [ - "http", - "https" - ], - "title": "Upstream Scheme", + } + }, + "required": [ + "run", + "credential", + "worker_host", + "executable_path" + ], + "title": "EngineeringReviewRunAssignment", + "type": "object" + }, + "EngineeringReviewRunClaimRequest": { + "additionalProperties": false, + "properties": { + "run_id": { + "title": "Run Id", "type": "string" } }, "required": [ - "endpoint_key", - "server_name", - "upstream_host", - "upstream_port", - "updated_at" + "run_id" ], - "title": "EdgeEndpointRecord", + "title": "EngineeringReviewRunClaimRequest", "type": "object" }, - "EdgeEndpointRecordResponse": { + "EngineeringReviewRunClaimResponse": { "additionalProperties": false, "properties": { - "record": { - "$ref": "#/components/schemas/EdgeEndpointRecord" + "assignment": { + "$ref": "#/components/schemas/EngineeringReviewRunAssignment" }, "status": { "const": "ok", @@ -6152,171 +6124,60 @@ }, "required": [ "trace_id", - "record" + "assignment" ], - "title": "EdgeEndpointRecordResponse", + "title": "EngineeringReviewRunClaimResponse", "type": "object" }, - "EdgeEndpointRecordsResponse": { - "additionalProperties": false, - "properties": { - "count": { - "title": "Count", - "type": "integer" - }, - "limit": { - "title": "Limit", - "type": "integer" - }, - "records": { - "items": { - "$ref": "#/components/schemas/EdgeEndpointRecord" - }, - "title": "Records", - "type": "array" - }, - "status": { - "const": "ok", - "default": "ok", - "title": "Status", - "type": "string" - }, - "trace_id": { - "title": "Trace Id", - "type": "string" - } - }, - "required": [ - "trace_id", - "limit", - "count", - "records" - ], - "title": "EdgeEndpointRecordsResponse", - "type": "object" - }, - "EffectiveAccessDecision": { + "EngineeringReviewRunCreateRequest": { "additionalProperties": false, "properties": { - "decision": { - "enum": [ - "allowed", - "denied" - ], - "title": "Decision", - "type": "string" - }, - "reason_code": { - "enum": [ - "allowed", - "authz_policy_schema_incompatible", - "instance_scope_required", - "principal_role_restricted", - "principal_binding_invalid", - "no_matching_grant" - ], - "title": "Reason Code", + "work_request_id": { + "title": "Work Request Id", "type": "string" } }, "required": [ - "decision", - "reason_code" + "work_request_id" ], - "title": "EffectiveAccessDecision", + "title": "EngineeringReviewRunCreateRequest", "type": "object" }, - "EffectiveAccessEvaluateRequest": { + "EngineeringReviewRunCreateResponse": { "additionalProperties": false, "properties": { - "action": { - "title": "Action", - "type": "string" - }, - "context": { - "title": "Context", - "type": "string" - }, - "instance": { - "default": "", - "title": "Instance", - "type": "string" - }, - "principal": { - "discriminator": { - "mapping": { - "github_actions": "#/components/schemas/GitHubActionsAccessPrincipal", - "github_human": "#/components/schemas/GitHubHumanAccessPrincipal", - "local_admin": "#/components/schemas/LocalAdminAccessPrincipal", - "local_operator": "#/components/schemas/LocalOperatorAccessPrincipal", - "terminal_agent": "#/components/schemas/TerminalAgentAccessPrincipal" - }, - "propertyName": "principal_type" + "runs": { + "items": { + "$ref": "#/components/schemas/EngineeringReviewRunView" }, - "oneOf": [ - { - "$ref": "#/components/schemas/GitHubActionsAccessPrincipal" - }, - { - "$ref": "#/components/schemas/GitHubHumanAccessPrincipal" - }, - { - "$ref": "#/components/schemas/TerminalAgentAccessPrincipal" - }, - { - "$ref": "#/components/schemas/LocalOperatorAccessPrincipal" - }, - { - "$ref": "#/components/schemas/LocalAdminAccessPrincipal" - } - ], - "title": "Principal" + "title": "Runs", + "type": "array" }, - "product": { - "title": "Product", + "status": { + "const": "ok", + "default": "ok", + "title": "Status", "type": "string" }, - "target_scope": { - "enum": [ - "context", - "instance" - ], - "title": "Target Scope", + "trace_id": { + "title": "Trace Id", "type": "string" } }, "required": [ - "principal", - "action", - "product", - "context", - "target_scope" + "trace_id", + "runs" ], - "title": "EffectiveAccessEvaluateRequest", + "title": "EngineeringReviewRunCreateResponse", "type": "object" }, - "EffectiveAccessEvaluateResponse": { + "EngineeringReviewRunMaintenanceResponse": { "additionalProperties": false, "properties": { - "evaluation": { - "$ref": "#/components/schemas/EffectiveAccessDecision" - }, - "policy_record_id": { - "title": "Policy Record Id", - "type": "string" - }, - "policy_revision": { - "minimum": 1.0, - "title": "Policy Revision", + "expired_count": { + "title": "Expired Count", "type": "integer" }, - "policy_sha256": { - "title": "Policy Sha256", - "type": "string" - }, - "request": { - "$ref": "#/components/schemas/EffectiveAccessRequestSummary" - }, "status": { "const": "ok", "default": "ok", @@ -6330,102 +6191,16 @@ }, "required": [ "trace_id", - "policy_record_id", - "policy_revision", - "policy_sha256", - "request", - "evaluation" - ], - "title": "EffectiveAccessEvaluateResponse", - "type": "object" - }, - "EffectiveAccessRequestSummary": { - "additionalProperties": false, - "properties": { - "action": { - "title": "Action", - "type": "string" - }, - "context": { - "title": "Context", - "type": "string" - }, - "instance": { - "default": "", - "title": "Instance", - "type": "string" - }, - "principal_type": { - "enum": [ - "github_actions", - "github_human", - "terminal_agent", - "local_operator", - "local_admin" - ], - "title": "Principal Type", - "type": "string" - }, - "product": { - "title": "Product", - "type": "string" - }, - "target_scope": { - "enum": [ - "context", - "instance" - ], - "title": "Target Scope", - "type": "string" - } - }, - "required": [ - "principal_type", - "action", - "product", - "context", - "target_scope" - ], - "title": "EffectiveAccessRequestSummary", - "type": "object" - }, - "EngineeringReviewAuthorityApplyEnvelope": { - "additionalProperties": false, - "properties": { - "expected_current_authority_digest": { - "default": "", - "title": "Expected Current Authority Digest", - "type": "string" - }, - "expected_current_authority_id": { - "default": "", - "title": "Expected Current Authority Id", - "type": "string" - }, - "mode": { - "default": "apply", - "enum": [ - "dry_run", - "apply" - ], - "title": "Mode", - "type": "string" - }, - "record": { - "$ref": "#/components/schemas/EngineeringReviewAuthorityRecord" - } - }, - "required": [ - "record" + "expired_count" ], - "title": "EngineeringReviewAuthorityApplyEnvelope", + "title": "EngineeringReviewRunMaintenanceResponse", "type": "object" }, - "EngineeringReviewAuthorityApplyResponse": { + "EngineeringReviewRunResponse": { "additionalProperties": false, "properties": { - "result": { - "$ref": "#/components/schemas/EngineeringReviewAuthorityApplyResult" + "run": { + "$ref": "#/components/schemas/EngineeringReviewRunView" }, "status": { "const": "ok", @@ -6440,58 +6215,66 @@ }, "required": [ "trace_id", - "result" + "run" ], - "title": "EngineeringReviewAuthorityApplyResponse", + "title": "EngineeringReviewRunResponse", "type": "object" }, - "EngineeringReviewAuthorityApplyResult": { + "EngineeringReviewRunSubmission": { "additionalProperties": false, + "description": "Credential plus bounded reviewer output; no target or identity authority.", "properties": { - "record": { - "$ref": "#/components/schemas/EngineeringReviewAuthorityRecord" - }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" + "credential": { + "maxLength": 256, + "minLength": 1, + "title": "Credential", + "type": "string" }, - "status": { + "decision": { "enum": [ - "would_apply", - "would_replay", - "applied", - "replayed" + "approved", + "changes_requested", + "blocked" ], - "title": "Status", + "title": "Decision", + "type": "string" + }, + "findings": { + "default": [], + "items": { + "$ref": "#/components/schemas/EngineeringReviewFinding" + }, + "maxItems": 50, + "title": "Findings", + "type": "array" + }, + "summary": { + "default": "", + "maxLength": 4000, + "title": "Summary", "type": "string" } }, "required": [ - "status", - "record" + "credential", + "decision" ], - "title": "EngineeringReviewAuthorityApplyResult", + "title": "EngineeringReviewRunSubmission", "type": "object" }, - "EngineeringReviewAuthorityRecord": { + "EngineeringReviewRunView": { "additionalProperties": false, - "description": "DB-backed shadow review policy and controlled worker runtime identity.", "properties": { "authoritative": { "const": false, - "default": false, "title": "Authoritative", "type": "boolean" }, "authority_digest": { - "default": "", "title": "Authority Digest", "type": "string" }, "authority_id": { - "default": "", "title": "Authority Id", "type": "string" }, @@ -6499,85 +6282,146 @@ "title": "Binary Sha256", "type": "string" }, + "claim_attempt": { + "title": "Claim Attempt", + "type": "integer" + }, + "claimed_at": { + "title": "Claimed At", + "type": "string" + }, + "created_at": { + "title": "Created At", + "type": "string" + }, + "decision": { + "anyOf": [ + { + "enum": [ + "approved", + "changes_requested", + "blocked" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Decision" + }, "enforcement_effect": { "const": "none", - "default": "none", "title": "Enforcement Effect", "type": "string" }, - "executable_path": { - "title": "Executable Path", + "error_message": { + "title": "Error Message", "type": "string" }, - "lease_seconds": { - "maximum": 7200.0, - "minimum": 300.0, - "title": "Lease Seconds", + "evidence_digest": { + "title": "Evidence Digest", + "type": "string" + }, + "fencing_token": { + "title": "Fencing Token", "type": "integer" }, - "model_slots": { + "findings": { "items": { - "$ref": "#/components/schemas/EngineeringReviewModelSlot" + "$ref": "#/components/schemas/EngineeringReviewFinding" }, - "maxItems": 4, - "minItems": 2, - "title": "Model Slots", + "title": "Findings", "type": "array" }, + "finished_at": { + "title": "Finished At", + "type": "string" + }, + "head_sha": { + "title": "Head Sha", + "type": "string" + }, + "lease_expires_at": { + "title": "Lease Expires At", + "type": "string" + }, + "model_family": { + "title": "Model Family", + "type": "string" + }, + "model_id": { + "title": "Model Id", + "type": "string" + }, "policy_revision": { - "minimum": 1.0, "title": "Policy Revision", "type": "integer" }, - "reason": { - "title": "Reason", - "type": "string" + "pr_number": { + "title": "Pr Number", + "type": "integer" }, - "recorded_at": { - "title": "Recorded At", + "pr_url": { + "title": "Pr Url", "type": "string" }, "repository": { "title": "Repository", "type": "string" }, + "review_slot": { + "title": "Review Slot", + "type": "integer" + }, "rollout_mode": { "const": "shadow", - "default": "shadow", "title": "Rollout Mode", "type": "string" }, + "run_id": { + "title": "Run Id", + "type": "string" + }, "schema_version": { - "default": 1, - "minimum": 1.0, "title": "Schema Version", "type": "integer" }, - "source": { - "title": "Source", + "started_at": { + "title": "Started At", "type": "string" }, - "status": { + "state": { "enum": [ - "active", - "retired" + "pending", + "claimed", + "running", + "completed", + "failed", + "expired", + "cancelled" ], - "title": "Status", + "title": "State", "type": "string" }, - "supersedes_authority_id": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Supersedes Authority Id" + "summary": { + "title": "Summary", + "type": "string" }, - "worker_host": { - "title": "Worker Host", + "tree_sha": { + "title": "Tree Sha", + "type": "string" + }, + "updated_at": { + "title": "Updated At", + "type": "string" + }, + "work_request_id": { + "title": "Work Request Id", + "type": "string" + }, + "work_request_lifecycle_id": { + "title": "Work Request Lifecycle Id", "type": "string" }, "worker_runtime_id": { @@ -6586,73 +6430,117 @@ } }, "required": [ + "schema_version", + "run_id", + "review_slot", + "state", "repository", - "status", + "pr_number", + "pr_url", + "head_sha", + "tree_sha", + "authority_id", + "authority_digest", "policy_revision", - "model_slots", + "work_request_id", + "work_request_lifecycle_id", + "model_id", + "model_family", "worker_runtime_id", - "worker_host", - "executable_path", "binary_sha256", - "lease_seconds", - "recorded_at", - "source", - "reason" + "rollout_mode", + "authoritative", + "enforcement_effect", + "created_at", + "updated_at", + "claimed_at", + "started_at", + "finished_at", + "lease_expires_at", + "claim_attempt", + "fencing_token", + "decision", + "findings", + "summary", + "evidence_digest", + "error_message" ], - "title": "EngineeringReviewAuthorityRecord", + "title": "EngineeringReviewRunView", "type": "object" }, - "EngineeringReviewAuthorityResponse": { + "EngineeringReviewRunWorkerFailure": { "additionalProperties": false, "properties": { - "authority": { - "$ref": "#/components/schemas/EngineeringReviewAuthorityRecord" - }, - "status": { - "const": "ok", - "default": "ok", - "title": "Status", + "error_code": { + "maxLength": 100, + "minLength": 1, + "title": "Error Code", "type": "string" }, - "trace_id": { - "title": "Trace Id", + "fencing_token": { + "minimum": 1.0, + "title": "Fencing Token", + "type": "integer" + }, + "run_id": { + "title": "Run Id", + "type": "string" + }, + "summary": { + "maxLength": 1000, + "minLength": 1, + "title": "Summary", "type": "string" } }, "required": [ - "trace_id", - "authority" + "run_id", + "fencing_token", + "error_code", + "summary" ], - "title": "EngineeringReviewAuthorityResponse", + "title": "EngineeringReviewRunWorkerFailure", "type": "object" }, - "EngineeringReviewDecisionProjectionRequest": { + "EngineeringReviewRunWorkerUpdate": { "additionalProperties": false, "properties": { - "decision_id": { - "maxLength": 128, - "minLength": 1, - "title": "Decision Id", - "type": "string" - }, - "schema_version": { - "default": 1, + "fencing_token": { "minimum": 1.0, - "title": "Schema Version", + "title": "Fencing Token", "type": "integer" + }, + "run_id": { + "title": "Run Id", + "type": "string" } }, "required": [ - "decision_id" + "run_id", + "fencing_token" ], - "title": "EngineeringReviewDecisionProjectionRequest", + "title": "EngineeringReviewRunWorkerUpdate", "type": "object" }, - "EngineeringReviewDecisionProjectionResponse": { + "EngineeringReviewRunsResponse": { "additionalProperties": false, "properties": { - "result": { - "$ref": "#/components/schemas/EngineeringReviewDecisionProjectionResult" + "repository": { + "default": "", + "title": "Repository", + "type": "string" + }, + "runs": { + "items": { + "$ref": "#/components/schemas/EngineeringReviewRunView" + }, + "title": "Runs", + "type": "array" + }, + "state_filter": { + "default": "", + "title": "State Filter", + "type": "string" }, "status": { "const": "ok", @@ -6667,49 +6555,72 @@ }, "required": [ "trace_id", - "result" + "runs" ], - "title": "EngineeringReviewDecisionProjectionResponse", + "title": "EngineeringReviewRunsResponse", "type": "object" }, - "EngineeringReviewDecisionProjectionResult": { + "EnvironmentInventory": { "additionalProperties": false, "properties": { - "app_id": { - "minimum": 1.0, - "title": "App Id", - "type": "integer" + "artifact_identity": { + "anyOf": [ + { + "$ref": "#/components/schemas/ArtifactIdentityReference" + }, + { + "type": "null" + } + ] }, - "check_run_id": { - "minimum": 1.0, - "title": "Check Run Id", - "type": "integer" + "bootstrap": { + "$ref": "#/components/schemas/BootstrapEvidence" }, - "conclusion": { - "const": "neutral", - "default": "neutral", - "title": "Conclusion", + "bootstrap_record_id": { + "default": "", + "title": "Bootstrap Record Id", "type": "string" }, "context": { - "const": "launchplane/engineering-review", - "default": "launchplane/engineering-review", "title": "Context", "type": "string" }, - "external_id": { - "pattern": "^[0-9a-f]{64}$", - "title": "External Id", + "deploy": { + "$ref": "#/components/schemas/DeploymentEvidence-Output" + }, + "deployment_record_id": { + "title": "Deployment Record Id", "type": "string" }, - "head_sha": { - "title": "Head Sha", + "destination_health": { + "$ref": "#/components/schemas/HealthcheckEvidence" + }, + "instance": { + "title": "Instance", "type": "string" }, - "installation_id": { - "minimum": 1.0, - "title": "Installation Id", - "type": "integer" + "post_deploy_update": { + "$ref": "#/components/schemas/PostDeployUpdateEvidence" + }, + "promoted_from_instance": { + "default": "", + "title": "Promoted From Instance", + "type": "string" + }, + "promotion_record_id": { + "default": "", + "title": "Promotion Record Id", + "type": "string" + }, + "runtime_identity": { + "anyOf": [ + { + "$ref": "#/components/schemas/RuntimeIdentity" + }, + { + "type": "null" + } + ] }, "schema_version": { "default": 1, @@ -6717,266 +6628,216 @@ "title": "Schema Version", "type": "integer" }, - "state": { - "enum": [ - "pending", - "success", - "failure", - "error" - ], - "title": "State", + "source_git_ref": { + "title": "Source Git Ref", "type": "string" }, - "status": { - "enum": [ - "projected", - "updated", - "replayed" - ], - "title": "Status", + "updated_at": { + "title": "Updated At", "type": "string" } }, "required": [ - "status", - "state", - "head_sha", - "external_id", - "app_id", - "installation_id", - "check_run_id" + "context", + "instance", + "source_git_ref", + "deploy", + "updated_at", + "deployment_record_id" ], - "title": "EngineeringReviewDecisionProjectionResult", + "title": "EnvironmentInventory", "type": "object" }, - "EngineeringReviewDecisionRecord": { + "EnvironmentInventoryResponse": { "additionalProperties": false, "properties": { - "authoritative": { - "const": false, - "default": false, - "title": "Authoritative", - "type": "boolean" + "record": { + "$ref": "#/components/schemas/EnvironmentInventory" }, - "authority_digest": { - "default": "", - "title": "Authority Digest", + "status": { + "const": "ok", + "default": "ok", + "title": "Status", "type": "string" }, - "authority_id": { - "default": "", - "title": "Authority Id", + "trace_id": { + "title": "Trace Id", + "type": "string" + } + }, + "required": [ + "trace_id", + "record" + ], + "title": "EnvironmentInventoryResponse", + "type": "object" + }, + "EnvironmentRouteBindingReadModel": { + "additionalProperties": false, + "properties": { + "context": { + "title": "Context", "type": "string" }, - "authority_policy_revision": { - "anyOf": [ - { - "minimum": 1.0, - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Authority Policy Revision" + "domains": { + "items": { + "$ref": "#/components/schemas/RouteBindingDomain" + }, + "title": "Domains", + "type": "array" }, - "binding_hash_version": { - "anyOf": [ - { - "const": 2, - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Binding Hash Version" + "ingress": { + "$ref": "#/components/schemas/RouteBindingIngressReadModel" }, - "change_impact_decision_digest": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Change Impact Decision Digest" + "instance": { + "title": "Instance", + "type": "string" }, - "change_impact_policy_digest": { - "default": "", - "title": "Change Impact Policy Digest", + "product": { + "title": "Product", "type": "string" }, - "change_impact_policy_record_id": { - "default": "", - "title": "Change Impact Policy Record Id", + "provider_target": { + "$ref": "#/components/schemas/RouteBindingProviderTargetReadModel" + }, + "record_sha256": { + "title": "Record Sha256", "type": "string" }, - "change_impact_policy_revision": { - "anyOf": [ - { - "minimum": 1.0, - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Change Impact Policy Revision" + "schema_version": { + "title": "Schema Version", + "type": "integer" }, - "change_impact_status": { + "source": { + "$ref": "#/components/schemas/RouteBindingSource" + }, + "status": { "enum": [ - "success", - "unknown", - "stale_head", - "stale_policy" + "active", + "disabled" ], - "title": "Change Impact Status", + "title": "Status", "type": "string" }, - "decision_binding_sha256": { + "tls": { + "$ref": "#/components/schemas/RouteBindingTlsReadModel" + }, + "updated_at": { + "title": "Updated At", + "type": "string" + } + }, + "required": [ + "schema_version", + "product", + "context", + "instance", + "provider_target", + "ingress", + "domains", + "tls", + "status", + "source", + "updated_at", + "record_sha256" + ], + "title": "EnvironmentRouteBindingReadModel", + "type": "object" + }, + "EveryCodeNotificationAttemptRecord": { + "additionalProperties": false, + "properties": { + "action": { "default": "", - "title": "Decision Binding Sha256", + "title": "Action", "type": "string" }, - "decision_id": { - "default": "", - "title": "Decision Id", + "attempt_id": { + "title": "Attempt Id", "type": "string" }, - "enforcement_effect": { - "const": "none", - "default": "none", - "title": "Enforcement Effect", + "attempted_at": { + "title": "Attempted At", "type": "string" }, - "engineering_review_tier": { + "delivery_status": { "enum": [ - "routine", - "sensitive" + "pending", + "delivered", + "failed", + "skipped" ], - "title": "Engineering Review Tier", + "title": "Delivery Status", "type": "string" }, - "evaluated_at": { - "title": "Evaluated At", + "destination_id": { + "title": "Destination Id", "type": "string" }, - "mode": { - "const": "shadow", - "default": "shadow", - "title": "Mode", + "destination_kind": { + "const": "discord", + "title": "Destination Kind", "type": "string" }, - "qualifying_model_families": { - "default": [], - "items": { - "type": "string" - }, - "title": "Qualifying Model Families", - "type": "array" + "error_message": { + "default": "", + "title": "Error Message", + "type": "string" }, - "qualifying_run_ids": { - "default": [], - "items": { - "type": "string" - }, - "title": "Qualifying Run Ids", - "type": "array" + "event": { + "const": "work_request_blocked", + "title": "Event", + "type": "string" }, - "reason_code": { - "title": "Reason Code", + "policy_id": { + "title": "Policy Id", "type": "string" }, - "required_review_count": { - "enum": [ - 1, - 2 - ], - "title": "Required Review Count", - "type": "integer" + "request_id": { + "title": "Request Id", + "type": "string" }, "schema_version": { "default": 1, "minimum": 1.0, "title": "Schema Version", "type": "integer" - }, - "status": { - "enum": [ - "approved", - "changes_requested", - "blocked", - "pending", - "unknown", - "stale" - ], - "title": "Status", - "type": "string" - }, - "target": { - "$ref": "#/components/schemas/ChangeImpactTarget" - }, - "work_request_id": { - "title": "Work Request Id", - "type": "string" - }, - "work_request_lifecycle_id": { - "title": "Work Request Lifecycle Id", - "type": "string" } }, "required": [ - "status", - "reason_code", - "target", - "work_request_id", - "work_request_lifecycle_id", - "change_impact_status", - "engineering_review_tier", - "required_review_count", - "evaluated_at" + "attempt_id", + "request_id", + "event", + "policy_id", + "destination_id", + "destination_kind", + "delivery_status", + "attempted_at" ], - "title": "EngineeringReviewDecisionRecord", + "title": "EveryCodeNotificationAttemptRecord", "type": "object" }, - "EngineeringReviewDecisionRequest": { + "EveryCodeNotificationAttemptRecordsResponse": { "additionalProperties": false, "properties": { - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" + "attempts": { + "items": { + "$ref": "#/components/schemas/EveryCodeNotificationAttemptRecord" + }, + "title": "Attempts", + "type": "array" }, - "target": { - "$ref": "#/components/schemas/ChangeImpactTargetReference" + "destination_kind_filter": { + "title": "Destination Kind Filter", + "type": "string" }, - "work_request_id": { - "maxLength": 128, - "minLength": 1, - "title": "Work Request Id", + "event_filter": { + "title": "Event Filter", "type": "string" - } - }, - "required": [ - "target", - "work_request_id" - ], - "title": "EngineeringReviewDecisionRequest", - "type": "object" - }, - "EngineeringReviewDecisionResponse": { - "additionalProperties": false, - "properties": { - "created": { - "default": false, - "title": "Created", - "type": "boolean" }, - "decision": { - "$ref": "#/components/schemas/EngineeringReviewDecisionRecord" + "request_id": { + "title": "Request Id", + "type": "string" }, "status": { "const": "ok", @@ -6991,216 +6852,261 @@ }, "required": [ "trace_id", - "decision" + "request_id", + "event_filter", + "destination_kind_filter", + "attempts" ], - "title": "EngineeringReviewDecisionResponse", + "title": "EveryCodeNotificationAttemptRecordsResponse", "type": "object" }, - "EngineeringReviewFinding": { + "EveryCodeNotificationDestination": { "additionalProperties": false, "properties": { - "code": { - "maxLength": 100, - "minLength": 1, - "title": "Code", + "destination_id": { + "title": "Destination Id", "type": "string" }, - "line": { - "anyOf": [ - { - "minimum": 1.0, - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Line" - }, - "message": { - "maxLength": 1000, - "minLength": 1, - "title": "Message", + "discord_webhook_secret": { + "default": "", + "title": "Discord Webhook Secret", "type": "string" }, - "path": { - "default": "", - "maxLength": 500, - "title": "Path", + "kind": { + "const": "discord", + "title": "Kind", "type": "string" - } - }, - "required": [ - "code", - "message" - ], - "title": "EngineeringReviewFinding", + }, + "status": { + "default": "enabled", + "enum": [ + "enabled", + "disabled" + ], + "title": "Status", + "type": "string" + } + }, + "required": [ + "destination_id", + "kind" + ], + "title": "EveryCodeNotificationDestination", "type": "object" }, - "EngineeringReviewModelSlot": { + "EveryCodeNotificationPolicyApplyEnvelope": { "additionalProperties": false, "properties": { - "model_family": { - "title": "Model Family", + "mode": { + "default": "dry-run", + "enum": [ + "dry-run", + "apply" + ], + "title": "Mode", "type": "string" }, - "model_id": { - "title": "Model Id", + "policy": { + "$ref": "#/components/schemas/EveryCodeNotificationPolicyRecord" + }, + "reason": { + "default": "", + "title": "Reason", "type": "string" }, - "review_slot": { - "maximum": 4.0, + "schema_version": { + "default": 1, "minimum": 1.0, - "title": "Review Slot", + "title": "Schema Version", "type": "integer" } }, "required": [ - "review_slot", - "model_id", - "model_family" + "policy" ], - "title": "EngineeringReviewModelSlot", + "title": "EveryCodeNotificationPolicyApplyEnvelope", "type": "object" }, - "EngineeringReviewRunAssignment": { + "EveryCodeNotificationPolicyRecord": { "additionalProperties": false, "properties": { - "credential": { - "title": "Credential", + "created_at": { + "title": "Created At", "type": "string" }, - "executable_path": { - "title": "Executable Path", - "type": "string" + "destinations": { + "default": [], + "items": { + "$ref": "#/components/schemas/EveryCodeNotificationDestination" + }, + "title": "Destinations", + "type": "array" }, - "run": { - "$ref": "#/components/schemas/EngineeringReviewRunView" + "policy_id": { + "title": "Policy Id", + "type": "string" }, - "worker_host": { - "title": "Worker Host", + "repository": { + "default": "", + "title": "Repository", "type": "string" - } - }, - "required": [ - "run", - "credential", - "worker_host", - "executable_path" - ], - "title": "EngineeringReviewRunAssignment", - "type": "object" - }, - "EngineeringReviewRunClaimRequest": { - "additionalProperties": false, - "properties": { - "run_id": { - "title": "Run Id", + }, + "schema_version": { + "default": 1, + "minimum": 1.0, + "title": "Schema Version", + "type": "integer" + }, + "source": { + "default": "", + "title": "Source", "type": "string" - } - }, - "required": [ - "run_id" - ], - "title": "EngineeringReviewRunClaimRequest", - "type": "object" - }, - "EngineeringReviewRunClaimResponse": { - "additionalProperties": false, - "properties": { - "assignment": { - "$ref": "#/components/schemas/EngineeringReviewRunAssignment" }, "status": { - "const": "ok", - "default": "ok", + "default": "enabled", + "enum": [ + "enabled", + "disabled" + ], "title": "Status", "type": "string" }, - "trace_id": { - "title": "Trace Id", + "updated_at": { + "title": "Updated At", "type": "string" } }, "required": [ - "trace_id", - "assignment" + "policy_id", + "created_at", + "updated_at" ], - "title": "EngineeringReviewRunClaimResponse", + "title": "EveryCodeNotificationPolicyRecord", "type": "object" }, - "EngineeringReviewRunCreateRequest": { + "EveryCodePrFeedbackRecord": { "additionalProperties": false, "properties": { - "work_request_id": { - "title": "Work Request Id", + "actor": { + "title": "Actor", "type": "string" - } - }, - "required": [ - "work_request_id" - ], - "title": "EngineeringReviewRunCreateRequest", - "type": "object" - }, - "EngineeringReviewRunCreateResponse": { - "additionalProperties": false, - "properties": { - "runs": { - "items": { - "$ref": "#/components/schemas/EngineeringReviewRunView" - }, - "title": "Runs", - "type": "array" }, - "status": { - "const": "ok", - "default": "ok", - "title": "Status", + "author_association": { + "default": "", + "title": "Author Association", "type": "string" }, - "trace_id": { - "title": "Trace Id", + "body": { + "title": "Body", "type": "string" - } - }, - "required": [ - "trace_id", - "runs" - ], - "title": "EngineeringReviewRunCreateResponse", - "type": "object" - }, - "EngineeringReviewRunMaintenanceResponse": { - "additionalProperties": false, - "properties": { - "expired_count": { - "title": "Expired Count", + }, + "feedback_id": { + "title": "Feedback Id", + "type": "string" + }, + "feedback_kind": { + "enum": [ + "issue_comment", + "pull_request_review", + "pull_request_review_comment" + ], + "title": "Feedback Kind", + "type": "string" + }, + "github_delivery_id": { + "title": "Github Delivery Id", + "type": "string" + }, + "github_id": { + "default": "", + "title": "Github Id", + "type": "string" + }, + "github_node_id": { + "default": "", + "title": "Github Node Id", + "type": "string" + }, + "html_url": { + "default": "", + "title": "Html Url", + "type": "string" + }, + "pr_number": { + "minimum": 1.0, + "title": "Pr Number", + "type": "integer" + }, + "pr_url": { + "title": "Pr Url", + "type": "string" + }, + "received_at": { + "title": "Received At", + "type": "string" + }, + "repository": { + "title": "Repository", + "type": "string" + }, + "request_id": { + "title": "Request Id", + "type": "string" + }, + "schema_version": { + "default": 1, + "minimum": 1.0, + "title": "Schema Version", "type": "integer" }, "status": { - "const": "ok", - "default": "ok", + "default": "pending", + "enum": [ + "pending", + "applied", + "ignored" + ], "title": "Status", "type": "string" }, - "trace_id": { - "title": "Trace Id", + "submitted_at": { + "default": "", + "title": "Submitted At", "type": "string" } }, "required": [ - "trace_id", - "expired_count" + "feedback_id", + "request_id", + "repository", + "pr_number", + "pr_url", + "feedback_kind", + "github_delivery_id", + "actor", + "body", + "received_at" ], - "title": "EngineeringReviewRunMaintenanceResponse", + "title": "EveryCodePrFeedbackRecord", "type": "object" }, - "EngineeringReviewRunResponse": { + "EveryCodePrFeedbackRecordsResponse": { "additionalProperties": false, "properties": { - "run": { - "$ref": "#/components/schemas/EngineeringReviewRunView" + "feedback": { + "items": { + "$ref": "#/components/schemas/EveryCodePrFeedbackRecord" + }, + "title": "Feedback", + "type": "array" + }, + "repository": { + "title": "Repository", + "type": "string" + }, + "request_id": { + "title": "Request Id", + "type": "string" }, "status": { "const": "ok", @@ -7208,6 +7114,10 @@ "title": "Status", "type": "string" }, + "status_filter": { + "title": "Status Filter", + "type": "string" + }, "trace_id": { "title": "Trace Id", "type": "string" @@ -7215,150 +7125,80 @@ }, "required": [ "trace_id", - "run" + "request_id", + "repository", + "status_filter", + "feedback" ], - "title": "EngineeringReviewRunResponse", + "title": "EveryCodePrFeedbackRecordsResponse", "type": "object" }, - "EngineeringReviewRunSubmission": { + "EveryCodePreviewGateRecord": { "additionalProperties": false, - "description": "Credential plus bounded reviewer output; no target or identity authority.", "properties": { - "credential": { - "maxLength": 256, - "minLength": 1, - "title": "Credential", - "type": "string" - }, - "decision": { - "enum": [ - "approved", - "changes_requested", - "blocked" - ], - "title": "Decision", - "type": "string" - }, - "findings": { - "default": [], - "items": { - "$ref": "#/components/schemas/EngineeringReviewFinding" - }, - "maxItems": 50, - "title": "Findings", - "type": "array" - }, - "summary": { + "blocked_at": { "default": "", - "maxLength": 4000, - "title": "Summary", - "type": "string" - } - }, - "required": [ - "credential", - "decision" - ], - "title": "EngineeringReviewRunSubmission", - "type": "object" - }, - "EngineeringReviewRunView": { - "additionalProperties": false, - "properties": { - "authoritative": { - "const": false, - "title": "Authoritative", - "type": "boolean" - }, - "authority_digest": { - "title": "Authority Digest", + "title": "Blocked At", "type": "string" }, - "authority_id": { - "title": "Authority Id", + "blocked_reason": { + "default": "", + "title": "Blocked Reason", "type": "string" }, - "binary_sha256": { - "title": "Binary Sha256", + "cancelled_at": { + "default": "", + "title": "Cancelled At", "type": "string" }, - "claim_attempt": { - "title": "Claim Attempt", - "type": "integer" - }, - "claimed_at": { - "title": "Claimed At", + "check_summary": { + "default": "", + "title": "Check Summary", "type": "string" }, "created_at": { "title": "Created At", "type": "string" }, - "decision": { - "anyOf": [ - { - "enum": [ - "approved", - "changes_requested", - "blocked" - ], - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Decision" - }, - "enforcement_effect": { - "const": "none", - "title": "Enforcement Effect", + "gate_id": { + "title": "Gate Id", "type": "string" }, - "error_message": { - "title": "Error Message", + "head_sha": { + "title": "Head Sha", "type": "string" }, - "evidence_digest": { - "title": "Evidence Digest", + "issue_author": { + "default": "", + "title": "Issue Author", "type": "string" }, - "fencing_token": { - "title": "Fencing Token", + "issue_number": { + "minimum": 1.0, + "title": "Issue Number", "type": "integer" }, - "findings": { - "items": { - "$ref": "#/components/schemas/EngineeringReviewFinding" - }, - "title": "Findings", - "type": "array" - }, - "finished_at": { - "title": "Finished At", - "type": "string" - }, - "head_sha": { - "title": "Head Sha", + "issue_url": { + "title": "Issue Url", "type": "string" }, - "lease_expires_at": { - "title": "Lease Expires At", + "labeled_at": { + "default": "", + "title": "Labeled At", "type": "string" }, - "model_family": { - "title": "Model Family", + "last_checked_at": { + "default": "", + "title": "Last Checked At", "type": "string" }, - "model_id": { - "title": "Model Id", + "pending_reason": { + "default": "", + "title": "Pending Reason", "type": "string" }, - "policy_revision": { - "title": "Policy Revision", - "type": "integer" - }, "pr_number": { + "minimum": 1.0, "title": "Pr Number", "type": "integer" }, @@ -7366,180 +7206,73 @@ "title": "Pr Url", "type": "string" }, - "repository": { - "title": "Repository", + "ready_at": { + "default": "", + "title": "Ready At", "type": "string" }, - "review_slot": { - "title": "Review Slot", - "type": "integer" - }, - "rollout_mode": { - "const": "shadow", - "title": "Rollout Mode", + "repository": { + "title": "Repository", "type": "string" }, - "run_id": { - "title": "Run Id", + "request_id": { + "title": "Request Id", "type": "string" }, "schema_version": { + "default": 1, + "minimum": 1.0, "title": "Schema Version", "type": "integer" }, - "started_at": { - "title": "Started At", - "type": "string" - }, - "state": { + "status": { "enum": [ "pending", - "claimed", - "running", - "completed", - "failed", - "expired", + "ready", + "blocked", + "labeled", "cancelled" ], - "title": "State", - "type": "string" - }, - "summary": { - "title": "Summary", - "type": "string" - }, - "tree_sha": { - "title": "Tree Sha", + "title": "Status", "type": "string" }, "updated_at": { "title": "Updated At", "type": "string" - }, - "work_request_id": { - "title": "Work Request Id", - "type": "string" - }, - "work_request_lifecycle_id": { - "title": "Work Request Lifecycle Id", - "type": "string" - }, - "worker_runtime_id": { - "title": "Worker Runtime Id", - "type": "string" } }, "required": [ - "schema_version", - "run_id", - "review_slot", - "state", + "gate_id", + "request_id", "repository", + "issue_number", + "issue_url", "pr_number", "pr_url", "head_sha", - "tree_sha", - "authority_id", - "authority_digest", - "policy_revision", - "work_request_id", - "work_request_lifecycle_id", - "model_id", - "model_family", - "worker_runtime_id", - "binary_sha256", - "rollout_mode", - "authoritative", - "enforcement_effect", + "status", "created_at", - "updated_at", - "claimed_at", - "started_at", - "finished_at", - "lease_expires_at", - "claim_attempt", - "fencing_token", - "decision", - "findings", - "summary", - "evidence_digest", - "error_message" - ], - "title": "EngineeringReviewRunView", - "type": "object" - }, - "EngineeringReviewRunWorkerFailure": { - "additionalProperties": false, - "properties": { - "error_code": { - "maxLength": 100, - "minLength": 1, - "title": "Error Code", - "type": "string" - }, - "fencing_token": { - "minimum": 1.0, - "title": "Fencing Token", - "type": "integer" - }, - "run_id": { - "title": "Run Id", - "type": "string" - }, - "summary": { - "maxLength": 1000, - "minLength": 1, - "title": "Summary", - "type": "string" - } - }, - "required": [ - "run_id", - "fencing_token", - "error_code", - "summary" + "updated_at" ], - "title": "EngineeringReviewRunWorkerFailure", + "title": "EveryCodePreviewGateRecord", "type": "object" }, - "EngineeringReviewRunWorkerUpdate": { + "EveryCodePreviewGateRecordsResponse": { "additionalProperties": false, "properties": { - "fencing_token": { - "minimum": 1.0, - "title": "Fencing Token", - "type": "integer" + "gates": { + "items": { + "$ref": "#/components/schemas/EveryCodePreviewGateRecord" + }, + "title": "Gates", + "type": "array" }, - "run_id": { - "title": "Run Id", - "type": "string" - } - }, - "required": [ - "run_id", - "fencing_token" - ], - "title": "EngineeringReviewRunWorkerUpdate", - "type": "object" - }, - "EngineeringReviewRunsResponse": { - "additionalProperties": false, - "properties": { "repository": { - "default": "", "title": "Repository", "type": "string" }, - "runs": { - "items": { - "$ref": "#/components/schemas/EngineeringReviewRunView" - }, - "title": "Runs", - "type": "array" - }, - "state_filter": { - "default": "", - "title": "State Filter", + "request_id": { + "title": "Request Id", "type": "string" }, "status": { @@ -7548,6 +7281,10 @@ "title": "Status", "type": "string" }, + "status_filter": { + "title": "Status Filter", + "type": "string" + }, "trace_id": { "title": "Trace Id", "type": "string" @@ -7555,111 +7292,91 @@ }, "required": [ "trace_id", - "runs" + "request_id", + "repository", + "status_filter", + "gates" ], - "title": "EngineeringReviewRunsResponse", + "title": "EveryCodePreviewGateRecordsResponse", "type": "object" }, - "EnvironmentInventory": { + "EveryCodeSummaryReadModel": { "additionalProperties": false, "properties": { - "artifact_identity": { + "generated_at": { + "title": "Generated At", + "type": "string" + }, + "issue_number": { "anyOf": [ { - "$ref": "#/components/schemas/ArtifactIdentityReference" + "minimum": 1.0, + "type": "integer" }, { "type": "null" } - ] - }, - "bootstrap": { - "$ref": "#/components/schemas/BootstrapEvidence" - }, - "bootstrap_record_id": { - "default": "", - "title": "Bootstrap Record Id", - "type": "string" - }, - "context": { - "title": "Context", - "type": "string" - }, - "deploy": { - "$ref": "#/components/schemas/DeploymentEvidence-Output" - }, - "deployment_record_id": { - "title": "Deployment Record Id", - "type": "string" - }, - "destination_health": { - "$ref": "#/components/schemas/HealthcheckEvidence" - }, - "instance": { - "title": "Instance", - "type": "string" - }, - "post_deploy_update": { - "$ref": "#/components/schemas/PostDeployUpdateEvidence" - }, - "promoted_from_instance": { - "default": "", - "title": "Promoted From Instance", - "type": "string" + ], + "title": "Issue Number" }, - "promotion_record_id": { + "repository": { "default": "", - "title": "Promotion Record Id", + "title": "Repository", "type": "string" }, - "runtime_identity": { - "anyOf": [ - { - "$ref": "#/components/schemas/RuntimeIdentity" - }, - { - "type": "null" - } - ] - }, "schema_version": { "default": 1, "minimum": 1.0, "title": "Schema Version", "type": "integer" }, - "source_git_ref": { - "title": "Source Git Ref", - "type": "string" + "state_filter": { + "anyOf": [ + { + "enum": [ + "queued", + "claimed", + "running", + "done", + "blocked" + ], + "type": "string" + }, + { + "const": "", + "type": "string" + } + ], + "default": "", + "title": "State Filter" }, - "updated_at": { - "title": "Updated At", - "type": "string" + "summaries": { + "items": { + "$ref": "#/components/schemas/EveryCodeWorkRequestSummary" + }, + "title": "Summaries", + "type": "array" } }, "required": [ - "context", - "instance", - "source_git_ref", - "deploy", - "updated_at", - "deployment_record_id" + "generated_at", + "summaries" ], - "title": "EnvironmentInventory", + "title": "EveryCodeSummaryReadModel", "type": "object" }, - "EnvironmentInventoryResponse": { + "EveryCodeSummaryResponse": { "additionalProperties": false, "properties": { - "record": { - "$ref": "#/components/schemas/EnvironmentInventory" - }, "status": { "const": "ok", "default": "ok", "title": "Status", "type": "string" }, + "summary": { + "$ref": "#/components/schemas/EveryCodeSummaryReadModel" + }, "trace_id": { "title": "Trace Id", "type": "string" @@ -7667,177 +7384,221 @@ }, "required": [ "trace_id", - "record" + "summary" ], - "title": "EnvironmentInventoryResponse", + "title": "EveryCodeSummaryResponse", "type": "object" }, - "EnvironmentRouteBindingReadModel": { + "EveryCodeWorkRequestCreateEnvelope": { "additionalProperties": false, "properties": { - "context": { - "title": "Context", + "github_delivery_id": { + "default": "", + "title": "Github Delivery Id", "type": "string" }, - "domains": { - "items": { - "$ref": "#/components/schemas/RouteBindingDomain" - }, - "title": "Domains", - "type": "array" - }, - "ingress": { - "$ref": "#/components/schemas/RouteBindingIngressReadModel" + "issue_number": { + "minimum": 1.0, + "title": "Issue Number", + "type": "integer" }, - "instance": { - "title": "Instance", + "issue_title": { + "default": "", + "title": "Issue Title", "type": "string" }, - "product": { - "title": "Product", + "issue_url": { + "title": "Issue Url", "type": "string" }, - "provider_target": { - "$ref": "#/components/schemas/RouteBindingProviderTargetReadModel" - }, - "record_sha256": { - "title": "Record Sha256", + "queued_at": { + "default": "", + "title": "Queued At", "type": "string" }, - "schema_version": { - "title": "Schema Version", - "type": "integer" + "repository": { + "title": "Repository", + "type": "string" }, "source": { - "$ref": "#/components/schemas/RouteBindingSource" - }, - "status": { + "default": "manual", "enum": [ - "active", - "disabled" + "github_issue_label", + "manual", + "reconciliation" ], - "title": "Status", + "title": "Source", "type": "string" }, - "tls": { - "$ref": "#/components/schemas/RouteBindingTlsReadModel" + "trigger_actor": { + "default": "", + "title": "Trigger Actor", + "type": "string" }, - "updated_at": { - "title": "Updated At", + "trigger_label": { + "default": "every-code", + "title": "Trigger Label", "type": "string" } }, "required": [ - "schema_version", - "product", - "context", - "instance", - "provider_target", - "ingress", - "domains", - "tls", - "status", - "source", - "updated_at", - "record_sha256" + "repository", + "issue_number", + "issue_url" ], - "title": "EnvironmentRouteBindingReadModel", + "title": "EveryCodeWorkRequestCreateEnvelope", "type": "object" }, - "EveryCodeNotificationAttemptRecord": { + "EveryCodeWorkRequestRecord": { "additionalProperties": false, "properties": { - "action": { + "attempt": { + "default": 0, + "minimum": 0.0, + "title": "Attempt", + "type": "integer" + }, + "claimed_at": { "default": "", - "title": "Action", + "title": "Claimed At", "type": "string" }, - "attempt_id": { - "title": "Attempt Id", + "claimed_by_host": { + "default": "", + "title": "Claimed By Host", "type": "string" }, - "attempted_at": { - "title": "Attempted At", + "error_message": { + "default": "", + "title": "Error Message", "type": "string" }, - "delivery_status": { - "enum": [ - "pending", - "delivered", - "failed", - "skipped" - ], - "title": "Delivery Status", + "fencing_token": { + "default": 0, + "minimum": 0.0, + "title": "Fencing Token", + "type": "integer" + }, + "finished_at": { + "default": "", + "title": "Finished At", "type": "string" }, - "destination_id": { - "title": "Destination Id", + "github_delivery_id": { + "default": "", + "title": "Github Delivery Id", "type": "string" }, - "destination_kind": { - "const": "discord", - "title": "Destination Kind", + "issue_number": { + "minimum": 1.0, + "title": "Issue Number", + "type": "integer" + }, + "issue_title": { + "default": "", + "title": "Issue Title", "type": "string" }, - "error_message": { + "issue_url": { + "title": "Issue Url", + "type": "string" + }, + "lease_expires_at": { "default": "", - "title": "Error Message", + "title": "Lease Expires At", "type": "string" }, - "event": { - "const": "work_request_blocked", - "title": "Event", + "lifecycle_id": { + "default": "", + "title": "Lifecycle Id", "type": "string" }, - "policy_id": { - "title": "Policy Id", + "queued_at": { + "title": "Queued At", + "type": "string" + }, + "repository": { + "title": "Repository", "type": "string" }, "request_id": { "title": "Request Id", "type": "string" }, + "result_pr_url": { + "default": "", + "title": "Result Pr Url", + "type": "string" + }, + "result_summary": { + "default": "", + "title": "Result Summary", + "type": "string" + }, "schema_version": { "default": 1, "minimum": 1.0, "title": "Schema Version", "type": "integer" + }, + "source": { + "enum": [ + "github_issue_label", + "manual", + "reconciliation" + ], + "title": "Source", + "type": "string" + }, + "started_at": { + "default": "", + "title": "Started At", + "type": "string" + }, + "state": { + "enum": [ + "queued", + "claimed", + "running", + "done", + "blocked" + ], + "title": "State", + "type": "string" + }, + "trigger_actor": { + "default": "", + "title": "Trigger Actor", + "type": "string" + }, + "trigger_label": { + "title": "Trigger Label", + "type": "string" + }, + "updated_at": { + "title": "Updated At", + "type": "string" } }, "required": [ - "attempt_id", "request_id", - "event", - "policy_id", - "destination_id", - "destination_kind", - "delivery_status", - "attempted_at" + "source", + "state", + "repository", + "issue_number", + "issue_url", + "trigger_label", + "queued_at", + "updated_at" ], - "title": "EveryCodeNotificationAttemptRecord", + "title": "EveryCodeWorkRequestRecord", "type": "object" }, - "EveryCodeNotificationAttemptRecordsResponse": { + "EveryCodeWorkRequestRecordResponse": { "additionalProperties": false, "properties": { - "attempts": { - "items": { - "$ref": "#/components/schemas/EveryCodeNotificationAttemptRecord" - }, - "title": "Attempts", - "type": "array" - }, - "destination_kind_filter": { - "title": "Destination Kind Filter", - "type": "string" - }, - "event_filter": { - "title": "Event Filter", - "type": "string" - }, - "request_id": { - "title": "Request Id", - "type": "string" + "request": { + "$ref": "#/components/schemas/EveryCodeWorkRequestRecord" }, "status": { "const": "ok", @@ -7852,123 +7613,159 @@ }, "required": [ "trace_id", - "request_id", - "event_filter", - "destination_kind_filter", - "attempts" + "request" ], - "title": "EveryCodeNotificationAttemptRecordsResponse", + "title": "EveryCodeWorkRequestRecordResponse", "type": "object" }, - "EveryCodeNotificationDestination": { + "EveryCodeWorkRequestRecordsResponse": { "additionalProperties": false, "properties": { - "destination_id": { - "title": "Destination Id", + "repository": { + "title": "Repository", "type": "string" }, - "discord_webhook_secret": { - "default": "", - "title": "Discord Webhook Secret", - "type": "string" + "requests": { + "items": { + "$ref": "#/components/schemas/EveryCodeWorkRequestRecord" + }, + "title": "Requests", + "type": "array" }, - "kind": { - "const": "discord", - "title": "Kind", + "state": { + "title": "State", "type": "string" }, "status": { - "default": "enabled", - "enum": [ - "enabled", - "disabled" - ], + "const": "ok", + "default": "ok", "title": "Status", "type": "string" + }, + "trace_id": { + "title": "Trace Id", + "type": "string" } }, "required": [ - "destination_id", - "kind" + "trace_id", + "state", + "repository", + "requests" ], - "title": "EveryCodeNotificationDestination", + "title": "EveryCodeWorkRequestRecordsResponse", "type": "object" }, - "EveryCodeNotificationPolicyApplyEnvelope": { + "EveryCodeWorkRequestSummary": { "additionalProperties": false, "properties": { - "mode": { - "default": "dry-run", - "enum": [ - "dry-run", - "apply" - ], - "title": "Mode", + "claimed_at": { + "default": "", + "title": "Claimed At", "type": "string" }, - "policy": { - "$ref": "#/components/schemas/EveryCodeNotificationPolicyRecord" + "claimed_by_host": { + "default": "", + "title": "Claimed By Host", + "type": "string" }, - "reason": { + "evidence": { + "default": [], + "items": { + "$ref": "#/components/schemas/AgentContextEvidence" + }, + "title": "Evidence", + "type": "array" + }, + "finished_at": { "default": "", - "title": "Reason", + "title": "Finished At", "type": "string" }, - "schema_version": { - "default": 1, + "issue_number": { "minimum": 1.0, - "title": "Schema Version", + "title": "Issue Number", "type": "integer" - } - }, - "required": [ - "policy" - ], - "title": "EveryCodeNotificationPolicyApplyEnvelope", - "type": "object" - }, - "EveryCodeNotificationPolicyRecord": { - "additionalProperties": false, - "properties": { - "created_at": { - "title": "Created At", + }, + "issue_title": { + "default": "", + "title": "Issue Title", "type": "string" }, - "destinations": { - "default": [], - "items": { - "$ref": "#/components/schemas/EveryCodeNotificationDestination" - }, - "title": "Destinations", - "type": "array" + "issue_url": { + "title": "Issue Url", + "type": "string" }, - "policy_id": { - "title": "Policy Id", + "next_action": { + "title": "Next Action", + "type": "string" + }, + "provenance": { + "$ref": "#/components/schemas/AgentContextProvenance" + }, + "queued_at": { + "title": "Queued At", "type": "string" }, "repository": { - "default": "", "title": "Repository", "type": "string" }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" + "request_id": { + "title": "Request Id", + "type": "string" }, - "source": { + "result_pr_url": { "default": "", + "title": "Result Pr Url", + "type": "string" + }, + "result_summary": { + "default": "", + "title": "Result Summary", + "type": "string" + }, + "safe_to_rerun": { + "title": "Safe To Rerun", + "type": "boolean" + }, + "source": { "title": "Source", "type": "string" }, - "status": { - "default": "enabled", + "started_at": { + "default": "", + "title": "Started At", + "type": "string" + }, + "state": { "enum": [ - "enabled", - "disabled" + "queued", + "claimed", + "running", + "done", + "blocked" ], - "title": "Status", + "title": "State", + "type": "string" + }, + "summary_status": { + "enum": [ + "active", + "stuck", + "complete", + "rerunnable" + ], + "title": "Summary Status", + "type": "string" + }, + "trigger_actor": { + "default": "", + "title": "Trigger Actor", + "type": "string" + }, + "trigger_label": { + "title": "Trigger Label", "type": "string" }, "updated_at": { @@ -7977,80 +7774,98 @@ } }, "required": [ - "policy_id", - "created_at", - "updated_at" + "request_id", + "repository", + "issue_number", + "issue_url", + "state", + "summary_status", + "source", + "trigger_label", + "queued_at", + "updated_at", + "safe_to_rerun", + "next_action", + "provenance" ], - "title": "EveryCodeNotificationPolicyRecord", + "title": "EveryCodeWorkRequestSummary", "type": "object" }, - "EveryCodePrFeedbackRecord": { + "ExternalRouteBindingReconcileEnvelope": { "additionalProperties": false, "properties": { - "actor": { - "title": "Actor", - "type": "string" - }, - "author_association": { + "confirmation": { "default": "", - "title": "Author Association", - "type": "string" - }, - "body": { - "title": "Body", + "title": "Confirmation", "type": "string" }, - "feedback_id": { - "title": "Feedback Id", + "context": { + "title": "Context", "type": "string" }, - "feedback_kind": { + "desired_status": { + "default": "active", "enum": [ - "issue_comment", - "pull_request_review", - "pull_request_review_comment" + "active", + "disabled" ], - "title": "Feedback Kind", + "title": "Desired Status", "type": "string" }, - "github_delivery_id": { - "title": "Github Delivery Id", + "expected_current": { + "$ref": "#/components/schemas/RouteBindingExpectedCurrent" + }, + "instance": { + "title": "Instance", "type": "string" }, - "github_id": { - "default": "", - "title": "Github Id", + "mode": { + "default": "dry-run", + "enum": [ + "dry-run", + "apply" + ], + "title": "Mode", "type": "string" }, - "github_node_id": { - "default": "", - "title": "Github Node Id", + "product": { + "title": "Product", "type": "string" }, - "html_url": { + "reason": { "default": "", - "title": "Html Url", + "title": "Reason", "type": "string" }, - "pr_number": { + "schema_version": { + "default": 1, "minimum": 1.0, - "title": "Pr Number", + "title": "Schema Version", "type": "integer" }, - "pr_url": { - "title": "Pr Url", - "type": "string" - }, - "received_at": { - "title": "Received At", - "type": "string" - }, - "repository": { - "title": "Repository", + "source_label": { + "default": "operator-external-reconcile", + "title": "Source Label", "type": "string" + } + }, + "required": [ + "product", + "context", + "instance", + "expected_current" + ], + "title": "ExternalRouteBindingReconcileEnvelope", + "type": "object" + }, + "GenericWebDeployEnvelope": { + "additionalProperties": false, + "properties": { + "deploy": { + "$ref": "#/components/schemas/GenericWebDeployRequest" }, - "request_id": { - "title": "Request Id", + "product": { + "title": "Product", "type": "string" }, "schema_version": { @@ -8058,811 +7873,702 @@ "minimum": 1.0, "title": "Schema Version", "type": "integer" - }, - "status": { - "default": "pending", - "enum": [ - "pending", - "applied", - "ignored" - ], - "title": "Status", - "type": "string" - }, - "submitted_at": { - "default": "", - "title": "Submitted At", - "type": "string" } }, "required": [ - "feedback_id", - "request_id", - "repository", - "pr_number", - "pr_url", - "feedback_kind", - "github_delivery_id", - "actor", - "body", - "received_at" + "product", + "deploy" ], - "title": "EveryCodePrFeedbackRecord", + "title": "GenericWebDeployEnvelope", "type": "object" }, - "EveryCodePrFeedbackRecordsResponse": { + "GenericWebDeployRecoveryApplyRequest": { "additionalProperties": false, "properties": { - "feedback": { - "items": { - "$ref": "#/components/schemas/EveryCodePrFeedbackRecord" - }, - "title": "Feedback", - "type": "array" - }, - "repository": { - "title": "Repository", + "expected_recovery_digest": { + "pattern": "^[0-9a-f]{64}$", + "title": "Expected Recovery Digest", "type": "string" }, - "request_id": { - "title": "Request Id", + "instance": { + "title": "Instance", "type": "string" }, - "status": { - "const": "ok", - "default": "ok", - "title": "Status", - "type": "string" + "original_deploy": { + "$ref": "#/components/schemas/GenericWebDeployEnvelope" }, - "status_filter": { - "title": "Status Filter", + "product": { + "title": "Product", "type": "string" }, - "trace_id": { - "title": "Trace Id", + "reason": { + "maxLength": 1000, + "title": "Reason", "type": "string" + }, + "schema_version": { + "const": 1, + "default": 1, + "title": "Schema Version", + "type": "integer" } }, "required": [ - "trace_id", - "request_id", - "repository", - "status_filter", - "feedback" + "product", + "instance", + "original_deploy", + "reason", + "expected_recovery_digest" ], - "title": "EveryCodePrFeedbackRecordsResponse", + "title": "GenericWebDeployRecoveryApplyRequest", "type": "object" }, - "EveryCodePreviewGateRecord": { + "GenericWebDeployRecoveryApplyResponse": { "additionalProperties": false, "properties": { - "blocked_at": { - "default": "", - "title": "Blocked At", - "type": "string" - }, - "blocked_reason": { - "default": "", - "title": "Blocked Reason", - "type": "string" - }, - "cancelled_at": { - "default": "", - "title": "Cancelled At", - "type": "string" - }, - "check_summary": { - "default": "", - "title": "Check Summary", - "type": "string" - }, - "created_at": { - "title": "Created At", + "context": { + "title": "Context", "type": "string" }, - "gate_id": { - "title": "Gate Id", + "instance": { + "title": "Instance", "type": "string" }, - "head_sha": { - "title": "Head Sha", + "mode": { + "const": "apply", + "default": "apply", + "title": "Mode", "type": "string" }, - "issue_author": { - "default": "", - "title": "Issue Author", + "product": { + "title": "Product", "type": "string" }, - "issue_number": { - "minimum": 1.0, - "title": "Issue Number", - "type": "integer" - }, - "issue_url": { - "title": "Issue Url", + "provider_outcome": { + "enum": [ + "present", + "absent", + "unknown", + "not_inspected" + ], + "title": "Provider Outcome", "type": "string" }, - "labeled_at": { + "provider_status": { "default": "", - "title": "Labeled At", + "maxLength": 128, + "title": "Provider Status", "type": "string" }, - "last_checked_at": { - "default": "", - "title": "Last Checked At", + "recovery_action": { + "enum": [ + "replay_completed", + "wait_for_active_lease", + "adopt_observed", + "retry_original_operation", + "hold_unknown" + ], + "title": "Recovery Action", "type": "string" }, - "pending_reason": { - "default": "", - "title": "Pending Reason", + "recovery_digest": { + "pattern": "^[0-9a-f]{64}$", + "title": "Recovery Digest", "type": "string" }, - "pr_number": { + "reservation_attempt": { "minimum": 1.0, - "title": "Pr Number", + "title": "Reservation Attempt", "type": "integer" }, - "pr_url": { - "title": "Pr Url", - "type": "string" - }, - "ready_at": { - "default": "", - "title": "Ready At", - "type": "string" - }, - "repository": { - "title": "Repository", + "reservation_state": { + "enum": [ + "completed", + "reconcile_required" + ], + "title": "Reservation State", "type": "string" }, - "request_id": { - "title": "Request Id", - "type": "string" + "retry_safe": { + "title": "Retry Safe", + "type": "boolean" }, "schema_version": { + "const": 1, "default": 1, - "minimum": 1.0, "title": "Schema Version", "type": "integer" }, "status": { - "enum": [ - "pending", - "ready", - "blocked", - "labeled", - "cancelled" - ], + "const": "accepted", + "default": "accepted", "title": "Status", "type": "string" }, - "updated_at": { - "title": "Updated At", + "trace_id": { + "title": "Trace Id", "type": "string" } }, "required": [ - "gate_id", - "request_id", - "repository", - "issue_number", - "issue_url", - "pr_number", - "pr_url", - "head_sha", - "status", - "created_at", - "updated_at" + "trace_id", + "product", + "context", + "instance", + "reservation_state", + "reservation_attempt", + "recovery_action", + "recovery_digest", + "provider_outcome", + "retry_safe" ], - "title": "EveryCodePreviewGateRecord", + "title": "GenericWebDeployRecoveryApplyResponse", "type": "object" }, - "EveryCodePreviewGateRecordsResponse": { + "GenericWebDeployRecoveryDryRunRequest": { "additionalProperties": false, "properties": { - "gates": { - "items": { - "$ref": "#/components/schemas/EveryCodePreviewGateRecord" - }, - "title": "Gates", - "type": "array" - }, - "repository": { - "title": "Repository", + "instance": { + "title": "Instance", "type": "string" }, - "request_id": { - "title": "Request Id", - "type": "string" + "original_deploy": { + "$ref": "#/components/schemas/GenericWebDeployEnvelope" }, - "status": { - "const": "ok", - "default": "ok", - "title": "Status", + "product": { + "title": "Product", "type": "string" }, - "status_filter": { - "title": "Status Filter", + "reason": { + "maxLength": 1000, + "title": "Reason", "type": "string" }, - "trace_id": { - "title": "Trace Id", - "type": "string" + "schema_version": { + "const": 1, + "default": 1, + "title": "Schema Version", + "type": "integer" } }, "required": [ - "trace_id", - "request_id", - "repository", - "status_filter", - "gates" + "product", + "instance", + "original_deploy", + "reason" ], - "title": "EveryCodePreviewGateRecordsResponse", + "title": "GenericWebDeployRecoveryDryRunRequest", "type": "object" }, - "EveryCodeSummaryReadModel": { + "GenericWebDeployRecoveryDryRunResponse": { "additionalProperties": false, "properties": { - "generated_at": { - "title": "Generated At", + "context": { + "title": "Context", "type": "string" }, - "issue_number": { - "anyOf": [ - { - "minimum": 1.0, - "type": "integer" - }, - { - "type": "null" - } + "instance": { + "title": "Instance", + "type": "string" + }, + "mode": { + "const": "dry-run", + "default": "dry-run", + "title": "Mode", + "type": "string" + }, + "observed_at": { + "title": "Observed At", + "type": "string" + }, + "product": { + "title": "Product", + "type": "string" + }, + "proposed_action": { + "enum": [ + "replay_completed", + "wait_for_active_lease", + "adopt_observed", + "retry_original_operation", + "hold_unknown" ], - "title": "Issue Number" + "title": "Proposed Action", + "type": "string" }, - "repository": { + "provider_effect_phase": { + "maxLength": 128, + "title": "Provider Effect Phase", + "type": "string" + }, + "provider_outcome": { + "enum": [ + "present", + "absent", + "unknown", + "not_inspected" + ], + "title": "Provider Outcome", + "type": "string" + }, + "provider_status": { "default": "", - "title": "Repository", + "maxLength": 128, + "title": "Provider Status", "type": "string" }, - "schema_version": { - "default": 1, + "provider_target_key_sha256": { + "pattern": "^[0-9a-f]{64}$", + "title": "Provider Target Key Sha256", + "type": "string" + }, + "reconciliation_key_sha256": { + "pattern": "^[0-9a-f]{64}$", + "title": "Reconciliation Key Sha256", + "type": "string" + }, + "recovery_digest": { + "pattern": "^[0-9a-f]{64}$", + "title": "Recovery Digest", + "type": "string" + }, + "reservation_attempt": { "minimum": 1.0, - "title": "Schema Version", + "title": "Reservation Attempt", "type": "integer" }, - "state_filter": { - "anyOf": [ - { - "enum": [ - "queued", - "claimed", - "running", - "done", - "blocked" - ], - "type": "string" - }, - { - "const": "", - "type": "string" - } - ], + "reservation_created_at": { + "title": "Reservation Created At", + "type": "string" + }, + "reservation_lease_expires_at": { "default": "", - "title": "State Filter" + "title": "Reservation Lease Expires At", + "type": "string" + }, + "reservation_state": { + "enum": [ + "running", + "completed", + "reconcile_required" + ], + "title": "Reservation State", + "type": "string" + }, + "reservation_updated_at": { + "title": "Reservation Updated At", + "type": "string" + }, + "retry_safe": { + "title": "Retry Safe", + "type": "boolean" + }, + "schema_version": { + "const": 1, + "default": 1, + "title": "Schema Version", + "type": "integer" }, - "summaries": { - "items": { - "$ref": "#/components/schemas/EveryCodeWorkRequestSummary" - }, - "title": "Summaries", - "type": "array" - } - }, - "required": [ - "generated_at", - "summaries" - ], - "title": "EveryCodeSummaryReadModel", - "type": "object" - }, - "EveryCodeSummaryResponse": { - "additionalProperties": false, - "properties": { "status": { "const": "ok", "default": "ok", "title": "Status", "type": "string" - }, - "summary": { - "$ref": "#/components/schemas/EveryCodeSummaryReadModel" - }, - "trace_id": { - "title": "Trace Id", - "type": "string" } }, "required": [ - "trace_id", - "summary" + "product", + "context", + "instance", + "reservation_state", + "reservation_attempt", + "reservation_created_at", + "reservation_updated_at", + "observed_at", + "reconciliation_key_sha256", + "provider_target_key_sha256", + "provider_effect_phase", + "provider_outcome", + "retry_safe", + "proposed_action", + "recovery_digest" ], - "title": "EveryCodeSummaryResponse", + "title": "GenericWebDeployRecoveryDryRunResponse", "type": "object" }, - "EveryCodeWorkRequestCreateEnvelope": { + "GenericWebDeployRecoveryProviderEvidenceResponse": { "additionalProperties": false, "properties": { - "github_delivery_id": { - "default": "", - "title": "Github Delivery Id", + "mode": { + "const": "provider-evidence", + "default": "provider-evidence", + "title": "Mode", "type": "string" }, - "issue_number": { - "minimum": 1.0, - "title": "Issue Number", - "type": "integer" + "observed_at": { + "title": "Observed At", + "type": "string" }, - "issue_title": { - "default": "", - "title": "Issue Title", + "provider_effect_phase": { + "maxLength": 128, + "title": "Provider Effect Phase", "type": "string" }, - "issue_url": { - "title": "Issue Url", + "provider_evidence": { + "enum": [ + "deployment_present", + "deployment_correlated_legacy", + "deployment_absent_before_effect", + "deployment_absent_after_effect", + "provider_status_unknown", + "provider_read_failed", + "not_inspected" + ], + "title": "Provider Evidence", "type": "string" }, - "queued_at": { + "provider_read_error_class": { "default": "", - "title": "Queued At", + "enum": [ + "", + "target_resolution_failed", + "provider_request_failed" + ], + "title": "Provider Read Error Class", "type": "string" }, - "repository": { - "title": "Repository", + "provider_status": { + "default": "", + "maxLength": 128, + "title": "Provider Status", "type": "string" }, - "source": { - "default": "manual", + "provider_target_key_sha256": { + "pattern": "^[0-9a-f]{64}$", + "title": "Provider Target Key Sha256", + "type": "string" + }, + "reconciliation_key_sha256": { + "pattern": "^[0-9a-f]{64}$", + "title": "Reconciliation Key Sha256", + "type": "string" + }, + "reservation_attempt": { + "minimum": 1.0, + "title": "Reservation Attempt", + "type": "integer" + }, + "reservation_state": { "enum": [ - "github_issue_label", - "manual", - "reconciliation" + "running", + "completed", + "reconcile_required" ], - "title": "Source", + "title": "Reservation State", "type": "string" }, - "trigger_actor": { - "default": "", - "title": "Trigger Actor", - "type": "string" + "schema_version": { + "const": 1, + "default": 1, + "title": "Schema Version", + "type": "integer" }, - "trigger_label": { - "default": "every-code", - "title": "Trigger Label", + "status": { + "const": "ok", + "default": "ok", + "title": "Status", "type": "string" } }, "required": [ - "repository", - "issue_number", - "issue_url" + "reservation_state", + "reservation_attempt", + "observed_at", + "reconciliation_key_sha256", + "provider_target_key_sha256", + "provider_effect_phase", + "provider_evidence" ], - "title": "EveryCodeWorkRequestCreateEnvelope", + "title": "GenericWebDeployRecoveryProviderEvidenceResponse", "type": "object" }, - "EveryCodeWorkRequestRecord": { + "GenericWebDeployRequest": { "additionalProperties": false, "properties": { - "attempt": { - "default": 0, - "minimum": 0.0, - "title": "Attempt", - "type": "integer" + "artifact_id": { + "title": "Artifact Id", + "type": "string" }, - "claimed_at": { + "deploy_reference": { "default": "", - "title": "Claimed At", + "title": "Deploy Reference", "type": "string" }, - "claimed_by_host": { - "default": "", - "title": "Claimed By Host", + "instance": { + "title": "Instance", "type": "string" }, - "error_message": { - "default": "", - "title": "Error Message", + "no_cache": { + "default": false, + "title": "No Cache", + "type": "boolean" + }, + "product": { + "title": "Product", "type": "string" }, - "fencing_token": { - "default": 0, - "minimum": 0.0, - "title": "Fencing Token", + "schema_version": { + "default": 1, + "minimum": 1.0, + "title": "Schema Version", "type": "integer" }, - "finished_at": { - "default": "", - "title": "Finished At", + "source_git_ref": { + "title": "Source Git Ref", "type": "string" }, - "github_delivery_id": { - "default": "", - "title": "Github Delivery Id", + "timeout_seconds": { + "anyOf": [ + { + "minimum": 1.0, + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Timeout Seconds" + } + }, + "required": [ + "product", + "instance", + "artifact_id", + "source_git_ref" + ], + "title": "GenericWebDeployRequest", + "type": "object" + }, + "GenericWebPreviewAuthzPlanRequest": { + "additionalProperties": false, + "properties": { + "default_branch": { + "default": "main", + "title": "Default Branch", "type": "string" }, - "issue_number": { - "minimum": 1.0, - "title": "Issue Number", - "type": "integer" + "include_ingress_operator": { + "default": false, + "title": "Include Ingress Operator", + "type": "boolean" }, - "issue_title": { - "default": "", - "title": "Issue Title", + "launchplane_sha": { + "title": "Launchplane Sha", "type": "string" }, - "issue_url": { - "title": "Issue Url", + "operation": { + "default": "onboard", + "enum": [ + "onboard", + "expand", + "contract", + "retire" + ], + "title": "Operation", "type": "string" }, - "lease_expires_at": { + "preview_context": { "default": "", - "title": "Lease Expires At", + "title": "Preview Context", "type": "string" }, - "lifecycle_id": { - "default": "", - "title": "Lifecycle Id", + "product": { + "const": "launchplane", + "default": "launchplane", + "title": "Product", "type": "string" }, - "queued_at": { - "title": "Queued At", + "reason": { + "title": "Reason", "type": "string" }, - "repository": { - "title": "Repository", + "related_issue": { + "title": "Related Issue", "type": "string" }, - "request_id": { - "title": "Request Id", + "repository": { + "default": "", + "title": "Repository", "type": "string" }, - "result_pr_url": { + "repository_id": { "default": "", - "title": "Result Pr Url", + "title": "Repository Id", "type": "string" }, - "result_summary": { + "repository_owner_id": { "default": "", - "title": "Result Summary", + "title": "Repository Owner Id", "type": "string" }, "schema_version": { + "const": 1, "default": 1, - "minimum": 1.0, "title": "Schema Version", "type": "integer" }, - "source": { - "enum": [ - "github_issue_label", - "manual", - "reconciliation" - ], - "title": "Source", - "type": "string" - }, - "started_at": { - "default": "", - "title": "Started At", - "type": "string" - }, - "state": { - "enum": [ - "queued", - "claimed", - "running", - "done", - "blocked" - ], - "title": "State", - "type": "string" - }, - "trigger_actor": { - "default": "", - "title": "Trigger Actor", - "type": "string" - }, - "trigger_label": { - "title": "Trigger Label", - "type": "string" - }, - "updated_at": { - "title": "Updated At", + "target_product": { + "title": "Target Product", "type": "string" } }, "required": [ - "request_id", - "source", - "state", - "repository", - "issue_number", - "issue_url", - "trigger_label", - "queued_at", - "updated_at" + "target_product", + "launchplane_sha", + "reason", + "related_issue" ], - "title": "EveryCodeWorkRequestRecord", + "title": "GenericWebPreviewAuthzPlanRequest", "type": "object" }, - "EveryCodeWorkRequestRecordResponse": { + "GenericWebPreviewDesiredStateEnvelope": { "additionalProperties": false, "properties": { - "request": { - "$ref": "#/components/schemas/EveryCodeWorkRequestRecord" + "desired_state": { + "$ref": "#/components/schemas/GenericWebPreviewDesiredStateRequest" }, - "status": { - "const": "ok", - "default": "ok", - "title": "Status", + "product": { + "title": "Product", "type": "string" }, - "trace_id": { - "title": "Trace Id", - "type": "string" + "schema_version": { + "default": 1, + "minimum": 1.0, + "title": "Schema Version", + "type": "integer" } }, "required": [ - "trace_id", - "request" + "product", + "desired_state" ], - "title": "EveryCodeWorkRequestRecordResponse", + "title": "GenericWebPreviewDesiredStateEnvelope", "type": "object" }, - "EveryCodeWorkRequestRecordsResponse": { + "GenericWebPreviewDesiredStateRequest": { "additionalProperties": false, "properties": { - "repository": { - "title": "Repository", + "label": { + "default": "preview", + "title": "Label", "type": "string" }, - "requests": { - "items": { - "$ref": "#/components/schemas/EveryCodeWorkRequestRecord" - }, - "title": "Requests", - "type": "array" + "max_pages": { + "default": 10, + "maximum": 20.0, + "minimum": 1.0, + "title": "Max Pages", + "type": "integer" }, - "state": { - "title": "State", + "product": { + "title": "Product", "type": "string" }, - "status": { - "const": "ok", - "default": "ok", - "title": "Status", - "type": "string" + "schema_version": { + "default": 1, + "minimum": 1.0, + "title": "Schema Version", + "type": "integer" }, - "trace_id": { - "title": "Trace Id", + "source": { + "default": "generic-web-preview", + "title": "Source", "type": "string" } }, "required": [ - "trace_id", - "state", - "repository", - "requests" + "product" ], - "title": "EveryCodeWorkRequestRecordsResponse", + "title": "GenericWebPreviewDesiredStateRequest", "type": "object" }, - "EveryCodeWorkRequestSummary": { + "GenericWebPreviewDestroyEnvelope": { "additionalProperties": false, "properties": { - "claimed_at": { - "default": "", - "title": "Claimed At", - "type": "string" - }, - "claimed_by_host": { - "default": "", - "title": "Claimed By Host", - "type": "string" - }, - "evidence": { - "default": [], - "items": { - "$ref": "#/components/schemas/AgentContextEvidence" - }, - "title": "Evidence", - "type": "array" + "destroy": { + "$ref": "#/components/schemas/GenericWebPreviewDestroyRequest" }, - "finished_at": { - "default": "", - "title": "Finished At", + "product": { + "title": "Product", "type": "string" }, - "issue_number": { + "schema_version": { + "default": 1, "minimum": 1.0, - "title": "Issue Number", + "title": "Schema Version", "type": "integer" - }, - "issue_title": { - "default": "", - "title": "Issue Title", - "type": "string" - }, - "issue_url": { - "title": "Issue Url", - "type": "string" - }, - "next_action": { - "title": "Next Action", - "type": "string" - }, - "provenance": { - "$ref": "#/components/schemas/AgentContextProvenance" - }, - "queued_at": { - "title": "Queued At", - "type": "string" - }, - "repository": { - "title": "Repository", - "type": "string" - }, - "request_id": { - "title": "Request Id", - "type": "string" - }, - "result_pr_url": { - "default": "", - "title": "Result Pr Url", - "type": "string" - }, - "result_summary": { - "default": "", - "title": "Result Summary", - "type": "string" - }, - "safe_to_rerun": { - "title": "Safe To Rerun", - "type": "boolean" - }, - "source": { - "title": "Source", - "type": "string" - }, - "started_at": { - "default": "", - "title": "Started At", - "type": "string" - }, - "state": { - "enum": [ - "queued", - "claimed", - "running", - "done", - "blocked" - ], - "title": "State", - "type": "string" - }, - "summary_status": { - "enum": [ - "active", - "stuck", - "complete", - "rerunnable" - ], - "title": "Summary Status", - "type": "string" - }, - "trigger_actor": { - "default": "", - "title": "Trigger Actor", - "type": "string" - }, - "trigger_label": { - "title": "Trigger Label", - "type": "string" - }, - "updated_at": { - "title": "Updated At", - "type": "string" } }, "required": [ - "request_id", - "repository", - "issue_number", - "issue_url", - "state", - "summary_status", - "source", - "trigger_label", - "queued_at", - "updated_at", - "safe_to_rerun", - "next_action", - "provenance" + "product", + "destroy" ], - "title": "EveryCodeWorkRequestSummary", + "title": "GenericWebPreviewDestroyEnvelope", "type": "object" }, - "ExternalRouteBindingReconcileEnvelope": { + "GenericWebPreviewDestroyRequest": { "additionalProperties": false, "properties": { - "confirmation": { - "default": "", - "title": "Confirmation", - "type": "string" - }, - "context": { - "title": "Context", - "type": "string" - }, - "desired_status": { - "default": "active", - "enum": [ - "active", - "disabled" + "anchor_pr_number": { + "anyOf": [ + { + "minimum": 1.0, + "type": "integer" + }, + { + "type": "null" + } ], - "title": "Desired Status", - "type": "string" - }, - "expected_current": { - "$ref": "#/components/schemas/RouteBindingExpectedCurrent" + "title": "Anchor Pr Number" }, - "instance": { - "title": "Instance", + "destroy_reason": { + "title": "Destroy Reason", "type": "string" }, - "mode": { - "default": "dry-run", - "enum": [ - "dry-run", - "apply" - ], - "title": "Mode", + "preview_slug": { + "default": "", + "title": "Preview Slug", "type": "string" }, "product": { "title": "Product", "type": "string" }, - "reason": { - "default": "", - "title": "Reason", - "type": "string" - }, "schema_version": { "default": 1, "minimum": 1.0, "title": "Schema Version", "type": "integer" }, - "source_label": { - "default": "operator-external-reconcile", - "title": "Source Label", - "type": "string" + "timeout_seconds": { + "default": 300, + "minimum": 1.0, + "title": "Timeout Seconds", + "type": "integer" } }, "required": [ "product", - "context", - "instance", - "expected_current" + "destroy_reason" ], - "title": "ExternalRouteBindingReconcileEnvelope", + "title": "GenericWebPreviewDestroyRequest", "type": "object" }, - "GenericWebDeployEnvelope": { + "GenericWebPreviewInventoryEnvelope": { "additionalProperties": false, "properties": { - "deploy": { - "$ref": "#/components/schemas/GenericWebDeployRequest" + "inventory": { + "$ref": "#/components/schemas/GenericWebPreviewInventoryRequest" }, "product": { "title": "Product", @@ -8877,437 +8583,414 @@ }, "required": [ "product", - "deploy" + "inventory" ], - "title": "GenericWebDeployEnvelope", + "title": "GenericWebPreviewInventoryEnvelope", "type": "object" }, - "GenericWebDeployRecoveryApplyRequest": { + "GenericWebPreviewInventoryRequest": { "additionalProperties": false, "properties": { - "expected_recovery_digest": { - "pattern": "^[0-9a-f]{64}$", - "title": "Expected Recovery Digest", - "type": "string" - }, - "instance": { - "title": "Instance", + "product": { + "title": "Product", "type": "string" }, - "original_deploy": { - "$ref": "#/components/schemas/GenericWebDeployEnvelope" + "schema_version": { + "default": 1, + "minimum": 1.0, + "title": "Schema Version", + "type": "integer" }, + "source": { + "default": "generic-web-preview-inventory", + "title": "Source", + "type": "string" + } + }, + "required": [ + "product" + ], + "title": "GenericWebPreviewInventoryRequest", + "type": "object" + }, + "GenericWebPreviewReadinessEnvelope": { + "additionalProperties": false, + "properties": { "product": { "title": "Product", "type": "string" }, - "reason": { - "maxLength": 1000, - "title": "Reason", - "type": "string" + "readiness": { + "$ref": "#/components/schemas/GenericWebPreviewReadinessRequest" }, "schema_version": { - "const": 1, "default": 1, + "minimum": 1.0, "title": "Schema Version", "type": "integer" } }, "required": [ "product", - "instance", - "original_deploy", - "reason", - "expected_recovery_digest" + "readiness" ], - "title": "GenericWebDeployRecoveryApplyRequest", + "title": "GenericWebPreviewReadinessEnvelope", "type": "object" }, - "GenericWebDeployRecoveryApplyResponse": { + "GenericWebPreviewReadinessRequest": { "additionalProperties": false, "properties": { - "context": { - "title": "Context", + "product": { + "title": "Product", "type": "string" }, - "instance": { - "title": "Instance", - "type": "string" + "schema_version": { + "default": 1, + "minimum": 1.0, + "title": "Schema Version", + "type": "integer" }, - "mode": { - "const": "apply", - "default": "apply", - "title": "Mode", + "source": { + "default": "generic-web-preview-readiness", + "title": "Source", "type": "string" - }, + } + }, + "required": [ + "product" + ], + "title": "GenericWebPreviewReadinessRequest", + "type": "object" + }, + "GenericWebPreviewRefreshEnvelope": { + "additionalProperties": false, + "properties": { "product": { "title": "Product", "type": "string" }, - "provider_outcome": { - "enum": [ - "present", - "absent", - "unknown", - "not_inspected" - ], - "title": "Provider Outcome", - "type": "string" + "refresh": { + "$ref": "#/components/schemas/GenericWebPreviewRefreshRequest" }, - "provider_status": { + "schema_version": { + "default": 1, + "minimum": 1.0, + "title": "Schema Version", + "type": "integer" + } + }, + "required": [ + "product", + "refresh" + ], + "title": "GenericWebPreviewRefreshEnvelope", + "type": "object" + }, + "GenericWebPreviewRefreshRequest": { + "additionalProperties": false, + "properties": { + "anchor_head_sha": { "default": "", - "maxLength": 128, - "title": "Provider Status", + "title": "Anchor Head Sha", "type": "string" }, - "recovery_action": { - "enum": [ - "replay_completed", - "wait_for_active_lease", - "adopt_observed", - "retry_original_operation", - "hold_unknown" + "anchor_pr_number": { + "anyOf": [ + { + "minimum": 1.0, + "type": "integer" + }, + { + "type": "null" + } ], - "title": "Recovery Action", + "title": "Anchor Pr Number" + }, + "anchor_pr_url": { + "default": "", + "title": "Anchor Pr Url", "type": "string" }, - "recovery_digest": { - "pattern": "^[0-9a-f]{64}$", - "title": "Recovery Digest", + "image_reference": { + "title": "Image Reference", "type": "string" }, - "reservation_attempt": { - "minimum": 1.0, - "title": "Reservation Attempt", - "type": "integer" + "no_cache": { + "default": false, + "title": "No Cache", + "type": "boolean" }, - "reservation_state": { - "enum": [ - "completed", - "reconcile_required" - ], - "title": "Reservation State", + "preview_slug": { + "default": "", + "title": "Preview Slug", "type": "string" }, - "retry_safe": { - "title": "Retry Safe", - "type": "boolean" + "preview_url": { + "default": "", + "title": "Preview Url", + "type": "string" + }, + "product": { + "title": "Product", + "type": "string" }, "schema_version": { - "const": 1, "default": 1, + "minimum": 1.0, "title": "Schema Version", "type": "integer" }, - "status": { - "const": "accepted", - "default": "accepted", - "title": "Status", + "source": { + "default": "generic-web-preview-refresh", + "title": "Source", "type": "string" }, - "trace_id": { - "title": "Trace Id", - "type": "string" + "timeout_seconds": { + "default": 300, + "minimum": 1.0, + "title": "Timeout Seconds", + "type": "integer" } }, "required": [ - "trace_id", "product", - "context", - "instance", - "reservation_state", - "reservation_attempt", - "recovery_action", - "recovery_digest", - "provider_outcome", - "retry_safe" + "image_reference" ], - "title": "GenericWebDeployRecoveryApplyResponse", + "title": "GenericWebPreviewRefreshRequest", "type": "object" }, - "GenericWebDeployRecoveryDryRunRequest": { + "GenericWebPreviewVerificationEnvelope": { "additionalProperties": false, "properties": { - "instance": { - "title": "Instance", - "type": "string" - }, - "original_deploy": { - "$ref": "#/components/schemas/GenericWebDeployEnvelope" - }, "product": { "title": "Product", "type": "string" }, - "reason": { - "maxLength": 1000, - "title": "Reason", - "type": "string" - }, "schema_version": { - "const": 1, "default": 1, + "minimum": 1.0, "title": "Schema Version", "type": "integer" + }, + "verification": { + "$ref": "#/components/schemas/GenericWebPreviewVerificationRequest" } }, "required": [ "product", - "instance", - "original_deploy", - "reason" + "verification" ], - "title": "GenericWebDeployRecoveryDryRunRequest", + "title": "GenericWebPreviewVerificationEnvelope", "type": "object" }, - "GenericWebDeployRecoveryDryRunResponse": { + "GenericWebPreviewVerificationRequest": { "additionalProperties": false, "properties": { + "anchor_pr_number": { + "minimum": 1.0, + "title": "Anchor Pr Number", + "type": "integer" + }, + "anchor_repo": { + "title": "Anchor Repo", + "type": "string" + }, + "checked_urls": { + "default": [], + "items": { + "type": "string" + }, + "title": "Checked Urls", + "type": "array" + }, "context": { + "default": "", "title": "Context", "type": "string" }, - "instance": { - "title": "Instance", + "failure_summary": { + "default": "", + "title": "Failure Summary", "type": "string" }, - "mode": { - "const": "dry-run", - "default": "dry-run", - "title": "Mode", - "type": "string" + "schema_version": { + "default": 1, + "minimum": 1.0, + "title": "Schema Version", + "type": "integer" }, - "observed_at": { - "title": "Observed At", - "type": "string" + "timeout_seconds": { + "anyOf": [ + { + "minimum": 1.0, + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Timeout Seconds" }, - "product": { - "title": "Product", + "verification_status": { + "enum": [ + "pending", + "pass", + "fail", + "skipped" + ], + "title": "Verification Status", "type": "string" }, - "proposed_action": { - "enum": [ - "replay_completed", - "wait_for_active_lease", - "adopt_observed", - "retry_original_operation", - "hold_unknown" - ], - "title": "Proposed Action", - "type": "string" - }, - "provider_effect_phase": { - "maxLength": 128, - "title": "Provider Effect Phase", - "type": "string" - }, - "provider_outcome": { - "enum": [ - "present", - "absent", - "unknown", - "not_inspected" - ], - "title": "Provider Outcome", - "type": "string" - }, - "provider_status": { - "default": "", - "maxLength": 128, - "title": "Provider Status", - "type": "string" - }, - "provider_target_key_sha256": { - "pattern": "^[0-9a-f]{64}$", - "title": "Provider Target Key Sha256", - "type": "string" - }, - "reconciliation_key_sha256": { - "pattern": "^[0-9a-f]{64}$", - "title": "Reconciliation Key Sha256", - "type": "string" - }, - "recovery_digest": { - "pattern": "^[0-9a-f]{64}$", - "title": "Recovery Digest", - "type": "string" - }, - "reservation_attempt": { - "minimum": 1.0, - "title": "Reservation Attempt", - "type": "integer" - }, - "reservation_created_at": { - "title": "Reservation Created At", - "type": "string" - }, - "reservation_lease_expires_at": { - "default": "", - "title": "Reservation Lease Expires At", - "type": "string" - }, - "reservation_state": { - "enum": [ - "running", - "completed", - "reconcile_required" - ], - "title": "Reservation State", + "verified_at": { + "title": "Verified At", "type": "string" - }, - "reservation_updated_at": { - "title": "Reservation Updated At", + } + }, + "required": [ + "anchor_repo", + "anchor_pr_number", + "verification_status", + "verified_at" + ], + "title": "GenericWebPreviewVerificationRequest", + "type": "object" + }, + "GenericWebProdPromotionEnvelope": { + "additionalProperties": false, + "properties": { + "product": { + "title": "Product", "type": "string" }, - "retry_safe": { - "title": "Retry Safe", - "type": "boolean" + "promotion": { + "$ref": "#/components/schemas/GenericWebProdPromotionRequest" }, "schema_version": { - "const": 1, "default": 1, + "minimum": 1.0, "title": "Schema Version", "type": "integer" - }, - "status": { - "const": "ok", - "default": "ok", - "title": "Status", - "type": "string" } }, "required": [ "product", - "context", - "instance", - "reservation_state", - "reservation_attempt", - "reservation_created_at", - "reservation_updated_at", - "observed_at", - "reconciliation_key_sha256", - "provider_target_key_sha256", - "provider_effect_phase", - "provider_outcome", - "retry_safe", - "proposed_action", - "recovery_digest" + "promotion" ], - "title": "GenericWebDeployRecoveryDryRunResponse", + "title": "GenericWebProdPromotionEnvelope", "type": "object" }, - "GenericWebDeployRecoveryProviderEvidenceResponse": { + "GenericWebProdPromotionRecords": { "additionalProperties": false, "properties": { - "mode": { - "const": "provider-evidence", - "default": "provider-evidence", - "title": "Mode", + "backup_record_id": { + "default": "", + "title": "Backup Record Id", "type": "string" }, - "observed_at": { - "title": "Observed At", + "backup_status": { + "default": "", + "title": "Backup Status", "type": "string" }, - "provider_effect_phase": { - "maxLength": 128, - "title": "Provider Effect Phase", + "deployment_record_id": { + "default": "", + "title": "Deployment Record Id", "type": "string" }, - "provider_evidence": { - "enum": [ - "deployment_present", - "deployment_correlated_legacy", - "deployment_absent_before_effect", - "deployment_absent_after_effect", - "provider_status_unknown", - "provider_read_failed", - "not_inspected" - ], - "title": "Provider Evidence", + "deployment_status": { + "default": "", + "title": "Deployment Status", "type": "string" }, - "provider_read_error_class": { + "destination_health_status": { "default": "", - "enum": [ - "", - "target_resolution_failed", - "provider_request_failed" - ], - "title": "Provider Read Error Class", + "title": "Destination Health Status", "type": "string" }, - "provider_status": { + "dry_run": { "default": "", - "maxLength": 128, - "title": "Provider Status", + "title": "Dry Run", "type": "string" }, - "provider_target_key_sha256": { - "pattern": "^[0-9a-f]{64}$", - "title": "Provider Target Key Sha256", + "inventory_record_id": { + "default": "", + "title": "Inventory Record Id", "type": "string" }, - "reconciliation_key_sha256": { - "pattern": "^[0-9a-f]{64}$", - "title": "Reconciliation Key Sha256", + "promotion_record_id": { + "default": "", + "title": "Promotion Record Id", "type": "string" }, - "reservation_attempt": { - "minimum": 1.0, - "title": "Reservation Attempt", - "type": "integer" + "promotion_status": { + "default": "", + "title": "Promotion Status", + "type": "string" }, - "reservation_state": { - "enum": [ - "running", - "completed", - "reconcile_required" - ], - "title": "Reservation State", + "release_status": { + "default": "", + "title": "Release Status", "type": "string" }, - "schema_version": { - "const": 1, - "default": 1, - "title": "Schema Version", - "type": "integer" + "release_tag": { + "default": "", + "title": "Release Tag", + "type": "string" }, - "status": { - "const": "ok", - "default": "ok", - "title": "Status", + "release_url": { + "default": "", + "title": "Release Url", + "type": "string" + }, + "source_health_status": { + "default": "", + "title": "Source Health Status", "type": "string" } }, - "required": [ - "reservation_state", - "reservation_attempt", - "observed_at", - "reconciliation_key_sha256", - "provider_target_key_sha256", - "provider_effect_phase", - "provider_evidence" - ], - "title": "GenericWebDeployRecoveryProviderEvidenceResponse", + "title": "GenericWebProdPromotionRecords", "type": "object" }, - "GenericWebDeployRequest": { + "GenericWebProdPromotionRequest": { "additionalProperties": false, "properties": { "artifact_id": { + "default": "", "title": "Artifact Id", "type": "string" }, + "backup_record_id": { + "default": "", + "title": "Backup Record Id", + "type": "string" + }, + "backup_required": { + "const": true, + "default": true, + "title": "Backup Required", + "type": "boolean" + }, "deploy_reference": { "default": "", "title": "Deploy Reference", "type": "string" }, - "instance": { - "title": "Instance", + "dry_run": { + "default": false, + "title": "Dry Run", + "type": "boolean" + }, + "from_instance": { + "default": "testing", + "title": "From Instance", "type": "string" }, + "health_timeout_seconds": { + "default": 60, + "minimum": 1.0, + "title": "Health Timeout Seconds", + "type": "integer" + }, "no_cache": { "default": false, "title": "No Cache", @@ -9317,6 +9000,16 @@ "title": "Product", "type": "string" }, + "promotion_intent_id": { + "default": "", + "title": "Promotion Intent Id", + "type": "string" + }, + "release_tag": { + "default": "", + "title": "Release Tag", + "type": "string" + }, "schema_version": { "default": 1, "minimum": 1.0, @@ -9324,6 +9017,7 @@ "type": "integer" }, "source_git_ref": { + "default": "", "title": "Source Git Ref", "type": "string" }, @@ -9338,238 +9032,264 @@ } ], "title": "Timeout Seconds" + }, + "to_instance": { + "default": "prod", + "title": "To Instance", + "type": "string" + }, + "verify_health": { + "default": true, + "title": "Verify Health", + "type": "boolean" + }, + "wait": { + "default": true, + "title": "Wait", + "type": "boolean" } }, "required": [ - "product", - "instance", - "artifact_id", - "source_git_ref" + "product" ], - "title": "GenericWebDeployRequest", + "title": "GenericWebProdPromotionRequest", "type": "object" }, - "GenericWebPreviewAuthzPlanRequest": { + "GenericWebProdPromotionResponse": { "additionalProperties": false, "properties": { - "default_branch": { - "default": "main", - "title": "Default Branch", + "original_trace_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Original Trace Id", + "x-launchplane-optional-response": true + }, + "records": { + "$ref": "#/components/schemas/GenericWebProdPromotionRecords" + }, + "replayed": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "title": "Replayed", + "x-launchplane-optional-response": true + }, + "result": { + "$ref": "#/components/schemas/GenericWebProdPromotionResponseResult" + }, + "status": { + "const": "accepted", + "default": "accepted", + "title": "Status", "type": "string" }, - "include_ingress_operator": { - "default": false, - "title": "Include Ingress Operator", - "type": "boolean" + "trace_id": { + "title": "Trace Id", + "type": "string" + } + }, + "required": [ + "trace_id", + "records", + "result" + ], + "title": "GenericWebProdPromotionResponse", + "type": "object" + }, + "GenericWebProdPromotionResponseResult": { + "additionalProperties": false, + "properties": { + "artifact_id": { + "default": "", + "title": "Artifact Id", + "type": "string" }, - "launchplane_sha": { - "title": "Launchplane Sha", + "backup_record_id": { + "default": "", + "title": "Backup Record Id", "type": "string" }, - "operation": { - "default": "onboard", + "backup_status": { + "default": "skipped", "enum": [ - "onboard", - "expand", - "contract", - "retire" + "pending", + "pass", + "fail", + "skipped" ], - "title": "Operation", + "title": "Backup Status", "type": "string" }, - "preview_context": { + "context": { "default": "", - "title": "Preview Context", + "title": "Context", "type": "string" }, - "product": { - "const": "launchplane", - "default": "launchplane", - "title": "Product", + "deploy_reference": { + "default": "", + "title": "Deploy Reference", "type": "string" }, - "reason": { - "title": "Reason", + "deployment_record_id": { + "default": "", + "title": "Deployment Record Id", "type": "string" }, - "related_issue": { - "title": "Related Issue", + "deployment_status": { + "default": "skipped", + "enum": [ + "pending", + "pass", + "fail", + "skipped" + ], + "title": "Deployment Status", "type": "string" }, - "repository": { - "default": "", - "title": "Repository", + "destination_health_status": { + "default": "skipped", + "enum": [ + "pending", + "pass", + "fail", + "skipped" + ], + "title": "Destination Health Status", "type": "string" }, - "repository_id": { + "dry_run": { + "default": false, + "title": "Dry Run", + "type": "boolean" + }, + "error_message": { "default": "", - "title": "Repository Id", + "title": "Error Message", "type": "string" }, - "repository_owner_id": { + "from_instance": { "default": "", - "title": "Repository Owner Id", + "title": "From Instance", "type": "string" }, - "schema_version": { - "const": 1, - "default": 1, - "title": "Schema Version", - "type": "integer" - }, - "target_product": { - "title": "Target Product", + "inventory_record_id": { + "default": "", + "title": "Inventory Record Id", "type": "string" - } - }, - "required": [ - "target_product", - "launchplane_sha", - "reason", - "related_issue" - ], - "title": "GenericWebPreviewAuthzPlanRequest", - "type": "object" - }, - "GenericWebPreviewDesiredStateEnvelope": { - "additionalProperties": false, - "properties": { - "desired_state": { - "$ref": "#/components/schemas/GenericWebPreviewDesiredStateRequest" }, "product": { + "default": "", "title": "Product", "type": "string" }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" - } - }, - "required": [ - "product", - "desired_state" - ], - "title": "GenericWebPreviewDesiredStateEnvelope", - "type": "object" - }, - "GenericWebPreviewDesiredStateRequest": { - "additionalProperties": false, - "properties": { - "label": { - "default": "preview", - "title": "Label", + "promotion_record_id": { + "default": "", + "title": "Promotion Record Id", "type": "string" }, - "max_pages": { - "default": 10, - "maximum": 20.0, - "minimum": 1.0, - "title": "Max Pages", - "type": "integer" - }, - "product": { - "title": "Product", + "promotion_status": { + "default": "pending", + "enum": [ + "pending", + "pass", + "fail" + ], + "title": "Promotion Status", "type": "string" }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" + "provider_id": { + "default": "", + "title": "Provider Id", + "type": "string" }, - "source": { - "default": "generic-web-preview", - "title": "Source", + "provider_target_type": { + "default": "", + "title": "Provider Target Type", "type": "string" - } - }, - "required": [ - "product" - ], - "title": "GenericWebPreviewDesiredStateRequest", - "type": "object" - }, - "GenericWebPreviewDestroyEnvelope": { - "additionalProperties": false, - "properties": { - "destroy": { - "$ref": "#/components/schemas/GenericWebPreviewDestroyRequest" }, - "product": { - "title": "Product", + "release_status": { + "default": "skipped", + "enum": [ + "pending", + "pass", + "fail", + "skipped" + ], + "title": "Release Status", "type": "string" }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" - } - }, - "required": [ - "product", - "destroy" - ], - "title": "GenericWebPreviewDestroyEnvelope", - "type": "object" - }, - "GenericWebPreviewDestroyRequest": { - "additionalProperties": false, - "properties": { - "anchor_pr_number": { - "anyOf": [ - { - "minimum": 1.0, - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Anchor Pr Number" + "release_tag": { + "default": "", + "title": "Release Tag", + "type": "string" }, - "destroy_reason": { - "title": "Destroy Reason", + "release_url": { + "default": "", + "title": "Release Url", "type": "string" }, - "preview_slug": { + "source_git_ref": { "default": "", - "title": "Preview Slug", + "title": "Source Git Ref", "type": "string" }, - "product": { - "title": "Product", + "source_health_status": { + "default": "skipped", + "enum": [ + "pending", + "pass", + "fail", + "skipped" + ], + "title": "Source Health Status", "type": "string" }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" + "target_category": { + "default": "unknown", + "enum": [ + "application", + "compose", + "container", + "service", + "static", + "unknown" + ], + "title": "Target Category", + "type": "string" }, - "timeout_seconds": { - "default": 300, - "minimum": 1.0, - "title": "Timeout Seconds", - "type": "integer" + "target_id": { + "default": "", + "title": "Target Id", + "type": "string" + }, + "target_name": { + "default": "", + "title": "Target Name", + "type": "string" + }, + "to_instance": { + "default": "", + "title": "To Instance", + "type": "string" } }, - "required": [ - "product", - "destroy_reason" - ], - "title": "GenericWebPreviewDestroyRequest", + "title": "GenericWebProdPromotionResponseResult", "type": "object" }, - "GenericWebPreviewInventoryEnvelope": { + "GenericWebPromotionWorkflowEnvelope": { "additionalProperties": false, "properties": { - "inventory": { - "$ref": "#/components/schemas/GenericWebPreviewInventoryRequest" - }, "product": { "title": "Product", "type": "string" @@ -9579,67 +9299,68 @@ "minimum": 1.0, "title": "Schema Version", "type": "integer" + }, + "workflow": { + "$ref": "#/components/schemas/GenericWebPromotionWorkflowRequest" } }, "required": [ "product", - "inventory" + "workflow" ], - "title": "GenericWebPreviewInventoryEnvelope", + "title": "GenericWebPromotionWorkflowEnvelope", "type": "object" }, - "GenericWebPreviewInventoryRequest": { + "GenericWebPromotionWorkflowRequest": { "additionalProperties": false, "properties": { - "product": { - "title": "Product", + "artifact_id": { + "default": "", + "title": "Artifact Id", "type": "string" }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" + "bump": { + "anyOf": [ + { + "enum": [ + "patch", + "minor", + "major" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Bump" }, - "source": { - "default": "generic-web-preview-inventory", - "title": "Source", + "context": { + "title": "Context", "type": "string" - } - }, - "required": [ - "product" - ], - "title": "GenericWebPreviewInventoryRequest", - "type": "object" - }, - "GenericWebPreviewReadinessEnvelope": { - "additionalProperties": false, - "properties": { - "product": { - "title": "Product", + }, + "deploy_reference": { + "default": "", + "title": "Deploy Reference", "type": "string" }, - "readiness": { - "$ref": "#/components/schemas/GenericWebPreviewReadinessRequest" + "dry_run": { + "default": true, + "title": "Dry Run", + "type": "boolean" }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", + "evidence_fingerprint": { + "default": "", + "title": "Evidence Fingerprint", + "type": "string" + }, + "observe_timeout_seconds": { + "default": 12, + "maximum": 60.0, + "minimum": 0.0, + "title": "Observe Timeout Seconds", "type": "integer" - } - }, - "required": [ - "product", - "readiness" - ], - "title": "GenericWebPreviewReadinessEnvelope", - "type": "object" - }, - "GenericWebPreviewReadinessRequest": { - "additionalProperties": false, - "properties": { + }, "product": { "title": "Product", "type": "string" @@ -9650,116 +9371,151 @@ "title": "Schema Version", "type": "integer" }, - "source": { - "default": "generic-web-preview-readiness", - "title": "Source", + "source_git_ref": { + "default": "", + "title": "Source Git Ref", "type": "string" } }, "required": [ - "product" + "product", + "context" ], - "title": "GenericWebPreviewReadinessRequest", + "title": "GenericWebPromotionWorkflowRequest", "type": "object" }, - "GenericWebPreviewRefreshEnvelope": { + "GenericWebPromotionWorkflowResponse": { "additionalProperties": false, "properties": { - "product": { - "title": "Product", - "type": "string" + "original_trace_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Original Trace Id", + "x-launchplane-optional-response": true }, - "refresh": { - "$ref": "#/components/schemas/GenericWebPreviewRefreshRequest" + "records": { + "additionalProperties": { + "type": "string" + }, + "title": "Records", + "type": "object" }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" + "replayed": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "title": "Replayed", + "x-launchplane-optional-response": true + }, + "result": { + "$ref": "#/components/schemas/GenericWebPromotionWorkflowResponseResult" + }, + "status": { + "const": "accepted", + "default": "accepted", + "title": "Status", + "type": "string" + }, + "trace_id": { + "title": "Trace Id", + "type": "string" } }, "required": [ - "product", - "refresh" + "trace_id", + "result" ], - "title": "GenericWebPreviewRefreshEnvelope", + "title": "GenericWebPromotionWorkflowResponse", "type": "object" }, - "GenericWebPreviewRefreshRequest": { + "GenericWebPromotionWorkflowResponseResult": { "additionalProperties": false, "properties": { - "anchor_head_sha": { - "default": "", - "title": "Anchor Head Sha", - "type": "string" - }, - "anchor_pr_number": { - "anyOf": [ - { - "minimum": 1.0, - "type": "integer" - }, - { - "type": "null" - } + "bump": { + "default": "patch", + "enum": [ + "patch", + "minor", + "major" ], - "title": "Anchor Pr Number" + "title": "Bump", + "type": "string" }, - "anchor_pr_url": { + "context": { "default": "", - "title": "Anchor Pr Url", + "title": "Context", "type": "string" }, - "image_reference": { - "title": "Image Reference", + "dispatch_status": { + "default": "pending", + "enum": [ + "pending", + "dispatched" + ], + "title": "Dispatch Status", "type": "string" }, - "no_cache": { + "dry_run": { "default": false, - "title": "No Cache", + "title": "Dry Run", "type": "boolean" }, - "preview_slug": { + "product": { "default": "", - "title": "Preview Slug", + "title": "Product", "type": "string" }, - "preview_url": { + "ref": { "default": "", - "title": "Preview Url", + "title": "Ref", "type": "string" }, - "product": { - "title": "Product", + "repository": { + "default": "", + "title": "Repository", "type": "string" }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", + "run_conclusion": { + "default": "", + "title": "Run Conclusion", + "type": "string" + }, + "run_id": { + "default": 0, + "title": "Run Id", "type": "integer" }, - "source": { - "default": "generic-web-preview-refresh", - "title": "Source", + "run_status": { + "default": "pending", + "title": "Run Status", "type": "string" }, - "timeout_seconds": { - "default": 300, - "minimum": 1.0, - "title": "Timeout Seconds", - "type": "integer" + "run_url": { + "default": "", + "title": "Run Url", + "type": "string" + }, + "workflow_id": { + "default": "", + "title": "Workflow Id", + "type": "string" } }, - "required": [ - "product", - "image_reference" - ], - "title": "GenericWebPreviewRefreshRequest", + "title": "GenericWebPromotionWorkflowResponseResult", "type": "object" }, - "GenericWebPreviewVerificationEnvelope": { + "GenericWebStableVerificationEnvelope": { "additionalProperties": false, "properties": { "product": { @@ -9773,28 +9529,19 @@ "type": "integer" }, "verification": { - "$ref": "#/components/schemas/GenericWebPreviewVerificationRequest" + "$ref": "#/components/schemas/GenericWebStableVerificationRequest" } }, "required": [ "product", "verification" ], - "title": "GenericWebPreviewVerificationEnvelope", + "title": "GenericWebStableVerificationEnvelope", "type": "object" }, - "GenericWebPreviewVerificationRequest": { + "GenericWebStableVerificationRequest": { "additionalProperties": false, "properties": { - "anchor_pr_number": { - "minimum": 1.0, - "title": "Anchor Pr Number", - "type": "integer" - }, - "anchor_repo": { - "title": "Anchor Repo", - "type": "string" - }, "checked_urls": { "default": [], "items": { @@ -9804,15 +9551,36 @@ "type": "array" }, "context": { - "default": "", "title": "Context", "type": "string" }, + "deployment_record_id": { + "title": "Deployment Record Id", + "type": "string" + }, "failure_summary": { "default": "", "title": "Failure Summary", "type": "string" }, + "health_payload": { + "anyOf": [ + {}, + { + "type": "null" + } + ], + "title": "Health Payload" + }, + "instance": { + "title": "Instance", + "type": "string" + }, + "promotion_record_id": { + "default": "", + "title": "Promotion Record Id", + "type": "string" + }, "schema_version": { "default": 1, "minimum": 1.0, @@ -9847,218 +9615,363 @@ } }, "required": [ - "anchor_repo", - "anchor_pr_number", + "context", + "instance", + "deployment_record_id", "verification_status", "verified_at" ], - "title": "GenericWebPreviewVerificationRequest", - "type": "object" - }, - "GenericWebProdPromotionEnvelope": { - "additionalProperties": false, - "properties": { - "product": { - "title": "Product", - "type": "string" - }, - "promotion": { - "$ref": "#/components/schemas/GenericWebProdPromotionRequest" - }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" - } - }, - "required": [ - "product", - "promotion" - ], - "title": "GenericWebProdPromotionEnvelope", + "title": "GenericWebStableVerificationRequest", "type": "object" }, - "GenericWebProdPromotionRecords": { + "GitHubActionsAccessPrincipal": { "additionalProperties": false, "properties": { - "backup_record_id": { + "environment": { "default": "", - "title": "Backup Record Id", + "title": "Environment", "type": "string" }, - "backup_status": { - "default": "", - "title": "Backup Status", + "event_name": { + "title": "Event Name", "type": "string" }, - "deployment_record_id": { + "job_workflow_ref": { "default": "", - "title": "Deployment Record Id", + "title": "Job Workflow Ref", "type": "string" }, - "deployment_status": { - "default": "", - "title": "Deployment Status", + "principal_type": { + "const": "github_actions", + "default": "github_actions", + "title": "Principal Type", "type": "string" }, - "destination_health_status": { - "default": "", - "title": "Destination Health Status", + "ref": { + "title": "Ref", "type": "string" }, - "dry_run": { - "default": "", - "title": "Dry Run", + "ref_type": { + "title": "Ref Type", "type": "string" }, - "inventory_record_id": { - "default": "", - "title": "Inventory Record Id", + "repository": { + "title": "Repository", "type": "string" }, - "promotion_record_id": { + "repository_id": { "default": "", - "title": "Promotion Record Id", + "title": "Repository Id", "type": "string" }, - "promotion_status": { - "default": "", - "title": "Promotion Status", + "repository_owner": { + "title": "Repository Owner", "type": "string" }, - "release_status": { + "repository_owner_id": { "default": "", - "title": "Release Status", + "title": "Repository Owner Id", "type": "string" }, - "release_tag": { + "sha": { "default": "", - "title": "Release Tag", + "title": "Sha", "type": "string" }, - "release_url": { - "default": "", - "title": "Release Url", + "subject": { + "title": "Subject", "type": "string" }, - "source_health_status": { - "default": "", - "title": "Source Health Status", + "workflow_ref": { + "title": "Workflow Ref", "type": "string" } }, - "title": "GenericWebProdPromotionRecords", - "type": "object" - }, - "GenericWebProdPromotionRequest": { - "additionalProperties": false, + "required": [ + "repository", + "repository_owner", + "workflow_ref", + "ref", + "ref_type", + "event_name", + "subject" + ], + "title": "GitHubActionsAccessPrincipal", + "type": "object" + }, + "GitHubActionsPolicyRule": { + "additionalProperties": false, "properties": { - "artifact_id": { - "default": "", - "title": "Artifact Id", + "actions": { + "default": [], + "items": { + "type": "string" + }, + "title": "Actions", + "type": "array" + }, + "contexts": { + "default": [], + "items": { + "type": "string" + }, + "title": "Contexts", + "type": "array" + }, + "environments": { + "default": [], + "items": { + "type": "string" + }, + "title": "Environments", + "type": "array" + }, + "event_names": { + "default": [], + "items": { + "type": "string" + }, + "title": "Event Names", + "type": "array" + }, + "instances": { + "default": [], + "items": { + "type": "string" + }, + "title": "Instances", + "type": "array" + }, + "job_workflow_refs": { + "default": [], + "items": { + "type": "string" + }, + "title": "Job Workflow Refs", + "type": "array" + }, + "managed_rule_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Managed Rule Id" + }, + "managed_set_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Managed Set Id" + }, + "products": { + "default": [], + "items": { + "type": "string" + }, + "title": "Products", + "type": "array" + }, + "refs": { + "default": [], + "items": { + "type": "string" + }, + "title": "Refs", + "type": "array" + }, + "repository": { + "title": "Repository", "type": "string" }, - "backup_record_id": { + "repository_id": { "default": "", - "title": "Backup Record Id", + "title": "Repository Id", "type": "string" }, - "backup_required": { - "const": true, - "default": true, - "title": "Backup Required", - "type": "boolean" - }, - "deploy_reference": { + "repository_owner_id": { "default": "", - "title": "Deploy Reference", + "title": "Repository Owner Id", "type": "string" }, - "dry_run": { - "default": false, - "title": "Dry Run", - "type": "boolean" + "workflow_refs": { + "default": [], + "items": { + "type": "string" + }, + "title": "Workflow Refs", + "type": "array" + } + }, + "required": [ + "repository" + ], + "title": "GitHubActionsPolicyRule", + "type": "object" + }, + "GitHubHumanAccessPrincipal": { + "additionalProperties": false, + "properties": { + "github_id": { + "default": 0, + "minimum": 0.0, + "title": "Github Id", + "type": "integer" }, - "from_instance": { - "default": "testing", - "title": "From Instance", + "login": { + "title": "Login", "type": "string" }, - "health_timeout_seconds": { - "default": 60, - "minimum": 1.0, - "title": "Health Timeout Seconds", - "type": "integer" - }, - "no_cache": { - "default": false, - "title": "No Cache", - "type": "boolean" + "organizations": { + "default": [], + "items": { + "type": "string" + }, + "title": "Organizations", + "type": "array" }, - "product": { - "title": "Product", + "principal_type": { + "const": "github_human", + "default": "github_human", + "title": "Principal Type", "type": "string" }, - "promotion_intent_id": { - "default": "", - "title": "Promotion Intent Id", + "role": { + "enum": [ + "read_only", + "admin" + ], + "title": "Role", "type": "string" }, - "release_tag": { - "default": "", - "title": "Release Tag", + "teams": { + "default": [], + "items": { + "type": "string" + }, + "title": "Teams", + "type": "array" + } + }, + "required": [ + "login", + "role" + ], + "title": "GitHubHumanAccessPrincipal", + "type": "object" + }, + "GitHubHumanIdentityResponse": { + "additionalProperties": false, + "properties": { + "email": { + "title": "Email", "type": "string" }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", + "github_id": { + "title": "Github Id", "type": "integer" }, - "source_git_ref": { - "default": "", - "title": "Source Git Ref", + "login": { + "title": "Login", "type": "string" }, - "timeout_seconds": { - "anyOf": [ - { - "minimum": 1.0, - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Timeout Seconds" + "name": { + "title": "Name", + "type": "string" }, - "to_instance": { - "default": "prod", - "title": "To Instance", + "organizations": { + "items": { + "type": "string" + }, + "title": "Organizations", + "type": "array" + }, + "provider": { + "const": "github", + "default": "github", + "title": "Provider", "type": "string" }, - "verify_health": { - "default": true, - "title": "Verify Health", - "type": "boolean" + "role": { + "enum": [ + "read_only", + "admin" + ], + "title": "Role", + "type": "string" }, - "wait": { - "default": true, - "title": "Wait", - "type": "boolean" + "teams": { + "items": { + "type": "string" + }, + "title": "Teams", + "type": "array" } }, "required": [ - "product" + "login", + "github_id", + "name", + "email", + "organizations", + "teams", + "role" ], - "title": "GenericWebProdPromotionRequest", + "title": "GitHubHumanIdentityResponse", "type": "object" }, - "GenericWebProdPromotionResponse": { + "GitHubHumanPolicyRule": { "additionalProperties": false, "properties": { - "original_trace_id": { + "actions": { + "default": [], + "items": { + "type": "string" + }, + "title": "Actions", + "type": "array" + }, + "contexts": { + "default": [], + "items": { + "type": "string" + }, + "title": "Contexts", + "type": "array" + }, + "github_ids": { + "default": [], + "items": { + "type": "integer" + }, + "title": "Github Ids", + "type": "array" + }, + "instances": { + "default": [], + "items": { + "type": "string" + }, + "title": "Instances", + "type": "array" + }, + "logins": { + "default": [], + "items": { + "type": "string" + }, + "title": "Logins", + "type": "array" + }, + "managed_rule_id": { "anyOf": [ { "type": "string" @@ -10067,231 +9980,268 @@ "type": "null" } ], - "title": "Original Trace Id", - "x-launchplane-optional-response": true - }, - "records": { - "$ref": "#/components/schemas/GenericWebProdPromotionRecords" + "title": "Managed Rule Id" }, - "replayed": { + "managed_set_id": { "anyOf": [ { - "type": "boolean" + "type": "string" }, { "type": "null" } ], - "title": "Replayed", - "x-launchplane-optional-response": true + "title": "Managed Set Id" }, - "result": { - "$ref": "#/components/schemas/GenericWebProdPromotionResponseResult" + "organizations": { + "default": [], + "items": { + "type": "string" + }, + "title": "Organizations", + "type": "array" }, - "status": { - "const": "accepted", - "default": "accepted", - "title": "Status", - "type": "string" + "products": { + "default": [], + "items": { + "type": "string" + }, + "title": "Products", + "type": "array" }, - "trace_id": { - "title": "Trace Id", - "type": "string" + "roles": { + "default": [], + "items": { + "enum": [ + "read_only", + "admin" + ], + "type": "string" + }, + "title": "Roles", + "type": "array" + }, + "teams": { + "default": [], + "items": { + "type": "string" + }, + "title": "Teams", + "type": "array" } }, - "required": [ - "trace_id", - "records", - "result" - ], - "title": "GenericWebProdPromotionResponse", + "title": "GitHubHumanPolicyRule", "type": "object" }, - "GenericWebProdPromotionResponseResult": { + "GitHubIssueInboxIssue": { "additionalProperties": false, "properties": { - "artifact_id": { + "author": { "default": "", - "title": "Artifact Id", + "title": "Author", "type": "string" }, - "backup_record_id": { + "created_at": { "default": "", - "title": "Backup Record Id", - "type": "string" - }, - "backup_status": { - "default": "skipped", - "enum": [ - "pending", - "pass", - "fail", - "skipped" - ], - "title": "Backup Status", + "title": "Created At", "type": "string" }, - "context": { - "default": "", - "title": "Context", + "key": { + "title": "Key", "type": "string" }, - "deploy_reference": { - "default": "", - "title": "Deploy Reference", - "type": "string" + "labels": { + "default": [], + "items": { + "type": "string" + }, + "title": "Labels", + "type": "array" }, - "deployment_record_id": { - "default": "", - "title": "Deployment Record Id", - "type": "string" + "number": { + "minimum": 1.0, + "title": "Number", + "type": "integer" }, - "deployment_status": { - "default": "skipped", - "enum": [ - "pending", - "pass", - "fail", - "skipped" + "present_in_project": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } ], - "title": "Deployment Status", - "type": "string" + "title": "Present In Project" }, - "destination_health_status": { - "default": "skipped", + "project_status": { + "default": "unconfigured", "enum": [ - "pending", - "pass", - "fail", - "skipped" + "present", + "missing", + "stale", + "closed", + "unconfigured" ], - "title": "Destination Health Status", + "title": "Project Status", "type": "string" }, - "dry_run": { - "default": false, - "title": "Dry Run", - "type": "boolean" - }, - "error_message": { - "default": "", - "title": "Error Message", + "repository": { + "title": "Repository", "type": "string" }, - "from_instance": { - "default": "", - "title": "From Instance", + "state": { + "default": "open", + "title": "State", "type": "string" }, - "inventory_record_id": { + "title": { "default": "", - "title": "Inventory Record Id", + "title": "Title", "type": "string" }, - "product": { + "updated_at": { "default": "", - "title": "Product", + "title": "Updated At", "type": "string" }, - "promotion_record_id": { + "url": { "default": "", - "title": "Promotion Record Id", + "title": "Url", + "type": "string" + } + }, + "required": [ + "key", + "repository", + "number" + ], + "title": "GitHubIssueInboxIssue", + "type": "object" + }, + "GitHubIssueInboxReadModel": { + "additionalProperties": false, + "properties": { + "generated_at": { + "title": "Generated At", "type": "string" }, - "promotion_status": { - "default": "pending", - "enum": [ - "pending", - "pass", - "fail" - ], - "title": "Promotion Status", - "type": "string" + "issue_count": { + "minimum": 0.0, + "title": "Issue Count", + "type": "integer" }, - "provider_id": { - "default": "", - "title": "Provider Id", - "type": "string" + "project_configured": { + "default": false, + "title": "Project Configured", + "type": "boolean" }, - "provider_target_type": { - "default": "", - "title": "Provider Target Type", - "type": "string" + "repositories": { + "default": [], + "items": { + "$ref": "#/components/schemas/GitHubIssueInboxRepositoryGroup" + }, + "title": "Repositories", + "type": "array" }, - "release_status": { - "default": "skipped", + "repository_count": { + "minimum": 0.0, + "title": "Repository Count", + "type": "integer" + }, + "schema_version": { + "default": 1, + "minimum": 1.0, + "title": "Schema Version", + "type": "integer" + }, + "stale_project_item_count": { + "default": 0, + "minimum": 0.0, + "title": "Stale Project Item Count", + "type": "integer" + } + }, + "required": [ + "generated_at", + "repository_count", + "issue_count" + ], + "title": "GitHubIssueInboxReadModel", + "type": "object" + }, + "GitHubIssueInboxReconcileItem": { + "additionalProperties": false, + "properties": { + "action": { "enum": [ - "pending", - "pass", - "fail", + "would_add", + "added", + "already_present", + "failed", "skipped" ], - "title": "Release Status", + "title": "Action", "type": "string" }, - "release_tag": { + "detail": { "default": "", - "title": "Release Tag", + "title": "Detail", "type": "string" }, - "release_url": { + "error_code": { "default": "", - "title": "Release Url", + "title": "Error Code", "type": "string" }, - "source_git_ref": { + "error_correlation_id": { "default": "", - "title": "Source Git Ref", + "title": "Error Correlation Id", "type": "string" }, - "source_health_status": { - "default": "skipped", - "enum": [ - "pending", - "pass", - "fail", - "skipped" - ], - "title": "Source Health Status", + "key": { + "title": "Key", "type": "string" }, - "target_category": { - "default": "unknown", - "enum": [ - "application", - "compose", - "container", - "service", - "static", - "unknown" - ], - "title": "Target Category", - "type": "string" + "number": { + "minimum": 1.0, + "title": "Number", + "type": "integer" }, - "target_id": { - "default": "", - "title": "Target Id", + "repository": { + "title": "Repository", "type": "string" }, - "target_name": { + "title": { "default": "", - "title": "Target Name", + "title": "Title", "type": "string" }, - "to_instance": { + "url": { "default": "", - "title": "To Instance", + "title": "Url", "type": "string" } }, - "title": "GenericWebProdPromotionResponseResult", + "required": [ + "key", + "repository", + "number", + "action" + ], + "title": "GitHubIssueInboxReconcileItem", "type": "object" }, - "GenericWebPromotionWorkflowEnvelope": { + "GitHubIssueInboxReconcileRequest": { "additionalProperties": false, "properties": { - "product": { - "title": "Product", + "mode": { + "default": "dry_run", + "enum": [ + "dry_run", + "apply" + ], + "title": "Mode", "type": "string" }, "schema_version": { @@ -10299,949 +10249,799 @@ "minimum": 1.0, "title": "Schema Version", "type": "integer" - }, - "workflow": { - "$ref": "#/components/schemas/GenericWebPromotionWorkflowRequest" } }, - "required": [ - "product", - "workflow" - ], - "title": "GenericWebPromotionWorkflowEnvelope", + "title": "GitHubIssueInboxReconcileRequest", "type": "object" }, - "GenericWebPromotionWorkflowRequest": { + "GitHubIssueInboxReconcileResult": { "additionalProperties": false, "properties": { - "artifact_id": { - "default": "", - "title": "Artifact Id", - "type": "string" + "added_count": { + "default": 0, + "minimum": 0.0, + "title": "Added Count", + "type": "integer" }, - "bump": { - "anyOf": [ - { - "enum": [ - "patch", - "minor", - "major" - ], - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Bump" + "already_present_count": { + "default": 0, + "minimum": 0.0, + "title": "Already Present Count", + "type": "integer" }, - "context": { - "title": "Context", - "type": "string" + "failed_count": { + "default": 0, + "minimum": 0.0, + "title": "Failed Count", + "type": "integer" }, - "deploy_reference": { - "default": "", - "title": "Deploy Reference", + "generated_at": { + "title": "Generated At", "type": "string" }, - "dry_run": { - "default": true, - "title": "Dry Run", - "type": "boolean" + "issue_count": { + "minimum": 0.0, + "title": "Issue Count", + "type": "integer" }, - "evidence_fingerprint": { - "default": "", - "title": "Evidence Fingerprint", + "items": { + "default": [], + "items": { + "$ref": "#/components/schemas/GitHubIssueInboxReconcileItem" + }, + "title": "Items", + "type": "array" + }, + "mode": { + "enum": [ + "dry_run", + "apply" + ], + "title": "Mode", "type": "string" }, - "observe_timeout_seconds": { - "default": 12, - "maximum": 60.0, + "repository_count": { "minimum": 0.0, - "title": "Observe Timeout Seconds", + "title": "Repository Count", "type": "integer" }, - "product": { - "title": "Product", - "type": "string" - }, "schema_version": { "default": 1, "minimum": 1.0, "title": "Schema Version", "type": "integer" }, - "source_git_ref": { - "default": "", - "title": "Source Git Ref", - "type": "string" + "skipped_count": { + "default": 0, + "minimum": 0.0, + "title": "Skipped Count", + "type": "integer" + }, + "would_add_count": { + "default": 0, + "minimum": 0.0, + "title": "Would Add Count", + "type": "integer" } }, "required": [ - "product", - "context" + "generated_at", + "mode", + "repository_count", + "issue_count" ], - "title": "GenericWebPromotionWorkflowRequest", + "title": "GitHubIssueInboxReconcileResult", "type": "object" }, - "GenericWebPromotionWorkflowResponse": { + "GitHubIssueInboxRepositoryGroup": { "additionalProperties": false, "properties": { - "original_trace_id": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Original Trace Id", - "x-launchplane-optional-response": true + "issue_count": { + "minimum": 0.0, + "title": "Issue Count", + "type": "integer" }, - "records": { - "additionalProperties": { - "type": "string" + "issues": { + "default": [], + "items": { + "$ref": "#/components/schemas/GitHubIssueInboxIssue" }, - "title": "Records", - "type": "object" - }, - "replayed": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Replayed", - "x-launchplane-optional-response": true + "title": "Issues", + "type": "array" }, - "result": { - "$ref": "#/components/schemas/GenericWebPromotionWorkflowResponseResult" + "missing_from_project_count": { + "minimum": 0.0, + "title": "Missing From Project Count", + "type": "integer" }, - "status": { - "const": "accepted", - "default": "accepted", - "title": "Status", - "type": "string" + "present_in_project_count": { + "minimum": 0.0, + "title": "Present In Project Count", + "type": "integer" }, - "trace_id": { - "title": "Trace Id", + "repository": { + "title": "Repository", "type": "string" } }, "required": [ - "trace_id", - "result" + "repository", + "issue_count", + "present_in_project_count", + "missing_from_project_count" ], - "title": "GenericWebPromotionWorkflowResponse", + "title": "GitHubIssueInboxRepositoryGroup", "type": "object" }, - "GenericWebPromotionWorkflowResponseResult": { + "GovernanceAdvisoryObservation": { "additionalProperties": false, "properties": { - "bump": { - "default": "patch", - "enum": [ - "patch", - "minor", - "major" - ], - "title": "Bump", - "type": "string" - }, - "context": { - "default": "", - "title": "Context", - "type": "string" - }, - "dispatch_status": { - "default": "pending", - "enum": [ - "pending", - "dispatched" - ], - "title": "Dispatch Status", - "type": "string" - }, - "dry_run": { + "authoritative": { + "const": false, "default": false, - "title": "Dry Run", + "title": "Authoritative", "type": "boolean" }, - "product": { - "default": "", - "title": "Product", - "type": "string" - }, - "ref": { - "default": "", - "title": "Ref", - "type": "string" - }, - "repository": { - "default": "", - "title": "Repository", - "type": "string" - }, - "run_conclusion": { - "default": "", - "title": "Run Conclusion", - "type": "string" - }, - "run_id": { - "default": 0, - "title": "Run Id", - "type": "integer" - }, - "run_status": { - "default": "pending", - "title": "Run Status", - "type": "string" + "authorizes": { + "default": [], + "items": { + "type": "string" + }, + "title": "Authorizes", + "type": "array" }, - "run_url": { - "default": "", - "title": "Run Url", - "type": "string" + "observation": { + "$ref": "#/components/schemas/MergeReadinessAdvisoryObservation" }, - "workflow_id": { - "default": "", - "title": "Workflow Id", - "type": "string" - } - }, - "title": "GenericWebPromotionWorkflowResponseResult", - "type": "object" - }, - "GenericWebStableVerificationEnvelope": { - "additionalProperties": false, - "properties": { - "product": { - "title": "Product", + "observation_scope": { + "enum": [ + "current_readiness", + "admission_readiness" + ], + "title": "Observation Scope", "type": "string" - }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" - }, - "verification": { - "$ref": "#/components/schemas/GenericWebStableVerificationRequest" } }, "required": [ - "product", - "verification" + "observation_scope", + "observation" ], - "title": "GenericWebStableVerificationEnvelope", + "title": "GovernanceAdvisoryObservation", "type": "object" }, - "GenericWebStableVerificationRequest": { + "GovernanceLandingOutcomeFacet": { "additionalProperties": false, "properties": { - "checked_urls": { + "authoritative": { + "const": false, + "default": false, + "title": "Authoritative", + "type": "boolean" + }, + "authorizes": { "default": [], "items": { "type": "string" }, - "title": "Checked Urls", + "title": "Authorizes", "type": "array" }, - "context": { - "title": "Context", - "type": "string" - }, - "deployment_record_id": { - "title": "Deployment Record Id", - "type": "string" - }, - "failure_summary": { - "default": "", - "title": "Failure Summary", - "type": "string" - }, - "health_payload": { - "anyOf": [ - {}, - { - "type": "null" - } - ], - "title": "Health Payload" - }, - "instance": { - "title": "Instance", - "type": "string" + "landed": { + "title": "Landed", + "type": "boolean" }, - "promotion_record_id": { - "default": "", - "title": "Promotion Record Id", + "mode": { + "const": "immutable_provider_observation", + "default": "immutable_provider_observation", + "title": "Mode", "type": "string" }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" - }, - "timeout_seconds": { + "record": { "anyOf": [ { - "minimum": 1.0, - "type": "integer" + "$ref": "#/components/schemas/MergeLandingOutcomeRecord" }, { "type": "null" } - ], - "title": "Timeout Seconds" + ] }, - "verification_status": { + "status": { "enum": [ - "pending", - "pass", - "fail", - "skipped" + "not_observed", + "landed", + "rejected", + "reconcile_required" ], - "title": "Verification Status", + "title": "Status", "type": "string" }, - "verified_at": { - "title": "Verified At", + "target_status": { + "enum": [ + "current", + "historical", + "none" + ], + "title": "Target Status", "type": "string" } }, "required": [ - "context", - "instance", - "deployment_record_id", - "verification_status", - "verified_at" - ], - "title": "GenericWebStableVerificationRequest", + "target_status", + "status", + "landed" + ], + "title": "GovernanceLandingOutcomeFacet", "type": "object" }, - "GitHubActionsAccessPrincipal": { + "GovernanceMergeAdmissionFacet": { "additionalProperties": false, "properties": { - "environment": { - "default": "", - "title": "Environment", - "type": "string" - }, - "event_name": { - "title": "Event Name", - "type": "string" - }, - "job_workflow_ref": { - "default": "", - "title": "Job Workflow Ref", - "type": "string" - }, - "principal_type": { - "const": "github_actions", - "default": "github_actions", - "title": "Principal Type", - "type": "string" - }, - "ref": { - "title": "Ref", - "type": "string" - }, - "ref_type": { - "title": "Ref Type", - "type": "string" - }, - "repository": { - "title": "Repository", - "type": "string" + "authoritative": { + "const": false, + "default": false, + "title": "Authoritative", + "type": "boolean" }, - "repository_id": { - "default": "", - "title": "Repository Id", - "type": "string" + "authorizes": { + "default": [], + "items": { + "const": "one_exact_merge_attempt", + "type": "string" + }, + "title": "Authorizes", + "type": "array" }, - "repository_owner": { - "title": "Repository Owner", - "type": "string" + "current_effect_authority": { + "const": false, + "default": false, + "title": "Current Effect Authority", + "type": "boolean" }, - "repository_owner_id": { - "default": "", - "title": "Repository Owner Id", - "type": "string" + "level": { + "const": 3, + "default": 3, + "title": "Level", + "type": "integer" }, - "sha": { - "default": "", - "title": "Sha", + "mode": { + "const": "immutable_attempt_authorization", + "default": "immutable_attempt_authorization", + "title": "Mode", "type": "string" }, - "subject": { - "title": "Subject", - "type": "string" + "record": { + "anyOf": [ + { + "$ref": "#/components/schemas/MergeAdmissionRecord" + }, + { + "type": "null" + } + ] }, - "workflow_ref": { - "title": "Workflow Ref", + "status": { + "enum": [ + "not_recorded", + "admitted_current_target", + "admitted_historical_target" + ], + "title": "Status", "type": "string" } }, "required": [ - "repository", - "repository_owner", - "workflow_ref", - "ref", - "ref_type", - "event_name", - "subject" + "status" ], - "title": "GitHubActionsAccessPrincipal", + "title": "GovernanceMergeAdmissionFacet", "type": "object" }, - "GitHubActionsPolicyRule": { + "GovernanceMergeReadinessFacet": { "additionalProperties": false, "properties": { - "actions": { - "default": [], - "items": { - "type": "string" - }, - "title": "Actions", - "type": "array" - }, - "contexts": { - "default": [], - "items": { - "type": "string" - }, - "title": "Contexts", - "type": "array" + "authoritative": { + "const": false, + "default": false, + "title": "Authoritative", + "type": "boolean" }, - "environments": { + "authorizes": { "default": [], "items": { "type": "string" }, - "title": "Environments", + "title": "Authorizes", "type": "array" }, - "event_names": { - "default": [], - "items": { - "type": "string" - }, - "title": "Event Names", - "type": "array" + "availability": { + "enum": [ + "available", + "not_active", + "unavailable" + ], + "title": "Availability", + "type": "string" }, - "instances": { - "default": [], - "items": { - "type": "string" - }, - "title": "Instances", - "type": "array" + "level": { + "const": 2, + "default": 2, + "title": "Level", + "type": "integer" }, - "job_workflow_refs": { - "default": [], - "items": { - "type": "string" - }, - "title": "Job Workflow Refs", - "type": "array" + "mode": { + "const": "ephemeral", + "default": "ephemeral", + "title": "Mode", + "type": "string" }, - "managed_rule_id": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } + "reason_code": { + "enum": [ + "current_evaluation_available", + "no_active_merge_lineage", + "current_evidence_unavailable" ], - "title": "Managed Rule Id" + "title": "Reason Code", + "type": "string" }, - "managed_set_id": { + "result": { "anyOf": [ { - "type": "string" + "$ref": "#/components/schemas/MergeReadinessResult" }, { "type": "null" } - ], - "title": "Managed Set Id" - }, - "products": { - "default": [], - "items": { - "type": "string" - }, - "title": "Products", - "type": "array" - }, - "refs": { - "default": [], - "items": { - "type": "string" - }, - "title": "Refs", - "type": "array" - }, - "repository": { - "title": "Repository", - "type": "string" - }, - "repository_id": { - "default": "", - "title": "Repository Id", - "type": "string" - }, - "repository_owner_id": { - "default": "", - "title": "Repository Owner Id", - "type": "string" - }, - "workflow_refs": { - "default": [], - "items": { - "type": "string" - }, - "title": "Workflow Refs", - "type": "array" + ] } }, "required": [ - "repository" + "availability", + "reason_code" ], - "title": "GitHubActionsPolicyRule", + "title": "GovernanceMergeReadinessFacet", "type": "object" }, - "GitHubHumanAccessPrincipal": { + "GovernanceOwnerHistoryEntry": { "additionalProperties": false, "properties": { - "github_id": { - "default": 0, - "minimum": 0.0, - "title": "Github Id", - "type": "integer" - }, - "login": { - "title": "Login", + "decision_relationship": { + "enum": [ + "current", + "historical" + ], + "title": "Decision Relationship", "type": "string" }, - "organizations": { - "default": [], - "items": { - "type": "string" - }, - "title": "Organizations", - "type": "array" - }, - "principal_type": { - "const": "github_human", - "default": "github_human", - "title": "Principal Type", + "human_action_semantics": { + "enum": [ + "none", + "product_review_accepted", + "product_review_changes_requested", + "product_review_revoked", + "product_review_superseded", + "product_review_invalidated" + ], + "title": "Human Action Semantics", "type": "string" }, - "role": { + "record": { + "$ref": "#/components/schemas/OwnerAcceptanceEventRecord" + }, + "target_status": { "enum": [ - "read_only", - "admin" + "current", + "historical" ], - "title": "Role", + "title": "Target Status", "type": "string" - }, - "teams": { - "default": [], - "items": { - "type": "string" - }, - "title": "Teams", - "type": "array" } }, "required": [ - "login", - "role" + "record", + "human_action_semantics", + "target_status", + "decision_relationship" ], - "title": "GitHubHumanAccessPrincipal", + "title": "GovernanceOwnerHistoryEntry", "type": "object" }, - "GitHubHumanIdentityResponse": { + "GovernanceOwnerJudgmentFacet": { "additionalProperties": false, "properties": { - "email": { - "title": "Email", - "type": "string" - }, - "github_id": { - "title": "Github Id", - "type": "integer" - }, - "login": { - "title": "Login", - "type": "string" + "authoritative": { + "const": true, + "default": true, + "title": "Authoritative", + "type": "boolean" }, - "name": { - "title": "Name", - "type": "string" + "current": { + "$ref": "#/components/schemas/OwnerAcceptanceDecision" }, - "organizations": { + "history": { + "default": [], "items": { - "type": "string" + "$ref": "#/components/schemas/GovernanceOwnerHistoryEntry" }, - "title": "Organizations", + "title": "History", "type": "array" }, - "provider": { - "const": "github", - "default": "github", - "title": "Provider", - "type": "string" + "level": { + "const": 1, + "default": 1, + "title": "Level", + "type": "integer" }, - "role": { - "enum": [ - "read_only", - "admin" - ], - "title": "Role", + "mode": { + "const": "owner_acceptance", + "default": "owner_acceptance", + "title": "Mode", "type": "string" - }, - "teams": { - "items": { - "type": "string" - }, - "title": "Teams", - "type": "array" } }, "required": [ - "login", - "github_id", - "name", - "email", - "organizations", - "teams", - "role" + "current" ], - "title": "GitHubHumanIdentityResponse", + "title": "GovernanceOwnerJudgmentFacet", "type": "object" }, - "GitHubHumanPolicyRule": { + "GovernanceProjection": { "additionalProperties": false, "properties": { - "actions": { + "advisory_observations": { "default": [], "items": { - "type": "string" + "$ref": "#/components/schemas/GovernanceAdvisoryObservation" }, - "title": "Actions", + "title": "Advisory Observations", "type": "array" }, - "contexts": { + "authoritative": { + "const": false, + "default": false, + "title": "Authoritative", + "type": "boolean" + }, + "authorizes": { "default": [], "items": { "type": "string" }, - "title": "Contexts", + "title": "Authorizes", "type": "array" }, - "github_ids": { - "default": [], - "items": { - "type": "integer" - }, - "title": "Github Ids", - "type": "array" + "generated_at": { + "title": "Generated At", + "type": "string" }, - "instances": { - "default": [], - "items": { - "type": "string" - }, - "title": "Instances", - "type": "array" + "landing_outcome": { + "$ref": "#/components/schemas/GovernanceLandingOutcomeFacet" }, - "logins": { - "default": [], - "items": { - "type": "string" - }, - "title": "Logins", - "type": "array" + "merge_admission": { + "$ref": "#/components/schemas/GovernanceMergeAdmissionFacet" }, - "managed_rule_id": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Managed Rule Id" + "merge_readiness": { + "$ref": "#/components/schemas/GovernanceMergeReadinessFacet" }, - "managed_set_id": { + "mode": { + "const": "read_only_projection", + "default": "read_only_projection", + "title": "Mode", + "type": "string" + }, + "owner_judgment": { "anyOf": [ { - "type": "string" + "$ref": "#/components/schemas/GovernanceOwnerJudgmentFacet" }, { "type": "null" } - ], - "title": "Managed Set Id" - }, - "organizations": { - "default": [], - "items": { - "type": "string" - }, - "title": "Organizations", - "type": "array" - }, - "products": { - "default": [], - "items": { - "type": "string" - }, - "title": "Products", - "type": "array" + ] }, - "roles": { - "default": [], - "items": { - "enum": [ - "read_only", - "admin" - ], - "type": "string" - }, - "title": "Roles", - "type": "array" + "schema_version": { + "const": 1, + "default": 1, + "title": "Schema Version", + "type": "integer" }, - "teams": { - "default": [], - "items": { - "type": "string" - }, - "title": "Teams", - "type": "array" + "target": { + "$ref": "#/components/schemas/RepositoryTarget" } }, - "title": "GitHubHumanPolicyRule", + "required": [ + "target", + "merge_readiness", + "merge_admission", + "landing_outcome", + "generated_at" + ], + "title": "GovernanceProjection", "type": "object" }, - "GitHubIssueInboxIssue": { + "GovernanceProjectionResponse": { "additionalProperties": false, "properties": { - "author": { - "default": "", - "title": "Author", - "type": "string" + "projection": { + "$ref": "#/components/schemas/GovernanceProjection" }, - "created_at": { - "default": "", - "title": "Created At", + "status": { + "const": "ok", + "default": "ok", + "title": "Status", "type": "string" }, - "key": { - "title": "Key", - "type": "string" - }, - "labels": { - "default": [], + "trace_id": { + "title": "Trace Id", + "type": "string" + } + }, + "required": [ + "trace_id", + "projection" + ], + "title": "GovernanceProjectionResponse", + "type": "object" + }, + "HTTPValidationError": { + "properties": { + "detail": { "items": { - "type": "string" + "$ref": "#/components/schemas/ValidationError" }, - "title": "Labels", + "title": "Detail", "type": "array" + } + }, + "title": "HTTPValidationError", + "type": "object" + }, + "HealthResponse": { + "additionalProperties": false, + "properties": { + "status": { + "const": "ok", + "default": "ok", + "title": "Status", + "type": "string" }, - "number": { - "minimum": 1.0, - "title": "Number", - "type": "integer" + "storage_backend": { + "title": "Storage Backend", + "type": "string" }, - "present_in_project": { + "trace_id": { + "title": "Trace Id", + "type": "string" + } + }, + "required": [ + "trace_id", + "storage_backend" + ], + "title": "HealthResponse", + "type": "object" + }, + "HealthcheckEvidence": { + "additionalProperties": false, + "properties": { + "observed_runtime_identity": { "anyOf": [ { - "type": "boolean" + "$ref": "#/components/schemas/RuntimeIdentity" }, { "type": "null" } - ], - "title": "Present In Project" + ] }, - "project_status": { - "default": "unconfigured", + "runtime_identity_detail": { + "default": "", + "title": "Runtime Identity Detail", + "type": "string" + }, + "runtime_identity_status": { + "default": "unchecked", "enum": [ - "present", + "unchecked", + "match", + "mismatch", "missing", - "stale", - "closed", - "unconfigured" + "malformed", + "unverifiable" ], - "title": "Project Status", + "title": "Runtime Identity Status", "type": "string" }, - "repository": { - "title": "Repository", + "status": { + "default": "skipped", + "enum": [ + "pending", + "pass", + "fail", + "skipped" + ], + "title": "Status", "type": "string" }, - "state": { - "default": "open", - "title": "State", - "type": "string" + "structured_health": { + "$ref": "#/components/schemas/StructuredHealthEvidence" }, - "title": { - "default": "", - "title": "Title", - "type": "string" + "timeout_seconds": { + "anyOf": [ + { + "minimum": 1.0, + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Timeout Seconds" }, - "updated_at": { - "default": "", - "title": "Updated At", - "type": "string" + "urls": { + "default": [], + "items": { + "type": "string" + }, + "title": "Urls", + "type": "array" }, - "url": { - "default": "", - "title": "Url", - "type": "string" + "verified": { + "default": false, + "title": "Verified", + "type": "boolean" } }, - "required": [ - "key", - "repository", - "number" - ], - "title": "GitHubIssueInboxIssue", + "title": "HealthcheckEvidence", "type": "object" }, - "GitHubIssueInboxReadModel": { + "HistoricalCompletionEntryAssessment": { "additionalProperties": false, "properties": { - "generated_at": { - "title": "Generated At", - "type": "string" + "position": { + "title": "Position", + "type": "integer" }, - "issue_count": { - "minimum": 0.0, - "title": "Issue Count", + "pull_request_number": { + "title": "Pull Request Number", "type": "integer" }, - "project_configured": { - "default": false, - "title": "Project Configured", - "type": "boolean" + "reason_code": { + "enum": [ + "exact_historical_completion", + "not_assessed", + "controller_unavailable", + "controller_busy", + "controller_not_reconciling", + "selector_mismatch", + "policy_unavailable", + "policy_changed", + "landing_plan_unavailable", + "landing_plan_ambiguous", + "landing_plan_binding_changed", + "stored_evidence_incomplete", + "entry_limit_exceeded", + "candidate_unavailable", + "candidate_history_limit_exceeded", + "candidate_ambiguous", + "candidate_binding_changed", + "stack_batch_unsupported", + "ordinary_target_unsupported", + "admission_present", + "store_unavailable", + "store_state_changed", + "provider_unavailable", + "provider_invalid_response", + "provider_binding_mismatch", + "pull_request_not_merged", + "no_op_unsupported", + "base_not_contains_merge", + "base_changed" + ], + "title": "Reason Code", + "type": "string" }, - "repositories": { - "default": [], - "items": { - "$ref": "#/components/schemas/GitHubIssueInboxRepositoryGroup" + "status": { + "enum": [ + "eligible", + "unsupported", + "indeterminate" + ], + "title": "Status", + "type": "string" + } + }, + "required": [ + "position", + "pull_request_number", + "status", + "reason_code" + ], + "title": "HistoricalCompletionEntryAssessment", + "type": "object" + }, + "HistoricalCompletionPreflightResponse": { + "additionalProperties": false, + "properties": { + "records": { + "additionalProperties": { + "type": "string" }, - "title": "Repositories", - "type": "array" + "title": "Records", + "type": "object" }, - "repository_count": { - "minimum": 0.0, - "title": "Repository Count", - "type": "integer" + "result": { + "$ref": "#/components/schemas/HistoricalCompletionPreflightResult" }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" + "status": { + "const": "accepted", + "default": "accepted", + "title": "Status", + "type": "string" }, - "stale_project_item_count": { - "default": 0, - "minimum": 0.0, - "title": "Stale Project Item Count", - "type": "integer" + "trace_id": { + "title": "Trace Id", + "type": "string" } }, "required": [ - "generated_at", - "repository_count", - "issue_count" + "trace_id", + "result" ], - "title": "GitHubIssueInboxReadModel", + "title": "HistoricalCompletionPreflightResponse", "type": "object" }, - "GitHubIssueInboxReconcileItem": { + "HistoricalCompletionPreflightResult": { "additionalProperties": false, "properties": { - "action": { - "enum": [ - "would_add", - "added", - "already_present", - "failed", - "skipped" - ], - "title": "Action", - "type": "string" - }, - "detail": { - "default": "", - "title": "Detail", + "base_branch": { + "title": "Base Branch", "type": "string" }, - "error_code": { - "default": "", - "title": "Error Code", + "controller_action": { + "const": "historical_completion_preflight", + "default": "historical_completion_preflight", + "title": "Controller Action", "type": "string" }, - "error_correlation_id": { - "default": "", - "title": "Error Correlation Id", - "type": "string" + "historical_completion_preflight": { + "$ref": "#/components/schemas/MergeTrainHistoricalCompletionPreflight" }, - "key": { - "title": "Key", + "mode": { + "const": "dry-run", + "default": "dry-run", + "title": "Mode", "type": "string" }, - "number": { - "minimum": 1.0, - "title": "Number", - "type": "integer" - }, "repository": { "title": "Repository", "type": "string" - }, - "title": { - "default": "", - "title": "Title", - "type": "string" - }, - "url": { - "default": "", - "title": "Url", - "type": "string" } }, "required": [ - "key", "repository", - "number", - "action" + "base_branch", + "historical_completion_preflight" ], - "title": "GitHubIssueInboxReconcileItem", + "title": "HistoricalCompletionPreflightResult", "type": "object" }, - "GitHubIssueInboxReconcileRequest": { + "IngressCanaryRouteApplyEnvelope": { "additionalProperties": false, "properties": { - "mode": { - "default": "dry_run", - "enum": [ - "dry_run", - "apply" - ], - "title": "Mode", + "canary_key": { + "title": "Canary Key", + "type": "string" + }, + "context": { + "title": "Context", + "type": "string" + }, + "product": { + "title": "Product", + "type": "string" + }, + "reason": { + "title": "Reason", "type": "string" }, "schema_version": { @@ -11251,563 +11051,436 @@ "type": "integer" } }, - "title": "GitHubIssueInboxReconcileRequest", + "required": [ + "product", + "context", + "canary_key", + "reason" + ], + "title": "IngressCanaryRouteApplyEnvelope", "type": "object" }, - "GitHubIssueInboxReconcileResult": { + "IngressCanaryRouteRecord": { "additionalProperties": false, "properties": { - "added_count": { - "default": 0, - "minimum": 0.0, - "title": "Added Count", - "type": "integer" - }, - "already_present_count": { - "default": 0, - "minimum": 0.0, - "title": "Already Present Count", - "type": "integer" + "canary_key": { + "title": "Canary Key", + "type": "string" }, - "failed_count": { - "default": 0, - "minimum": 0.0, - "title": "Failed Count", - "type": "integer" + "certificate_id": { + "anyOf": [ + { + "type": "integer" + }, + { + "const": "new", + "type": "string" + } + ], + "title": "Certificate Id" }, - "generated_at": { - "title": "Generated At", + "context": { + "title": "Context", "type": "string" }, - "issue_count": { - "minimum": 0.0, - "title": "Issue Count", - "type": "integer" - }, - "items": { - "default": [], - "items": { - "$ref": "#/components/schemas/GitHubIssueInboxReconcileItem" - }, - "title": "Items", - "type": "array" + "domain_name": { + "title": "Domain Name", + "type": "string" }, - "mode": { - "enum": [ - "dry_run", - "apply" - ], - "title": "Mode", + "edge_endpoint_key": { + "title": "Edge Endpoint Key", "type": "string" }, - "repository_count": { - "minimum": 0.0, - "title": "Repository Count", + "expected_host_id": { + "minimum": 1.0, + "title": "Expected Host Id", "type": "integer" }, + "product": { + "title": "Product", + "type": "string" + }, "schema_version": { "default": 1, "minimum": 1.0, "title": "Schema Version", "type": "integer" }, - "skipped_count": { - "default": 0, - "minimum": 0.0, - "title": "Skipped Count", - "type": "integer" + "source_label": { + "default": "", + "title": "Source Label", + "type": "string" }, - "would_add_count": { - "default": 0, - "minimum": 0.0, - "title": "Would Add Count", - "type": "integer" + "status": { + "default": "active", + "enum": [ + "active", + "disabled" + ], + "title": "Status", + "type": "string" + }, + "updated_at": { + "title": "Updated At", + "type": "string" } }, "required": [ - "generated_at", - "mode", - "repository_count", - "issue_count" + "canary_key", + "product", + "context", + "domain_name", + "expected_host_id", + "edge_endpoint_key", + "certificate_id", + "updated_at" ], - "title": "GitHubIssueInboxReconcileResult", + "title": "IngressCanaryRouteRecord", "type": "object" }, - "GitHubIssueInboxRepositoryGroup": { + "IngressCanaryRouteRecordApplyEnvelope": { "additionalProperties": false, "properties": { - "issue_count": { - "minimum": 0.0, - "title": "Issue Count", - "type": "integer" + "confirmation": { + "default": "", + "title": "Confirmation", + "type": "string" }, - "issues": { - "default": [], - "items": { - "$ref": "#/components/schemas/GitHubIssueInboxIssue" - }, - "title": "Issues", - "type": "array" + "mode": { + "default": "dry-run", + "enum": [ + "dry-run", + "apply" + ], + "title": "Mode", + "type": "string" }, - "missing_from_project_count": { - "minimum": 0.0, - "title": "Missing From Project Count", - "type": "integer" + "reason": { + "default": "", + "title": "Reason", + "type": "string" }, - "present_in_project_count": { - "minimum": 0.0, - "title": "Present In Project Count", - "type": "integer" + "route": { + "$ref": "#/components/schemas/IngressCanaryRouteRecord" }, - "repository": { - "title": "Repository", - "type": "string" + "schema_version": { + "default": 1, + "minimum": 1.0, + "title": "Schema Version", + "type": "integer" } }, "required": [ - "repository", - "issue_count", - "present_in_project_count", - "missing_from_project_count" + "route" ], - "title": "GitHubIssueInboxRepositoryGroup", + "title": "IngressCanaryRouteRecordApplyEnvelope", "type": "object" }, - "GovernanceAdvisoryObservation": { + "IngressCanaryRouteRecordResponse": { "additionalProperties": false, "properties": { - "authoritative": { - "const": false, - "default": false, - "title": "Authoritative", - "type": "boolean" - }, - "authorizes": { - "default": [], - "items": { - "type": "string" - }, - "title": "Authorizes", - "type": "array" + "record": { + "$ref": "#/components/schemas/IngressCanaryRouteRecord" }, - "observation": { - "$ref": "#/components/schemas/MergeReadinessAdvisoryObservation" + "status": { + "const": "ok", + "default": "ok", + "title": "Status", + "type": "string" }, - "observation_scope": { - "enum": [ - "current_readiness", - "admission_readiness" - ], - "title": "Observation Scope", + "trace_id": { + "title": "Trace Id", "type": "string" } }, "required": [ - "observation_scope", - "observation" + "trace_id", + "record" ], - "title": "GovernanceAdvisoryObservation", + "title": "IngressCanaryRouteRecordResponse", "type": "object" }, - "GovernanceLandingOutcomeFacet": { + "IngressCanaryRouteRecordsResponse": { "additionalProperties": false, "properties": { - "authoritative": { - "const": false, - "default": false, - "title": "Authoritative", - "type": "boolean" + "count": { + "title": "Count", + "type": "integer" }, - "authorizes": { - "default": [], + "limit": { + "title": "Limit", + "type": "integer" + }, + "records": { "items": { - "type": "string" + "$ref": "#/components/schemas/IngressCanaryRouteRecord" }, - "title": "Authorizes", + "title": "Records", "type": "array" }, - "landed": { - "title": "Landed", - "type": "boolean" - }, - "mode": { - "const": "immutable_provider_observation", - "default": "immutable_provider_observation", - "title": "Mode", - "type": "string" - }, - "record": { - "anyOf": [ - { - "$ref": "#/components/schemas/MergeLandingOutcomeRecord" - }, - { - "type": "null" - } - ] - }, "status": { - "enum": [ - "not_observed", - "landed", - "rejected", - "reconcile_required" - ], + "const": "ok", + "default": "ok", "title": "Status", "type": "string" }, - "target_status": { - "enum": [ - "current", - "historical", - "none" - ], - "title": "Target Status", + "trace_id": { + "title": "Trace Id", "type": "string" } }, "required": [ - "target_status", - "status", - "landed" + "trace_id", + "limit", + "count", + "records" ], - "title": "GovernanceLandingOutcomeFacet", + "title": "IngressCanaryRouteRecordsResponse", "type": "object" }, - "GovernanceMergeAdmissionFacet": { + "IngressIdentityAccessBinding": { "additionalProperties": false, "properties": { - "authoritative": { - "const": false, + "mode": { + "$ref": "#/components/schemas/IngressIdentityAccessMode", + "default": "none" + }, + "provider": { + "$ref": "#/components/schemas/IngressIdentityAccessProvider", + "default": "none" + }, + "schema_version": { + "default": 1, + "title": "Schema Version", + "type": "integer" + }, + "send_basic_auth": { "default": false, - "title": "Authoritative", + "title": "Send Basic Auth", "type": "boolean" + } + }, + "title": "IngressIdentityAccessBinding", + "type": "object" + }, + "IngressIdentityAccessMode": { + "enum": [ + "none", + "forward-auth" + ], + "type": "string" + }, + "IngressIdentityAccessProvider": { + "enum": [ + "none", + "anubis", + "tinyauth", + "authelia", + "authentik" + ], + "type": "string" + }, + "IngressRouteAuditOperation": { + "additionalProperties": false, + "properties": { + "action": { + "title": "Action", + "type": "string" }, - "authorizes": { + "change_categories": { "default": [], "items": { - "const": "one_exact_merge_attempt", "type": "string" }, - "title": "Authorizes", + "title": "Change Categories", "type": "array" }, - "current_effect_authority": { - "const": false, - "default": false, - "title": "Current Effect Authority", - "type": "boolean" - }, - "level": { - "const": 3, - "default": 3, - "title": "Level", - "type": "integer" - }, - "mode": { - "const": "immutable_attempt_authorization", - "default": "immutable_attempt_authorization", - "title": "Mode", - "type": "string" + "domain_names": { + "items": { + "type": "string" + }, + "title": "Domain Names", + "type": "array" }, - "record": { + "host_id": { "anyOf": [ { - "$ref": "#/components/schemas/MergeAdmissionRecord" + "minimum": 1.0, + "type": "integer" }, { "type": "null" } - ] - }, - "status": { - "enum": [ - "not_recorded", - "admitted_current_target", - "admitted_historical_target" ], - "title": "Status", - "type": "string" + "title": "Host Id" + }, + "requires_apply": { + "title": "Requires Apply", + "type": "boolean" } }, "required": [ - "status" + "action", + "domain_names", + "requires_apply" ], - "title": "GovernanceMergeAdmissionFacet", + "title": "IngressRouteAuditOperation", "type": "object" }, - "GovernanceMergeReadinessFacet": { + "IngressRouteAuditRecord": { "additionalProperties": false, "properties": { - "authoritative": { - "const": false, - "default": false, - "title": "Authoritative", - "type": "boolean" - }, - "authorizes": { - "default": [], - "items": { - "type": "string" - }, - "title": "Authorizes", - "type": "array" - }, - "availability": { - "enum": [ - "available", - "not_active", - "unavailable" - ], - "title": "Availability", + "context": { + "title": "Context", "type": "string" }, - "level": { - "const": 2, - "default": 2, - "title": "Level", - "type": "integer" - }, - "mode": { - "const": "ephemeral", - "default": "ephemeral", - "title": "Mode", - "type": "string" + "dry_run": { + "title": "Dry Run", + "type": "boolean" }, - "reason_code": { - "enum": [ - "current_evaluation_available", - "no_active_merge_lineage", - "current_evidence_unavailable" - ], - "title": "Reason Code", + "edge_endpoint_key": { + "default": "", + "title": "Edge Endpoint Key", "type": "string" }, - "result": { + "expected_host_id": { "anyOf": [ { - "$ref": "#/components/schemas/MergeReadinessResult" + "minimum": 1.0, + "type": "integer" }, { "type": "null" } - ] - } - }, - "required": [ - "availability", - "reason_code" - ], - "title": "GovernanceMergeReadinessFacet", - "type": "object" - }, - "GovernanceOwnerHistoryEntry": { - "additionalProperties": false, - "properties": { - "decision_relationship": { - "enum": [ - "current", - "historical" ], - "title": "Decision Relationship", - "type": "string" + "title": "Expected Host Id" }, - "human_action_semantics": { - "enum": [ - "none", - "product_review_accepted", - "product_review_changes_requested", - "product_review_revoked", - "product_review_superseded", - "product_review_invalidated" - ], - "title": "Human Action Semantics", + "idempotency_key": { + "default": "", + "title": "Idempotency Key", "type": "string" }, - "record": { - "$ref": "#/components/schemas/OwnerAcceptanceEventRecord" - }, - "target_status": { + "mode": { "enum": [ - "current", - "historical" + "dry-run", + "apply" ], - "title": "Target Status", - "type": "string" - } - }, - "required": [ - "record", - "human_action_semantics", - "target_status", - "decision_relationship" - ], - "title": "GovernanceOwnerHistoryEntry", - "type": "object" - }, - "GovernanceOwnerJudgmentFacet": { - "additionalProperties": false, - "properties": { - "authoritative": { - "const": true, - "default": true, - "title": "Authoritative", - "type": "boolean" - }, - "current": { - "$ref": "#/components/schemas/OwnerAcceptanceDecision" - }, - "history": { - "default": [], - "items": { - "$ref": "#/components/schemas/GovernanceOwnerHistoryEntry" - }, - "title": "History", - "type": "array" - }, - "level": { - "const": 1, - "default": 1, - "title": "Level", - "type": "integer" - }, - "mode": { - "const": "owner_acceptance", - "default": "owner_acceptance", "title": "Mode", "type": "string" - } - }, - "required": [ - "current" - ], - "title": "GovernanceOwnerJudgmentFacet", - "type": "object" - }, - "GovernanceProjection": { - "additionalProperties": false, - "properties": { - "advisory_observations": { - "default": [], - "items": { - "$ref": "#/components/schemas/GovernanceAdvisoryObservation" - }, - "title": "Advisory Observations", - "type": "array" }, - "authoritative": { - "const": false, - "default": false, - "title": "Authoritative", - "type": "boolean" - }, - "authorizes": { - "default": [], + "operations": { "items": { - "type": "string" + "$ref": "#/components/schemas/IngressRouteAuditOperation" }, - "title": "Authorizes", + "title": "Operations", "type": "array" }, - "generated_at": { - "title": "Generated At", + "product": { + "title": "Product", "type": "string" }, - "landing_outcome": { - "$ref": "#/components/schemas/GovernanceLandingOutcomeFacet" - }, - "merge_admission": { - "$ref": "#/components/schemas/GovernanceMergeAdmissionFacet" - }, - "merge_readiness": { - "$ref": "#/components/schemas/GovernanceMergeReadinessFacet" + "provider": { + "default": "npmplus", + "title": "Provider", + "type": "string" }, - "mode": { - "const": "read_only_projection", - "default": "read_only_projection", - "title": "Mode", + "provider_certificate_ref": { + "default": "", + "title": "Provider Certificate Ref", "type": "string" }, - "owner_judgment": { + "provider_host_id": { "anyOf": [ { - "$ref": "#/components/schemas/GovernanceOwnerJudgmentFacet" + "minimum": 1.0, + "type": "integer" }, { "type": "null" } - ] + ], + "title": "Provider Host Id" + }, + "reason": { + "title": "Reason", + "type": "string" + }, + "record_id": { + "title": "Record Id", + "type": "string" + }, + "recorded_at": { + "title": "Recorded At", + "type": "string" + }, + "requested_domains": { + "items": { + "type": "string" + }, + "title": "Requested Domains", + "type": "array" }, "schema_version": { - "const": 1, "default": 1, + "minimum": 1.0, "title": "Schema Version", "type": "integer" }, - "target": { - "$ref": "#/components/schemas/ChangeImpactTarget" - } - }, - "required": [ - "target", - "merge_readiness", - "merge_admission", - "landing_outcome", - "generated_at" - ], - "title": "GovernanceProjection", - "type": "object" - }, - "GovernanceProjectionResponse": { - "additionalProperties": false, - "properties": { - "projection": { - "$ref": "#/components/schemas/GovernanceProjection" - }, "status": { - "const": "ok", - "default": "ok", + "enum": [ + "pending", + "planned", + "applied", + "unchanged" + ], "title": "Status", "type": "string" }, + "tls_owner": { + "default": "unknown", + "enum": [ + "launchplane", + "provider", + "external", + "none", + "unknown" + ], + "title": "Tls Owner", + "type": "string" + }, "trace_id": { "title": "Trace Id", "type": "string" } }, "required": [ + "record_id", + "product", + "context", + "mode", + "status", + "dry_run", + "requested_domains", + "operations", "trace_id", - "projection" + "reason", + "recorded_at" ], - "title": "GovernanceProjectionResponse", - "type": "object" - }, - "HTTPValidationError": { - "properties": { - "detail": { - "items": { - "$ref": "#/components/schemas/ValidationError" - }, - "title": "Detail", - "type": "array" - } - }, - "title": "HTTPValidationError", + "title": "IngressRouteAuditRecord", "type": "object" }, - "HealthResponse": { + "IngressRouteAuditRecordResponse": { "additionalProperties": false, "properties": { + "record": { + "$ref": "#/components/schemas/IngressRouteAuditRecord" + }, "status": { "const": "ok", "default": "ok", "title": "Status", "type": "string" }, - "storage_backend": { - "title": "Storage Backend", - "type": "string" - }, "trace_id": { "title": "Trace Id", "type": "string" @@ -11815,376 +11488,347 @@ }, "required": [ "trace_id", - "storage_backend" + "record" ], - "title": "HealthResponse", + "title": "IngressRouteAuditRecordResponse", "type": "object" }, - "HealthcheckEvidence": { + "IngressRouteAuditRecordsResponse": { "additionalProperties": false, "properties": { - "observed_runtime_identity": { - "anyOf": [ - { - "$ref": "#/components/schemas/RuntimeIdentity" - }, - { - "type": "null" - } - ] - }, - "runtime_identity_detail": { - "default": "", - "title": "Runtime Identity Detail", - "type": "string" - }, - "runtime_identity_status": { - "default": "unchecked", - "enum": [ - "unchecked", - "match", - "mismatch", - "missing", - "malformed", - "unverifiable" - ], - "title": "Runtime Identity Status", + "context": { + "title": "Context", "type": "string" }, - "status": { - "default": "skipped", - "enum": [ - "pending", - "pass", - "fail", - "skipped" - ], - "title": "Status", - "type": "string" + "count": { + "title": "Count", + "type": "integer" }, - "structured_health": { - "$ref": "#/components/schemas/StructuredHealthEvidence" + "limit": { + "title": "Limit", + "type": "integer" }, - "timeout_seconds": { - "anyOf": [ - { - "minimum": 1.0, - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Timeout Seconds" + "product": { + "title": "Product", + "type": "string" }, - "urls": { - "default": [], + "records": { "items": { - "type": "string" + "$ref": "#/components/schemas/IngressRouteAuditRecord" }, - "title": "Urls", + "title": "Records", "type": "array" }, - "verified": { - "default": false, - "title": "Verified", - "type": "boolean" + "status": { + "const": "ok", + "default": "ok", + "title": "Status", + "type": "string" + }, + "trace_id": { + "title": "Trace Id", + "type": "string" } }, - "title": "HealthcheckEvidence", + "required": [ + "trace_id", + "product", + "context", + "limit", + "count", + "records" + ], + "title": "IngressRouteAuditRecordsResponse", "type": "object" }, - "HistoricalCompletionEntryAssessment": { + "InspectionSetupManagedSecretMetadata": { "additionalProperties": false, "properties": { - "position": { - "title": "Position", - "type": "integer" + "binding_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Binding Id" }, - "pull_request_number": { - "title": "Pull Request Number", - "type": "integer" + "current_version_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Current Version Id" }, - "reason_code": { - "enum": [ - "exact_historical_completion", - "not_assessed", - "controller_unavailable", - "controller_busy", - "controller_not_reconciling", - "selector_mismatch", - "policy_unavailable", - "policy_changed", - "landing_plan_unavailable", - "landing_plan_ambiguous", - "landing_plan_binding_changed", - "stored_evidence_incomplete", - "entry_limit_exceeded", - "candidate_unavailable", - "candidate_history_limit_exceeded", - "candidate_ambiguous", - "candidate_binding_changed", - "stack_batch_unsupported", - "ordinary_target_unsupported", - "admission_present", - "store_unavailable", - "store_state_changed", - "provider_unavailable", - "provider_invalid_response", - "provider_binding_mismatch", - "pull_request_not_merged", - "no_op_unsupported", - "base_not_contains_merge", - "base_changed" + "secret_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } ], - "title": "Reason Code", - "type": "string" + "title": "Secret Id" }, - "status": { + "state": { + "default": "not_evaluated", "enum": [ - "eligible", - "unsupported", - "indeterminate" + "not_evaluated", + "metadata_recorded", + "secret_missing", + "secret_unreadable", + "secret_ambiguous", + "binding_missing", + "binding_unreadable", + "binding_ambiguous", + "binding_mismatch", + "version_pointer_missing", + "unavailable" ], - "title": "Status", + "title": "State", "type": "string" } }, - "required": [ - "position", - "pull_request_number", - "status", - "reason_code" - ], - "title": "HistoricalCompletionEntryAssessment", + "title": "InspectionSetupManagedSecretMetadata", "type": "object" }, - "HistoricalCompletionPreflightResponse": { + "InspectionSetupMetadata": { "additionalProperties": false, "properties": { - "records": { - "additionalProperties": { - "type": "string" - }, - "title": "Records", - "type": "object" - }, - "result": { - "$ref": "#/components/schemas/HistoricalCompletionPreflightResult" + "managed_secret": { + "$ref": "#/components/schemas/InspectionSetupManagedSecretMetadata" }, - "status": { - "const": "accepted", - "default": "accepted", - "title": "Status", - "type": "string" + "runtime": { + "$ref": "#/components/schemas/InspectionSetupRuntimeMetadata" }, - "trace_id": { - "title": "Trace Id", + "state": { + "default": "not_evaluated", + "enum": [ + "not_evaluated", + "metadata_recorded", + "incomplete", + "unavailable" + ], + "title": "State", "type": "string" } }, - "required": [ - "trace_id", - "result" - ], - "title": "HistoricalCompletionPreflightResponse", + "title": "InspectionSetupMetadata", "type": "object" }, - "HistoricalCompletionPreflightResult": { + "InspectionSetupRuntimeMetadata": { "additionalProperties": false, "properties": { - "base_branch": { - "title": "Base Branch", - "type": "string" - }, - "controller_action": { - "const": "historical_completion_preflight", - "default": "historical_completion_preflight", - "title": "Controller Action", - "type": "string" - }, - "historical_completion_preflight": { - "$ref": "#/components/schemas/MergeTrainHistoricalCompletionPreflight" + "app_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "App Id" }, - "mode": { - "const": "dry-run", - "default": "dry-run", - "title": "Mode", - "type": "string" + "recorded_at": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Recorded At" }, - "repository": { - "title": "Repository", + "state": { + "default": "not_evaluated", + "enum": [ + "not_evaluated", + "metadata_recorded", + "record_missing", + "record_unreadable", + "record_ambiguous", + "app_id_missing", + "app_id_invalid", + "unavailable" + ], + "title": "State", "type": "string" } }, - "required": [ - "repository", - "base_branch", - "historical_completion_preflight" - ], - "title": "HistoricalCompletionPreflightResult", + "title": "InspectionSetupRuntimeMetadata", "type": "object" }, - "IngressCanaryRouteApplyEnvelope": { + "LaunchplaneActiveAuthzPolicyResponse": { "additionalProperties": false, "properties": { - "canary_key": { - "title": "Canary Key", - "type": "string" - }, - "context": { - "title": "Context", - "type": "string" + "policy": { + "additionalProperties": true, + "title": "Policy", + "type": "object" }, - "product": { - "title": "Product", + "status": { + "const": "ok", + "default": "ok", + "title": "Status", "type": "string" }, - "reason": { - "title": "Reason", + "trace_id": { + "title": "Trace Id", "type": "string" - }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" } }, "required": [ - "product", - "context", - "canary_key", - "reason" + "trace_id", + "policy" ], - "title": "IngressCanaryRouteApplyEnvelope", + "title": "LaunchplaneActiveAuthzPolicyResponse", "type": "object" }, - "IngressCanaryRouteRecord": { + "LaunchplaneAuthzPolicy-Input": { "additionalProperties": false, "properties": { - "canary_key": { - "title": "Canary Key", - "type": "string" - }, - "certificate_id": { + "administrator_quorum": { "anyOf": [ { + "minimum": 1.0, "type": "integer" }, { - "const": "new", - "type": "string" + "type": "null" } ], - "title": "Certificate Id" + "title": "Administrator Quorum" }, - "context": { - "title": "Context", - "type": "string" + "github_actions": { + "default": [], + "items": { + "$ref": "#/components/schemas/GitHubActionsPolicyRule" + }, + "title": "Github Actions", + "type": "array" }, - "domain_name": { - "title": "Domain Name", - "type": "string" + "github_humans": { + "default": [], + "items": { + "$ref": "#/components/schemas/GitHubHumanPolicyRule" + }, + "title": "Github Humans", + "type": "array" }, - "edge_endpoint_key": { - "title": "Edge Endpoint Key", - "type": "string" + "local_admins": { + "default": [], + "items": { + "$ref": "#/components/schemas/LocalAdminPolicyRule" + }, + "title": "Local Admins", + "type": "array" }, - "expected_host_id": { - "minimum": 1.0, - "title": "Expected Host Id", - "type": "integer" + "local_operators": { + "default": [], + "items": { + "$ref": "#/components/schemas/LocalOperatorPolicyRule" + }, + "title": "Local Operators", + "type": "array" }, - "product": { - "title": "Product", - "type": "string" + "ordinary_agents": { + "default": [], + "items": { + "$ref": "#/components/schemas/OrdinaryAgentPolicyRule" + }, + "title": "Ordinary Agents", + "type": "array" }, "schema_version": { "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" - }, - "source_label": { - "default": "", - "title": "Source Label", - "type": "string" - }, - "status": { - "default": "active", "enum": [ - "active", - "disabled" + 1, + 2, + 3 ], - "title": "Status", - "type": "string" + "title": "Schema Version", + "type": "integer" }, - "updated_at": { - "title": "Updated At", - "type": "string" + "terminal_agents": { + "default": [], + "items": { + "$ref": "#/components/schemas/TerminalAgentPolicyRule" + }, + "title": "Terminal Agents", + "type": "array" } }, - "required": [ - "canary_key", - "product", - "context", - "domain_name", - "expected_host_id", - "edge_endpoint_key", - "certificate_id", - "updated_at" - ], - "title": "IngressCanaryRouteRecord", + "title": "LaunchplaneAuthzPolicy", "type": "object" }, - "IngressCanaryRouteRecordApplyEnvelope": { + "LaunchplaneAuthzPolicy-Output": { + "additionalProperties": true, + "type": "object" + }, + "LaunchplaneErrorDetail": { "additionalProperties": false, "properties": { - "confirmation": { - "default": "", - "title": "Confirmation", - "type": "string" - }, - "mode": { - "default": "dry-run", - "enum": [ - "dry-run", - "apply" - ], - "title": "Mode", + "code": { + "title": "Code", "type": "string" }, - "reason": { - "default": "", - "title": "Reason", + "message": { + "title": "Message", "type": "string" - }, - "route": { - "$ref": "#/components/schemas/IngressCanaryRouteRecord" - }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" } }, "required": [ - "route" + "code", + "message" ], - "title": "IngressCanaryRouteRecordApplyEnvelope", + "title": "LaunchplaneErrorDetail", "type": "object" }, - "IngressCanaryRouteRecordResponse": { + "LaunchplaneErrorResponse": { "additionalProperties": false, "properties": { - "record": { - "$ref": "#/components/schemas/IngressCanaryRouteRecord" + "authz": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Authz", + "x-launchplane-optional-response": true + }, + "error": { + "$ref": "#/components/schemas/LaunchplaneErrorDetail" + }, + "records": { + "anyOf": [ + { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Records", + "x-launchplane-optional-response": true }, "status": { - "const": "ok", - "default": "ok", + "default": "rejected", "title": "Status", "type": "string" }, @@ -12195,230 +11839,306 @@ }, "required": [ "trace_id", - "record" + "error" ], - "title": "IngressCanaryRouteRecordResponse", + "title": "LaunchplaneErrorResponse", "type": "object" }, - "IngressCanaryRouteRecordsResponse": { + "LaunchplaneLaneSummary": { "additionalProperties": false, "properties": { - "count": { - "title": "Count", - "type": "integer" - }, - "limit": { - "title": "Limit", - "type": "integer" - }, - "records": { - "items": { - "$ref": "#/components/schemas/IngressCanaryRouteRecord" - }, - "title": "Records", - "type": "array" + "artifact_manifest": { + "anyOf": [ + { + "$ref": "#/components/schemas/ArtifactIdentityManifest" + }, + { + "type": "null" + } + ] }, - "status": { - "const": "ok", - "default": "ok", - "title": "Status", + "context": { + "title": "Context", "type": "string" }, - "trace_id": { - "title": "Trace Id", - "type": "string" - } - }, - "required": [ - "trace_id", - "limit", - "count", - "records" - ], - "title": "IngressCanaryRouteRecordsResponse", - "type": "object" - }, - "IngressIdentityAccessBinding": { - "additionalProperties": false, - "properties": { - "mode": { - "$ref": "#/components/schemas/IngressIdentityAccessMode", - "default": "none" + "deployed_target": { + "anyOf": [ + { + "$ref": "#/components/schemas/DeployedTargetReference" + }, + { + "type": "null" + } + ] }, - "provider": { - "$ref": "#/components/schemas/IngressIdentityAccessProvider", - "default": "none" + "dokploy_target": { + "anyOf": [ + { + "$ref": "#/components/schemas/DokployTargetRecord" + }, + { + "type": "null" + } + ] }, - "schema_version": { - "default": 1, - "title": "Schema Version", - "type": "integer" + "dokploy_target_id": { + "anyOf": [ + { + "$ref": "#/components/schemas/DokployTargetIdRecord" + }, + { + "type": "null" + } + ] }, - "send_basic_auth": { - "default": false, - "title": "Send Basic Auth", - "type": "boolean" - } - }, - "title": "IngressIdentityAccessBinding", - "type": "object" - }, - "IngressIdentityAccessMode": { - "enum": [ - "none", - "forward-auth" - ], - "type": "string" - }, - "IngressIdentityAccessProvider": { - "enum": [ - "none", - "anubis", - "tinyauth", - "authelia", - "authentik" - ], - "type": "string" - }, - "IngressRouteAuditOperation": { - "additionalProperties": false, - "properties": { - "action": { - "title": "Action", + "instance": { + "title": "Instance", "type": "string" }, - "change_categories": { + "inventory": { + "anyOf": [ + { + "$ref": "#/components/schemas/EnvironmentInventory" + }, + { + "type": "null" + } + ] + }, + "latest_backup_gate": { + "anyOf": [ + { + "$ref": "#/components/schemas/BackupGateRecord" + }, + { + "type": "null" + } + ] + }, + "latest_deployment": { + "anyOf": [ + { + "$ref": "#/components/schemas/DeploymentRecord-Output" + }, + { + "type": "null" + } + ] + }, + "latest_promotion": { + "anyOf": [ + { + "$ref": "#/components/schemas/PromotionRecord-Output" + }, + { + "type": "null" + } + ] + }, + "odoo_instance_override": { + "anyOf": [ + { + "$ref": "#/components/schemas/OdooInstanceOverrideRecord" + }, + { + "type": "null" + } + ] + }, + "provenance": { + "$ref": "#/components/schemas/DataProvenance", + "default": { + "detail": "Launchplane has not recorded lane evidence for this context and instance.", + "freshness_status": "missing", + "recorded_at": "", + "refreshed_at": "", + "source_kind": "record", + "source_record_id": "", + "stale_after": "" + } + }, + "provider_target": { + "anyOf": [ + { + "$ref": "#/components/schemas/ProviderTargetRecord" + }, + { + "type": "null" + } + ] + }, + "release_tuple": { + "anyOf": [ + { + "$ref": "#/components/schemas/ReleaseTupleRecord" + }, + { + "type": "null" + } + ] + }, + "runtime_environment_records": { "default": [], "items": { - "type": "string" + "$ref": "#/components/schemas/RuntimeEnvironmentRecord" }, - "title": "Change Categories", + "title": "Runtime Environment Records", "type": "array" }, - "domain_names": { + "secret_bindings": { + "default": [], "items": { - "type": "string" + "$ref": "#/components/schemas/SecretBinding" }, - "title": "Domain Names", + "title": "Secret Bindings", "type": "array" - }, - "host_id": { + } + }, + "required": [ + "context", + "instance" + ], + "title": "LaunchplaneLaneSummary", + "type": "object" + }, + "LaunchplanePreviewSummary": { + "additionalProperties": false, + "properties": { + "latest_generation": { "anyOf": [ { - "minimum": 1.0, - "type": "integer" + "$ref": "#/components/schemas/PreviewGenerationRecord" }, { "type": "null" } - ], - "title": "Host Id" + ] }, - "requires_apply": { - "title": "Requires Apply", - "type": "boolean" + "preview": { + "$ref": "#/components/schemas/PreviewRecord" + }, + "provenance": { + "$ref": "#/components/schemas/DataProvenance", + "default": { + "detail": "Launchplane has not recorded preview generation evidence.", + "freshness_status": "missing", + "recorded_at": "", + "refreshed_at": "", + "source_kind": "record", + "source_record_id": "", + "stale_after": "" + } + }, + "recent_generations": { + "items": { + "$ref": "#/components/schemas/PreviewGenerationRecord" + }, + "title": "Recent Generations", + "type": "array" } }, "required": [ - "action", - "domain_names", - "requires_apply" + "preview" ], - "title": "IngressRouteAuditOperation", + "title": "LaunchplanePreviewSummary", "type": "object" }, - "IngressRouteAuditRecord": { + "LaunchplaneProductProfileRecord": { "additionalProperties": false, "properties": { - "context": { - "title": "Context", + "default_branch": { + "default": "main", + "title": "Default Branch", "type": "string" }, - "dry_run": { - "title": "Dry Run", - "type": "boolean" + "display_name": { + "title": "Display Name", + "type": "string" }, - "edge_endpoint_key": { - "default": "", - "title": "Edge Endpoint Key", + "driver_id": { + "title": "Driver Id", "type": "string" }, - "expected_host_id": { - "anyOf": [ - { - "minimum": 1.0, - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Expected Host Id" + "expected_config": { + "$ref": "#/components/schemas/ProductExpectedConfigProfile" }, - "idempotency_key": { + "health_path": { "default": "", - "title": "Idempotency Key", + "title": "Health Path", "type": "string" }, - "mode": { - "enum": [ - "dry-run", - "apply" - ], - "title": "Mode", - "type": "string" + "historical_contexts": { + "default": [], + "items": { + "type": "string" + }, + "title": "Historical Contexts", + "type": "array" }, - "operations": { + "image": { + "$ref": "#/components/schemas/ProductImageProfile" + }, + "lanes": { + "default": [], "items": { - "$ref": "#/components/schemas/IngressRouteAuditOperation" + "$ref": "#/components/schemas/ProductLaneProfile" }, - "title": "Operations", + "title": "Lanes", "type": "array" }, - "product": { - "title": "Product", + "lifecycle_state": { + "default": "active", + "enum": [ + "active", + "retiring", + "retired" + ], + "title": "Lifecycle State", "type": "string" }, - "provider": { - "default": "npmplus", - "title": "Provider", - "type": "string" + "owner": { + "$ref": "#/components/schemas/ProductOwnerProfile" }, - "provider_certificate_ref": { - "default": "", - "title": "Provider Certificate Ref", + "preview": { + "$ref": "#/components/schemas/ProductPreviewProfile" + }, + "product": { + "title": "Product", "type": "string" }, - "provider_host_id": { - "anyOf": [ - { - "minimum": 1.0, - "type": "integer" - }, - { - "type": "null" - } + "production_use": { + "default": "unknown", + "enum": [ + "unknown", + "prelaunch", + "live" ], - "title": "Provider Host Id" + "title": "Production Use", + "type": "string" }, - "reason": { - "title": "Reason", + "promotion_workflow": { + "$ref": "#/components/schemas/ProductPromotionWorkflowProfile" + }, + "repository": { + "title": "Repository", "type": "string" }, - "record_id": { - "title": "Record Id", + "repository_id": { + "default": "", + "title": "Repository Id", "type": "string" }, - "recorded_at": { - "title": "Recorded At", + "repository_owner_id": { + "default": "", + "title": "Repository Owner Id", "type": "string" }, - "requested_domains": { - "items": { - "type": "string" - }, - "title": "Requested Domains", - "type": "array" + "runtime_port": { + "default": 0, + "maximum": 65535.0, + "minimum": 0.0, + "title": "Runtime Port", + "type": "integer" }, "schema_version": { "default": 1, @@ -12426,54 +12146,32 @@ "title": "Schema Version", "type": "integer" }, - "status": { - "enum": [ - "pending", - "planned", - "applied", - "unchanged" - ], - "title": "Status", - "type": "string" - }, - "tls_owner": { - "default": "unknown", - "enum": [ - "launchplane", - "provider", - "external", - "none", - "unknown" - ], - "title": "Tls Owner", + "source": { + "title": "Source", "type": "string" }, - "trace_id": { - "title": "Trace Id", + "updated_at": { + "title": "Updated At", "type": "string" } }, "required": [ - "record_id", "product", - "context", - "mode", - "status", - "dry_run", - "requested_domains", - "operations", - "trace_id", - "reason", - "recorded_at" + "display_name", + "repository", + "driver_id", + "image", + "updated_at", + "source" ], - "title": "IngressRouteAuditRecord", + "title": "LaunchplaneProductProfileRecord", "type": "object" }, - "IngressRouteAuditRecordResponse": { + "LaunchplaneRuntimeResponse": { "additionalProperties": false, "properties": { - "record": { - "$ref": "#/components/schemas/IngressRouteAuditRecord" + "runtime": { + "$ref": "#/components/schemas/LaunchplaneRuntimeStatus" }, "status": { "const": "ok", @@ -12488,145 +12186,234 @@ }, "required": [ "trace_id", - "record" + "runtime" ], - "title": "IngressRouteAuditRecordResponse", + "title": "LaunchplaneRuntimeResponse", "type": "object" }, - "IngressRouteAuditRecordsResponse": { + "LaunchplaneRuntimeStatus": { "additionalProperties": false, "properties": { - "context": { - "title": "Context", - "type": "string" - }, - "count": { - "title": "Count", + "authz_policy_schema_version": { + "enum": [ + 1, + 2, + 3 + ], + "title": "Authz Policy Schema Version", "type": "integer" }, - "limit": { - "title": "Limit", - "type": "integer" + "authz_policy_sha256": { + "title": "Authz Policy Sha256", + "type": "string" }, - "product": { - "title": "Product", + "authz_policy_source": { + "title": "Authz Policy Source", "type": "string" }, - "records": { + "bootstrap_authz_policy_sha256": { + "title": "Bootstrap Authz Policy Sha256", + "type": "string" + }, + "compatible_database_schema_revisions": { "items": { - "$ref": "#/components/schemas/IngressRouteAuditRecord" + "type": "string" }, - "title": "Records", + "title": "Compatible Database Schema Revisions", "type": "array" }, - "status": { - "const": "ok", - "default": "ok", - "title": "Status", + "database_schema_revision": { + "title": "Database Schema Revision", "type": "string" }, - "trace_id": { - "title": "Trace Id", + "deployment_marker": { + "title": "Deployment Marker", + "type": "string" + }, + "docker_image_reference": { + "title": "Docker Image Reference", + "type": "string" + }, + "schema_migration_target_revision": { + "title": "Schema Migration Target Revision", + "type": "string" + }, + "service_audience": { + "title": "Service Audience", + "type": "string" + }, + "storage_backend": { + "title": "Storage Backend", "type": "string" } }, "required": [ - "trace_id", - "product", - "context", - "limit", - "count", - "records" - ], - "title": "IngressRouteAuditRecordsResponse", - "type": "object" - }, - "InspectionSetupManagedSecretMetadata": { - "additionalProperties": false, - "properties": { - "binding_id": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Binding Id" + "authz_policy_sha256", + "authz_policy_schema_version", + "authz_policy_source", + "bootstrap_authz_policy_sha256", + "compatible_database_schema_revisions", + "database_schema_revision", + "deployment_marker", + "docker_image_reference", + "schema_migration_target_revision", + "service_audience", + "storage_backend" + ], + "title": "LaunchplaneRuntimeStatus", + "type": "object" + }, + "LegacyProductionBackupMigrationRequest": { + "additionalProperties": false, + "properties": { + "context": { + "title": "Context", + "type": "string" }, - "current_version_id": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Current Version Id" + "destination_target_id": { + "title": "Destination Target Id", + "type": "string" }, - "secret_id": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Secret Id" + "effective_at": { + "title": "Effective At", + "type": "string" }, - "state": { - "default": "not_evaluated", + "independent_backup_max_evidence_age_seconds": { + "maximum": 604800.0, + "minimum": 1.0, + "title": "Independent Backup Max Evidence Age Seconds", + "type": "integer" + }, + "instance": { + "title": "Instance", + "type": "string" + }, + "mode": { "enum": [ - "not_evaluated", - "metadata_recorded", - "secret_missing", - "secret_unreadable", - "secret_ambiguous", - "binding_missing", - "binding_unreadable", - "binding_ambiguous", - "binding_mismatch", - "version_pointer_missing", - "unavailable" + "dry_run", + "apply" ], - "title": "State", + "title": "Mode", + "type": "string" + }, + "product": { + "title": "Product", + "type": "string" + }, + "promotion_action": { + "title": "Promotion Action", + "type": "string" + }, + "reason": { + "title": "Reason", + "type": "string" + }, + "review_after": { + "title": "Review After", + "type": "string" + }, + "reviewed_authority_digest": { + "default": "", + "title": "Reviewed Authority Digest", + "type": "string" + }, + "runtime_environment_updated_at": { + "title": "Runtime Environment Updated At", + "type": "string" + }, + "schema_version": { + "default": 1, + "minimum": 1.0, + "title": "Schema Version", + "type": "integer" + }, + "snapshot_max_evidence_age_seconds": { + "maximum": 86400.0, + "minimum": 1.0, + "title": "Snapshot Max Evidence Age Seconds", + "type": "integer" + }, + "source": { + "title": "Source", + "type": "string" + }, + "source_target_id": { + "title": "Source Target Id", "type": "string" } }, - "title": "InspectionSetupManagedSecretMetadata", + "required": [ + "mode", + "product", + "context", + "instance", + "promotion_action", + "source_target_id", + "destination_target_id", + "runtime_environment_updated_at", + "effective_at", + "review_after", + "snapshot_max_evidence_age_seconds", + "independent_backup_max_evidence_age_seconds", + "source", + "reason" + ], + "title": "LegacyProductionBackupMigrationRequest", "type": "object" }, - "InspectionSetupMetadata": { + "LocalAdminAccessPrincipal": { "additionalProperties": false, "properties": { - "managed_secret": { - "$ref": "#/components/schemas/InspectionSetupManagedSecretMetadata" + "principal_type": { + "const": "local_admin", + "default": "local_admin", + "title": "Principal Type", + "type": "string" }, - "runtime": { - "$ref": "#/components/schemas/InspectionSetupRuntimeMetadata" + "subject": { + "title": "Subject", + "type": "string" }, - "state": { - "default": "not_evaluated", - "enum": [ - "not_evaluated", - "metadata_recorded", - "incomplete", - "unavailable" - ], - "title": "State", + "token_label": { + "title": "Token Label", "type": "string" } }, - "title": "InspectionSetupMetadata", + "required": [ + "subject", + "token_label" + ], + "title": "LocalAdminAccessPrincipal", "type": "object" }, - "InspectionSetupRuntimeMetadata": { + "LocalAdminPolicyRule": { "additionalProperties": false, "properties": { - "app_id": { + "actions": { + "default": [], + "items": { + "type": "string" + }, + "title": "Actions", + "type": "array" + }, + "contexts": { + "default": [], + "items": { + "type": "string" + }, + "title": "Contexts", + "type": "array" + }, + "instances": { + "default": [], + "items": { + "type": "string" + }, + "title": "Instances", + "type": "array" + }, + "managed_rule_id": { "anyOf": [ { "type": "string" @@ -12635,9 +12422,9 @@ "type": "null" } ], - "title": "App Id" + "title": "Managed Rule Id" }, - "recorded_at": { + "managed_set_id": { "anyOf": [ { "type": "string" @@ -12646,499 +12433,569 @@ "type": "null" } ], - "title": "Recorded At" + "title": "Managed Set Id" }, - "state": { - "default": "not_evaluated", - "enum": [ - "not_evaluated", - "metadata_recorded", - "record_missing", - "record_unreadable", - "record_ambiguous", - "app_id_missing", - "app_id_invalid", - "unavailable" - ], - "title": "State", - "type": "string" + "products": { + "default": [], + "items": { + "type": "string" + }, + "title": "Products", + "type": "array" + }, + "subjects": { + "default": [], + "items": { + "type": "string" + }, + "title": "Subjects", + "type": "array" + }, + "token_labels": { + "default": [], + "items": { + "type": "string" + }, + "title": "Token Labels", + "type": "array" } }, - "title": "InspectionSetupRuntimeMetadata", + "title": "LocalAdminPolicyRule", "type": "object" }, - "LaunchplaneActiveAuthzPolicyResponse": { + "LocalOperatorAccessPrincipal": { "additionalProperties": false, "properties": { - "policy": { - "additionalProperties": true, - "title": "Policy", - "type": "object" + "principal_type": { + "const": "local_operator", + "default": "local_operator", + "title": "Principal Type", + "type": "string" }, - "status": { - "const": "ok", - "default": "ok", - "title": "Status", + "subject": { + "title": "Subject", "type": "string" }, - "trace_id": { - "title": "Trace Id", + "token_label": { + "title": "Token Label", "type": "string" } }, "required": [ - "trace_id", - "policy" + "subject", + "token_label" ], - "title": "LaunchplaneActiveAuthzPolicyResponse", + "title": "LocalOperatorAccessPrincipal", "type": "object" }, - "LaunchplaneAuthzPolicy-Input": { + "LocalOperatorPolicyRule": { "additionalProperties": false, "properties": { - "administrator_quorum": { - "anyOf": [ - { - "minimum": 1.0, - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Administrator Quorum" - }, - "github_actions": { + "actions": { "default": [], "items": { - "$ref": "#/components/schemas/GitHubActionsPolicyRule" + "type": "string" }, - "title": "Github Actions", + "title": "Actions", "type": "array" }, - "github_humans": { + "contexts": { "default": [], "items": { - "$ref": "#/components/schemas/GitHubHumanPolicyRule" + "type": "string" }, - "title": "Github Humans", + "title": "Contexts", "type": "array" }, - "local_admins": { + "instances": { "default": [], "items": { - "$ref": "#/components/schemas/LocalAdminPolicyRule" + "type": "string" }, - "title": "Local Admins", + "title": "Instances", "type": "array" }, - "local_operators": { + "managed_rule_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Managed Rule Id" + }, + "managed_set_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Managed Set Id" + }, + "products": { "default": [], "items": { - "$ref": "#/components/schemas/LocalOperatorPolicyRule" + "type": "string" }, - "title": "Local Operators", + "title": "Products", "type": "array" }, - "ordinary_agents": { + "subjects": { "default": [], "items": { - "$ref": "#/components/schemas/OrdinaryAgentPolicyRule" + "type": "string" }, - "title": "Ordinary Agents", + "title": "Subjects", "type": "array" }, - "schema_version": { - "default": 1, - "enum": [ - 1, - 2, - 3 - ], - "title": "Schema Version", - "type": "integer" - }, - "terminal_agents": { + "token_labels": { "default": [], "items": { - "$ref": "#/components/schemas/TerminalAgentPolicyRule" + "type": "string" }, - "title": "Terminal Agents", + "title": "Token Labels", "type": "array" } }, - "title": "LaunchplaneAuthzPolicy", - "type": "object" - }, - "LaunchplaneAuthzPolicy-Output": { - "additionalProperties": true, + "title": "LocalOperatorPolicyRule", "type": "object" }, - "LaunchplaneErrorDetail": { + "ManagedAuthzPolicySetAgentSummary": { "additionalProperties": false, "properties": { - "code": { - "title": "Code", + "added_rule_count": { + "minimum": 0.0, + "title": "Added Rule Count", + "type": "integer" + }, + "adopted_rule_count": { + "minimum": 0.0, + "title": "Adopted Rule Count", + "type": "integer" + }, + "changed": { + "title": "Changed", + "type": "boolean" + }, + "created_at": { + "title": "Created At", "type": "string" }, - "message": { - "title": "Message", + "descriptor_id": { + "const": "managed-authz-policy-set", + "default": "managed-authz-policy-set", + "title": "Descriptor Id", "type": "string" - } - }, - "required": [ - "code", - "message" - ], - "title": "LaunchplaneErrorDetail", - "type": "object" - }, - "LaunchplaneErrorResponse": { - "additionalProperties": false, - "properties": { - "authz": { - "anyOf": [ - { - "additionalProperties": true, - "type": "object" - }, - { - "type": "null" - } - ], - "title": "Authz", - "x-launchplane-optional-response": true }, - "error": { - "$ref": "#/components/schemas/LaunchplaneErrorDetail" + "descriptor_version": { + "title": "Descriptor Version", + "type": "integer" }, - "records": { - "anyOf": [ - { - "additionalProperties": { - "type": "string" - }, - "type": "object" - }, - { - "type": "null" - } + "expires_at": { + "title": "Expires At", + "type": "string" + }, + "operation_id": { + "title": "Operation Id", + "type": "string" + }, + "operational_readiness_blocked_rule_count": { + "minimum": 0.0, + "title": "Operational Readiness Blocked Rule Count", + "type": "integer" + }, + "policy_safety_blocker_count": { + "minimum": 0.0, + "title": "Policy Safety Blocker Count", + "type": "integer" + }, + "removed_rule_count": { + "minimum": 0.0, + "title": "Removed Rule Count", + "type": "integer" + }, + "result_status": { + "enum": [ + "ok", + "blocked" ], - "title": "Records", - "x-launchplane-optional-response": true + "title": "Result Status", + "type": "string" + }, + "schema_version": { + "default": 1, + "minimum": 1.0, + "title": "Schema Version", + "type": "integer" }, "status": { - "default": "rejected", + "enum": [ + "planned", + "approved", + "revoked", + "executing", + "executed", + "execution_failed", + "expired", + "cancelled" + ], "title": "Status", "type": "string" }, - "trace_id": { - "title": "Trace Id", - "type": "string" + "unchanged_rule_count": { + "minimum": 0.0, + "title": "Unchanged Rule Count", + "type": "integer" + }, + "updated_rule_count": { + "minimum": 0.0, + "title": "Updated Rule Count", + "type": "integer" } }, "required": [ - "trace_id", - "error" + "operation_id", + "descriptor_version", + "status", + "result_status", + "changed", + "added_rule_count", + "adopted_rule_count", + "updated_rule_count", + "removed_rule_count", + "unchanged_rule_count", + "policy_safety_blocker_count", + "operational_readiness_blocked_rule_count", + "created_at", + "expires_at" ], - "title": "LaunchplaneErrorResponse", + "title": "ManagedAuthzPolicySetAgentSummary", "type": "object" }, - "LaunchplaneLaneSummary": { + "ManagedAuthzPolicySetExecutionEvidence": { "additionalProperties": false, + "description": "Redacted policy-record evidence written by the service-internal worker.", "properties": { - "artifact_manifest": { - "anyOf": [ - { - "$ref": "#/components/schemas/ArtifactIdentityManifest" - }, - { - "type": "null" - } - ] + "changed": { + "title": "Changed", + "type": "boolean" }, - "context": { - "title": "Context", + "failure_code": { + "default": "", + "maxLength": 160, + "title": "Failure Code", "type": "string" }, - "deployed_target": { - "anyOf": [ - { - "$ref": "#/components/schemas/DeployedTargetReference" - }, - { - "type": "null" - } - ] + "previous_policy_sha256": { + "default": "", + "title": "Previous Policy Sha256", + "type": "string" }, - "dokploy_target": { - "anyOf": [ - { - "$ref": "#/components/schemas/DokployTargetRecord" - }, - { - "type": "null" - } - ] + "previous_record_id": { + "default": "", + "title": "Previous Record Id", + "type": "string" }, - "dokploy_target_id": { - "anyOf": [ - { - "$ref": "#/components/schemas/DokployTargetIdRecord" - }, - { - "type": "null" - } - ] + "previous_revision": { + "default": 0, + "minimum": 0.0, + "title": "Previous Revision", + "type": "integer" }, - "instance": { - "title": "Instance", + "reconciliation_required": { + "title": "Reconciliation Required", + "type": "boolean" + }, + "result_digest": { + "title": "Result Digest", "type": "string" }, - "inventory": { - "anyOf": [ - { - "$ref": "#/components/schemas/EnvironmentInventory" - }, - { - "type": "null" - } - ] + "result_status": { + "enum": [ + "ok", + "error" + ], + "title": "Result Status", + "type": "string" }, - "latest_backup_gate": { - "anyOf": [ - { - "$ref": "#/components/schemas/BackupGateRecord" - }, - { - "type": "null" - } - ] + "resulting_policy_sha256": { + "default": "", + "title": "Resulting Policy Sha256", + "type": "string" }, - "latest_deployment": { - "anyOf": [ - { - "$ref": "#/components/schemas/DeploymentRecord-Output" - }, - { - "type": "null" - } - ] + "resulting_record_id": { + "default": "", + "title": "Resulting Record Id", + "type": "string" }, - "latest_promotion": { - "anyOf": [ - { - "$ref": "#/components/schemas/PromotionRecord-Output" - }, - { - "type": "null" - } - ] + "resulting_revision": { + "default": 0, + "minimum": 0.0, + "title": "Resulting Revision", + "type": "integer" }, - "odoo_instance_override": { - "anyOf": [ - { - "$ref": "#/components/schemas/OdooInstanceOverrideRecord" - }, - { - "type": "null" - } - ] + "schema_version": { + "default": 1, + "minimum": 1.0, + "title": "Schema Version", + "type": "integer" + } + }, + "required": [ + "result_status", + "result_digest", + "changed", + "reconciliation_required" + ], + "title": "ManagedAuthzPolicySetExecutionEvidence", + "type": "object" + }, + "ManagedAuthzPolicySetHumanEvidence": { + "additionalProperties": false, + "properties": { + "diff": { + "$ref": "#/components/schemas/AuthzManagedPolicyDiff" }, - "provenance": { - "$ref": "#/components/schemas/DataProvenance", - "default": { - "detail": "Launchplane has not recorded lane evidence for this context and instance.", - "freshness_status": "missing", - "recorded_at": "", - "refreshed_at": "", - "source_kind": "record", - "source_record_id": "", - "stale_after": "" - } + "plan_digest": { + "title": "Plan Digest", + "type": "string" }, - "provider_target": { + "result_status": { + "enum": [ + "ok", + "blocked" + ], + "title": "Result Status", + "type": "string" + }, + "schema_version": { + "default": 1, + "minimum": 1.0, + "title": "Schema Version", + "type": "integer" + } + }, + "required": [ + "result_status", + "plan_digest", + "diff" + ], + "title": "ManagedAuthzPolicySetHumanEvidence", + "type": "object" + }, + "ManagedAuthzPolicySetProposalInput-Input": { + "additionalProperties": false, + "properties": { + "administrator_quorum_change": { "anyOf": [ { - "$ref": "#/components/schemas/ProviderTargetRecord" + "minimum": 1.0, + "type": "integer" }, { "type": "null" } - ] + ], + "title": "Administrator Quorum Change" }, - "release_tuple": { + "desired_policy": { + "$ref": "#/components/schemas/LaunchplaneAuthzPolicy-Input" + }, + "managed_set_id": { + "maxLength": 96, + "minLength": 1, + "title": "Managed Set Id", + "type": "string" + }, + "ordinary_agent_preparation_context": { "anyOf": [ { - "$ref": "#/components/schemas/ReleaseTupleRecord" + "$ref": "#/components/schemas/ManagedOrdinaryAgentPolicyPreparationContext" }, { "type": "null" } - ] + ], + "x-launchplane-optional-response": true }, - "runtime_environment_records": { - "default": [], - "items": { - "$ref": "#/components/schemas/RuntimeEnvironmentRecord" - }, - "title": "Runtime Environment Records", - "type": "array" + "reason": { + "maxLength": 240, + "minLength": 1, + "title": "Reason", + "type": "string" }, - "secret_bindings": { - "default": [], - "items": { - "$ref": "#/components/schemas/SecretBinding" - }, - "title": "Secret Bindings", - "type": "array" + "related_issue": { + "default": "", + "maxLength": 128, + "title": "Related Issue", + "type": "string" + }, + "schema_migration": { + "default": "reject", + "enum": [ + "reject", + "migrate_v2_to_v3" + ], + "title": "Schema Migration", + "type": "string", + "x-launchplane-optional-response": true + }, + "schema_version": { + "default": 1, + "minimum": 1.0, + "title": "Schema Version", + "type": "integer" } }, "required": [ - "context", - "instance" + "managed_set_id", + "desired_policy", + "reason" ], - "title": "LaunchplaneLaneSummary", + "title": "ManagedAuthzPolicySetProposalInput", "type": "object" }, - "LaunchplanePreviewSummary": { + "ManagedAuthzPolicySetProposalInput-Output": { "additionalProperties": false, "properties": { - "latest_generation": { + "administrator_quorum_change": { "anyOf": [ { - "$ref": "#/components/schemas/PreviewGenerationRecord" + "minimum": 1.0, + "type": "integer" }, { "type": "null" } - ] + ], + "title": "Administrator Quorum Change" }, - "preview": { - "$ref": "#/components/schemas/PreviewRecord" + "desired_policy": { + "$ref": "#/components/schemas/LaunchplaneAuthzPolicy-Output" }, - "provenance": { - "$ref": "#/components/schemas/DataProvenance", - "default": { - "detail": "Launchplane has not recorded preview generation evidence.", - "freshness_status": "missing", - "recorded_at": "", - "refreshed_at": "", - "source_kind": "record", - "source_record_id": "", - "stale_after": "" - } + "managed_set_id": { + "maxLength": 96, + "minLength": 1, + "title": "Managed Set Id", + "type": "string" }, - "recent_generations": { - "items": { - "$ref": "#/components/schemas/PreviewGenerationRecord" - }, - "title": "Recent Generations", - "type": "array" + "ordinary_agent_preparation_context": { + "anyOf": [ + { + "$ref": "#/components/schemas/ManagedOrdinaryAgentPolicyPreparationContext" + }, + { + "type": "null" + } + ], + "x-launchplane-optional-response": true + }, + "reason": { + "maxLength": 240, + "minLength": 1, + "title": "Reason", + "type": "string" + }, + "related_issue": { + "default": "", + "maxLength": 128, + "title": "Related Issue", + "type": "string" + }, + "schema_migration": { + "default": "reject", + "enum": [ + "reject", + "migrate_v2_to_v3" + ], + "title": "Schema Migration", + "type": "string", + "x-launchplane-optional-response": true + }, + "schema_version": { + "default": 1, + "minimum": 1.0, + "title": "Schema Version", + "type": "integer" } }, "required": [ - "preview" + "managed_set_id", + "desired_policy", + "reason" ], - "title": "LaunchplanePreviewSummary", + "title": "ManagedAuthzPolicySetProposalInput", "type": "object" }, - "LaunchplaneProductProfileRecord": { + "ManagedMergeTrainPolicyImportAgentSummary": { "additionalProperties": false, "properties": { - "default_branch": { - "default": "main", - "title": "Default Branch", - "type": "string" - }, - "display_name": { - "title": "Display Name", - "type": "string" - }, - "driver_id": { - "title": "Driver Id", - "type": "string" - }, - "expected_config": { - "$ref": "#/components/schemas/ProductExpectedConfigProfile" - }, - "health_path": { - "default": "", - "title": "Health Path", - "type": "string" + "active_target_count": { + "minimum": 0.0, + "title": "Active Target Count", + "type": "integer" }, - "historical_contexts": { + "added_policy_keys": { "default": [], "items": { "type": "string" }, - "title": "Historical Contexts", + "title": "Added Policy Keys", "type": "array" }, - "image": { - "$ref": "#/components/schemas/ProductImageProfile" + "candidate_target_count": { + "minimum": 0.0, + "title": "Candidate Target Count", + "type": "integer" }, - "lanes": { + "changed_policy_keys": { "default": [], "items": { - "$ref": "#/components/schemas/ProductLaneProfile" + "type": "string" }, - "title": "Lanes", + "title": "Changed Policy Keys", "type": "array" }, - "lifecycle_state": { - "default": "active", - "enum": [ - "active", - "retiring", - "retired" - ], - "title": "Lifecycle State", - "type": "string" - }, - "owner": { - "$ref": "#/components/schemas/ProductOwnerProfile" - }, - "preview": { - "$ref": "#/components/schemas/ProductPreviewProfile" - }, - "product": { - "title": "Product", + "created_at": { + "title": "Created At", "type": "string" }, - "production_use": { - "default": "unknown", - "enum": [ - "unknown", - "prelaunch", - "live" - ], - "title": "Production Use", + "descriptor_id": { + "const": "managed-merge-train-policy-import", + "default": "managed-merge-train-policy-import", + "title": "Descriptor Id", "type": "string" }, - "promotion_workflow": { - "$ref": "#/components/schemas/ProductPromotionWorkflowProfile" + "descriptor_version": { + "title": "Descriptor Version", + "type": "integer" }, - "repository": { - "title": "Repository", + "expires_at": { + "title": "Expires At", "type": "string" }, - "repository_id": { - "default": "", - "title": "Repository Id", + "operation_id": { + "title": "Operation Id", "type": "string" }, - "repository_owner_id": { - "default": "", - "title": "Repository Owner Id", - "type": "string" + "removed_policy_keys": { + "default": [], + "items": { + "type": "string" + }, + "title": "Removed Policy Keys", + "type": "array" }, - "runtime_port": { - "default": 0, - "maximum": 65535.0, - "minimum": 0.0, - "title": "Runtime Port", - "type": "integer" + "result_status": { + "const": "ok", + "default": "ok", + "title": "Result Status", + "type": "string" }, "schema_version": { "default": 1, @@ -13146,179 +13003,192 @@ "title": "Schema Version", "type": "integer" }, - "source": { - "title": "Source", + "status": { + "enum": [ + "planned", + "approved", + "revoked", + "executing", + "executed", + "execution_failed", + "expired", + "cancelled" + ], + "title": "Status", "type": "string" }, - "updated_at": { - "title": "Updated At", - "type": "string" + "unchanged_policy_key_count": { + "minimum": 0.0, + "title": "Unchanged Policy Key Count", + "type": "integer" } }, "required": [ - "product", - "display_name", - "repository", - "driver_id", - "image", - "updated_at", - "source" + "operation_id", + "descriptor_version", + "status", + "active_target_count", + "candidate_target_count", + "unchanged_policy_key_count", + "created_at", + "expires_at" ], - "title": "LaunchplaneProductProfileRecord", + "title": "ManagedMergeTrainPolicyImportAgentSummary", "type": "object" }, - "LaunchplaneRuntimeResponse": { + "ManagedMergeTrainPolicyImportExecutionEvidence": { "additionalProperties": false, + "description": "Redacted merge-train policy transition evidence written by the worker.", "properties": { - "runtime": { - "$ref": "#/components/schemas/LaunchplaneRuntimeStatus" + "active_policy_count": { + "default": 0, + "minimum": 0.0, + "title": "Active Policy Count", + "type": "integer" }, - "status": { - "const": "ok", - "default": "ok", - "title": "Status", - "type": "string" + "changed": { + "title": "Changed", + "type": "boolean" }, - "trace_id": { - "title": "Trace Id", - "type": "string" - } - }, - "required": [ - "trace_id", - "runtime" - ], - "title": "LaunchplaneRuntimeResponse", - "type": "object" - }, - "LaunchplaneRuntimeStatus": { - "additionalProperties": false, - "properties": { - "authz_policy_schema_version": { - "enum": [ - 1, - 2, - 3 - ], - "title": "Authz Policy Schema Version", - "type": "integer" - }, - "authz_policy_sha256": { - "title": "Authz Policy Sha256", + "failure_code": { + "default": "", + "maxLength": 160, + "title": "Failure Code", "type": "string" }, - "authz_policy_source": { - "title": "Authz Policy Source", + "previous_policy_sha256": { + "default": "", + "title": "Previous Policy Sha256", "type": "string" }, - "bootstrap_authz_policy_sha256": { - "title": "Bootstrap Authz Policy Sha256", + "previous_record_id": { + "default": "", + "title": "Previous Record Id", "type": "string" }, - "compatible_database_schema_revisions": { - "items": { - "type": "string" - }, - "title": "Compatible Database Schema Revisions", - "type": "array" + "reconciliation_required": { + "title": "Reconciliation Required", + "type": "boolean" }, - "database_schema_revision": { - "title": "Database Schema Revision", + "result_digest": { + "title": "Result Digest", "type": "string" }, - "deployment_marker": { - "title": "Deployment Marker", + "result_status": { + "enum": [ + "ok", + "error" + ], + "title": "Result Status", "type": "string" }, - "docker_image_reference": { - "title": "Docker Image Reference", + "resulting_policy_sha256": { + "default": "", + "title": "Resulting Policy Sha256", "type": "string" }, - "schema_migration_target_revision": { - "title": "Schema Migration Target Revision", + "resulting_record_id": { + "default": "", + "title": "Resulting Record Id", "type": "string" }, - "service_audience": { - "title": "Service Audience", + "schema_version": { + "default": 1, + "minimum": 1.0, + "title": "Schema Version", + "type": "integer" + }, + "superseded_policy_sha256": { + "default": "", + "title": "Superseded Policy Sha256", "type": "string" }, - "storage_backend": { - "title": "Storage Backend", + "superseded_record_id": { + "default": "", + "title": "Superseded Record Id", "type": "string" } }, "required": [ - "authz_policy_sha256", - "authz_policy_schema_version", - "authz_policy_source", - "bootstrap_authz_policy_sha256", - "compatible_database_schema_revisions", - "database_schema_revision", - "deployment_marker", - "docker_image_reference", - "schema_migration_target_revision", - "service_audience", - "storage_backend" + "result_status", + "result_digest", + "changed", + "reconciliation_required" ], - "title": "LaunchplaneRuntimeStatus", + "title": "ManagedMergeTrainPolicyImportExecutionEvidence", "type": "object" }, - "LegacyProductionBackupMigrationRequest": { + "ManagedMergeTrainPolicyImportHumanEvidence": { "additionalProperties": false, "properties": { - "context": { - "title": "Context", + "active_policy_sha256": { + "title": "Active Policy Sha256", "type": "string" }, - "destination_target_id": { - "title": "Destination Target Id", + "active_record_id": { + "title": "Active Record Id", "type": "string" }, - "effective_at": { - "title": "Effective At", + "active_status": { + "const": "active", + "default": "active", + "title": "Active Status", "type": "string" }, - "independent_backup_max_evidence_age_seconds": { - "maximum": 604800.0, - "minimum": 1.0, - "title": "Independent Backup Max Evidence Age Seconds", + "active_target_count": { + "minimum": 0.0, + "title": "Active Target Count", "type": "integer" }, - "instance": { - "title": "Instance", + "active_updated_at": { + "title": "Active Updated At", "type": "string" }, - "mode": { - "enum": [ - "dry_run", - "apply" - ], - "title": "Mode", - "type": "string" + "added_policy_keys": { + "default": [], + "items": { + "type": "string" + }, + "title": "Added Policy Keys", + "type": "array" }, - "product": { - "title": "Product", + "candidate_policy_sha256": { + "title": "Candidate Policy Sha256", "type": "string" }, - "promotion_action": { - "title": "Promotion Action", + "candidate_record_id": { + "title": "Candidate Record Id", "type": "string" }, - "reason": { - "title": "Reason", - "type": "string" + "candidate_target_count": { + "minimum": 0.0, + "title": "Candidate Target Count", + "type": "integer" }, - "review_after": { - "title": "Review After", - "type": "string" + "changed_policy_keys": { + "default": [], + "items": { + "type": "string" + }, + "title": "Changed Policy Keys", + "type": "array" }, - "reviewed_authority_digest": { - "default": "", - "title": "Reviewed Authority Digest", + "plan_digest": { + "title": "Plan Digest", "type": "string" }, - "runtime_environment_updated_at": { - "title": "Runtime Environment Updated At", + "removed_policy_keys": { + "default": [], + "items": { + "type": "string" + }, + "title": "Removed Policy Keys", + "type": "array" + }, + "result_status": { + "const": "ok", + "default": "ok", + "title": "Result Status", "type": "string" }, "schema_version": { @@ -13327,304 +13197,245 @@ "title": "Schema Version", "type": "integer" }, - "snapshot_max_evidence_age_seconds": { - "maximum": 86400.0, - "minimum": 1.0, - "title": "Snapshot Max Evidence Age Seconds", - "type": "integer" - }, - "source": { - "title": "Source", - "type": "string" - }, - "source_target_id": { - "title": "Source Target Id", - "type": "string" + "unchanged_policy_keys": { + "default": [], + "items": { + "type": "string" + }, + "title": "Unchanged Policy Keys", + "type": "array" } }, "required": [ - "mode", - "product", - "context", - "instance", - "promotion_action", - "source_target_id", - "destination_target_id", - "runtime_environment_updated_at", - "effective_at", - "review_after", - "snapshot_max_evidence_age_seconds", - "independent_backup_max_evidence_age_seconds", - "source", - "reason" + "plan_digest", + "active_record_id", + "active_updated_at", + "active_policy_sha256", + "active_target_count", + "candidate_record_id", + "candidate_policy_sha256", + "candidate_target_count" ], - "title": "LegacyProductionBackupMigrationRequest", + "title": "ManagedMergeTrainPolicyImportHumanEvidence", "type": "object" }, - "LocalAdminAccessPrincipal": { + "ManagedMergeTrainPolicyImportProposalInput-Input": { "additionalProperties": false, "properties": { - "principal_type": { - "const": "local_admin", - "default": "local_admin", - "title": "Principal Type", - "type": "string" + "preparation_context": { + "anyOf": [ + { + "$ref": "#/components/schemas/ManagedMergeTrainPolicyPreparationContext-Input" + }, + { + "type": "null" + } + ], + "x-launchplane-optional-response": true }, - "subject": { - "title": "Subject", + "reason": { + "maxLength": 240, + "minLength": 1, + "title": "Reason", "type": "string" }, - "token_label": { - "title": "Token Label", + "record": { + "$ref": "#/components/schemas/MergeTrainPolicyRecord-Input" + }, + "related_issue": { + "default": "", + "maxLength": 128, + "title": "Related Issue", "type": "string" + }, + "schema_version": { + "default": 1, + "minimum": 1.0, + "title": "Schema Version", + "type": "integer" } }, "required": [ - "subject", - "token_label" + "record", + "reason" ], - "title": "LocalAdminAccessPrincipal", + "title": "ManagedMergeTrainPolicyImportProposalInput", "type": "object" }, - "LocalAdminPolicyRule": { + "ManagedMergeTrainPolicyImportProposalInput-Output": { "additionalProperties": false, "properties": { - "actions": { - "default": [], - "items": { - "type": "string" - }, - "title": "Actions", - "type": "array" - }, - "contexts": { - "default": [], - "items": { - "type": "string" - }, - "title": "Contexts", - "type": "array" - }, - "instances": { - "default": [], - "items": { - "type": "string" - }, - "title": "Instances", - "type": "array" - }, - "managed_rule_id": { + "preparation_context": { "anyOf": [ { - "type": "string" + "$ref": "#/components/schemas/ManagedMergeTrainPolicyPreparationContext-Output" }, { "type": "null" } ], - "title": "Managed Rule Id" + "x-launchplane-optional-response": true }, - "managed_set_id": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Managed Set Id" + "reason": { + "maxLength": 240, + "minLength": 1, + "title": "Reason", + "type": "string" }, - "products": { - "default": [], - "items": { - "type": "string" - }, - "title": "Products", - "type": "array" + "record": { + "$ref": "#/components/schemas/MergeTrainPolicyRecord-Output" }, - "subjects": { - "default": [], - "items": { - "type": "string" - }, - "title": "Subjects", - "type": "array" + "related_issue": { + "default": "", + "maxLength": 128, + "title": "Related Issue", + "type": "string" }, - "token_labels": { - "default": [], - "items": { - "type": "string" - }, - "title": "Token Labels", - "type": "array" + "schema_version": { + "default": 1, + "minimum": 1.0, + "title": "Schema Version", + "type": "integer" } }, - "title": "LocalAdminPolicyRule", + "required": [ + "record", + "reason" + ], + "title": "ManagedMergeTrainPolicyImportProposalInput", "type": "object" }, - "LocalOperatorAccessPrincipal": { + "ManagedMergeTrainPolicyPreparationContext-Input": { "additionalProperties": false, + "description": "Server-derived fence binding an ordinary target proposal to one baseline.", "properties": { - "principal_type": { - "const": "local_operator", - "default": "local_operator", - "title": "Principal Type", + "expected_active_policy_sha256": { + "title": "Expected Active Policy Sha256", "type": "string" }, - "subject": { - "title": "Subject", + "expected_active_record_id": { + "title": "Expected Active Record Id", "type": "string" }, - "token_label": { - "title": "Token Label", + "expected_active_updated_at": { + "title": "Expected Active Updated At", + "type": "string" + }, + "intent": { + "$ref": "#/components/schemas/OrdinaryAgentMergeTrainTargetIntent-Input" + }, + "schema_version": { + "default": 1, + "minimum": 1.0, + "title": "Schema Version", + "type": "integer" + }, + "target_policy_key": { + "title": "Target Policy Key", "type": "string" } }, "required": [ - "subject", - "token_label" + "intent", + "expected_active_record_id", + "expected_active_policy_sha256", + "expected_active_updated_at", + "target_policy_key" ], - "title": "LocalOperatorAccessPrincipal", + "title": "ManagedMergeTrainPolicyPreparationContext", "type": "object" }, - "LocalOperatorPolicyRule": { + "ManagedMergeTrainPolicyPreparationContext-Output": { "additionalProperties": false, + "description": "Server-derived fence binding an ordinary target proposal to one baseline.", "properties": { - "actions": { - "default": [], - "items": { - "type": "string" - }, - "title": "Actions", - "type": "array" - }, - "contexts": { - "default": [], - "items": { - "type": "string" - }, - "title": "Contexts", - "type": "array" - }, - "instances": { - "default": [], - "items": { - "type": "string" - }, - "title": "Instances", - "type": "array" + "expected_active_policy_sha256": { + "title": "Expected Active Policy Sha256", + "type": "string" }, - "managed_rule_id": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Managed Rule Id" + "expected_active_record_id": { + "title": "Expected Active Record Id", + "type": "string" }, - "managed_set_id": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Managed Set Id" + "expected_active_updated_at": { + "title": "Expected Active Updated At", + "type": "string" }, - "products": { - "default": [], - "items": { - "type": "string" - }, - "title": "Products", - "type": "array" + "intent": { + "$ref": "#/components/schemas/OrdinaryAgentMergeTrainTargetIntent-Output" }, - "subjects": { - "default": [], - "items": { - "type": "string" - }, - "title": "Subjects", - "type": "array" + "schema_version": { + "default": 1, + "minimum": 1.0, + "title": "Schema Version", + "type": "integer" }, - "token_labels": { - "default": [], - "items": { - "type": "string" - }, - "title": "Token Labels", - "type": "array" + "target_policy_key": { + "title": "Target Policy Key", + "type": "string" } }, - "title": "LocalOperatorPolicyRule", + "required": [ + "intent", + "expected_active_record_id", + "expected_active_policy_sha256", + "expected_active_updated_at", + "target_policy_key" + ], + "title": "ManagedMergeTrainPolicyPreparationContext", "type": "object" }, - "ManagedAuthzPolicySetAgentSummary": { + "ManagedOrdinaryAgentPolicyPreparationContext": { "additionalProperties": false, + "description": "Server-bound baseline retained with a typed ordinary policy proposal.", "properties": { - "added_rule_count": { - "minimum": 0.0, - "title": "Added Rule Count", - "type": "integer" + "expected_inventory_record_id": { + "title": "Expected Inventory Record Id", + "type": "string" }, - "adopted_rule_count": { - "minimum": 0.0, - "title": "Adopted Rule Count", + "expected_inventory_revision": { + "minimum": 1.0, + "title": "Expected Inventory Revision", "type": "integer" }, - "changed": { - "title": "Changed", - "type": "boolean" - }, - "created_at": { - "title": "Created At", + "expected_inventory_sha256": { + "title": "Expected Inventory Sha256", "type": "string" }, - "descriptor_id": { - "const": "managed-authz-policy-set", - "default": "managed-authz-policy-set", - "title": "Descriptor Id", + "expected_merge_policy_record_id": { + "title": "Expected Merge Policy Record Id", "type": "string" }, - "descriptor_version": { - "title": "Descriptor Version", - "type": "integer" - }, - "expires_at": { - "title": "Expires At", + "expected_merge_policy_sha256": { + "title": "Expected Merge Policy Sha256", "type": "string" }, - "operation_id": { - "title": "Operation Id", + "expected_policy_record_id": { + "title": "Expected Policy Record Id", "type": "string" }, - "operational_readiness_blocked_rule_count": { - "minimum": 0.0, - "title": "Operational Readiness Blocked Rule Count", + "expected_policy_revision": { + "minimum": 1.0, + "title": "Expected Policy Revision", "type": "integer" }, - "policy_safety_blocker_count": { - "minimum": 0.0, - "title": "Policy Safety Blocker Count", - "type": "integer" + "expected_policy_sha256": { + "title": "Expected Policy Sha256", + "type": "string" }, - "removed_rule_count": { - "minimum": 0.0, - "title": "Removed Rule Count", - "type": "integer" + "intent": { + "$ref": "#/components/schemas/OrdinaryAgentDeliveryPolicyIntent" }, - "result_status": { - "enum": [ - "ok", - "blocked" - ], - "title": "Result Status", + "managed_rule_id": { + "maxLength": 128, + "minLength": 1, + "title": "Managed Rule Id", + "type": "string" + }, + "managed_set_id": { + "maxLength": 96, + "minLength": 1, + "title": "Managed Set Id", "type": "string" }, "schema_version": { @@ -13632,87 +13443,42 @@ "minimum": 1.0, "title": "Schema Version", "type": "integer" - }, - "status": { - "enum": [ - "planned", - "approved", - "revoked", - "executing", - "executed", - "execution_failed", - "expired", - "cancelled" - ], - "title": "Status", - "type": "string" - }, - "unchanged_rule_count": { - "minimum": 0.0, - "title": "Unchanged Rule Count", - "type": "integer" - }, - "updated_rule_count": { - "minimum": 0.0, - "title": "Updated Rule Count", - "type": "integer" } }, "required": [ - "operation_id", - "descriptor_version", - "status", - "result_status", - "changed", - "added_rule_count", - "adopted_rule_count", - "updated_rule_count", - "removed_rule_count", - "unchanged_rule_count", - "policy_safety_blocker_count", - "operational_readiness_blocked_rule_count", - "created_at", - "expires_at" + "intent", + "managed_set_id", + "managed_rule_id", + "expected_policy_record_id", + "expected_policy_revision", + "expected_policy_sha256", + "expected_inventory_record_id", + "expected_inventory_revision", + "expected_inventory_sha256", + "expected_merge_policy_record_id", + "expected_merge_policy_sha256" ], - "title": "ManagedAuthzPolicySetAgentSummary", + "title": "ManagedOrdinaryAgentPolicyPreparationContext", "type": "object" }, - "ManagedAuthzPolicySetExecutionEvidence": { + "ManagedSecretReencryptionHumanEvidence": { "additionalProperties": false, - "description": "Redacted policy-record evidence written by the service-internal worker.", "properties": { - "changed": { - "title": "Changed", - "type": "boolean" - }, - "failure_code": { - "default": "", - "maxLength": 160, - "title": "Failure Code", - "type": "string" - }, - "previous_policy_sha256": { - "default": "", - "title": "Previous Policy Sha256", - "type": "string" - }, - "previous_record_id": { - "default": "", - "title": "Previous Record Id", + "active_key_id": { + "title": "Active Key Id", "type": "string" }, - "previous_revision": { - "default": 0, + "configured_secret_count": { "minimum": 0.0, - "title": "Previous Revision", + "title": "Configured Secret Count", "type": "integer" }, - "reconciliation_required": { - "title": "Reconciliation Required", + "legacy_compatibility_key_loaded": { + "title": "Legacy Compatibility Key Loaded", "type": "boolean" }, - "result_digest": { - "title": "Result Digest", + "plan_digest": { + "title": "Plan Digest", "type": "string" }, "result_status": { @@ -13723,20 +13489,25 @@ "title": "Result Status", "type": "string" }, - "resulting_policy_sha256": { - "default": "", - "title": "Resulting Policy Sha256", - "type": "string" + "retirement_blocked_key_ids": { + "default": [], + "items": { + "type": "string" + }, + "title": "Retirement Blocked Key Ids", + "type": "array" }, - "resulting_record_id": { - "default": "", - "title": "Resulting Record Id", - "type": "string" + "retirement_ready_key_ids": { + "default": [], + "items": { + "type": "string" + }, + "title": "Retirement Ready Key Ids", + "type": "array" }, - "resulting_revision": { - "default": 0, + "rotation_candidate_count": { "minimum": 0.0, - "title": "Resulting Revision", + "title": "Rotation Candidate Count", "type": "integer" }, "schema_version": { @@ -13744,1398 +13515,1376 @@ "minimum": 1.0, "title": "Schema Version", "type": "integer" - } - }, - "required": [ - "result_status", - "result_digest", - "changed", - "reconciliation_required" - ], - "title": "ManagedAuthzPolicySetExecutionEvidence", - "type": "object" - }, - "ManagedAuthzPolicySetHumanEvidence": { - "additionalProperties": false, - "properties": { - "diff": { - "$ref": "#/components/schemas/AuthzManagedPolicyDiff" - }, - "plan_digest": { - "title": "Plan Digest", - "type": "string" }, - "result_status": { - "enum": [ - "ok", - "blocked" - ], - "title": "Result Status", - "type": "string" + "unchanged_count": { + "minimum": 0.0, + "title": "Unchanged Count", + "type": "integer" }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", + "unreadable_secret_count": { + "minimum": 0.0, + "title": "Unreadable Secret Count", "type": "integer" } }, "required": [ "result_status", "plan_digest", - "diff" + "configured_secret_count", + "rotation_candidate_count", + "unchanged_count", + "unreadable_secret_count", + "active_key_id", + "legacy_compatibility_key_loaded" ], - "title": "ManagedAuthzPolicySetHumanEvidence", + "title": "ManagedSecretReencryptionHumanEvidence", "type": "object" }, - "ManagedAuthzPolicySetProposalInput-Input": { + "ManagedSecretReencryptionPlanInput": { "additionalProperties": false, "properties": { - "administrator_quorum_change": { - "anyOf": [ - { - "minimum": 1.0, - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Administrator Quorum Change" - }, - "desired_policy": { - "$ref": "#/components/schemas/LaunchplaneAuthzPolicy-Input" - }, - "managed_set_id": { - "maxLength": 96, - "minLength": 1, - "title": "Managed Set Id", - "type": "string" - }, - "ordinary_agent_preparation_context": { - "anyOf": [ - { - "$ref": "#/components/schemas/ManagedOrdinaryAgentPolicyPreparationContext" - }, - { - "type": "null" - } - ], - "x-launchplane-optional-response": true - }, "reason": { "maxLength": 240, "minLength": 1, "title": "Reason", "type": "string" }, - "related_issue": { - "default": "", - "maxLength": 128, - "title": "Related Issue", - "type": "string" - }, - "schema_migration": { - "default": "reject", - "enum": [ - "reject", - "migrate_v2_to_v3" - ], - "title": "Schema Migration", - "type": "string", - "x-launchplane-optional-response": true - }, "schema_version": { "default": 1, "minimum": 1.0, "title": "Schema Version", "type": "integer" + }, + "source_label": { + "default": "privileged-operation-plan", + "maxLength": 96, + "minLength": 1, + "title": "Source Label", + "type": "string" } }, "required": [ - "managed_set_id", - "desired_policy", "reason" ], - "title": "ManagedAuthzPolicySetProposalInput", + "title": "ManagedSecretReencryptionPlanInput", "type": "object" }, - "ManagedAuthzPolicySetProposalInput-Output": { + "MergeAdmissionRecord": { "additionalProperties": false, "properties": { - "administrator_quorum_change": { - "anyOf": [ - { - "minimum": 1.0, - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Administrator Quorum Change" - }, - "desired_policy": { - "$ref": "#/components/schemas/LaunchplaneAuthzPolicy-Output" - }, - "managed_set_id": { - "maxLength": 96, - "minLength": 1, - "title": "Managed Set Id", + "admission_algorithm_version": { + "title": "Admission Algorithm Version", "type": "string" }, - "ordinary_agent_preparation_context": { - "anyOf": [ - { - "$ref": "#/components/schemas/ManagedOrdinaryAgentPolicyPreparationContext" - }, - { - "type": "null" - } - ], - "x-launchplane-optional-response": true - }, - "reason": { - "maxLength": 240, - "minLength": 1, - "title": "Reason", + "admission_binding_sha256": { + "default": "", + "title": "Admission Binding Sha256", "type": "string" }, - "related_issue": { + "admission_id": { "default": "", - "maxLength": 128, - "title": "Related Issue", + "title": "Admission Id", "type": "string" }, - "schema_migration": { - "default": "reject", - "enum": [ - "reject", - "migrate_v2_to_v3" - ], - "title": "Schema Migration", - "type": "string", - "x-launchplane-optional-response": true + "attempt_id": { + "title": "Attempt Id", + "type": "string" }, - "schema_version": { - "default": 1, + "attempt_sequence": { "minimum": 1.0, - "title": "Schema Version", - "type": "integer" - } - }, - "required": [ - "managed_set_id", - "desired_policy", - "reason" - ], - "title": "ManagedAuthzPolicySetProposalInput", - "type": "object" - }, - "ManagedMergeTrainPolicyImportAgentSummary": { - "additionalProperties": false, - "properties": { - "active_target_count": { - "minimum": 0.0, - "title": "Active Target Count", - "type": "integer" - }, - "added_policy_keys": { - "default": [], - "items": { - "type": "string" - }, - "title": "Added Policy Keys", - "type": "array" - }, - "candidate_target_count": { - "minimum": 0.0, - "title": "Candidate Target Count", + "title": "Attempt Sequence", "type": "integer" }, - "changed_policy_keys": { - "default": [], - "items": { - "type": "string" - }, - "title": "Changed Policy Keys", - "type": "array" - }, - "created_at": { - "title": "Created At", + "base_branch": { + "title": "Base Branch", "type": "string" }, - "descriptor_id": { - "const": "managed-merge-train-policy-import", - "default": "managed-merge-train-policy-import", - "title": "Descriptor Id", + "batch_id": { + "title": "Batch Id", "type": "string" }, - "descriptor_version": { - "title": "Descriptor Version", - "type": "integer" + "candidate_record_id": { + "title": "Candidate Record Id", + "type": "string" }, - "expires_at": { - "title": "Expires At", + "candidate_sha": { + "title": "Candidate Sha", "type": "string" }, - "operation_id": { - "title": "Operation Id", + "candidate_sha256": { + "title": "Candidate Sha256", "type": "string" }, - "removed_policy_keys": { - "default": [], - "items": { - "type": "string" - }, - "title": "Removed Policy Keys", - "type": "array" + "candidate_tree_sha": { + "title": "Candidate Tree Sha", + "type": "string" }, - "result_status": { - "const": "ok", - "default": "ok", - "title": "Result Status", + "controller_key": { + "title": "Controller Key", "type": "string" }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" + "created_at": { + "title": "Created At", + "type": "string" }, - "status": { - "enum": [ - "planned", - "approved", - "revoked", - "executing", - "executed", - "execution_failed", - "expired", - "cancelled" - ], - "title": "Status", + "decision": { + "const": "admitted", + "default": "admitted", + "title": "Decision", "type": "string" }, - "unchanged_policy_key_count": { - "minimum": 0.0, - "title": "Unchanged Policy Key Count", - "type": "integer" - } - }, - "required": [ - "operation_id", - "descriptor_version", - "status", - "active_target_count", - "candidate_target_count", - "unchanged_policy_key_count", - "created_at", - "expires_at" - ], - "title": "ManagedMergeTrainPolicyImportAgentSummary", - "type": "object" - }, - "ManagedMergeTrainPolicyImportExecutionEvidence": { - "additionalProperties": false, - "description": "Redacted merge-train policy transition evidence written by the worker.", - "properties": { - "active_policy_count": { - "default": 0, - "minimum": 0.0, - "title": "Active Policy Count", - "type": "integer" + "effective_base_sha": { + "title": "Effective Base Sha", + "type": "string" }, - "changed": { - "title": "Changed", - "type": "boolean" + "effective_base_tree_sha": { + "title": "Effective Base Tree Sha", + "type": "string" }, - "failure_code": { - "default": "", - "maxLength": 160, - "title": "Failure Code", + "expected_effect_sha": { + "title": "Expected Effect Sha", "type": "string" }, - "previous_policy_sha256": { - "default": "", - "title": "Previous Policy Sha256", + "landing_plan_id": { + "title": "Landing Plan Id", "type": "string" }, - "previous_record_id": { - "default": "", - "title": "Previous Record Id", + "landing_plan_record_id": { + "title": "Landing Plan Record Id", "type": "string" }, - "reconciliation_required": { - "title": "Reconciliation Required", - "type": "boolean" + "landing_plan_sha256": { + "title": "Landing Plan Sha256", + "type": "string" }, - "result_digest": { - "title": "Result Digest", + "lease_acquired_at": { + "title": "Lease Acquired At", "type": "string" }, - "result_status": { + "lease_expires_at": { + "title": "Lease Expires At", + "type": "string" + }, + "lease_owner": { + "title": "Lease Owner", + "type": "string" + }, + "merge_method": { "enum": [ - "ok", - "error" + "merge", + "squash", + "rebase" ], - "title": "Result Status", + "title": "Merge Method", "type": "string" }, - "resulting_policy_sha256": { - "default": "", - "title": "Resulting Policy Sha256", + "pull_request_head_sha": { + "title": "Pull Request Head Sha", "type": "string" }, - "resulting_record_id": { - "default": "", - "title": "Resulting Record Id", + "pull_request_head_tree_sha": { + "title": "Pull Request Head Tree Sha", + "type": "string" + }, + "pull_request_number": { + "minimum": 1.0, + "title": "Pull Request Number", + "type": "integer" + }, + "queue_position": { + "minimum": 1.0, + "title": "Queue Position", + "type": "integer" + }, + "readiness": { + "$ref": "#/components/schemas/MergeReadinessResult" + }, + "repository": { + "title": "Repository", "type": "string" }, "schema_version": { + "const": 1, "default": 1, - "minimum": 1.0, "title": "Schema Version", "type": "integer" }, - "superseded_policy_sha256": { - "default": "", - "title": "Superseded Policy Sha256", + "source": { + "title": "Source", "type": "string" }, - "superseded_record_id": { - "default": "", - "title": "Superseded Record Id", + "structural_provenance_sha256": { + "title": "Structural Provenance Sha256", "type": "string" + }, + "structural_result": { + "$ref": "#/components/schemas/MergeTrainStructuralCandidateResult" } }, "required": [ - "result_status", - "result_digest", - "changed", - "reconciliation_required" + "attempt_id", + "attempt_sequence", + "source", + "repository", + "base_branch", + "pull_request_number", + "queue_position", + "batch_id", + "candidate_record_id", + "landing_plan_record_id", + "landing_plan_id", + "merge_method", + "effective_base_sha", + "effective_base_tree_sha", + "pull_request_head_sha", + "pull_request_head_tree_sha", + "candidate_sha", + "candidate_tree_sha", + "expected_effect_sha", + "candidate_sha256", + "structural_provenance_sha256", + "landing_plan_sha256", + "readiness", + "structural_result", + "admission_algorithm_version", + "controller_key", + "lease_owner", + "lease_acquired_at", + "lease_expires_at", + "created_at" ], - "title": "ManagedMergeTrainPolicyImportExecutionEvidence", + "title": "MergeAdmissionRecord", "type": "object" }, - "ManagedMergeTrainPolicyImportHumanEvidence": { + "MergeBatchNoEffectEvidence": { "additionalProperties": false, "properties": { - "active_policy_sha256": { - "title": "Active Policy Sha256", + "base_contains_head": { + "const": false, + "title": "Base Contains Head", + "type": "boolean" + }, + "head_sha": { + "title": "Head Sha", "type": "string" }, - "active_record_id": { - "title": "Active Record Id", + "merged": { + "const": false, + "title": "Merged", + "type": "boolean" + }, + "pull_request_number": { + "exclusiveMinimum": 0.0, + "title": "Pull Request Number", + "type": "integer" + }, + "state": { + "enum": [ + "open", + "closed" + ], + "title": "State", + "type": "string" + } + }, + "required": [ + "pull_request_number", + "head_sha", + "state", + "merged", + "base_contains_head" + ], + "title": "MergeBatchNoEffectEvidence", + "type": "object" + }, + "MergeLandingOutcomeRecord": { + "additionalProperties": false, + "properties": { + "admission_binding_sha256": { + "title": "Admission Binding Sha256", "type": "string" }, - "active_status": { - "const": "active", - "default": "active", - "title": "Active Status", + "admission_id": { + "title": "Admission Id", "type": "string" }, - "active_target_count": { - "minimum": 0.0, - "title": "Active Target Count", - "type": "integer" + "attempt_id": { + "title": "Attempt Id", + "type": "string" }, - "active_updated_at": { - "title": "Active Updated At", + "base_branch": { + "title": "Base Branch", "type": "string" }, - "added_policy_keys": { - "default": [], - "items": { - "type": "string" - }, - "title": "Added Policy Keys", - "type": "array" + "base_contains_merge_commit": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "title": "Base Contains Merge Commit" }, - "candidate_policy_sha256": { - "title": "Candidate Policy Sha256", + "batch_no_effect": { + "anyOf": [ + { + "$ref": "#/components/schemas/MergeBatchNoEffectEvidence" + }, + { + "type": "null" + } + ], + "x-launchplane-optional-response": true + }, + "exact_landing_confirmed": { + "default": false, + "title": "Exact Landing Confirmed", + "type": "boolean" + }, + "merge_commit_sha": { + "default": "", + "title": "Merge Commit Sha", "type": "string" }, - "candidate_record_id": { - "title": "Candidate Record Id", + "merge_commit_tree_sha": { + "default": "", + "title": "Merge Commit Tree Sha", "type": "string" }, - "candidate_target_count": { - "minimum": 0.0, - "title": "Candidate Target Count", + "observation_sequence": { + "minimum": 1.0, + "title": "Observation Sequence", "type": "integer" }, - "changed_policy_keys": { - "default": [], - "items": { - "type": "string" - }, - "title": "Changed Policy Keys", - "type": "array" + "observed_at": { + "title": "Observed At", + "type": "string" }, - "plan_digest": { - "title": "Plan Digest", + "observed_base_sha": { + "default": "", + "title": "Observed Base Sha", "type": "string" }, - "removed_policy_keys": { - "default": [], - "items": { - "type": "string" - }, - "title": "Removed Policy Keys", - "type": "array" + "observed_base_tree_sha": { + "default": "", + "title": "Observed Base Tree Sha", + "type": "string" }, - "result_status": { - "const": "ok", - "default": "ok", - "title": "Result Status", + "observed_pull_request_head_sha": { + "default": "", + "title": "Observed Pull Request Head Sha", "type": "string" }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" + "observed_pull_request_head_tree_sha": { + "default": "", + "title": "Observed Pull Request Head Tree Sha", + "type": "string" }, - "unchanged_policy_keys": { - "default": [], - "items": { - "type": "string" - }, - "title": "Unchanged Policy Keys", - "type": "array" - } - }, - "required": [ - "plan_digest", - "active_record_id", - "active_updated_at", - "active_policy_sha256", - "active_target_count", - "candidate_record_id", - "candidate_policy_sha256", - "candidate_target_count" - ], - "title": "ManagedMergeTrainPolicyImportHumanEvidence", - "type": "object" - }, - "ManagedMergeTrainPolicyImportProposalInput-Input": { - "additionalProperties": false, - "properties": { - "preparation_context": { + "observed_pull_request_state": { + "default": "", + "enum": [ + "", + "open", + "closed", + "merged" + ], + "title": "Observed Pull Request State", + "type": "string" + }, + "outcome_binding_sha256": { + "default": "", + "title": "Outcome Binding Sha256", + "type": "string" + }, + "outcome_id": { + "default": "", + "title": "Outcome Id", + "type": "string" + }, + "prior_outcome_id": { + "default": "", + "title": "Prior Outcome Id", + "type": "string" + }, + "provider_conclusive_rejection": { + "default": false, + "title": "Provider Conclusive Rejection", + "type": "boolean" + }, + "provider_effect_attempted": { + "title": "Provider Effect Attempted", + "type": "boolean" + }, + "provider_message": { + "default": "", + "title": "Provider Message", + "type": "string" + }, + "provider_request_id": { + "default": "", + "title": "Provider Request Id", + "type": "string" + }, + "provider_status_code": { "anyOf": [ { - "$ref": "#/components/schemas/ManagedMergeTrainPolicyPreparationContext-Input" + "maximum": 599.0, + "minimum": 100.0, + "type": "integer" }, { "type": "null" } ], - "x-launchplane-optional-response": true + "title": "Provider Status Code" + }, + "pull_request_number": { + "minimum": 1.0, + "title": "Pull Request Number", + "type": "integer" }, "reason": { - "maxLength": 240, - "minLength": 1, + "enum": [ + "provider_and_git_confirmed", + "already_contained_no_provider_effect", + "provider_rejected", + "dispatch_not_attempted", + "batch_not_dispatched", + "batch_reconciliation_confirmed_no_effect", + "reconciliation_confirmed_no_effect", + "provider_transport_ambiguous", + "process_interrupted", + "lease_lost_after_admission", + "landing_evidence_incomplete", + "landing_evidence_contradicted" + ], "title": "Reason", "type": "string" }, - "record": { - "$ref": "#/components/schemas/MergeTrainPolicyRecord-Input" - }, - "related_issue": { - "default": "", - "maxLength": 128, - "title": "Related Issue", + "repository": { + "title": "Repository", "type": "string" }, "schema_version": { + "const": 1, "default": 1, - "minimum": 1.0, "title": "Schema Version", "type": "integer" + }, + "source": { + "title": "Source", + "type": "string" + }, + "status": { + "enum": [ + "landed", + "rejected", + "reconcile_required" + ], + "title": "Status", + "type": "string" } }, "required": [ - "record", - "reason" + "admission_id", + "admission_binding_sha256", + "attempt_id", + "observation_sequence", + "source", + "repository", + "base_branch", + "pull_request_number", + "status", + "reason", + "provider_effect_attempted", + "observed_at" ], - "title": "ManagedMergeTrainPolicyImportProposalInput", + "title": "MergeLandingOutcomeRecord", "type": "object" }, - "ManagedMergeTrainPolicyImportProposalInput-Output": { + "MergeReadinessAdvisoryObservation": { "additionalProperties": false, "properties": { - "preparation_context": { + "app_id": { "anyOf": [ { - "$ref": "#/components/schemas/ManagedMergeTrainPolicyPreparationContext-Output" + "minimum": 1.0, + "type": "integer" }, { "type": "null" } ], - "x-launchplane-optional-response": true + "title": "App Id" }, - "reason": { - "maxLength": 240, - "minLength": 1, - "title": "Reason", + "name": { + "title": "Name", "type": "string" }, - "record": { - "$ref": "#/components/schemas/MergeTrainPolicyRecord-Output" - }, - "related_issue": { - "default": "", - "maxLength": 128, - "title": "Related Issue", + "state": { + "title": "State", "type": "string" - }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" } }, "required": [ - "record", - "reason" + "name", + "state" ], - "title": "ManagedMergeTrainPolicyImportProposalInput", + "title": "MergeReadinessAdvisoryObservation", "type": "object" }, - "ManagedMergeTrainPolicyPreparationContext-Input": { + "MergeReadinessCandidateEvidence": { "additionalProperties": false, - "description": "Server-derived fence binding an ordinary target proposal to one baseline.", "properties": { - "expected_active_policy_sha256": { - "title": "Expected Active Policy Sha256", + "base_sha": { + "default": "", + "title": "Base Sha", "type": "string" }, - "expected_active_record_id": { - "title": "Expected Active Record Id", + "candidate_sha": { + "default": "", + "title": "Candidate Sha", "type": "string" }, - "expected_active_updated_at": { - "title": "Expected Active Updated At", + "pull_request_head_sha": { + "default": "", + "title": "Pull Request Head Sha", "type": "string" }, - "intent": { - "$ref": "#/components/schemas/OrdinaryAgentMergeTrainTargetIntent-Input" + "pull_request_number": { + "anyOf": [ + { + "minimum": 1.0, + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Pull Request Number" }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" + "queue_position": { + "anyOf": [ + { + "minimum": 1.0, + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Queue Position" }, - "target_policy_key": { - "title": "Target Policy Key", + "record_id": { + "default": "", + "title": "Record Id", + "type": "string" + }, + "repository": { + "default": "", + "title": "Repository", + "type": "string" + }, + "structural_status": { + "enum": [ + "exact", + "recorded_rolling", + "mismatch", + "unknown" + ], + "title": "Structural Status", "type": "string" } }, "required": [ - "intent", - "expected_active_record_id", - "expected_active_policy_sha256", - "expected_active_updated_at", - "target_policy_key" + "structural_status" ], - "title": "ManagedMergeTrainPolicyPreparationContext", + "title": "MergeReadinessCandidateEvidence", "type": "object" }, - "ManagedMergeTrainPolicyPreparationContext-Output": { + "MergeReadinessCandidateFacet": { "additionalProperties": false, - "description": "Server-derived fence binding an ordinary target proposal to one baseline.", "properties": { - "expected_active_policy_sha256": { - "title": "Expected Active Policy Sha256", - "type": "string" - }, - "expected_active_record_id": { - "title": "Expected Active Record Id", - "type": "string" + "evidence": { + "$ref": "#/components/schemas/MergeReadinessCandidateEvidence" }, - "expected_active_updated_at": { - "title": "Expected Active Updated At", - "type": "string" - }, - "intent": { - "$ref": "#/components/schemas/OrdinaryAgentMergeTrainTargetIntent-Output" - }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" + "reason_codes": { + "items": { + "enum": [ + "owner_not_required", + "owner_acceptance_valid", + "owner_acceptance_missing", + "owner_changes_requested", + "owner_acceptance_revoked", + "owner_acceptance_stale", + "owner_evidence_stale", + "owner_evidence_unavailable", + "owner_authority_unavailable", + "owner_authority_denied", + "owner_preview_evidence_unavailable", + "owner_preview_evidence_stale", + "owner_review_expired", + "owner_preview_isolation_insufficient", + "owner_contributing_identity_unknown", + "owner_self_review_denied", + "owner_review_context_missing", + "owner_evidence_head_mismatch", + "owner_evidence_tree_mismatch", + "checks_passed", + "checks_pending", + "checks_failed", + "checks_unknown", + "checks_head_mismatch", + "engineering_review_approved", + "engineering_review_pending", + "engineering_review_changes_requested", + "engineering_review_blocked", + "engineering_review_unknown", + "engineering_review_stale", + "engineering_review_head_mismatch", + "engineering_review_tree_mismatch", + "engineering_review_evidence_missing", + "policy_fingerprints_match", + "policy_impact_missing", + "policy_impact_drift", + "policy_technical_checks_missing", + "policy_technical_checks_drift", + "policy_engineering_review_missing", + "policy_engineering_review_drift", + "policy_ruleset_missing", + "policy_ruleset_drift", + "policy_merge_train_missing", + "policy_merge_train_drift", + "policy_authorization_missing", + "policy_authorization_drift", + "policy_admission_algorithm_missing", + "policy_admission_algorithm_drift", + "candidate_exact", + "candidate_recorded_rolling", + "candidate_identity_mismatch", + "candidate_identity_unknown", + "candidate_queue_mismatch", + "candidate_base_mismatch", + "candidate_head_mismatch", + "controller_scope_mismatch", + "controller_lease_held", + "controller_lease_missing", + "controller_lease_lost", + "controller_lease_expired", + "expected_sha_match", + "expected_sha_mismatch" + ], + "type": "string" + }, + "title": "Reason Codes", + "type": "array" }, - "target_policy_key": { - "title": "Target Policy Key", + "state": { + "enum": [ + "ready", + "blocked_owner_evidence", + "blocked_checks", + "blocked_engineering_review", + "blocked_policy", + "blocked_candidate_identity", + "unknown" + ], + "title": "State", "type": "string" } }, "required": [ - "intent", - "expected_active_record_id", - "expected_active_policy_sha256", - "expected_active_updated_at", - "target_policy_key" + "evidence", + "state", + "reason_codes" ], - "title": "ManagedMergeTrainPolicyPreparationContext", + "title": "MergeReadinessCandidateFacet", "type": "object" }, - "ManagedOrdinaryAgentPolicyPreparationContext": { + "MergeReadinessEngineeringEvidenceReference": { "additionalProperties": false, - "description": "Server-bound baseline retained with a typed ordinary policy proposal.", "properties": { - "expected_inventory_record_id": { - "title": "Expected Inventory Record Id", - "type": "string" - }, - "expected_inventory_revision": { - "minimum": 1.0, - "title": "Expected Inventory Revision", - "type": "integer" - }, - "expected_inventory_sha256": { - "title": "Expected Inventory Sha256", - "type": "string" - }, - "expected_merge_policy_record_id": { - "title": "Expected Merge Policy Record Id", - "type": "string" - }, - "expected_merge_policy_sha256": { - "title": "Expected Merge Policy Sha256", - "type": "string" - }, - "expected_policy_record_id": { - "title": "Expected Policy Record Id", + "evidence_digest": { + "title": "Evidence Digest", "type": "string" }, - "expected_policy_revision": { - "minimum": 1.0, - "title": "Expected Policy Revision", - "type": "integer" - }, - "expected_policy_sha256": { - "title": "Expected Policy Sha256", + "head_sha": { + "title": "Head Sha", "type": "string" }, - "intent": { - "$ref": "#/components/schemas/OrdinaryAgentDeliveryPolicyIntent" - }, - "managed_rule_id": { - "maxLength": 128, - "minLength": 1, - "title": "Managed Rule Id", + "run_id": { + "title": "Run Id", "type": "string" }, - "managed_set_id": { - "maxLength": 96, - "minLength": 1, - "title": "Managed Set Id", + "tree_sha": { + "title": "Tree Sha", "type": "string" - }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" } }, "required": [ - "intent", - "managed_set_id", - "managed_rule_id", - "expected_policy_record_id", - "expected_policy_revision", - "expected_policy_sha256", - "expected_inventory_record_id", - "expected_inventory_revision", - "expected_inventory_sha256", - "expected_merge_policy_record_id", - "expected_merge_policy_sha256" + "run_id", + "head_sha", + "tree_sha", + "evidence_digest" ], - "title": "ManagedOrdinaryAgentPolicyPreparationContext", + "title": "MergeReadinessEngineeringEvidenceReference", "type": "object" }, - "ManagedSecretReencryptionHumanEvidence": { + "MergeReadinessEngineeringReviewFacet": { "additionalProperties": false, "properties": { - "active_key_id": { - "title": "Active Key Id", - "type": "string" - }, - "configured_secret_count": { - "minimum": 0.0, - "title": "Configured Secret Count", - "type": "integer" - }, - "legacy_compatibility_key_loaded": { - "title": "Legacy Compatibility Key Loaded", - "type": "boolean" - }, - "plan_digest": { - "title": "Plan Digest", + "decision_binding_sha256": { + "default": "", + "title": "Decision Binding Sha256", "type": "string" }, - "result_status": { - "enum": [ - "ok", - "error" - ], - "title": "Result Status", + "decision_id": { + "default": "", + "title": "Decision Id", "type": "string" }, - "retirement_blocked_key_ids": { + "evidence": { "default": [], "items": { - "type": "string" + "$ref": "#/components/schemas/MergeReadinessEngineeringEvidenceReference" }, - "title": "Retirement Blocked Key Ids", + "title": "Evidence", "type": "array" }, - "retirement_ready_key_ids": { - "default": [], + "head_sha": { + "default": "", + "title": "Head Sha", + "type": "string" + }, + "reason_codes": { "items": { + "enum": [ + "owner_not_required", + "owner_acceptance_valid", + "owner_acceptance_missing", + "owner_changes_requested", + "owner_acceptance_revoked", + "owner_acceptance_stale", + "owner_evidence_stale", + "owner_evidence_unavailable", + "owner_authority_unavailable", + "owner_authority_denied", + "owner_preview_evidence_unavailable", + "owner_preview_evidence_stale", + "owner_review_expired", + "owner_preview_isolation_insufficient", + "owner_contributing_identity_unknown", + "owner_self_review_denied", + "owner_review_context_missing", + "owner_evidence_head_mismatch", + "owner_evidence_tree_mismatch", + "checks_passed", + "checks_pending", + "checks_failed", + "checks_unknown", + "checks_head_mismatch", + "engineering_review_approved", + "engineering_review_pending", + "engineering_review_changes_requested", + "engineering_review_blocked", + "engineering_review_unknown", + "engineering_review_stale", + "engineering_review_head_mismatch", + "engineering_review_tree_mismatch", + "engineering_review_evidence_missing", + "policy_fingerprints_match", + "policy_impact_missing", + "policy_impact_drift", + "policy_technical_checks_missing", + "policy_technical_checks_drift", + "policy_engineering_review_missing", + "policy_engineering_review_drift", + "policy_ruleset_missing", + "policy_ruleset_drift", + "policy_merge_train_missing", + "policy_merge_train_drift", + "policy_authorization_missing", + "policy_authorization_drift", + "policy_admission_algorithm_missing", + "policy_admission_algorithm_drift", + "candidate_exact", + "candidate_recorded_rolling", + "candidate_identity_mismatch", + "candidate_identity_unknown", + "candidate_queue_mismatch", + "candidate_base_mismatch", + "candidate_head_mismatch", + "controller_scope_mismatch", + "controller_lease_held", + "controller_lease_missing", + "controller_lease_lost", + "controller_lease_expired", + "expected_sha_match", + "expected_sha_mismatch" + ], "type": "string" }, - "title": "Retirement Ready Key Ids", + "title": "Reason Codes", "type": "array" }, - "rotation_candidate_count": { - "minimum": 0.0, - "title": "Rotation Candidate Count", - "type": "integer" - }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" + "state": { + "enum": [ + "ready", + "blocked_owner_evidence", + "blocked_checks", + "blocked_engineering_review", + "blocked_policy", + "blocked_candidate_identity", + "unknown" + ], + "title": "State", + "type": "string" }, - "unchanged_count": { - "minimum": 0.0, - "title": "Unchanged Count", - "type": "integer" + "status": { + "title": "Status", + "type": "string" }, - "unreadable_secret_count": { - "minimum": 0.0, - "title": "Unreadable Secret Count", - "type": "integer" + "tree_sha": { + "default": "", + "title": "Tree Sha", + "type": "string" } }, "required": [ - "result_status", - "plan_digest", - "configured_secret_count", - "rotation_candidate_count", - "unchanged_count", - "unreadable_secret_count", - "active_key_id", - "legacy_compatibility_key_loaded" + "status", + "state", + "reason_codes" ], - "title": "ManagedSecretReencryptionHumanEvidence", + "title": "MergeReadinessEngineeringReviewFacet", "type": "object" }, - "ManagedSecretReencryptionPlanInput": { + "MergeReadinessFenceEvidence": { "additionalProperties": false, "properties": { - "reason": { - "maxLength": 240, - "minLength": 1, - "title": "Reason", + "controller_base_branch": { + "default": "", + "title": "Controller Base Branch", "type": "string" }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" + "controller_key": { + "default": "", + "title": "Controller Key", + "type": "string" }, - "source_label": { - "default": "privileged-operation-plan", - "maxLength": 96, - "minLength": 1, - "title": "Source Label", + "controller_repository": { + "default": "", + "title": "Controller Repository", + "type": "string" + }, + "controller_status": { + "default": "", + "title": "Controller Status", + "type": "string" + }, + "expected_lease_owner": { + "title": "Expected Lease Owner", + "type": "string" + }, + "lease_expires_at": { + "default": "", + "title": "Lease Expires At", + "type": "string" + }, + "observed_effect_sha": { + "default": "", + "title": "Observed Effect Sha", + "type": "string" + }, + "observed_lease_owner": { + "default": "", + "title": "Observed Lease Owner", "type": "string" } }, "required": [ - "reason" + "expected_lease_owner" ], - "title": "ManagedSecretReencryptionPlanInput", + "title": "MergeReadinessFenceEvidence", "type": "object" }, - "ManagerPreviewApprovalReconcileEnvelope": { + "MergeReadinessFenceFacet": { "additionalProperties": false, "properties": { - "pr_number": { - "minimum": 1.0, - "title": "Pr Number", - "type": "integer" + "evidence": { + "$ref": "#/components/schemas/MergeReadinessFenceEvidence" }, - "repository": { - "title": "Repository", - "type": "string" + "reason_codes": { + "items": { + "enum": [ + "owner_not_required", + "owner_acceptance_valid", + "owner_acceptance_missing", + "owner_changes_requested", + "owner_acceptance_revoked", + "owner_acceptance_stale", + "owner_evidence_stale", + "owner_evidence_unavailable", + "owner_authority_unavailable", + "owner_authority_denied", + "owner_preview_evidence_unavailable", + "owner_preview_evidence_stale", + "owner_review_expired", + "owner_preview_isolation_insufficient", + "owner_contributing_identity_unknown", + "owner_self_review_denied", + "owner_review_context_missing", + "owner_evidence_head_mismatch", + "owner_evidence_tree_mismatch", + "checks_passed", + "checks_pending", + "checks_failed", + "checks_unknown", + "checks_head_mismatch", + "engineering_review_approved", + "engineering_review_pending", + "engineering_review_changes_requested", + "engineering_review_blocked", + "engineering_review_unknown", + "engineering_review_stale", + "engineering_review_head_mismatch", + "engineering_review_tree_mismatch", + "engineering_review_evidence_missing", + "policy_fingerprints_match", + "policy_impact_missing", + "policy_impact_drift", + "policy_technical_checks_missing", + "policy_technical_checks_drift", + "policy_engineering_review_missing", + "policy_engineering_review_drift", + "policy_ruleset_missing", + "policy_ruleset_drift", + "policy_merge_train_missing", + "policy_merge_train_drift", + "policy_authorization_missing", + "policy_authorization_drift", + "policy_admission_algorithm_missing", + "policy_admission_algorithm_drift", + "candidate_exact", + "candidate_recorded_rolling", + "candidate_identity_mismatch", + "candidate_identity_unknown", + "candidate_queue_mismatch", + "candidate_base_mismatch", + "candidate_head_mismatch", + "controller_scope_mismatch", + "controller_lease_held", + "controller_lease_missing", + "controller_lease_lost", + "controller_lease_expired", + "expected_sha_match", + "expected_sha_mismatch" + ], + "type": "string" + }, + "title": "Reason Codes", + "type": "array" }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" + "state": { + "enum": [ + "ready", + "blocked_owner_evidence", + "blocked_checks", + "blocked_engineering_review", + "blocked_policy", + "blocked_candidate_identity", + "unknown" + ], + "title": "State", + "type": "string" } }, "required": [ - "repository", - "pr_number" + "evidence", + "state", + "reason_codes" ], - "title": "ManagerPreviewApprovalReconcileEnvelope", + "title": "MergeReadinessFenceFacet", "type": "object" }, - "MergeAdmissionRecord": { + "MergeReadinessOwnerFacet": { "additionalProperties": false, "properties": { - "admission_algorithm_version": { - "title": "Admission Algorithm Version", + "action": { + "title": "Action", "type": "string" }, - "admission_binding_sha256": { + "binding_sha256": { "default": "", - "title": "Admission Binding Sha256", + "title": "Binding Sha256", "type": "string" }, - "admission_id": { + "environment": { + "title": "Environment", + "type": "string" + }, + "event_id": { "default": "", - "title": "Admission Id", + "title": "Event Id", "type": "string" }, - "attempt_id": { - "title": "Attempt Id", - "type": "string" - }, - "attempt_sequence": { - "minimum": 1.0, - "title": "Attempt Sequence", - "type": "integer" - }, - "base_branch": { - "title": "Base Branch", - "type": "string" - }, - "batch_id": { - "title": "Batch Id", - "type": "string" - }, - "candidate_record_id": { - "title": "Candidate Record Id", - "type": "string" - }, - "candidate_sha": { - "title": "Candidate Sha", - "type": "string" - }, - "candidate_sha256": { - "title": "Candidate Sha256", - "type": "string" - }, - "candidate_tree_sha": { - "title": "Candidate Tree Sha", - "type": "string" - }, - "controller_key": { - "title": "Controller Key", - "type": "string" - }, - "created_at": { - "title": "Created At", - "type": "string" - }, - "decision": { - "const": "admitted", - "default": "admitted", - "title": "Decision", - "type": "string" - }, - "effective_base_sha": { - "title": "Effective Base Sha", - "type": "string" - }, - "effective_base_tree_sha": { - "title": "Effective Base Tree Sha", - "type": "string" - }, - "expected_effect_sha": { - "title": "Expected Effect Sha", - "type": "string" - }, - "landing_plan_id": { - "title": "Landing Plan Id", - "type": "string" - }, - "landing_plan_record_id": { - "title": "Landing Plan Record Id", - "type": "string" - }, - "landing_plan_sha256": { - "title": "Landing Plan Sha256", + "owner_reason_code": { + "title": "Owner Reason Code", "type": "string" }, - "lease_acquired_at": { - "title": "Lease Acquired At", + "owner_status": { + "title": "Owner Status", "type": "string" }, - "lease_expires_at": { - "title": "Lease Expires At", + "product": { + "title": "Product", "type": "string" }, - "lease_owner": { - "title": "Lease Owner", - "type": "string" + "reason_codes": { + "items": { + "enum": [ + "owner_not_required", + "owner_acceptance_valid", + "owner_acceptance_missing", + "owner_changes_requested", + "owner_acceptance_revoked", + "owner_acceptance_stale", + "owner_evidence_stale", + "owner_evidence_unavailable", + "owner_authority_unavailable", + "owner_authority_denied", + "owner_preview_evidence_unavailable", + "owner_preview_evidence_stale", + "owner_review_expired", + "owner_preview_isolation_insufficient", + "owner_contributing_identity_unknown", + "owner_self_review_denied", + "owner_review_context_missing", + "owner_evidence_head_mismatch", + "owner_evidence_tree_mismatch", + "checks_passed", + "checks_pending", + "checks_failed", + "checks_unknown", + "checks_head_mismatch", + "engineering_review_approved", + "engineering_review_pending", + "engineering_review_changes_requested", + "engineering_review_blocked", + "engineering_review_unknown", + "engineering_review_stale", + "engineering_review_head_mismatch", + "engineering_review_tree_mismatch", + "engineering_review_evidence_missing", + "policy_fingerprints_match", + "policy_impact_missing", + "policy_impact_drift", + "policy_technical_checks_missing", + "policy_technical_checks_drift", + "policy_engineering_review_missing", + "policy_engineering_review_drift", + "policy_ruleset_missing", + "policy_ruleset_drift", + "policy_merge_train_missing", + "policy_merge_train_drift", + "policy_authorization_missing", + "policy_authorization_drift", + "policy_admission_algorithm_missing", + "policy_admission_algorithm_drift", + "candidate_exact", + "candidate_recorded_rolling", + "candidate_identity_mismatch", + "candidate_identity_unknown", + "candidate_queue_mismatch", + "candidate_base_mismatch", + "candidate_head_mismatch", + "controller_scope_mismatch", + "controller_lease_held", + "controller_lease_missing", + "controller_lease_lost", + "controller_lease_expired", + "expected_sha_match", + "expected_sha_mismatch" + ], + "type": "string" + }, + "title": "Reason Codes", + "type": "array" }, - "merge_method": { + "state": { "enum": [ - "merge", - "squash", - "rebase" + "ready", + "blocked_owner_evidence", + "blocked_checks", + "blocked_engineering_review", + "blocked_policy", + "blocked_candidate_identity", + "unknown" ], - "title": "Merge Method", - "type": "string" - }, - "pull_request_head_sha": { - "title": "Pull Request Head Sha", - "type": "string" - }, - "pull_request_head_tree_sha": { - "title": "Pull Request Head Tree Sha", - "type": "string" - }, - "pull_request_number": { - "minimum": 1.0, - "title": "Pull Request Number", - "type": "integer" - }, - "queue_position": { - "minimum": 1.0, - "title": "Queue Position", - "type": "integer" - }, - "readiness": { - "$ref": "#/components/schemas/MergeReadinessResult" - }, - "repository": { - "title": "Repository", - "type": "string" - }, - "schema_version": { - "const": 1, - "default": 1, - "title": "Schema Version", - "type": "integer" - }, - "source": { - "title": "Source", + "title": "State", "type": "string" }, - "structural_provenance_sha256": { - "title": "Structural Provenance Sha256", + "system": { + "title": "System", "type": "string" - }, - "structural_result": { - "$ref": "#/components/schemas/MergeTrainStructuralCandidateResult" } }, "required": [ - "attempt_id", - "attempt_sequence", - "source", - "repository", - "base_branch", - "pull_request_number", - "queue_position", - "batch_id", - "candidate_record_id", - "landing_plan_record_id", - "landing_plan_id", - "merge_method", - "effective_base_sha", - "effective_base_tree_sha", - "pull_request_head_sha", - "pull_request_head_tree_sha", - "candidate_sha", - "candidate_tree_sha", - "expected_effect_sha", - "candidate_sha256", - "structural_provenance_sha256", - "landing_plan_sha256", - "readiness", - "structural_result", - "admission_algorithm_version", - "controller_key", - "lease_owner", - "lease_acquired_at", - "lease_expires_at", - "created_at" + "product", + "system", + "action", + "environment", + "owner_status", + "owner_reason_code", + "state", + "reason_codes" ], - "title": "MergeAdmissionRecord", + "title": "MergeReadinessOwnerFacet", "type": "object" }, - "MergeBatchNoEffectEvidence": { + "MergeReadinessPolicyFacet": { "additionalProperties": false, "properties": { - "base_contains_head": { - "const": false, - "title": "Base Contains Head", - "type": "boolean" - }, - "head_sha": { - "title": "Head Sha", - "type": "string" - }, - "merged": { - "const": false, - "title": "Merged", - "type": "boolean" + "fingerprints": { + "$ref": "#/components/schemas/MergeReadinessPolicyFingerprints" }, - "pull_request_number": { - "exclusiveMinimum": 0.0, - "title": "Pull Request Number", - "type": "integer" + "reason_codes": { + "items": { + "enum": [ + "owner_not_required", + "owner_acceptance_valid", + "owner_acceptance_missing", + "owner_changes_requested", + "owner_acceptance_revoked", + "owner_acceptance_stale", + "owner_evidence_stale", + "owner_evidence_unavailable", + "owner_authority_unavailable", + "owner_authority_denied", + "owner_preview_evidence_unavailable", + "owner_preview_evidence_stale", + "owner_review_expired", + "owner_preview_isolation_insufficient", + "owner_contributing_identity_unknown", + "owner_self_review_denied", + "owner_review_context_missing", + "owner_evidence_head_mismatch", + "owner_evidence_tree_mismatch", + "checks_passed", + "checks_pending", + "checks_failed", + "checks_unknown", + "checks_head_mismatch", + "engineering_review_approved", + "engineering_review_pending", + "engineering_review_changes_requested", + "engineering_review_blocked", + "engineering_review_unknown", + "engineering_review_stale", + "engineering_review_head_mismatch", + "engineering_review_tree_mismatch", + "engineering_review_evidence_missing", + "policy_fingerprints_match", + "policy_impact_missing", + "policy_impact_drift", + "policy_technical_checks_missing", + "policy_technical_checks_drift", + "policy_engineering_review_missing", + "policy_engineering_review_drift", + "policy_ruleset_missing", + "policy_ruleset_drift", + "policy_merge_train_missing", + "policy_merge_train_drift", + "policy_authorization_missing", + "policy_authorization_drift", + "policy_admission_algorithm_missing", + "policy_admission_algorithm_drift", + "candidate_exact", + "candidate_recorded_rolling", + "candidate_identity_mismatch", + "candidate_identity_unknown", + "candidate_queue_mismatch", + "candidate_base_mismatch", + "candidate_head_mismatch", + "controller_scope_mismatch", + "controller_lease_held", + "controller_lease_missing", + "controller_lease_lost", + "controller_lease_expired", + "expected_sha_match", + "expected_sha_mismatch" + ], + "type": "string" + }, + "title": "Reason Codes", + "type": "array" }, "state": { "enum": [ - "open", - "closed" + "ready", + "blocked_owner_evidence", + "blocked_checks", + "blocked_engineering_review", + "blocked_policy", + "blocked_candidate_identity", + "unknown" ], "title": "State", "type": "string" } }, "required": [ - "pull_request_number", - "head_sha", + "fingerprints", "state", - "merged", - "base_contains_head" + "reason_codes" ], - "title": "MergeBatchNoEffectEvidence", + "title": "MergeReadinessPolicyFacet", "type": "object" }, - "MergeLandingOutcomeRecord": { + "MergeReadinessPolicyFingerprintEvidence": { "additionalProperties": false, "properties": { - "admission_binding_sha256": { - "title": "Admission Binding Sha256", - "type": "string" - }, - "admission_id": { - "title": "Admission Id", - "type": "string" - }, - "attempt_id": { - "title": "Attempt Id", - "type": "string" - }, - "base_branch": { - "title": "Base Branch", - "type": "string" - }, - "base_contains_merge_commit": { + "current_sha256": { "anyOf": [ { - "type": "boolean" + "type": "string" }, { "type": "null" } ], - "title": "Base Contains Merge Commit" + "title": "Current Sha256" }, - "batch_no_effect": { + "dimension": { + "enum": [ + "impact", + "technical_checks", + "engineering_review", + "ruleset", + "merge_train", + "authorization", + "admission_algorithm" + ], + "title": "Dimension", + "type": "string" + }, + "expected_sha256": { + "title": "Expected Sha256", + "type": "string" + } + }, + "required": [ + "dimension", + "expected_sha256" + ], + "title": "MergeReadinessPolicyFingerprintEvidence", + "type": "object" + }, + "MergeReadinessPolicyFingerprints": { + "additionalProperties": false, + "properties": { + "admission_algorithm": { + "$ref": "#/components/schemas/MergeReadinessPolicyFingerprintEvidence" + }, + "authorization": { + "$ref": "#/components/schemas/MergeReadinessPolicyFingerprintEvidence" + }, + "engineering_review": { + "$ref": "#/components/schemas/MergeReadinessPolicyFingerprintEvidence" + }, + "impact": { "anyOf": [ { - "$ref": "#/components/schemas/MergeBatchNoEffectEvidence" + "$ref": "#/components/schemas/MergeReadinessPolicyFingerprintEvidence" }, { "type": "null" } - ], - "x-launchplane-optional-response": true + ] }, - "exact_landing_confirmed": { + "merge_train": { + "$ref": "#/components/schemas/MergeReadinessPolicyFingerprintEvidence" + }, + "ruleset": { + "$ref": "#/components/schemas/MergeReadinessPolicyFingerprintEvidence" + }, + "technical_checks": { + "$ref": "#/components/schemas/MergeReadinessPolicyFingerprintEvidence" + } + }, + "required": [ + "technical_checks", + "engineering_review", + "ruleset", + "merge_train", + "authorization", + "admission_algorithm" + ], + "title": "MergeReadinessPolicyFingerprints", + "type": "object" + }, + "MergeReadinessResult": { + "additionalProperties": false, + "properties": { + "authoritative": { + "const": false, "default": false, - "title": "Exact Landing Confirmed", + "title": "Authoritative", "type": "boolean" }, - "merge_commit_sha": { - "default": "", - "title": "Merge Commit Sha", + "authorizes": { + "default": [], + "items": { + "type": "string" + }, + "title": "Authorizes", + "type": "array" + }, + "candidate": { + "$ref": "#/components/schemas/MergeReadinessCandidateFacet" + }, + "engineering_review": { + "$ref": "#/components/schemas/MergeReadinessEngineeringReviewFacet" + }, + "engineering_review_authority": { + "default": "required", + "enum": [ + "required", + "advisory" + ], + "title": "Engineering Review Authority", "type": "string" }, - "merge_commit_tree_sha": { - "default": "", - "title": "Merge Commit Tree Sha", + "evaluated_at": { + "title": "Evaluated At", "type": "string" }, - "observation_sequence": { - "minimum": 1.0, - "title": "Observation Sequence", - "type": "integer" + "fence": { + "$ref": "#/components/schemas/MergeReadinessFenceFacet" }, - "observed_at": { - "title": "Observed At", + "mode": { + "const": "ephemeral", + "default": "ephemeral", + "title": "Mode", "type": "string" }, - "observed_base_sha": { - "default": "", - "title": "Observed Base Sha", - "type": "string" + "owner_facets": { + "default": [], + "items": { + "$ref": "#/components/schemas/MergeReadinessOwnerFacet" + }, + "title": "Owner Facets", + "type": "array" }, - "observed_base_tree_sha": { + "policy": { + "$ref": "#/components/schemas/MergeReadinessPolicyFacet" + }, + "readiness_digest": { "default": "", - "title": "Observed Base Tree Sha", + "title": "Readiness Digest", "type": "string" }, - "observed_pull_request_head_sha": { - "default": "", - "title": "Observed Pull Request Head Sha", - "type": "string" - }, - "observed_pull_request_head_tree_sha": { - "default": "", - "title": "Observed Pull Request Head Tree Sha", - "type": "string" - }, - "observed_pull_request_state": { - "default": "", - "enum": [ - "", - "open", - "closed", - "merged" - ], - "title": "Observed Pull Request State", - "type": "string" - }, - "outcome_binding_sha256": { - "default": "", - "title": "Outcome Binding Sha256", - "type": "string" - }, - "outcome_id": { - "default": "", - "title": "Outcome Id", - "type": "string" - }, - "prior_outcome_id": { - "default": "", - "title": "Prior Outcome Id", - "type": "string" - }, - "provider_conclusive_rejection": { - "default": false, - "title": "Provider Conclusive Rejection", - "type": "boolean" - }, - "provider_effect_attempted": { - "title": "Provider Effect Attempted", - "type": "boolean" - }, - "provider_message": { - "default": "", - "title": "Provider Message", - "type": "string" - }, - "provider_request_id": { - "default": "", - "title": "Provider Request Id", - "type": "string" - }, - "provider_status_code": { - "anyOf": [ - { - "maximum": 599.0, - "minimum": 100.0, - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Provider Status Code" - }, - "pull_request_number": { - "minimum": 1.0, - "title": "Pull Request Number", - "type": "integer" - }, - "reason": { - "enum": [ - "provider_and_git_confirmed", - "already_contained_no_provider_effect", - "provider_rejected", - "dispatch_not_attempted", - "batch_not_dispatched", - "batch_reconciliation_confirmed_no_effect", - "reconciliation_confirmed_no_effect", - "provider_transport_ambiguous", - "process_interrupted", - "lease_lost_after_admission", - "landing_evidence_incomplete", - "landing_evidence_contradicted" - ], - "title": "Reason", - "type": "string" - }, - "repository": { - "title": "Repository", - "type": "string" - }, - "schema_version": { - "const": 1, - "default": 1, - "title": "Schema Version", - "type": "integer" - }, - "source": { - "title": "Source", - "type": "string" - }, - "status": { - "enum": [ - "landed", - "rejected", - "reconcile_required" - ], - "title": "Status", - "type": "string" - } - }, - "required": [ - "admission_id", - "admission_binding_sha256", - "attempt_id", - "observation_sequence", - "source", - "repository", - "base_branch", - "pull_request_number", - "status", - "reason", - "provider_effect_attempted", - "observed_at" - ], - "title": "MergeLandingOutcomeRecord", - "type": "object" - }, - "MergeReadinessAdvisoryObservation": { - "additionalProperties": false, - "properties": { - "app_id": { - "anyOf": [ - { - "minimum": 1.0, - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "App Id" - }, - "name": { - "title": "Name", - "type": "string" - }, - "state": { - "title": "State", - "type": "string" - } - }, - "required": [ - "name", - "state" - ], - "title": "MergeReadinessAdvisoryObservation", - "type": "object" - }, - "MergeReadinessCandidateEvidence": { - "additionalProperties": false, - "properties": { - "base_sha": { - "default": "", - "title": "Base Sha", - "type": "string" - }, - "candidate_sha": { - "default": "", - "title": "Candidate Sha", - "type": "string" - }, - "pull_request_head_sha": { - "default": "", - "title": "Pull Request Head Sha", - "type": "string" - }, - "pull_request_number": { - "anyOf": [ - { - "minimum": 1.0, - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Pull Request Number" - }, - "queue_position": { - "anyOf": [ - { - "minimum": 1.0, - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Queue Position" - }, - "record_id": { - "default": "", - "title": "Record Id", - "type": "string" - }, - "repository": { - "default": "", - "title": "Repository", - "type": "string" - }, - "structural_status": { - "enum": [ - "exact", - "recorded_rolling", - "mismatch", - "unknown" - ], - "title": "Structural Status", - "type": "string" - } - }, - "required": [ - "structural_status" - ], - "title": "MergeReadinessCandidateEvidence", - "type": "object" - }, - "MergeReadinessCandidateFacet": { - "additionalProperties": false, - "properties": { - "evidence": { - "$ref": "#/components/schemas/MergeReadinessCandidateEvidence" - }, "reason_codes": { "items": { "enum": [ @@ -15207,6 +14956,12 @@ "title": "Reason Codes", "type": "array" }, + "schema_version": { + "default": 1, + "minimum": 1.0, + "title": "Schema Version", + "type": "integer" + }, "state": { "enum": [ "ready", @@ -15219,68 +14974,97 @@ ], "title": "State", "type": "string" + }, + "target": { + "$ref": "#/components/schemas/MergeReadinessTarget" + }, + "technical_checks": { + "$ref": "#/components/schemas/MergeReadinessTechnicalChecksFacet" } }, "required": [ - "evidence", + "target", "state", - "reason_codes" + "reason_codes", + "technical_checks", + "engineering_review", + "policy", + "candidate", + "fence", + "evaluated_at" ], - "title": "MergeReadinessCandidateFacet", + "title": "MergeReadinessResult", "type": "object" }, - "MergeReadinessEngineeringEvidenceReference": { + "MergeReadinessTarget": { "additionalProperties": false, "properties": { - "evidence_digest": { - "title": "Evidence Digest", + "base_branch": { + "title": "Base Branch", "type": "string" }, - "head_sha": { - "title": "Head Sha", + "base_sha": { + "title": "Base Sha", "type": "string" }, - "run_id": { - "title": "Run Id", + "expected_effect_sha": { + "title": "Expected Effect Sha", "type": "string" }, - "tree_sha": { - "title": "Tree Sha", + "pull_request_head_sha": { + "title": "Pull Request Head Sha", + "type": "string" + }, + "pull_request_number": { + "minimum": 1.0, + "title": "Pull Request Number", + "type": "integer" + }, + "pull_request_tree_sha": { + "title": "Pull Request Tree Sha", + "type": "string" + }, + "queue_position": { + "minimum": 1.0, + "title": "Queue Position", + "type": "integer" + }, + "repository": { + "title": "Repository", "type": "string" + }, + "schema_version": { + "default": 1, + "minimum": 1.0, + "title": "Schema Version", + "type": "integer" } }, "required": [ - "run_id", - "head_sha", - "tree_sha", - "evidence_digest" + "repository", + "pull_request_number", + "base_branch", + "base_sha", + "pull_request_head_sha", + "pull_request_tree_sha", + "expected_effect_sha", + "queue_position" ], - "title": "MergeReadinessEngineeringEvidenceReference", + "title": "MergeReadinessTarget", "type": "object" }, - "MergeReadinessEngineeringReviewFacet": { + "MergeReadinessTechnicalChecksFacet": { "additionalProperties": false, "properties": { - "decision_binding_sha256": { - "default": "", - "title": "Decision Binding Sha256", - "type": "string" - }, - "decision_id": { - "default": "", - "title": "Decision Id", - "type": "string" - }, - "evidence": { + "advisory_observations": { "default": [], "items": { - "$ref": "#/components/schemas/MergeReadinessEngineeringEvidenceReference" + "$ref": "#/components/schemas/MergeReadinessAdvisoryObservation" }, - "title": "Evidence", + "title": "Advisory Observations", "type": "array" }, "head_sha": { - "default": "", "title": "Head Sha", "type": "string" }, @@ -15355,6 +15139,13 @@ "title": "Reason Codes", "type": "array" }, + "required_checks": { + "items": { + "type": "string" + }, + "title": "Required Checks", + "type": "array" + }, "state": { "enum": [ "ready", @@ -15369,691 +15160,584 @@ "type": "string" }, "status": { + "enum": [ + "pass", + "pending", + "fail", + "unknown" + ], "title": "Status", "type": "string" - }, - "tree_sha": { - "default": "", - "title": "Tree Sha", - "type": "string" } }, "required": [ + "head_sha", "status", + "required_checks", "state", "reason_codes" ], - "title": "MergeReadinessEngineeringReviewFacet", + "title": "MergeReadinessTechnicalChecksFacet", "type": "object" }, - "MergeReadinessFenceEvidence": { + "MergeTrainAdmissionDecision": { "additionalProperties": false, "properties": { - "controller_base_branch": { + "base_branch": { + "title": "Base Branch", + "type": "string" + }, + "controller_action": { "default": "", - "title": "Controller Base Branch", + "title": "Controller Action", "type": "string" }, - "controller_key": { + "controller_candidate_record_id": { "default": "", - "title": "Controller Key", + "title": "Controller Candidate Record Id", "type": "string" }, - "controller_repository": { + "controller_landing_plan_record_id": { "default": "", - "title": "Controller Repository", + "title": "Controller Landing Plan Record Id", "type": "string" }, - "controller_status": { + "controller_reason": { "default": "", - "title": "Controller Status", + "title": "Controller Reason", "type": "string" }, - "expected_lease_owner": { - "title": "Expected Lease Owner", + "controller_stack_collapse_plan_record_id": { + "default": "", + "title": "Controller Stack Collapse Plan Record Id", "type": "string" }, - "lease_expires_at": { + "detail": { + "title": "Detail", + "type": "string" + }, + "latest_run_id": { "default": "", - "title": "Lease Expires At", + "title": "Latest Run Id", "type": "string" }, - "observed_effect_sha": { + "latest_run_recorded_at": { "default": "", - "title": "Observed Effect Sha", + "title": "Latest Run Recorded At", "type": "string" }, - "observed_lease_owner": { + "latest_run_status": { "default": "", - "title": "Observed Lease Owner", + "title": "Latest Run Status", + "type": "string" + }, + "next_allowed_at": { + "title": "Next Allowed At", + "type": "string" + }, + "reason_code": { + "enum": [ + "no_prior_run", + "dry_run_history_only", + "reread_required", + "poll_interval_elapsed", + "poll_interval_pending", + "backoff_elapsed", + "backoff_pending" + ], + "title": "Reason Code", + "type": "string" + }, + "repository": { + "title": "Repository", + "type": "string" + }, + "requested_at": { + "title": "Requested At", + "type": "string" + }, + "schema_version": { + "default": 1, + "minimum": 1.0, + "title": "Schema Version", + "type": "integer" + }, + "status": { + "enum": [ + "admitted", + "deferred" + ], + "title": "Status", "type": "string" } }, "required": [ - "expected_lease_owner" + "repository", + "base_branch", + "status", + "reason_code", + "requested_at", + "next_allowed_at", + "detail" ], - "title": "MergeReadinessFenceEvidence", + "title": "MergeTrainAdmissionDecision", "type": "object" }, - "MergeReadinessFenceFacet": { + "MergeTrainAdmissionResponse": { "additionalProperties": false, "properties": { - "evidence": { - "$ref": "#/components/schemas/MergeReadinessFenceEvidence" + "admission": { + "additionalProperties": true, + "title": "Admission", + "type": "object" }, - "reason_codes": { - "items": { - "enum": [ - "owner_not_required", - "owner_acceptance_valid", - "owner_acceptance_missing", - "owner_changes_requested", - "owner_acceptance_revoked", - "owner_acceptance_stale", - "owner_evidence_stale", - "owner_evidence_unavailable", - "owner_authority_unavailable", - "owner_authority_denied", - "owner_preview_evidence_unavailable", - "owner_preview_evidence_stale", - "owner_review_expired", - "owner_preview_isolation_insufficient", - "owner_contributing_identity_unknown", - "owner_self_review_denied", - "owner_review_context_missing", - "owner_evidence_head_mismatch", - "owner_evidence_tree_mismatch", - "checks_passed", - "checks_pending", - "checks_failed", - "checks_unknown", - "checks_head_mismatch", - "engineering_review_approved", - "engineering_review_pending", - "engineering_review_changes_requested", - "engineering_review_blocked", - "engineering_review_unknown", - "engineering_review_stale", - "engineering_review_head_mismatch", - "engineering_review_tree_mismatch", - "engineering_review_evidence_missing", - "policy_fingerprints_match", - "policy_impact_missing", - "policy_impact_drift", - "policy_technical_checks_missing", - "policy_technical_checks_drift", - "policy_engineering_review_missing", - "policy_engineering_review_drift", - "policy_ruleset_missing", - "policy_ruleset_drift", - "policy_merge_train_missing", - "policy_merge_train_drift", - "policy_authorization_missing", - "policy_authorization_drift", - "policy_admission_algorithm_missing", - "policy_admission_algorithm_drift", - "candidate_exact", - "candidate_recorded_rolling", - "candidate_identity_mismatch", - "candidate_identity_unknown", - "candidate_queue_mismatch", - "candidate_base_mismatch", - "candidate_head_mismatch", - "controller_scope_mismatch", - "controller_lease_held", - "controller_lease_missing", - "controller_lease_lost", - "controller_lease_expired", - "expected_sha_match", - "expected_sha_mismatch" - ], - "type": "string" - }, - "title": "Reason Codes", - "type": "array" + "status": { + "const": "ok", + "default": "ok", + "title": "Status", + "type": "string" }, - "state": { - "enum": [ - "ready", - "blocked_owner_evidence", - "blocked_checks", - "blocked_engineering_review", - "blocked_policy", - "blocked_candidate_identity", - "unknown" - ], - "title": "State", + "trace_id": { + "title": "Trace Id", "type": "string" } }, "required": [ - "evidence", - "state", - "reason_codes" + "trace_id", + "admission" ], - "title": "MergeReadinessFenceFacet", + "title": "MergeTrainAdmissionResponse", "type": "object" }, - "MergeReadinessOwnerFacet": { + "MergeTrainControllerLeaseDiagnostics": { "additionalProperties": false, "properties": { - "action": { - "title": "Action", + "active_action": { + "default": "", + "title": "Active Action", "type": "string" }, - "binding_sha256": { + "active_phase": { "default": "", - "title": "Binding Sha256", + "title": "Active Phase", "type": "string" }, - "environment": { - "title": "Environment", - "type": "string" + "active_pull_request_number": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Active Pull Request Number" }, - "event_id": { + "active_record_id": { "default": "", - "title": "Event Id", + "title": "Active Record Id", "type": "string" }, - "owner_reason_code": { - "title": "Owner Reason Code", - "type": "string" + "heartbeat_age_seconds": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Heartbeat Age Seconds" }, - "owner_status": { - "title": "Owner Status", + "lease_age_seconds": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Lease Age Seconds" + }, + "lease_expires_at": { + "default": "", + "title": "Lease Expires At", "type": "string" }, - "product": { - "title": "Product", + "owner": { + "default": "", + "title": "Owner", "type": "string" }, - "reason_codes": { - "items": { - "enum": [ - "owner_not_required", - "owner_acceptance_valid", - "owner_acceptance_missing", - "owner_changes_requested", - "owner_acceptance_revoked", - "owner_acceptance_stale", - "owner_evidence_stale", - "owner_evidence_unavailable", - "owner_authority_unavailable", - "owner_authority_denied", - "owner_preview_evidence_unavailable", - "owner_preview_evidence_stale", - "owner_review_expired", - "owner_preview_isolation_insufficient", - "owner_contributing_identity_unknown", - "owner_self_review_denied", - "owner_review_context_missing", - "owner_evidence_head_mismatch", - "owner_evidence_tree_mismatch", - "checks_passed", - "checks_pending", - "checks_failed", - "checks_unknown", - "checks_head_mismatch", - "engineering_review_approved", - "engineering_review_pending", - "engineering_review_changes_requested", - "engineering_review_blocked", - "engineering_review_unknown", - "engineering_review_stale", - "engineering_review_head_mismatch", - "engineering_review_tree_mismatch", - "engineering_review_evidence_missing", - "policy_fingerprints_match", - "policy_impact_missing", - "policy_impact_drift", - "policy_technical_checks_missing", - "policy_technical_checks_drift", - "policy_engineering_review_missing", - "policy_engineering_review_drift", - "policy_ruleset_missing", - "policy_ruleset_drift", - "policy_merge_train_missing", - "policy_merge_train_drift", - "policy_authorization_missing", - "policy_authorization_drift", - "policy_admission_algorithm_missing", - "policy_admission_algorithm_drift", - "candidate_exact", - "candidate_recorded_rolling", - "candidate_identity_mismatch", - "candidate_identity_unknown", - "candidate_queue_mismatch", - "candidate_base_mismatch", - "candidate_head_mismatch", - "controller_scope_mismatch", - "controller_lease_held", - "controller_lease_missing", - "controller_lease_lost", - "controller_lease_expired", - "expected_sha_match", - "expected_sha_mismatch" - ], - "type": "string" - }, - "title": "Reason Codes", - "type": "array" + "reconciliation_detail": { + "default": "", + "title": "Reconciliation Detail", + "type": "string" }, - "state": { - "enum": [ - "ready", - "blocked_owner_evidence", - "blocked_checks", - "blocked_engineering_review", - "blocked_policy", - "blocked_candidate_identity", - "unknown" - ], - "title": "State", + "reconciliation_status": { + "title": "Reconciliation Status", "type": "string" }, - "system": { - "title": "System", + "status": { + "title": "Status", "type": "string" } }, "required": [ - "product", - "system", - "action", - "environment", - "owner_status", - "owner_reason_code", - "state", - "reason_codes" + "status", + "reconciliation_status" ], - "title": "MergeReadinessOwnerFacet", + "title": "MergeTrainControllerLeaseDiagnostics", "type": "object" }, - "MergeReadinessPolicyFacet": { + "MergeTrainControllerRecordSummary": { "additionalProperties": false, "properties": { - "fingerprints": { - "$ref": "#/components/schemas/MergeReadinessPolicyFingerprints" + "batch_id": { + "default": "", + "title": "Batch Id", + "type": "string" }, - "reason_codes": { + "blocked_count": { + "default": 0, + "title": "Blocked Count", + "type": "integer" + }, + "candidate_sha": { + "default": "", + "title": "Candidate Sha", + "type": "string" + }, + "merged_count": { + "default": 0, + "title": "Merged Count", + "type": "integer" + }, + "planned_count": { + "default": 0, + "title": "Planned Count", + "type": "integer" + }, + "policy_key": { + "default": "", + "title": "Policy Key", + "type": "string" + }, + "policy_sha256": { + "default": "", + "title": "Policy Sha256", + "type": "string" + }, + "policy_status": { + "default": "unchecked", + "enum": [ + "current", + "stale", + "unchecked" + ], + "title": "Policy Status", + "type": "string" + }, + "pull_request_numbers": { + "default": [], "items": { - "enum": [ - "owner_not_required", - "owner_acceptance_valid", - "owner_acceptance_missing", - "owner_changes_requested", - "owner_acceptance_revoked", - "owner_acceptance_stale", - "owner_evidence_stale", - "owner_evidence_unavailable", - "owner_authority_unavailable", - "owner_authority_denied", - "owner_preview_evidence_unavailable", - "owner_preview_evidence_stale", - "owner_review_expired", - "owner_preview_isolation_insufficient", - "owner_contributing_identity_unknown", - "owner_self_review_denied", - "owner_review_context_missing", - "owner_evidence_head_mismatch", - "owner_evidence_tree_mismatch", - "checks_passed", - "checks_pending", - "checks_failed", - "checks_unknown", - "checks_head_mismatch", - "engineering_review_approved", - "engineering_review_pending", - "engineering_review_changes_requested", - "engineering_review_blocked", - "engineering_review_unknown", - "engineering_review_stale", - "engineering_review_head_mismatch", - "engineering_review_tree_mismatch", - "engineering_review_evidence_missing", - "policy_fingerprints_match", - "policy_impact_missing", - "policy_impact_drift", - "policy_technical_checks_missing", - "policy_technical_checks_drift", - "policy_engineering_review_missing", - "policy_engineering_review_drift", - "policy_ruleset_missing", - "policy_ruleset_drift", - "policy_merge_train_missing", - "policy_merge_train_drift", - "policy_authorization_missing", - "policy_authorization_drift", - "policy_admission_algorithm_missing", - "policy_admission_algorithm_drift", - "candidate_exact", - "candidate_recorded_rolling", - "candidate_identity_mismatch", - "candidate_identity_unknown", - "candidate_queue_mismatch", - "candidate_base_mismatch", - "candidate_head_mismatch", - "controller_scope_mismatch", - "controller_lease_held", - "controller_lease_missing", - "controller_lease_lost", - "controller_lease_expired", - "expected_sha_match", - "expected_sha_mismatch" - ], - "type": "string" + "type": "integer" }, - "title": "Reason Codes", + "title": "Pull Request Numbers", "type": "array" }, - "state": { - "enum": [ - "ready", - "blocked_owner_evidence", - "blocked_checks", - "blocked_engineering_review", - "blocked_policy", - "blocked_candidate_identity", - "unknown" - ], - "title": "State", + "record_id": { + "title": "Record Id", + "type": "string" + }, + "record_type": { + "title": "Record Type", + "type": "string" + }, + "required_checks_status": { + "default": "", + "title": "Required Checks Status", + "type": "string" + }, + "skipped_count": { + "default": 0, + "title": "Skipped Count", + "type": "integer" + }, + "stale_count": { + "default": 0, + "title": "Stale Count", + "type": "integer" + }, + "stale_reason": { + "default": "", + "title": "Stale Reason", + "type": "string" + }, + "status": { + "title": "Status", + "type": "string" + }, + "updated_at": { + "title": "Updated At", "type": "string" } }, "required": [ - "fingerprints", - "state", - "reason_codes" + "record_id", + "record_type", + "status", + "updated_at" ], - "title": "MergeReadinessPolicyFacet", + "title": "MergeTrainControllerRecordSummary", "type": "object" }, - "MergeReadinessPolicyFingerprintEvidence": { + "MergeTrainControllerStateRecord": { "additionalProperties": false, "properties": { - "current_sha256": { + "active_action": { + "default": "", + "title": "Active Action", + "type": "string" + }, + "active_phase": { + "default": "", + "title": "Active Phase", + "type": "string" + }, + "active_pull_request_number": { "anyOf": [ { - "type": "string" + "type": "integer" }, { "type": "null" } ], - "title": "Current Sha256" + "title": "Active Pull Request Number" }, - "dimension": { - "enum": [ - "impact", - "technical_checks", - "engineering_review", - "ruleset", - "merge_train", - "authorization", - "admission_algorithm" + "active_record_id": { + "default": "", + "title": "Active Record Id", + "type": "string" + }, + "base_branch": { + "title": "Base Branch", + "type": "string" + }, + "controller_key": { + "title": "Controller Key", + "type": "string" + }, + "heartbeat_at": { + "default": "", + "title": "Heartbeat At", + "type": "string" + }, + "last_action": { + "default": "", + "title": "Last Action", + "type": "string" + }, + "last_owner": { + "default": "", + "title": "Last Owner", + "type": "string" + }, + "last_phase": { + "default": "", + "title": "Last Phase", + "type": "string" + }, + "last_pull_request_number": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } ], - "title": "Dimension", + "title": "Last Pull Request Number" + }, + "last_record_id": { + "default": "", + "title": "Last Record Id", "type": "string" }, - "expected_sha256": { - "title": "Expected Sha256", + "last_transition_at": { + "default": "", + "title": "Last Transition At", "type": "string" - } - }, - "required": [ - "dimension", - "expected_sha256" - ], - "title": "MergeReadinessPolicyFingerprintEvidence", - "type": "object" - }, - "MergeReadinessPolicyFingerprints": { - "additionalProperties": false, - "properties": { - "admission_algorithm": { - "$ref": "#/components/schemas/MergeReadinessPolicyFingerprintEvidence" }, - "authorization": { - "$ref": "#/components/schemas/MergeReadinessPolicyFingerprintEvidence" + "lease_acquired_at": { + "default": "", + "title": "Lease Acquired At", + "type": "string" }, - "engineering_review": { - "$ref": "#/components/schemas/MergeReadinessPolicyFingerprintEvidence" + "lease_expires_at": { + "default": "", + "title": "Lease Expires At", + "type": "string" }, - "impact": { + "lease_owner": { + "default": "", + "title": "Lease Owner", + "type": "string" + }, + "ordinary_job_binding": { "anyOf": [ { - "$ref": "#/components/schemas/MergeReadinessPolicyFingerprintEvidence" + "$ref": "#/components/schemas/OrdinaryAgentJobBinding" }, { "type": "null" } ] }, - "merge_train": { - "$ref": "#/components/schemas/MergeReadinessPolicyFingerprintEvidence" + "policy_key": { + "title": "Policy Key", + "type": "string" }, - "ruleset": { - "$ref": "#/components/schemas/MergeReadinessPolicyFingerprintEvidence" + "policy_sha256": { + "title": "Policy Sha256", + "type": "string" }, - "technical_checks": { - "$ref": "#/components/schemas/MergeReadinessPolicyFingerprintEvidence" - } - }, - "required": [ - "technical_checks", - "engineering_review", - "ruleset", - "merge_train", - "authorization", - "admission_algorithm" - ], - "title": "MergeReadinessPolicyFingerprints", - "type": "object" - }, - "MergeReadinessResult": { - "additionalProperties": false, - "properties": { - "authoritative": { - "const": false, - "default": false, - "title": "Authoritative", - "type": "boolean" - }, - "authorizes": { - "default": [], - "items": { - "type": "string" - }, - "title": "Authorizes", - "type": "array" - }, - "candidate": { - "$ref": "#/components/schemas/MergeReadinessCandidateFacet" - }, - "engineering_review": { - "$ref": "#/components/schemas/MergeReadinessEngineeringReviewFacet" + "reconciliation_detail": { + "default": "", + "title": "Reconciliation Detail", + "type": "string" }, - "engineering_review_authority": { - "default": "required", + "reconciliation_status": { + "default": "clean", "enum": [ - "required", - "advisory" + "clean", + "adopted", + "required" ], - "title": "Engineering Review Authority", - "type": "string" - }, - "evaluated_at": { - "title": "Evaluated At", - "type": "string" - }, - "fence": { - "$ref": "#/components/schemas/MergeReadinessFenceFacet" - }, - "mode": { - "const": "ephemeral", - "default": "ephemeral", - "title": "Mode", + "title": "Reconciliation Status", "type": "string" }, - "owner_facets": { - "default": [], - "items": { - "$ref": "#/components/schemas/MergeReadinessOwnerFacet" - }, - "title": "Owner Facets", - "type": "array" - }, - "policy": { - "$ref": "#/components/schemas/MergeReadinessPolicyFacet" - }, - "readiness_digest": { - "default": "", - "title": "Readiness Digest", + "repository": { + "title": "Repository", "type": "string" }, - "reason_codes": { - "items": { - "enum": [ - "owner_not_required", - "owner_acceptance_valid", - "owner_acceptance_missing", - "owner_changes_requested", - "owner_acceptance_revoked", - "owner_acceptance_stale", - "owner_evidence_stale", - "owner_evidence_unavailable", - "owner_authority_unavailable", - "owner_authority_denied", - "owner_preview_evidence_unavailable", - "owner_preview_evidence_stale", - "owner_review_expired", - "owner_preview_isolation_insufficient", - "owner_contributing_identity_unknown", - "owner_self_review_denied", - "owner_review_context_missing", - "owner_evidence_head_mismatch", - "owner_evidence_tree_mismatch", - "checks_passed", - "checks_pending", - "checks_failed", - "checks_unknown", - "checks_head_mismatch", - "engineering_review_approved", - "engineering_review_pending", - "engineering_review_changes_requested", - "engineering_review_blocked", - "engineering_review_unknown", - "engineering_review_stale", - "engineering_review_head_mismatch", - "engineering_review_tree_mismatch", - "engineering_review_evidence_missing", - "policy_fingerprints_match", - "policy_impact_missing", - "policy_impact_drift", - "policy_technical_checks_missing", - "policy_technical_checks_drift", - "policy_engineering_review_missing", - "policy_engineering_review_drift", - "policy_ruleset_missing", - "policy_ruleset_drift", - "policy_merge_train_missing", - "policy_merge_train_drift", - "policy_authorization_missing", - "policy_authorization_drift", - "policy_admission_algorithm_missing", - "policy_admission_algorithm_drift", - "candidate_exact", - "candidate_recorded_rolling", - "candidate_identity_mismatch", - "candidate_identity_unknown", - "candidate_queue_mismatch", - "candidate_base_mismatch", - "candidate_head_mismatch", - "controller_scope_mismatch", - "controller_lease_held", - "controller_lease_missing", - "controller_lease_lost", - "controller_lease_expired", - "expected_sha_match", - "expected_sha_mismatch" - ], - "type": "string" - }, - "title": "Reason Codes", - "type": "array" - }, "schema_version": { "default": 1, "minimum": 1.0, "title": "Schema Version", "type": "integer" }, - "state": { + "status": { + "default": "idle", "enum": [ - "ready", - "blocked_owner_evidence", - "blocked_checks", - "blocked_engineering_review", - "blocked_policy", - "blocked_candidate_identity", - "unknown" + "idle", + "running", + "reconcile_required" ], - "title": "State", + "title": "Status", "type": "string" }, - "target": { - "$ref": "#/components/schemas/MergeReadinessTarget" + "step_payload": { + "additionalProperties": true, + "title": "Step Payload", + "type": "object" }, - "technical_checks": { - "$ref": "#/components/schemas/MergeReadinessTechnicalChecksFacet" + "updated_at": { + "title": "Updated At", + "type": "string" } }, "required": [ - "target", - "state", - "reason_codes", - "technical_checks", - "engineering_review", - "policy", - "candidate", - "fence", - "evaluated_at" + "controller_key", + "repository", + "base_branch", + "policy_key", + "policy_sha256", + "updated_at" ], - "title": "MergeReadinessResult", + "title": "MergeTrainControllerStateRecord", "type": "object" }, - "MergeReadinessTarget": { + "MergeTrainControllerStatusReadModel": { "additionalProperties": false, "properties": { + "admission": { + "$ref": "#/components/schemas/MergeTrainAdmissionDecision" + }, "base_branch": { "title": "Base Branch", "type": "string" }, - "base_sha": { - "title": "Base Sha", + "controller_diagnostics": { + "anyOf": [ + { + "$ref": "#/components/schemas/MergeTrainControllerLeaseDiagnostics" + }, + { + "type": "null" + } + ] + }, + "controller_records": { + "items": { + "$ref": "#/components/schemas/MergeTrainControllerRecordSummary" + }, + "title": "Controller Records", + "type": "array" + }, + "controller_state": { + "anyOf": [ + { + "$ref": "#/components/schemas/MergeTrainControllerStateRecord" + }, + { + "type": "null" + } + ] + }, + "current_policy_key": { + "default": "", + "title": "Current Policy Key", "type": "string" }, - "expected_effect_sha": { - "title": "Expected Effect Sha", + "current_policy_sha256": { + "default": "", + "title": "Current Policy Sha256", "type": "string" }, - "pull_request_head_sha": { - "title": "Pull Request Head Sha", + "generated_at": { + "title": "Generated At", "type": "string" }, - "pull_request_number": { - "minimum": 1.0, - "title": "Pull Request Number", - "type": "integer" + "latest_dry_run": { + "anyOf": [ + { + "$ref": "#/components/schemas/MergeTrainLatestDryRunSummary" + }, + { + "type": "null" + } + ] }, - "pull_request_tree_sha": { - "title": "Pull Request Tree Sha", - "type": "string" + "latest_run": { + "anyOf": [ + { + "$ref": "#/components/schemas/MergeTrainRunRecord" + }, + { + "type": "null" + } + ] }, - "queue_position": { - "minimum": 1.0, - "title": "Queue Position", - "type": "integer" + "reconciliation_diagnostics": { + "default": [], + "items": { + "$ref": "#/components/schemas/MergeTrainReconciliationDiagnostic" + }, + "title": "Reconciliation Diagnostics", + "type": "array" }, "repository": { "title": "Repository", @@ -16061,478 +15745,578 @@ }, "schema_version": { "default": 1, - "minimum": 1.0, "title": "Schema Version", "type": "integer" } }, "required": [ "repository", - "pull_request_number", "base_branch", - "base_sha", - "pull_request_head_sha", - "pull_request_tree_sha", - "expected_effect_sha", - "queue_position" + "generated_at", + "admission", + "controller_records" ], - "title": "MergeReadinessTarget", + "title": "MergeTrainControllerStatusReadModel", "type": "object" }, - "MergeReadinessTechnicalChecksFacet": { + "MergeTrainControllerStatusResponse": { "additionalProperties": false, "properties": { - "advisory_observations": { + "controller_status": { + "$ref": "#/components/schemas/MergeTrainControllerStatusReadModel" + }, + "status": { + "const": "ok", + "default": "ok", + "title": "Status", + "type": "string" + }, + "trace_id": { + "title": "Trace Id", + "type": "string" + } + }, + "required": [ + "trace_id", + "controller_status" + ], + "title": "MergeTrainControllerStatusResponse", + "type": "object" + }, + "MergeTrainDryRunQueueEntrySummary": { + "additionalProperties": false, + "properties": { + "eligible": { + "default": false, + "title": "Eligible", + "type": "boolean" + }, + "ineligible_reasons": { "default": [], "items": { - "$ref": "#/components/schemas/MergeReadinessAdvisoryObservation" + "type": "string" }, - "title": "Advisory Observations", + "title": "Ineligible Reasons", "type": "array" }, - "head_sha": { - "title": "Head Sha", + "mergeable": { + "default": "", + "title": "Mergeable", "type": "string" }, - "reason_codes": { + "pull_request_number": { + "title": "Pull Request Number", + "type": "integer" + }, + "required_checks_status": { + "default": "", + "title": "Required Checks Status", + "type": "string" + }, + "title": { + "default": "", + "title": "Title", + "type": "string" + }, + "url": { + "default": "", + "title": "Url", + "type": "string" + } + }, + "required": [ + "pull_request_number" + ], + "title": "MergeTrainDryRunQueueEntrySummary", + "type": "object" + }, + "MergeTrainEnqueuePolicy-Input": { + "additionalProperties": false, + "properties": { + "allowed_actor_roles": { + "default": [ + "repo_owner", + "repo_admin" + ], "items": { "enum": [ - "owner_not_required", - "owner_acceptance_valid", - "owner_acceptance_missing", - "owner_changes_requested", - "owner_acceptance_revoked", - "owner_acceptance_stale", - "owner_evidence_stale", - "owner_evidence_unavailable", - "owner_authority_unavailable", - "owner_authority_denied", - "owner_preview_evidence_unavailable", - "owner_preview_evidence_stale", - "owner_review_expired", - "owner_preview_isolation_insufficient", - "owner_contributing_identity_unknown", - "owner_self_review_denied", - "owner_review_context_missing", - "owner_evidence_head_mismatch", - "owner_evidence_tree_mismatch", - "checks_passed", - "checks_pending", - "checks_failed", - "checks_unknown", - "checks_head_mismatch", - "engineering_review_approved", - "engineering_review_pending", - "engineering_review_changes_requested", - "engineering_review_blocked", - "engineering_review_unknown", - "engineering_review_stale", - "engineering_review_head_mismatch", - "engineering_review_tree_mismatch", - "engineering_review_evidence_missing", - "policy_fingerprints_match", - "policy_impact_missing", - "policy_impact_drift", - "policy_technical_checks_missing", - "policy_technical_checks_drift", - "policy_engineering_review_missing", - "policy_engineering_review_drift", - "policy_ruleset_missing", - "policy_ruleset_drift", - "policy_merge_train_missing", - "policy_merge_train_drift", - "policy_authorization_missing", - "policy_authorization_drift", - "policy_admission_algorithm_missing", - "policy_admission_algorithm_drift", - "candidate_exact", - "candidate_recorded_rolling", - "candidate_identity_mismatch", - "candidate_identity_unknown", - "candidate_queue_mismatch", - "candidate_base_mismatch", - "candidate_head_mismatch", - "controller_scope_mismatch", - "controller_lease_held", - "controller_lease_missing", - "controller_lease_lost", - "controller_lease_expired", - "expected_sha_match", - "expected_sha_mismatch" + "repo_owner", + "repo_admin" ], "type": "string" }, - "title": "Reason Codes", + "title": "Allowed Actor Roles", "type": "array" }, - "required_checks": { + "label_required": { + "default": true, + "title": "Label Required", + "type": "boolean" + }, + "trusted_automation_github_user_ids": { + "default": [], "items": { - "type": "string" + "exclusiveMinimum": 0.0, + "type": "integer" }, - "title": "Required Checks", + "title": "Trusted Automation Github User Ids", "type": "array" + } + }, + "title": "MergeTrainEnqueuePolicy", + "type": "object" + }, + "MergeTrainEnqueuePolicy-Output": { + "additionalProperties": true, + "type": "object" + }, + "MergeTrainGitHubAppSource": { + "additionalProperties": false, + "properties": { + "app_id": { + "exclusiveMinimum": 0.0, + "title": "App Id", + "type": "integer" }, - "state": { - "enum": [ - "ready", - "blocked_owner_evidence", - "blocked_checks", - "blocked_engineering_review", - "blocked_policy", - "blocked_candidate_identity", - "unknown" - ], - "title": "State", + "private_key_context": { + "minLength": 1, + "title": "Private Key Context", "type": "string" }, - "status": { - "enum": [ - "pass", - "pending", - "fail", - "unknown" - ], - "title": "Status", - "type": "string" + "repository_id": { + "exclusiveMinimum": 0.0, + "title": "Repository Id", + "type": "integer" } }, "required": [ - "head_sha", - "status", - "required_checks", - "state", - "reason_codes" + "app_id", + "repository_id", + "private_key_context" ], - "title": "MergeReadinessTechnicalChecksFacet", + "title": "MergeTrainGitHubAppSource", "type": "object" }, - "MergeTrainAdmissionDecision": { + "MergeTrainGitHubTokenSource": { "additionalProperties": false, "properties": { - "base_branch": { - "title": "Base Branch", - "type": "string" - }, - "controller_action": { - "default": "", - "title": "Controller Action", - "type": "string" - }, - "controller_candidate_record_id": { - "default": "", - "title": "Controller Candidate Record Id", - "type": "string" - }, - "controller_landing_plan_record_id": { + "env_var": { "default": "", - "title": "Controller Landing Plan Record Id", + "title": "Env Var", "type": "string" }, - "controller_reason": { - "default": "", - "title": "Controller Reason", - "type": "string" + "github_app": { + "anyOf": [ + { + "$ref": "#/components/schemas/MergeTrainGitHubAppSource" + }, + { + "type": "null" + } + ], + "x-launchplane-optional-response": true }, - "controller_stack_collapse_plan_record_id": { + "runtime_context": { "default": "", - "title": "Controller Stack Collapse Plan Record Id", + "title": "Runtime Context", + "type": "string", + "x-launchplane-optional-response": true + } + }, + "title": "MergeTrainGitHubTokenSource", + "type": "object" + }, + "MergeTrainHistoricalCompletionEntryEvidence": { + "additionalProperties": false, + "properties": { + "base_contains_merge_commit": { + "title": "Base Contains Merge Commit", + "type": "boolean" + }, + "expected_base_sha": { + "title": "Expected Base Sha", "type": "string" }, - "detail": { - "title": "Detail", + "expected_head_sha": { + "title": "Expected Head Sha", "type": "string" }, - "latest_run_id": { - "default": "", - "title": "Latest Run Id", + "expected_head_tree_sha": { + "title": "Expected Head Tree Sha", "type": "string" }, - "latest_run_recorded_at": { - "default": "", - "title": "Latest Run Recorded At", + "expected_parent_tree_sha": { + "title": "Expected Parent Tree Sha", "type": "string" }, - "latest_run_status": { - "default": "", - "title": "Latest Run Status", + "expected_result_tree_sha": { + "title": "Expected Result Tree Sha", "type": "string" }, - "next_allowed_at": { - "title": "Next Allowed At", + "observed_head_sha": { + "title": "Observed Head Sha", "type": "string" }, - "reason_code": { - "enum": [ - "no_prior_run", - "dry_run_history_only", - "reread_required", - "poll_interval_elapsed", - "poll_interval_pending", - "backoff_elapsed", - "backoff_pending" - ], - "title": "Reason Code", + "observed_head_tree_sha": { + "title": "Observed Head Tree Sha", "type": "string" }, - "repository": { - "title": "Repository", + "observed_merge_commit_sha": { + "title": "Observed Merge Commit Sha", "type": "string" }, - "requested_at": { - "title": "Requested At", + "observed_merge_commit_tree_sha": { + "title": "Observed Merge Commit Tree Sha", "type": "string" }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" + "observed_parent_sha": { + "title": "Observed Parent Sha", + "type": "string" }, - "status": { - "enum": [ - "admitted", - "deferred" - ], - "title": "Status", + "observed_parent_tree_sha": { + "title": "Observed Parent Tree Sha", "type": "string" + }, + "position": { + "exclusiveMinimum": 0.0, + "title": "Position", + "type": "integer" + }, + "pull_request_number": { + "exclusiveMinimum": 0.0, + "title": "Pull Request Number", + "type": "integer" } }, "required": [ - "repository", - "base_branch", - "status", - "reason_code", - "requested_at", - "next_allowed_at", - "detail" + "pull_request_number", + "position", + "expected_head_sha", + "expected_head_tree_sha", + "expected_base_sha", + "expected_parent_tree_sha", + "expected_result_tree_sha", + "observed_head_sha", + "observed_head_tree_sha", + "observed_merge_commit_sha", + "observed_merge_commit_tree_sha", + "observed_parent_sha", + "observed_parent_tree_sha", + "base_contains_merge_commit" ], - "title": "MergeTrainAdmissionDecision", + "title": "MergeTrainHistoricalCompletionEntryEvidence", "type": "object" }, - "MergeTrainAdmissionResponse": { + "MergeTrainHistoricalCompletionEntrySelector": { "additionalProperties": false, "properties": { - "admission": { - "additionalProperties": true, - "title": "Admission", - "type": "object" - }, - "status": { - "const": "ok", - "default": "ok", - "title": "Status", + "expected_head_sha": { + "title": "Expected Head Sha", "type": "string" }, - "trace_id": { - "title": "Trace Id", + "expected_head_tree_sha": { + "title": "Expected Head Tree Sha", "type": "string" + }, + "position": { + "exclusiveMinimum": 0.0, + "title": "Position", + "type": "integer" + }, + "pull_request_number": { + "exclusiveMinimum": 0.0, + "title": "Pull Request Number", + "type": "integer" } }, "required": [ - "trace_id", - "admission" + "position", + "pull_request_number", + "expected_head_sha", + "expected_head_tree_sha" ], - "title": "MergeTrainAdmissionResponse", + "title": "MergeTrainHistoricalCompletionEntrySelector", "type": "object" }, - "MergeTrainControllerLeaseDiagnostics": { + "MergeTrainHistoricalCompletionPreflight": { "additionalProperties": false, "properties": { - "active_action": { - "default": "", - "title": "Active Action", - "type": "string" + "admission_created": { + "const": false, + "default": false, + "title": "Admission Created", + "type": "boolean" }, - "active_phase": { - "default": "", - "title": "Active Phase", + "automatic_retry_allowed": { + "const": false, + "default": false, + "title": "Automatic Retry Allowed", + "type": "boolean" + }, + "base_branch": { + "title": "Base Branch", "type": "string" }, - "active_pull_request_number": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Active Pull Request Number" + "disposition_supported": { + "default": false, + "title": "Disposition Supported", + "type": "boolean" }, - "active_record_id": { + "entries": { + "default": [], + "items": { + "$ref": "#/components/schemas/HistoricalCompletionEntryAssessment" + }, + "title": "Entries", + "type": "array" + }, + "evidence_eligible": { + "default": false, + "title": "Evidence Eligible", + "type": "boolean" + }, + "fence_released": { + "const": false, + "default": false, + "title": "Fence Released", + "type": "boolean" + }, + "generated_at": { + "title": "Generated At", + "type": "string" + }, + "landing_plan_sha256": { "default": "", - "title": "Active Record Id", + "title": "Landing Plan Sha256", "type": "string" }, - "heartbeat_age_seconds": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Heartbeat Age Seconds" + "mutation_enabled": { + "default": false, + "title": "Mutation Enabled", + "type": "boolean" }, - "lease_age_seconds": { + "provider_effect_attempted": { + "const": false, + "default": false, + "title": "Provider Effect Attempted", + "type": "boolean" + }, + "provider_evidence": { "anyOf": [ { - "type": "integer" + "$ref": "#/components/schemas/MergeTrainHistoricalCompletionProviderEvidence" }, { "type": "null" } - ], - "title": "Lease Age Seconds" + ] }, - "lease_expires_at": { - "default": "", - "title": "Lease Expires At", + "reason_code": { + "enum": [ + "exact_historical_completion", + "not_assessed", + "controller_unavailable", + "controller_busy", + "controller_not_reconciling", + "selector_mismatch", + "policy_unavailable", + "policy_changed", + "landing_plan_unavailable", + "landing_plan_ambiguous", + "landing_plan_binding_changed", + "stored_evidence_incomplete", + "entry_limit_exceeded", + "candidate_unavailable", + "candidate_history_limit_exceeded", + "candidate_ambiguous", + "candidate_binding_changed", + "stack_batch_unsupported", + "ordinary_target_unsupported", + "admission_present", + "store_unavailable", + "store_state_changed", + "provider_unavailable", + "provider_invalid_response", + "provider_binding_mismatch", + "pull_request_not_merged", + "no_op_unsupported", + "base_not_contains_merge", + "base_changed" + ], + "title": "Reason Code", "type": "string" }, - "owner": { - "default": "", - "title": "Owner", + "repository": { + "title": "Repository", "type": "string" }, - "reconciliation_detail": { - "default": "", - "title": "Reconciliation Detail", - "type": "string" + "schema_version": { + "const": 1, + "default": 1, + "title": "Schema Version", + "type": "integer" }, - "reconciliation_status": { - "title": "Reconciliation Status", - "type": "string" + "selector": { + "$ref": "#/components/schemas/MergeTrainHistoricalCompletionSelector" }, "status": { + "enum": [ + "eligible", + "unsupported", + "indeterminate" + ], "title": "Status", "type": "string" } }, "required": [ + "repository", + "base_branch", + "generated_at", + "selector", "status", - "reconciliation_status" + "reason_code" ], - "title": "MergeTrainControllerLeaseDiagnostics", + "title": "MergeTrainHistoricalCompletionPreflight", "type": "object" }, - "MergeTrainControllerRecordSummary": { + "MergeTrainHistoricalCompletionProviderEvidence": { "additionalProperties": false, "properties": { - "batch_id": { - "default": "", - "title": "Batch Id", - "type": "string" - }, - "blocked_count": { - "default": 0, - "title": "Blocked Count", - "type": "integer" + "entries": { + "items": { + "$ref": "#/components/schemas/MergeTrainHistoricalCompletionEntryEvidence" + }, + "maxItems": 25, + "minItems": 1, + "title": "Entries", + "type": "array" }, - "candidate_sha": { - "default": "", - "title": "Candidate Sha", + "final_observed_base_sha": { + "title": "Final Observed Base Sha", "type": "string" }, - "merged_count": { - "default": 0, - "title": "Merged Count", - "type": "integer" + "observed_at": { + "title": "Observed At", + "type": "string" }, - "planned_count": { - "default": 0, - "title": "Planned Count", - "type": "integer" + "observed_base_sha": { + "title": "Observed Base Sha", + "type": "string" }, - "policy_key": { - "default": "", - "title": "Policy Key", + "observed_base_tree_sha": { + "title": "Observed Base Tree Sha", "type": "string" }, - "policy_sha256": { - "default": "", - "title": "Policy Sha256", + "provider_effect_attempted": { + "const": false, + "default": false, + "title": "Provider Effect Attempted", + "type": "boolean" + } + }, + "required": [ + "observed_at", + "observed_base_sha", + "observed_base_tree_sha", + "final_observed_base_sha", + "entries" + ], + "title": "MergeTrainHistoricalCompletionProviderEvidence", + "type": "object" + }, + "MergeTrainHistoricalCompletionSelector": { + "additionalProperties": false, + "description": "The controller binding a future completion disposition would validate.", + "properties": { + "expected_active_record_id": { + "title": "Expected Active Record Id", "type": "string" }, - "policy_status": { - "default": "unchecked", - "enum": [ - "current", - "stale", - "unchecked" - ], - "title": "Policy Status", + "expected_effect_sha": { + "title": "Expected Effect Sha", "type": "string" }, - "pull_request_numbers": { - "default": [], + "expected_entries": { "items": { - "type": "integer" + "$ref": "#/components/schemas/MergeTrainHistoricalCompletionEntrySelector" }, - "title": "Pull Request Numbers", + "maxItems": 25, + "minItems": 1, + "title": "Expected Entries", "type": "array" }, - "record_id": { - "title": "Record Id", - "type": "string" - }, - "record_type": { - "title": "Record Type", - "type": "string" - }, - "required_checks_status": { - "default": "", - "title": "Required Checks Status", + "expected_landing_plan_id": { + "title": "Expected Landing Plan Id", "type": "string" }, - "skipped_count": { - "default": 0, - "title": "Skipped Count", - "type": "integer" - }, - "stale_count": { - "default": 0, - "title": "Stale Count", - "type": "integer" - }, - "stale_reason": { - "default": "", - "title": "Stale Reason", + "expected_policy_sha256": { + "title": "Expected Policy Sha256", "type": "string" - }, - "status": { - "title": "Status", + } + }, + "required": [ + "expected_active_record_id", + "expected_effect_sha", + "expected_policy_sha256", + "expected_landing_plan_id", + "expected_entries" + ], + "title": "MergeTrainHistoricalCompletionSelector", + "type": "object" + }, + "MergeTrainIdentity": { + "additionalProperties": false, + "properties": { + "kind": { + "enum": [ + "github_actions_oidc", + "github_app", + "github_token_secret" + ], + "title": "Kind", "type": "string" }, - "updated_at": { - "title": "Updated At", + "name": { + "title": "Name", "type": "string" } }, "required": [ - "record_id", - "record_type", - "status", - "updated_at" + "kind", + "name" ], - "title": "MergeTrainControllerRecordSummary", + "title": "MergeTrainIdentity", "type": "object" }, - "MergeTrainControllerStateRecord": { + "MergeTrainLatestDryRunSummary": { "additionalProperties": false, "properties": { - "active_action": { - "default": "", - "title": "Active Action", + "eligible_count": { + "title": "Eligible Count", + "type": "integer" + }, + "intended_next_action": { + "title": "Intended Next Action", "type": "string" }, - "active_phase": { - "default": "", - "title": "Active Phase", + "next_action_detail": { + "title": "Next Action Detail", "type": "string" }, - "active_pull_request_number": { + "queue_count": { + "title": "Queue Count", + "type": "integer" + }, + "queue_entries": { + "default": [], + "items": { + "$ref": "#/components/schemas/MergeTrainDryRunQueueEntrySummary" + }, + "title": "Queue Entries", + "type": "array" + }, + "selected_pr_number": { "anyOf": [ { "type": "integer" @@ -16541,112 +16325,149 @@ "type": "null" } ], - "title": "Active Pull Request Number" - }, - "active_record_id": { - "default": "", - "title": "Active Record Id", - "type": "string" - }, - "base_branch": { - "title": "Base Branch", - "type": "string" - }, - "controller_key": { - "title": "Controller Key", - "type": "string" + "title": "Selected Pr Number" + } + }, + "required": [ + "intended_next_action", + "next_action_detail", + "queue_count", + "eligible_count" + ], + "title": "MergeTrainLatestDryRunSummary", + "type": "object" + }, + "MergeTrainPolicy-Input": { + "additionalProperties": false, + "properties": { + "policies": { + "items": { + "$ref": "#/components/schemas/MergeTrainRepositoryPolicy-Input" + }, + "title": "Policies", + "type": "array" }, - "heartbeat_at": { - "default": "", - "title": "Heartbeat At", - "type": "string" + "schema_version": { + "default": 1, + "minimum": 1.0, + "title": "Schema Version", + "type": "integer" + } + }, + "required": [ + "policies" + ], + "title": "MergeTrainPolicy", + "type": "object" + }, + "MergeTrainPolicy-Output": { + "additionalProperties": false, + "properties": { + "policies": { + "items": { + "$ref": "#/components/schemas/MergeTrainRepositoryPolicy-Output" + }, + "title": "Policies", + "type": "array" }, - "last_action": { - "default": "", - "title": "Last Action", - "type": "string" + "schema_version": { + "default": 1, + "minimum": 1.0, + "title": "Schema Version", + "type": "integer" + } + }, + "required": [ + "policies" + ], + "title": "MergeTrainPolicy", + "type": "object" + }, + "MergeTrainPolicyReadResponse": { + "additionalProperties": false, + "properties": { + "record": { + "$ref": "#/components/schemas/MergeTrainPolicyRecord-Output" }, - "last_owner": { - "default": "", - "title": "Last Owner", + "status": { + "const": "ok", + "default": "ok", + "title": "Status", "type": "string" }, - "last_phase": { - "default": "", - "title": "Last Phase", + "trace_id": { + "title": "Trace Id", "type": "string" + } + }, + "required": [ + "trace_id", + "record" + ], + "title": "MergeTrainPolicyReadResponse", + "type": "object" + }, + "MergeTrainPolicyRecord-Input": { + "additionalProperties": false, + "properties": { + "policy": { + "$ref": "#/components/schemas/MergeTrainPolicy-Input" }, - "last_pull_request_number": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Last Pull Request Number" - }, - "last_record_id": { + "policy_sha256": { "default": "", - "title": "Last Record Id", + "title": "Policy Sha256", "type": "string" }, - "last_transition_at": { - "default": "", - "title": "Last Transition At", + "record_id": { + "title": "Record Id", "type": "string" }, - "lease_acquired_at": { - "default": "", - "title": "Lease Acquired At", - "type": "string" + "schema_version": { + "default": 1, + "minimum": 1.0, + "title": "Schema Version", + "type": "integer" }, - "lease_expires_at": { - "default": "", - "title": "Lease Expires At", + "source": { + "title": "Source", "type": "string" }, - "lease_owner": { - "default": "", - "title": "Lease Owner", + "status": { + "default": "active", + "enum": [ + "active", + "superseded" + ], + "title": "Status", "type": "string" }, - "ordinary_job_binding": { - "anyOf": [ - { - "$ref": "#/components/schemas/OrdinaryAgentJobBinding" - }, - { - "type": "null" - } - ] - }, - "policy_key": { - "title": "Policy Key", + "updated_at": { + "title": "Updated At", "type": "string" + } + }, + "required": [ + "record_id", + "source", + "updated_at", + "policy" + ], + "title": "MergeTrainPolicyRecord", + "type": "object" + }, + "MergeTrainPolicyRecord-Output": { + "additionalProperties": false, + "properties": { + "policy": { + "$ref": "#/components/schemas/MergeTrainPolicy-Output" }, "policy_sha256": { - "title": "Policy Sha256", - "type": "string" - }, - "reconciliation_detail": { "default": "", - "title": "Reconciliation Detail", - "type": "string" - }, - "reconciliation_status": { - "default": "clean", - "enum": [ - "clean", - "adopted", - "required" - ], - "title": "Reconciliation Status", + "title": "Policy Sha256", "type": "string" }, - "repository": { - "title": "Repository", + "record_id": { + "title": "Record Id", "type": "string" }, "schema_version": { @@ -16655,141 +16476,94 @@ "title": "Schema Version", "type": "integer" }, + "source": { + "title": "Source", + "type": "string" + }, "status": { - "default": "idle", + "default": "active", "enum": [ - "idle", - "running", - "reconcile_required" + "active", + "superseded" ], "title": "Status", "type": "string" }, - "step_payload": { - "additionalProperties": true, - "title": "Step Payload", - "type": "object" - }, "updated_at": { "title": "Updated At", "type": "string" } }, "required": [ - "controller_key", - "repository", - "base_branch", - "policy_key", - "policy_sha256", - "updated_at" + "record_id", + "source", + "updated_at", + "policy" ], - "title": "MergeTrainControllerStateRecord", + "title": "MergeTrainPolicyRecord", "type": "object" }, - "MergeTrainControllerStatusReadModel": { + "MergeTrainPolicySummary": { "additionalProperties": false, "properties": { - "admission": { - "$ref": "#/components/schemas/MergeTrainAdmissionDecision" - }, - "base_branch": { - "title": "Base Branch", + "policy_sha256": { + "title": "Policy Sha256", "type": "string" }, - "controller_diagnostics": { - "anyOf": [ - { - "$ref": "#/components/schemas/MergeTrainControllerLeaseDiagnostics" - }, - { - "type": "null" - } - ] - }, - "controller_records": { - "items": { - "$ref": "#/components/schemas/MergeTrainControllerRecordSummary" - }, - "title": "Controller Records", - "type": "array" - }, - "controller_state": { - "anyOf": [ - { - "$ref": "#/components/schemas/MergeTrainControllerStateRecord" - }, - { - "type": "null" - } - ] - }, - "current_policy_key": { - "default": "", - "title": "Current Policy Key", + "record_id": { + "title": "Record Id", "type": "string" }, - "current_policy_sha256": { - "default": "", - "title": "Current Policy Sha256", + "updated_at": { + "title": "Updated At", "type": "string" - }, - "generated_at": { - "title": "Generated At", + } + }, + "required": [ + "record_id", + "updated_at", + "policy_sha256" + ], + "title": "MergeTrainPolicySummary", + "type": "object" + }, + "MergeTrainPolicyTarget": { + "additionalProperties": false, + "properties": { + "base_branch": { + "title": "Base Branch", "type": "string" }, - "latest_dry_run": { - "anyOf": [ - { - "$ref": "#/components/schemas/MergeTrainLatestDryRunSummary" - }, - { - "type": "null" - } - ] - }, - "latest_run": { - "anyOf": [ - { - "$ref": "#/components/schemas/MergeTrainRunRecord" - }, - { - "type": "null" - } - ] - }, - "reconciliation_diagnostics": { - "default": [], - "items": { - "$ref": "#/components/schemas/MergeTrainReconciliationDiagnostic" - }, - "title": "Reconciliation Diagnostics", - "type": "array" + "policy_key": { + "title": "Policy Key", + "type": "string" }, "repository": { "title": "Repository", "type": "string" }, - "schema_version": { - "default": 1, - "title": "Schema Version", - "type": "integer" + "scheduler": { + "$ref": "#/components/schemas/MergeTrainSchedulerPolicy" + }, + "service_authz": { + "$ref": "#/components/schemas/MergeTrainServiceAuthz" } }, "required": [ "repository", "base_branch", - "generated_at", - "admission", - "controller_records" + "policy_key", + "scheduler", + "service_authz" ], - "title": "MergeTrainControllerStatusReadModel", + "title": "MergeTrainPolicyTarget", "type": "object" }, - "MergeTrainControllerStatusResponse": { + "MergeTrainPolicyTargetsResponse": { "additionalProperties": false, "properties": { - "controller_status": { - "$ref": "#/components/schemas/MergeTrainControllerStatusReadModel" + "policy": { + "$ref": "#/components/schemas/MergeTrainPolicySummary" }, "status": { "const": "ok", @@ -16797,6 +16571,13 @@ "title": "Status", "type": "string" }, + "targets": { + "items": { + "$ref": "#/components/schemas/MergeTrainPolicyTarget" + }, + "title": "Targets", + "type": "array" + }, "trace_id": { "title": "Trace Id", "type": "string" @@ -16804,137 +16585,158 @@ }, "required": [ "trace_id", - "controller_status" + "policy", + "targets" ], - "title": "MergeTrainControllerStatusResponse", + "title": "MergeTrainPolicyTargetsResponse", "type": "object" }, - "MergeTrainDryRunQueueEntrySummary": { + "MergeTrainReconciliationDiagnostic": { "additionalProperties": false, "properties": { - "eligible": { - "default": false, - "title": "Eligible", - "type": "boolean" - }, - "ineligible_reasons": { - "default": [], - "items": { - "type": "string" - }, - "title": "Ineligible Reasons", - "type": "array" + "base_branch": { + "title": "Base Branch", + "type": "string" }, - "mergeable": { + "binding_detail": { "default": "", - "title": "Mergeable", + "enum": [ + "", + "current_policy_unavailable", + "controller_policy_changed", + "plan_reference_conflict", + "plan_reference_incomplete", + "plan_record_unavailable", + "plan_binding_changed", + "plan_entry_limit_exceeded", + "selected_entry_unavailable", + "history_reader_unavailable", + "admission_limit_exceeded", + "admission_binding_changed", + "admission_identity_missing", + "outcome_binding_changed", + "history_unavailable", + "outcome_status_unknown" + ], + "title": "Binding Detail", "type": "string" }, - "pull_request_number": { - "title": "Pull Request Number", - "type": "integer" + "classification": { + "enum": [ + "missing_preceding_admission", + "admission_without_outcome", + "outcome_reconcile_required", + "outcome_rejected", + "outcome_landed", + "binding_unavailable", + "binding_stale" + ], + "title": "Classification", + "type": "string" }, - "required_checks_status": { + "expected_head_sha": { "default": "", - "title": "Required Checks Status", + "title": "Expected Head Sha", "type": "string" }, - "title": { + "expected_head_tree_sha": { "default": "", - "title": "Title", + "title": "Expected Head Tree Sha", "type": "string" }, - "url": { + "landing_plan_id": { "default": "", - "title": "Url", + "title": "Landing Plan Id", "type": "string" - } - }, - "required": [ - "pull_request_number" - ], - "title": "MergeTrainDryRunQueueEntrySummary", - "type": "object" - }, - "MergeTrainEnqueuePolicy-Input": { - "additionalProperties": false, - "properties": { - "allowed_actor_roles": { - "default": [ - "repo_owner", - "repo_admin" - ], - "items": { - "enum": [ - "repo_owner", - "repo_admin" - ], - "type": "string" - }, - "title": "Allowed Actor Roles", - "type": "array" }, - "label_required": { - "default": true, - "title": "Label Required", - "type": "boolean" + "landing_plan_record_id": { + "default": "", + "title": "Landing Plan Record Id", + "type": "string" }, - "trusted_automation_github_user_ids": { - "default": [], - "items": { - "exclusiveMinimum": 0.0, - "type": "integer" - }, - "title": "Trusted Automation Github User Ids", - "type": "array" - } - }, - "title": "MergeTrainEnqueuePolicy", - "type": "object" - }, - "MergeTrainEnqueuePolicy-Output": { - "additionalProperties": true, - "type": "object" - }, - "MergeTrainGitHubAppSource": { - "additionalProperties": false, - "properties": { - "app_id": { - "exclusiveMinimum": 0.0, - "title": "App Id", - "type": "integer" + "pull_request_number": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Pull Request Number" }, - "private_key_context": { - "minLength": 1, - "title": "Private Key Context", + "repository": { + "title": "Repository", "type": "string" }, - "repository_id": { - "exclusiveMinimum": 0.0, - "title": "Repository Id", + "schema_version": { + "default": 1, + "title": "Schema Version", "type": "integer" } }, "required": [ - "app_id", - "repository_id", - "private_key_context" + "classification", + "repository", + "base_branch" ], - "title": "MergeTrainGitHubAppSource", + "title": "MergeTrainReconciliationDiagnostic", "type": "object" }, - "MergeTrainGitHubTokenSource": { + "MergeTrainRepositoryPolicy-Input": { "additionalProperties": false, "properties": { - "env_var": { - "default": "", - "title": "Env Var", + "base_branch": { + "title": "Base Branch", "type": "string" }, - "github_app": { - "anyOf": [ - { - "$ref": "#/components/schemas/MergeTrainGitHubAppSource" + "blocked_label": { + "title": "Blocked Label", + "type": "string" + }, + "engineering_review_mode": { + "default": "advisory", + "enum": [ + "advisory", + "required" + ], + "title": "Engineering Review Mode", + "type": "string" + }, + "enqueue": { + "$ref": "#/components/schemas/MergeTrainEnqueuePolicy-Input" + }, + "enqueue_label": { + "title": "Enqueue Label", + "type": "string" + }, + "failure_policy": { + "enum": [ + "pause_train", + "continue_after_blocking_pr" + ], + "title": "Failure Policy", + "type": "string" + }, + "github_token": { + "$ref": "#/components/schemas/MergeTrainGitHubTokenSource" + }, + "merge_identity": { + "$ref": "#/components/schemas/MergeTrainIdentity" + }, + "merge_method": { + "enum": [ + "merge", + "squash", + "rebase" + ], + "title": "Merge Method", + "type": "string" + }, + "provider_delivery_protection_expectation": { + "anyOf": [ + { + "$ref": "#/components/schemas/ProviderDeliveryProtectionExpectationV1" }, { "type": "null" @@ -16942,436 +16744,413 @@ ], "x-launchplane-optional-response": true }, - "runtime_context": { + "repository": { + "title": "Repository", + "type": "string" + }, + "scheduler": { + "$ref": "#/components/schemas/MergeTrainSchedulerPolicy" + }, + "service_authz": { + "$ref": "#/components/schemas/MergeTrainServiceAuthz" + }, + "stack_child_disposition_label": { "default": "", - "title": "Runtime Context", - "type": "string", - "x-launchplane-optional-response": true + "title": "Stack Child Disposition Label", + "type": "string" } }, - "title": "MergeTrainGitHubTokenSource", + "required": [ + "repository", + "base_branch", + "enqueue_label", + "blocked_label", + "merge_method", + "failure_policy", + "enqueue", + "merge_identity" + ], + "title": "MergeTrainRepositoryPolicy", "type": "object" }, - "MergeTrainHistoricalCompletionEntryEvidence": { + "MergeTrainRepositoryPolicy-Output": { "additionalProperties": false, "properties": { - "base_contains_merge_commit": { - "title": "Base Contains Merge Commit", - "type": "boolean" - }, - "expected_base_sha": { - "title": "Expected Base Sha", + "base_branch": { + "title": "Base Branch", "type": "string" }, - "expected_head_sha": { - "title": "Expected Head Sha", + "blocked_label": { + "title": "Blocked Label", "type": "string" }, - "expected_head_tree_sha": { - "title": "Expected Head Tree Sha", + "engineering_review_mode": { + "default": "advisory", + "enum": [ + "advisory", + "required" + ], + "title": "Engineering Review Mode", "type": "string" }, - "expected_parent_tree_sha": { - "title": "Expected Parent Tree Sha", - "type": "string" + "enqueue": { + "$ref": "#/components/schemas/MergeTrainEnqueuePolicy-Output" }, - "expected_result_tree_sha": { - "title": "Expected Result Tree Sha", + "enqueue_label": { + "title": "Enqueue Label", "type": "string" }, - "observed_head_sha": { - "title": "Observed Head Sha", + "failure_policy": { + "enum": [ + "pause_train", + "continue_after_blocking_pr" + ], + "title": "Failure Policy", "type": "string" }, - "observed_head_tree_sha": { - "title": "Observed Head Tree Sha", - "type": "string" + "github_token": { + "$ref": "#/components/schemas/MergeTrainGitHubTokenSource" }, - "observed_merge_commit_sha": { - "title": "Observed Merge Commit Sha", - "type": "string" + "merge_identity": { + "$ref": "#/components/schemas/MergeTrainIdentity" }, - "observed_merge_commit_tree_sha": { - "title": "Observed Merge Commit Tree Sha", + "merge_method": { + "enum": [ + "merge", + "squash", + "rebase" + ], + "title": "Merge Method", "type": "string" }, - "observed_parent_sha": { - "title": "Observed Parent Sha", - "type": "string" + "provider_delivery_protection_expectation": { + "anyOf": [ + { + "$ref": "#/components/schemas/ProviderDeliveryProtectionExpectationV1" + }, + { + "type": "null" + } + ], + "x-launchplane-optional-response": true }, - "observed_parent_tree_sha": { - "title": "Observed Parent Tree Sha", + "repository": { + "title": "Repository", "type": "string" }, - "position": { - "exclusiveMinimum": 0.0, - "title": "Position", - "type": "integer" + "scheduler": { + "$ref": "#/components/schemas/MergeTrainSchedulerPolicy" }, - "pull_request_number": { - "exclusiveMinimum": 0.0, - "title": "Pull Request Number", - "type": "integer" + "service_authz": { + "$ref": "#/components/schemas/MergeTrainServiceAuthz" + }, + "stack_child_disposition_label": { + "default": "", + "title": "Stack Child Disposition Label", + "type": "string" } }, "required": [ - "pull_request_number", - "position", - "expected_head_sha", - "expected_head_tree_sha", - "expected_base_sha", - "expected_parent_tree_sha", - "expected_result_tree_sha", - "observed_head_sha", - "observed_head_tree_sha", - "observed_merge_commit_sha", - "observed_merge_commit_tree_sha", - "observed_parent_sha", - "observed_parent_tree_sha", - "base_contains_merge_commit" + "repository", + "base_branch", + "enqueue_label", + "blocked_label", + "merge_method", + "failure_policy", + "enqueue", + "merge_identity" ], - "title": "MergeTrainHistoricalCompletionEntryEvidence", + "title": "MergeTrainRepositoryPolicy", "type": "object" }, - "MergeTrainHistoricalCompletionEntrySelector": { + "MergeTrainRunRecord": { "additionalProperties": false, "properties": { - "expected_head_sha": { - "title": "Expected Head Sha", + "base_branch": { + "title": "Base Branch", "type": "string" }, - "expected_head_tree_sha": { - "title": "Expected Head Tree Sha", - "type": "string" + "dry_run_result": { + "additionalProperties": true, + "title": "Dry Run Result", + "type": "object" }, - "position": { - "exclusiveMinimum": 0.0, - "title": "Position", - "type": "integer" + "intended_next_action": { + "title": "Intended Next Action", + "type": "string" }, - "pull_request_number": { - "exclusiveMinimum": 0.0, - "title": "Pull Request Number", - "type": "integer" - } - }, - "required": [ - "position", - "pull_request_number", - "expected_head_sha", - "expected_head_tree_sha" - ], - "title": "MergeTrainHistoricalCompletionEntrySelector", - "type": "object" - }, - "MergeTrainHistoricalCompletionPreflight": { - "additionalProperties": false, - "properties": { - "admission_created": { - "const": false, - "default": false, - "title": "Admission Created", - "type": "boolean" + "mode": { + "enum": [ + "dry_run", + "mutate" + ], + "title": "Mode", + "type": "string" }, - "automatic_retry_allowed": { - "const": false, - "default": false, - "title": "Automatic Retry Allowed", - "type": "boolean" + "policy_key": { + "title": "Policy Key", + "type": "string" }, - "base_branch": { - "title": "Base Branch", + "policy_sha256": { + "title": "Policy Sha256", "type": "string" }, - "disposition_supported": { + "poll_required": { "default": false, - "title": "Disposition Supported", + "title": "Poll Required", "type": "boolean" }, - "entries": { - "default": [], - "items": { - "$ref": "#/components/schemas/HistoricalCompletionEntryAssessment" - }, - "title": "Entries", - "type": "array" + "recorded_at": { + "title": "Recorded At", + "type": "string" }, - "evidence_eligible": { - "default": false, - "title": "Evidence Eligible", - "type": "boolean" + "repository": { + "title": "Repository", + "type": "string" }, - "fence_released": { - "const": false, + "reread_required": { "default": false, - "title": "Fence Released", + "title": "Reread Required", "type": "boolean" }, - "generated_at": { - "title": "Generated At", + "run_id": { + "title": "Run Id", "type": "string" }, - "landing_plan_sha256": { + "schema_version": { + "default": 1, + "minimum": 1.0, + "title": "Schema Version", + "type": "integer" + }, + "selected_head_sha": { "default": "", - "title": "Landing Plan Sha256", + "title": "Selected Head Sha", "type": "string" }, - "mutation_enabled": { - "default": false, - "title": "Mutation Enabled", - "type": "boolean" - }, - "provider_effect_attempted": { - "const": false, - "default": false, - "title": "Provider Effect Attempted", - "type": "boolean" + "selected_mergeable": { + "default": "", + "title": "Selected Mergeable", + "type": "string" }, - "provider_evidence": { + "selected_pr_number": { "anyOf": [ { - "$ref": "#/components/schemas/MergeTrainHistoricalCompletionProviderEvidence" + "type": "integer" }, { "type": "null" } - ] - }, - "reason_code": { - "enum": [ - "exact_historical_completion", - "not_assessed", - "controller_unavailable", - "controller_busy", - "controller_not_reconciling", - "selector_mismatch", - "policy_unavailable", - "policy_changed", - "landing_plan_unavailable", - "landing_plan_ambiguous", - "landing_plan_binding_changed", - "stored_evidence_incomplete", - "entry_limit_exceeded", - "candidate_unavailable", - "candidate_history_limit_exceeded", - "candidate_ambiguous", - "candidate_binding_changed", - "stack_batch_unsupported", - "ordinary_target_unsupported", - "admission_present", - "store_unavailable", - "store_state_changed", - "provider_unavailable", - "provider_invalid_response", - "provider_binding_mismatch", - "pull_request_not_merged", - "no_op_unsupported", - "base_not_contains_merge", - "base_changed" ], - "title": "Reason Code", - "type": "string" + "title": "Selected Pr Number" }, - "repository": { - "title": "Repository", + "selected_required_checks_status": { + "default": "", + "title": "Selected Required Checks Status", "type": "string" }, - "schema_version": { - "const": 1, - "default": 1, - "title": "Schema Version", - "type": "integer" - }, - "selector": { - "$ref": "#/components/schemas/MergeTrainHistoricalCompletionSelector" + "snapshot": { + "additionalProperties": true, + "title": "Snapshot", + "type": "object" }, "status": { - "enum": [ - "eligible", - "unsupported", - "indeterminate" - ], "title": "Status", "type": "string" + }, + "trace_id": { + "title": "Trace Id", + "type": "string" + }, + "worker_step_result": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Worker Step Result" } }, "required": [ + "run_id", + "recorded_at", + "trace_id", "repository", "base_branch", - "generated_at", - "selector", + "mode", "status", - "reason_code" + "intended_next_action", + "policy_key", + "policy_sha256", + "dry_run_result", + "snapshot" ], - "title": "MergeTrainHistoricalCompletionPreflight", + "title": "MergeTrainRunRecord", "type": "object" }, - "MergeTrainHistoricalCompletionProviderEvidence": { + "MergeTrainSchedulerPolicy": { "additionalProperties": false, "properties": { - "entries": { - "items": { - "$ref": "#/components/schemas/MergeTrainHistoricalCompletionEntryEvidence" - }, - "maxItems": 25, - "minItems": 1, - "title": "Entries", - "type": "array" - }, - "final_observed_base_sha": { - "title": "Final Observed Base Sha", - "type": "string" - }, - "observed_at": { - "title": "Observed At", - "type": "string" - }, - "observed_base_sha": { - "title": "Observed Base Sha", - "type": "string" - }, - "observed_base_tree_sha": { - "title": "Observed Base Tree Sha", - "type": "string" + "enabled": { + "default": false, + "title": "Enabled", + "type": "boolean" }, - "provider_effect_attempted": { - "const": false, + "mutate": { "default": false, - "title": "Provider Effect Attempted", + "title": "Mutate", "type": "boolean" + }, + "runner_mode": { + "default": "controller", + "enum": [ + "level1", + "controller" + ], + "title": "Runner Mode", + "type": "string" } }, - "required": [ - "observed_at", - "observed_base_sha", - "observed_base_tree_sha", - "final_observed_base_sha", - "entries" - ], - "title": "MergeTrainHistoricalCompletionProviderEvidence", + "title": "MergeTrainSchedulerPolicy", "type": "object" }, - "MergeTrainHistoricalCompletionSelector": { + "MergeTrainServiceAuthz": { "additionalProperties": false, - "description": "The controller binding a future completion disposition would validate.", "properties": { - "expected_active_record_id": { - "title": "Expected Active Record Id", - "type": "string" - }, - "expected_effect_sha": { - "title": "Expected Effect Sha", + "action": { + "default": "merge_train.run_once", + "title": "Action", "type": "string" }, - "expected_entries": { - "items": { - "$ref": "#/components/schemas/MergeTrainHistoricalCompletionEntrySelector" - }, - "maxItems": 25, - "minItems": 1, - "title": "Expected Entries", - "type": "array" - }, - "expected_landing_plan_id": { - "title": "Expected Landing Plan Id", + "context": { + "default": "launchplane", + "title": "Context", "type": "string" }, - "expected_policy_sha256": { - "title": "Expected Policy Sha256", + "product": { + "default": "launchplane", + "title": "Product", "type": "string" } }, - "required": [ - "expected_active_record_id", - "expected_effect_sha", - "expected_policy_sha256", - "expected_landing_plan_id", - "expected_entries" - ], - "title": "MergeTrainHistoricalCompletionSelector", + "title": "MergeTrainServiceAuthz", "type": "object" }, - "MergeTrainIdentity": { + "MergeTrainStructuralCandidateResult": { "additionalProperties": false, "properties": { - "kind": { - "enum": [ - "github_actions_oidc", - "github_app", - "github_token_secret" - ], - "title": "Kind", + "candidate_sha256": { + "default": "", + "title": "Candidate Sha256", "type": "string" }, - "name": { - "title": "Name", + "effective_base_sha": { + "default": "", + "title": "Effective Base Sha", "type": "string" - } - }, - "required": [ - "kind", - "name" - ], - "title": "MergeTrainIdentity", - "type": "object" - }, - "MergeTrainLatestDryRunSummary": { - "additionalProperties": false, - "properties": { - "eligible_count": { - "title": "Eligible Count", - "type": "integer" }, - "intended_next_action": { - "title": "Intended Next Action", + "effective_base_tree_sha": { + "default": "", + "title": "Effective Base Tree Sha", "type": "string" }, - "next_action_detail": { - "title": "Next Action Detail", + "landing_plan_sha256": { + "default": "", + "title": "Landing Plan Sha256", "type": "string" }, - "queue_count": { - "title": "Queue Count", - "type": "integer" + "provenance_sha256": { + "default": "", + "title": "Provenance Sha256", + "type": "string" }, - "queue_entries": { - "default": [], + "reason_codes": { "items": { - "$ref": "#/components/schemas/MergeTrainDryRunQueueEntrySummary" + "enum": [ + "structural_single_entry_exact", + "structural_batch_entry_exact", + "structural_rolling_chain_recorded", + "structural_stack_root_recorded", + "structural_combined_owner_review_recorded", + "structural_evidence_unavailable", + "structural_record_superseded", + "structural_candidate_incomplete", + "structural_provenance_missing", + "structural_landing_plan_missing", + "structural_landing_plan_superseded", + "structural_repository_mismatch", + "structural_base_branch_mismatch", + "structural_policy_mismatch", + "structural_candidate_digest_mismatch", + "structural_landing_plan_digest_mismatch", + "structural_provenance_digest_mismatch", + "structural_candidate_sha_mismatch", + "structural_candidate_tree_mismatch", + "structural_entry_absent", + "structural_position_mismatch", + "structural_head_sha_mismatch", + "structural_head_tree_mismatch", + "structural_queue_drift", + "structural_base_sha_mismatch", + "structural_base_tree_mismatch", + "structural_rolling_chain_broken", + "structural_prior_entry_not_landed", + "structural_landing_evidence_unavailable", + "structural_prior_entry_head_mismatch", + "structural_prior_entry_tree_mismatch", + "structural_impact_unknown", + "structural_delta_drift", + "structural_changed_path_overlap", + "structural_impact_expanded", + "structural_same_subject_combined_review_required", + "structural_combined_owner_review_mismatch", + "structural_stack_root_unproven" + ], + "type": "string" }, - "title": "Queue Entries", + "title": "Reason Codes", "type": "array" }, - "selected_pr_number": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } + "status": { + "enum": [ + "exact", + "recorded_rolling", + "mismatch", + "unknown" ], - "title": "Selected Pr Number" + "title": "Status", + "type": "string" } }, "required": [ - "intended_next_action", - "next_action_detail", - "queue_count", - "eligible_count" + "status", + "reason_codes" ], - "title": "MergeTrainLatestDryRunSummary", + "title": "MergeTrainStructuralCandidateResult", "type": "object" }, - "MergeTrainPolicy-Input": { + "NpmplusIngressApplyEnvelope": { "additionalProperties": false, "properties": { - "policies": { - "items": { - "$ref": "#/components/schemas/MergeTrainRepositoryPolicy-Input" - }, - "title": "Policies", - "type": "array" + "context": { + "title": "Context", + "type": "string" + }, + "ingress": { + "$ref": "#/components/schemas/NpmplusIngressApplyRequest" + }, + "instance": { + "default": "", + "title": "Instance", + "type": "string" + }, + "product": { + "title": "Product", + "type": "string" }, "schema_version": { "default": 1, @@ -17381,567 +17160,476 @@ } }, "required": [ - "policies" + "product", + "context", + "ingress" ], - "title": "MergeTrainPolicy", + "title": "NpmplusIngressApplyEnvelope", "type": "object" }, - "MergeTrainPolicy-Output": { + "NpmplusIngressApplyRequest": { "additionalProperties": false, "properties": { - "policies": { - "items": { - "$ref": "#/components/schemas/MergeTrainRepositoryPolicy-Output" - }, - "title": "Policies", - "type": "array" + "allow_create": { + "default": true, + "title": "Allow Create", + "type": "boolean" + }, + "allow_enable_disable": { + "default": true, + "title": "Allow Enable Disable", + "type": "boolean" + }, + "allow_update": { + "default": true, + "title": "Allow Update", + "type": "boolean" + }, + "expected_host_id": { + "anyOf": [ + { + "minimum": 1.0, + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Expected Host Id" + }, + "mode": { + "$ref": "#/components/schemas/NpmplusIngressMode", + "default": "dry-run" + }, + "reason": { + "default": "", + "title": "Reason", + "type": "string" + }, + "require_exact_expected_host_domains": { + "default": false, + "title": "Require Exact Expected Host Domains", + "type": "boolean" + }, + "route": { + "$ref": "#/components/schemas/NpmplusIngressRouteDesiredState" }, "schema_version": { "default": 1, - "minimum": 1.0, "title": "Schema Version", "type": "integer" } }, "required": [ - "policies" + "route" ], - "title": "MergeTrainPolicy", + "title": "NpmplusIngressApplyRequest", "type": "object" }, - "MergeTrainPolicyReadResponse": { - "additionalProperties": false, - "properties": { - "record": { - "$ref": "#/components/schemas/MergeTrainPolicyRecord-Output" - }, - "status": { - "const": "ok", - "default": "ok", - "title": "Status", - "type": "string" - }, - "trace_id": { - "title": "Trace Id", - "type": "string" - } - }, - "required": [ - "trace_id", - "record" + "NpmplusIngressMode": { + "enum": [ + "dry-run", + "apply" ], - "title": "MergeTrainPolicyReadResponse", - "type": "object" + "type": "string" }, - "MergeTrainPolicyRecord-Input": { + "NpmplusIngressRouteDesiredState": { "additionalProperties": false, "properties": { - "policy": { - "$ref": "#/components/schemas/MergeTrainPolicy-Input" + "access_list_id": { + "default": 0, + "minimum": 0.0, + "title": "Access List Id", + "type": "integer" }, - "policy_sha256": { + "advanced_config": { "default": "", - "title": "Policy Sha256", + "title": "Advanced Config", "type": "string" }, - "record_id": { - "title": "Record Id", + "certificate_id": { + "anyOf": [ + { + "type": "integer" + }, + { + "const": "new", + "type": "string" + } + ], + "default": 0, + "title": "Certificate Id" + }, + "domain_names": { + "items": { + "type": "string" + }, + "title": "Domain Names", + "type": "array" + }, + "edge_endpoint_key": { + "default": "", + "title": "Edge Endpoint Key", "type": "string" }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" + "enabled": { + "default": true, + "title": "Enabled", + "type": "boolean" }, - "source": { - "title": "Source", + "forward_host": { + "default": "", + "title": "Forward Host", "type": "string" }, - "status": { - "default": "active", + "forward_port": { + "anyOf": [ + { + "maximum": 65535.0, + "minimum": 1.0, + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Forward Port" + }, + "forward_scheme": { + "default": "https", "enum": [ - "active", - "superseded" + "http", + "https", + "path", + "empty", + "grpc", + "grpcs" ], - "title": "Status", + "title": "Forward Scheme", "type": "string" }, - "updated_at": { - "title": "Updated At", - "type": "string" - } - }, - "required": [ - "record_id", - "source", - "updated_at", - "policy" - ], - "title": "MergeTrainPolicyRecord", - "type": "object" - }, - "MergeTrainPolicyRecord-Output": { - "additionalProperties": false, - "properties": { - "policy": { - "$ref": "#/components/schemas/MergeTrainPolicy-Output" + "hsts_enabled": { + "default": false, + "title": "Hsts Enabled", + "type": "boolean" }, - "policy_sha256": { - "default": "", - "title": "Policy Sha256", - "type": "string" + "hsts_subdomains": { + "default": false, + "title": "Hsts Subdomains", + "type": "boolean" }, - "record_id": { - "title": "Record Id", - "type": "string" + "http2_support": { + "default": true, + "title": "Http2 Support", + "type": "boolean" }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" + "identity_access": { + "anyOf": [ + { + "$ref": "#/components/schemas/IngressIdentityAccessBinding" + }, + { + "type": "null" + } + ] }, - "source": { - "title": "Source", - "type": "string" + "locations": { + "default": [], + "items": { + "$ref": "#/components/schemas/NpmplusLocationPayload" + }, + "title": "Locations", + "type": "array" }, - "status": { - "default": "active", + "npmplus_auth_request": { + "default": "none", "enum": [ - "active", - "superseded" + "none", + "anubis", + "tinyauth", + "authelia", + "authentik", + "authentik-send-basic-auth" ], - "title": "Status", + "title": "Npmplus Auth Request", "type": "string" }, - "updated_at": { - "title": "Updated At", - "type": "string" - } - }, - "required": [ - "record_id", - "source", - "updated_at", - "policy" - ], - "title": "MergeTrainPolicyRecord", - "type": "object" - }, - "MergeTrainPolicySummary": { - "additionalProperties": false, - "properties": { - "policy_sha256": { - "title": "Policy Sha256", - "type": "string" + "npmplus_crowdsec_appsec": { + "default": false, + "title": "Npmplus Crowdsec Appsec", + "type": "boolean" }, - "record_id": { - "title": "Record Id", - "type": "string" + "npmplus_fancyindex": { + "default": false, + "title": "Npmplus Fancyindex", + "type": "boolean" }, - "updated_at": { - "title": "Updated At", - "type": "string" - } - }, - "required": [ - "record_id", - "updated_at", - "policy_sha256" - ], - "title": "MergeTrainPolicySummary", - "type": "object" - }, - "MergeTrainPolicyTarget": { - "additionalProperties": false, - "properties": { - "base_branch": { - "title": "Base Branch", - "type": "string" + "npmplus_http3_support": { + "default": true, + "title": "Npmplus Http3 Support", + "type": "boolean" }, - "policy_key": { - "title": "Policy Key", - "type": "string" + "npmplus_noindex": { + "default": false, + "title": "Npmplus Noindex", + "type": "boolean" }, - "repository": { - "title": "Repository", - "type": "string" + "npmplus_proxy_request_buffering": { + "default": false, + "title": "Npmplus Proxy Request Buffering", + "type": "boolean" }, - "scheduler": { - "$ref": "#/components/schemas/MergeTrainSchedulerPolicy" + "npmplus_proxy_response_buffering": { + "default": false, + "title": "Npmplus Proxy Response Buffering", + "type": "boolean" }, - "service_authz": { - "$ref": "#/components/schemas/MergeTrainServiceAuthz" - } - }, - "required": [ - "repository", - "base_branch", - "policy_key", - "scheduler", - "service_authz" - ], - "title": "MergeTrainPolicyTarget", - "type": "object" - }, - "MergeTrainPolicyTargetsResponse": { - "additionalProperties": false, - "properties": { - "policy": { - "$ref": "#/components/schemas/MergeTrainPolicySummary" + "npmplus_upstream_compression": { + "default": false, + "title": "Npmplus Upstream Compression", + "type": "boolean" }, - "status": { - "const": "ok", - "default": "ok", - "title": "Status", + "npmplus_x_frame_options": { + "default": "SAMEORIGIN", + "enum": [ + "DENY", + "SAMEORIGIN", + "upstream", + "none" + ], + "title": "Npmplus X Frame Options", "type": "string" }, - "targets": { - "items": { - "$ref": "#/components/schemas/MergeTrainPolicyTarget" - }, - "title": "Targets", - "type": "array" + "ssl_forced": { + "default": true, + "title": "Ssl Forced", + "type": "boolean" }, - "trace_id": { - "title": "Trace Id", - "type": "string" + "trust_forwarded_proto": { + "default": false, + "title": "Trust Forwarded Proto", + "type": "boolean" } }, "required": [ - "trace_id", - "policy", - "targets" + "domain_names" ], - "title": "MergeTrainPolicyTargetsResponse", + "title": "NpmplusIngressRouteDesiredState", "type": "object" }, - "MergeTrainReconciliationDiagnostic": { + "NpmplusLocationPayload": { "additionalProperties": false, "properties": { - "base_branch": { - "title": "Base Branch", + "advanced_config": { + "default": "", + "title": "Advanced Config", "type": "string" }, - "binding_detail": { - "default": "", - "enum": [ - "", - "current_policy_unavailable", - "controller_policy_changed", - "plan_reference_conflict", - "plan_reference_incomplete", - "plan_record_unavailable", - "plan_binding_changed", - "plan_entry_limit_exceeded", - "selected_entry_unavailable", - "history_reader_unavailable", - "admission_limit_exceeded", - "admission_binding_changed", - "admission_identity_missing", - "outcome_binding_changed", - "history_unavailable", - "outcome_status_unknown" - ], - "title": "Binding Detail", - "type": "string" - }, - "classification": { - "enum": [ - "missing_preceding_admission", - "admission_without_outcome", - "outcome_reconcile_required", - "outcome_rejected", - "outcome_landed", - "binding_unavailable", - "binding_stale" - ], - "title": "Classification", - "type": "string" - }, - "expected_head_sha": { - "default": "", - "title": "Expected Head Sha", - "type": "string" - }, - "expected_head_tree_sha": { - "default": "", - "title": "Expected Head Tree Sha", - "type": "string" - }, - "landing_plan_id": { - "default": "", - "title": "Landing Plan Id", - "type": "string" - }, - "landing_plan_record_id": { - "default": "", - "title": "Landing Plan Record Id", + "forward_host": { + "title": "Forward Host", "type": "string" }, - "pull_request_number": { + "forward_port": { "anyOf": [ { + "maximum": 65535.0, + "minimum": 1.0, "type": "integer" }, { "type": "null" } ], - "title": "Pull Request Number" - }, - "repository": { - "title": "Repository", - "type": "string" - }, - "schema_version": { - "default": 1, - "title": "Schema Version", - "type": "integer" - } - }, - "required": [ - "classification", - "repository", - "base_branch" - ], - "title": "MergeTrainReconciliationDiagnostic", - "type": "object" - }, - "MergeTrainRepositoryPolicy-Input": { - "additionalProperties": false, - "properties": { - "base_branch": { - "title": "Base Branch", - "type": "string" - }, - "blocked_label": { - "title": "Blocked Label", - "type": "string" - }, - "engineering_review_mode": { - "default": "advisory", - "enum": [ - "advisory", - "required" - ], - "title": "Engineering Review Mode", - "type": "string" - }, - "enqueue": { - "$ref": "#/components/schemas/MergeTrainEnqueuePolicy-Input" - }, - "enqueue_label": { - "title": "Enqueue Label", - "type": "string" - }, - "failure_policy": { - "enum": [ - "pause_train", - "continue_after_blocking_pr" - ], - "title": "Failure Policy", - "type": "string" - }, - "github_token": { - "$ref": "#/components/schemas/MergeTrainGitHubTokenSource" - }, - "merge_identity": { - "$ref": "#/components/schemas/MergeTrainIdentity" + "title": "Forward Port" }, - "merge_method": { + "forward_scheme": { "enum": [ - "merge", - "squash", - "rebase" + "http", + "https", + "path", + "empty", + "grpc", + "grpcs" ], - "title": "Merge Method", + "title": "Forward Scheme", "type": "string" }, - "provider_delivery_protection_expectation": { + "id": { "anyOf": [ { - "$ref": "#/components/schemas/ProviderDeliveryProtectionExpectationV1" + "type": "integer" }, { "type": "null" } ], - "x-launchplane-optional-response": true - }, - "repository": { - "title": "Repository", - "type": "string" - }, - "scheduler": { - "$ref": "#/components/schemas/MergeTrainSchedulerPolicy" - }, - "service_authz": { - "$ref": "#/components/schemas/MergeTrainServiceAuthz" + "title": "Id" }, - "stack_child_disposition_label": { + "location_type": { "default": "", - "title": "Stack Child Disposition Label", - "type": "string" - } - }, - "required": [ - "repository", - "base_branch", - "enqueue_label", - "blocked_label", - "merge_method", - "failure_policy", - "enqueue", - "merge_identity" - ], - "title": "MergeTrainRepositoryPolicy", - "type": "object" - }, - "MergeTrainRepositoryPolicy-Output": { - "additionalProperties": false, - "properties": { - "base_branch": { - "title": "Base Branch", - "type": "string" - }, - "blocked_label": { - "title": "Blocked Label", + "title": "Location Type", "type": "string" }, - "engineering_review_mode": { - "default": "advisory", + "npmplus_auth_request": { + "default": "none", "enum": [ - "advisory", - "required" + "none", + "anubis", + "tinyauth", + "authelia", + "authentik", + "authentik-send-basic-auth" ], - "title": "Engineering Review Mode", + "title": "Npmplus Auth Request", "type": "string" }, - "enqueue": { - "$ref": "#/components/schemas/MergeTrainEnqueuePolicy-Output" + "npmplus_crowdsec_appsec": { + "default": false, + "title": "Npmplus Crowdsec Appsec", + "type": "boolean" }, - "enqueue_label": { - "title": "Enqueue Label", - "type": "string" + "npmplus_enabled": { + "default": true, + "title": "Npmplus Enabled", + "type": "boolean" }, - "failure_policy": { - "enum": [ - "pause_train", - "continue_after_blocking_pr" - ], - "title": "Failure Policy", - "type": "string" + "npmplus_fancyindex": { + "default": false, + "title": "Npmplus Fancyindex", + "type": "boolean" }, - "github_token": { - "$ref": "#/components/schemas/MergeTrainGitHubTokenSource" + "npmplus_noindex": { + "default": false, + "title": "Npmplus Noindex", + "type": "boolean" }, - "merge_identity": { - "$ref": "#/components/schemas/MergeTrainIdentity" + "npmplus_proxy_request_buffering": { + "default": false, + "title": "Npmplus Proxy Request Buffering", + "type": "boolean" }, - "merge_method": { - "enum": [ - "merge", - "squash", - "rebase" - ], - "title": "Merge Method", - "type": "string" + "npmplus_proxy_response_buffering": { + "default": false, + "title": "Npmplus Proxy Response Buffering", + "type": "boolean" }, - "provider_delivery_protection_expectation": { - "anyOf": [ - { - "$ref": "#/components/schemas/ProviderDeliveryProtectionExpectationV1" - }, - { - "type": "null" - } - ], - "x-launchplane-optional-response": true + "npmplus_upstream_compression": { + "default": false, + "title": "Npmplus Upstream Compression", + "type": "boolean" }, - "repository": { - "title": "Repository", + "npmplus_x_frame_options": { + "default": "SAMEORIGIN", + "enum": [ + "DENY", + "SAMEORIGIN", + "upstream", + "none" + ], + "title": "Npmplus X Frame Options", "type": "string" }, - "scheduler": { - "$ref": "#/components/schemas/MergeTrainSchedulerPolicy" - }, - "service_authz": { - "$ref": "#/components/schemas/MergeTrainServiceAuthz" - }, - "stack_child_disposition_label": { - "default": "", - "title": "Stack Child Disposition Label", + "path": { + "title": "Path", "type": "string" } }, "required": [ - "repository", - "base_branch", - "enqueue_label", - "blocked_label", - "merge_method", - "failure_policy", - "enqueue", - "merge_identity" + "path", + "forward_scheme", + "forward_host" ], - "title": "MergeTrainRepositoryPolicy", + "title": "NpmplusLocationPayload", "type": "object" }, - "MergeTrainRunRecord": { + "OdooAddonSettingOverride": { "additionalProperties": false, "properties": { - "base_branch": { - "title": "Base Branch", + "addon": { + "title": "Addon", "type": "string" }, - "dry_run_result": { - "additionalProperties": true, - "title": "Dry Run Result", - "type": "object" - }, - "intended_next_action": { - "title": "Intended Next Action", + "setting": { + "title": "Setting", "type": "string" }, - "mode": { - "enum": [ - "dry_run", - "mutate" - ], - "title": "Mode", + "value": { + "$ref": "#/components/schemas/OdooOverrideValue" + } + }, + "required": [ + "addon", + "setting", + "value" + ], + "title": "OdooAddonSettingOverride", + "type": "object" + }, + "OdooConfigParameterOverride": { + "additionalProperties": false, + "properties": { + "key": { + "title": "Key", "type": "string" }, - "policy_key": { - "title": "Policy Key", - "type": "string" + "value": { + "$ref": "#/components/schemas/OdooOverrideValue" + } + }, + "required": [ + "key", + "value" + ], + "title": "OdooConfigParameterOverride", + "type": "object" + }, + "OdooInstanceOverrideRecord": { + "additionalProperties": false, + "properties": { + "addon_settings": { + "default": [], + "items": { + "$ref": "#/components/schemas/OdooAddonSettingOverride" + }, + "title": "Addon Settings", + "type": "array" }, - "policy_sha256": { - "title": "Policy Sha256", - "type": "string" + "apply_on": { + "default": [ + "deploy", + "promotion" + ], + "items": { + "enum": [ + "restore", + "deploy", + "promotion", + "preview", + "manual" + ], + "type": "string" + }, + "title": "Apply On", + "type": "array" }, - "poll_required": { - "default": false, - "title": "Poll Required", - "type": "boolean" + "config_parameters": { + "default": [], + "items": { + "$ref": "#/components/schemas/OdooConfigParameterOverride" + }, + "title": "Config Parameters", + "type": "array" }, - "recorded_at": { - "title": "Recorded At", + "context": { + "title": "Context", "type": "string" }, - "repository": { - "title": "Repository", + "instance": { + "title": "Instance", "type": "string" }, - "reread_required": { - "default": false, - "title": "Reread Required", - "type": "boolean" - }, - "run_id": { - "title": "Run Id", - "type": "string" + "last_apply": { + "$ref": "#/components/schemas/OdooOverrideApplyResult" }, "schema_version": { "default": 1, @@ -17949,760 +17637,487 @@ "title": "Schema Version", "type": "integer" }, - "selected_head_sha": { + "source_label": { "default": "", - "title": "Selected Head Sha", + "title": "Source Label", "type": "string" }, - "selected_mergeable": { - "default": "", - "title": "Selected Mergeable", + "updated_at": { + "title": "Updated At", "type": "string" }, - "selected_pr_number": { + "website_bootstrap": { "anyOf": [ { - "type": "integer" + "$ref": "#/components/schemas/OdooWebsiteBootstrapPayload" }, { "type": "null" } - ], - "title": "Selected Pr Number" + ] + } + }, + "required": [ + "context", + "instance", + "updated_at" + ], + "title": "OdooInstanceOverrideRecord", + "type": "object" + }, + "OdooOutgoingEmailResponse": { + "additionalProperties": false, + "properties": { + "email": { + "$ref": "#/components/schemas/OutgoingEmailResult" }, - "selected_required_checks_status": { - "default": "", - "title": "Selected Required Checks Status", - "type": "string" + "query": { + "$ref": "#/components/schemas/OutgoingEmailQuery" }, - "snapshot": { - "additionalProperties": true, - "title": "Snapshot", - "type": "object" + "runtime": { + "$ref": "#/components/schemas/OdooRuntimeEvidence" }, "status": { + "const": "ok", + "default": "ok", "title": "Status", "type": "string" }, "trace_id": { "title": "Trace Id", "type": "string" - }, - "worker_step_result": { - "anyOf": [ - { - "additionalProperties": true, - "type": "object" - }, - { - "type": "null" - } - ], - "title": "Worker Step Result" } }, "required": [ - "run_id", - "recorded_at", "trace_id", - "repository", - "base_branch", - "mode", - "status", - "intended_next_action", - "policy_key", - "policy_sha256", - "dry_run_result", - "snapshot" + "runtime", + "query", + "email" ], - "title": "MergeTrainRunRecord", + "title": "OdooOutgoingEmailResponse", "type": "object" }, - "MergeTrainSchedulerPolicy": { + "OdooOverrideApplyResult": { "additionalProperties": false, "properties": { - "enabled": { - "default": false, - "title": "Enabled", - "type": "boolean" + "applied_at": { + "default": "", + "title": "Applied At", + "type": "string" }, - "mutate": { + "attempted": { "default": false, - "title": "Mutate", + "title": "Attempted", "type": "boolean" }, - "runner_mode": { - "default": "controller", + "detail": { + "default": "", + "title": "Detail", + "type": "string" + }, + "status": { + "default": "skipped", "enum": [ - "level1", - "controller" + "skipped", + "pending", + "pass", + "fail" ], - "title": "Runner Mode", + "title": "Status", "type": "string" } }, - "title": "MergeTrainSchedulerPolicy", + "title": "OdooOverrideApplyResult", "type": "object" }, - "MergeTrainServiceAuthz": { + "OdooOverrideValue": { "additionalProperties": false, "properties": { - "action": { - "default": "merge_train.run_once", - "title": "Action", - "type": "string" - }, - "context": { - "default": "launchplane", - "title": "Context", + "secret_binding_id": { + "default": "", + "title": "Secret Binding Id", "type": "string" }, - "product": { - "default": "launchplane", - "title": "Product", + "source": { + "enum": [ + "literal", + "secret_binding" + ], + "title": "Source", "type": "string" + }, + "value": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + }, + { + "type": "number" + }, + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "title": "Value" } }, - "title": "MergeTrainServiceAuthz", + "required": [ + "source" + ], + "title": "OdooOverrideValue", "type": "object" }, - "MergeTrainStructuralCandidateResult": { + "OdooProdBackupRestoreOperationStatusResponse": { "additionalProperties": false, "properties": { - "candidate_sha256": { - "default": "", - "title": "Candidate Sha256", - "type": "string" - }, - "effective_base_sha": { - "default": "", - "title": "Effective Base Sha", - "type": "string" + "operation": { + "additionalProperties": true, + "title": "Operation", + "type": "object" }, - "effective_base_tree_sha": { - "default": "", - "title": "Effective Base Tree Sha", - "type": "string" + "result": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Result" }, - "landing_plan_sha256": { - "default": "", - "title": "Landing Plan Sha256", + "status": { + "const": "ok", + "default": "ok", + "title": "Status", "type": "string" }, - "provenance_sha256": { - "default": "", - "title": "Provenance Sha256", + "trace_id": { + "title": "Trace Id", "type": "string" + } + }, + "required": [ + "trace_id", + "operation" + ], + "title": "OdooProdBackupRestoreOperationStatusResponse", + "type": "object" + }, + "OdooProdRetainedVolumeBackupImportOperationStatusResponse": { + "additionalProperties": false, + "properties": { + "operation": { + "additionalProperties": true, + "title": "Operation", + "type": "object" }, - "reason_codes": { - "items": { - "enum": [ - "structural_single_entry_exact", - "structural_batch_entry_exact", - "structural_rolling_chain_recorded", - "structural_stack_root_recorded", - "structural_combined_owner_review_recorded", - "structural_evidence_unavailable", - "structural_record_superseded", - "structural_candidate_incomplete", - "structural_provenance_missing", - "structural_landing_plan_missing", - "structural_landing_plan_superseded", - "structural_repository_mismatch", - "structural_base_branch_mismatch", - "structural_policy_mismatch", - "structural_candidate_digest_mismatch", - "structural_landing_plan_digest_mismatch", - "structural_provenance_digest_mismatch", - "structural_candidate_sha_mismatch", - "structural_candidate_tree_mismatch", - "structural_entry_absent", - "structural_position_mismatch", - "structural_head_sha_mismatch", - "structural_head_tree_mismatch", - "structural_queue_drift", - "structural_base_sha_mismatch", - "structural_base_tree_mismatch", - "structural_rolling_chain_broken", - "structural_prior_entry_not_landed", - "structural_landing_evidence_unavailable", - "structural_prior_entry_head_mismatch", - "structural_prior_entry_tree_mismatch", - "structural_impact_unknown", - "structural_delta_drift", - "structural_changed_path_overlap", - "structural_impact_expanded", - "structural_same_subject_combined_review_required", - "structural_combined_owner_review_mismatch", - "structural_stack_root_unproven" - ], - "type": "string" - }, - "title": "Reason Codes", - "type": "array" + "result": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Result" }, "status": { - "enum": [ - "exact", - "recorded_rolling", - "mismatch", - "unknown" - ], + "const": "ok", + "default": "ok", "title": "Status", "type": "string" + }, + "trace_id": { + "title": "Trace Id", + "type": "string" } }, "required": [ - "status", - "reason_codes" + "trace_id", + "operation" ], - "title": "MergeTrainStructuralCandidateResult", + "title": "OdooProdRetainedVolumeBackupImportOperationStatusResponse", "type": "object" }, - "NpmplusIngressApplyEnvelope": { + "OdooRuntimeEvidence": { "additionalProperties": false, "properties": { - "context": { - "title": "Context", + "container_id": { + "title": "Container Id", "type": "string" }, - "ingress": { - "$ref": "#/components/schemas/NpmplusIngressApplyRequest" + "identity": { + "$ref": "#/components/schemas/RuntimeIdentity" }, - "instance": { - "default": "", - "title": "Instance", + "observed_at": { + "title": "Observed At", "type": "string" }, - "product": { - "title": "Product", + "target_id": { + "title": "Target Id", "type": "string" }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" + "target_name": { + "title": "Target Name", + "type": "string" } }, "required": [ - "product", - "context", - "ingress" + "identity", + "target_id", + "target_name", + "container_id", + "observed_at" ], - "title": "NpmplusIngressApplyEnvelope", + "title": "OdooRuntimeEvidence", "type": "object" }, - "NpmplusIngressApplyRequest": { + "OdooRuntimeLogsResponse": { "additionalProperties": false, "properties": { - "allow_create": { - "default": true, - "title": "Allow Create", - "type": "boolean" + "line_limit": { + "title": "Line Limit", + "type": "integer" }, - "allow_enable_disable": { - "default": true, - "title": "Allow Enable Disable", - "type": "boolean" + "lines": { + "items": { + "type": "string" + }, + "title": "Lines", + "type": "array" }, - "allow_update": { + "redacted": { "default": true, - "title": "Allow Update", + "title": "Redacted", "type": "boolean" }, - "expected_host_id": { - "anyOf": [ - { - "minimum": 1.0, - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Expected Host Id" - }, - "mode": { - "$ref": "#/components/schemas/NpmplusIngressMode", - "default": "dry-run" + "runtime": { + "$ref": "#/components/schemas/OdooRuntimeEvidence" }, - "reason": { - "default": "", - "title": "Reason", + "search": { + "title": "Search", "type": "string" }, - "require_exact_expected_host_domains": { - "default": false, - "title": "Require Exact Expected Host Domains", - "type": "boolean" + "since": { + "title": "Since", + "type": "string" }, - "route": { - "$ref": "#/components/schemas/NpmplusIngressRouteDesiredState" + "status": { + "const": "ok", + "default": "ok", + "title": "Status", + "type": "string" }, - "schema_version": { - "default": 1, - "title": "Schema Version", - "type": "integer" + "trace_id": { + "title": "Trace Id", + "type": "string" } }, "required": [ - "route" + "trace_id", + "runtime", + "lines", + "line_limit", + "since", + "search" ], - "title": "NpmplusIngressApplyRequest", + "title": "OdooRuntimeLogsResponse", "type": "object" }, - "NpmplusIngressMode": { - "enum": [ - "dry-run", - "apply" - ], - "type": "string" - }, - "NpmplusIngressRouteDesiredState": { + "OdooStableBootstrapOperationStatusResponse": { "additionalProperties": false, "properties": { - "access_list_id": { - "default": 0, - "minimum": 0.0, - "title": "Access List Id", - "type": "integer" - }, - "advanced_config": { - "default": "", - "title": "Advanced Config", - "type": "string" + "operation": { + "additionalProperties": true, + "title": "Operation", + "type": "object" }, - "certificate_id": { + "result": { "anyOf": [ { - "type": "integer" + "additionalProperties": true, + "type": "object" }, { - "const": "new", - "type": "string" + "type": "null" } ], - "default": 0, - "title": "Certificate Id" - }, - "domain_names": { - "items": { - "type": "string" - }, - "title": "Domain Names", - "type": "array" + "title": "Result" }, - "edge_endpoint_key": { - "default": "", - "title": "Edge Endpoint Key", + "status": { + "const": "ok", + "default": "ok", + "title": "Status", "type": "string" }, - "enabled": { - "default": true, - "title": "Enabled", - "type": "boolean" + "trace_id": { + "title": "Trace Id", + "type": "string" + } + }, + "required": [ + "trace_id", + "operation" + ], + "title": "OdooStableBootstrapOperationStatusResponse", + "type": "object" + }, + "OdooStableOperationLeaseSummaryResponse": { + "additionalProperties": false, + "properties": { + "attempt": { + "title": "Attempt", + "type": "integer" }, - "forward_host": { - "default": "", - "title": "Forward Host", + "context": { + "title": "Context", "type": "string" }, - "forward_port": { + "heartbeat_age_seconds": { "anyOf": [ { - "maximum": 65535.0, - "minimum": 1.0, "type": "integer" }, { "type": "null" } ], - "title": "Forward Port" + "title": "Heartbeat Age Seconds" }, - "forward_scheme": { - "default": "https", - "enum": [ - "http", - "https", - "path", - "empty", - "grpc", - "grpcs" - ], - "title": "Forward Scheme", + "heartbeat_at": { + "title": "Heartbeat At", "type": "string" }, - "hsts_enabled": { - "default": false, - "title": "Hsts Enabled", - "type": "boolean" - }, - "hsts_subdomains": { - "default": false, - "title": "Hsts Subdomains", - "type": "boolean" - }, - "http2_support": { - "default": true, - "title": "Http2 Support", - "type": "boolean" - }, - "identity_access": { - "anyOf": [ - { - "$ref": "#/components/schemas/IngressIdentityAccessBinding" - }, - { - "type": "null" - } - ] - }, - "locations": { - "default": [], - "items": { - "$ref": "#/components/schemas/NpmplusLocationPayload" - }, - "title": "Locations", - "type": "array" - }, - "npmplus_auth_request": { - "default": "none", - "enum": [ - "none", - "anubis", - "tinyauth", - "authelia", - "authentik", - "authentik-send-basic-auth" - ], - "title": "Npmplus Auth Request", + "instance": { + "title": "Instance", "type": "string" }, - "npmplus_crowdsec_appsec": { - "default": false, - "title": "Npmplus Crowdsec Appsec", - "type": "boolean" - }, - "npmplus_fancyindex": { - "default": false, - "title": "Npmplus Fancyindex", - "type": "boolean" - }, - "npmplus_http3_support": { - "default": true, - "title": "Npmplus Http3 Support", + "lease_expired": { + "title": "Lease Expired", "type": "boolean" }, - "npmplus_noindex": { - "default": false, - "title": "Npmplus Noindex", - "type": "boolean" + "lease_expires_at": { + "title": "Lease Expires At", + "type": "string" }, - "npmplus_proxy_request_buffering": { - "default": false, - "title": "Npmplus Proxy Request Buffering", - "type": "boolean" + "lease_owner": { + "title": "Lease Owner", + "type": "string" }, - "npmplus_proxy_response_buffering": { - "default": false, - "title": "Npmplus Proxy Response Buffering", - "type": "boolean" + "operation_id": { + "title": "Operation Id", + "type": "string" }, - "npmplus_upstream_compression": { - "default": false, - "title": "Npmplus Upstream Compression", - "type": "boolean" + "operation_kind": { + "title": "Operation Kind", + "type": "string" }, - "npmplus_x_frame_options": { - "default": "SAMEORIGIN", - "enum": [ - "DENY", - "SAMEORIGIN", - "upstream", - "none" - ], - "title": "Npmplus X Frame Options", + "phase": { + "title": "Phase", "type": "string" }, - "ssl_forced": { - "default": true, - "title": "Ssl Forced", - "type": "boolean" + "product": { + "title": "Product", + "type": "string" }, - "trust_forwarded_proto": { - "default": false, - "title": "Trust Forwarded Proto", - "type": "boolean" + "status": { + "title": "Status", + "type": "string" } }, "required": [ - "domain_names" + "operation_kind", + "operation_id", + "product", + "context", + "instance", + "status", + "phase", + "attempt", + "lease_owner", + "lease_expires_at", + "heartbeat_at", + "heartbeat_age_seconds", + "lease_expired" ], - "title": "NpmplusIngressRouteDesiredState", + "title": "OdooStableOperationLeaseSummaryResponse", "type": "object" }, - "NpmplusLocationPayload": { + "OdooStableOperationWorkerReconcileResponse": { "additionalProperties": false, "properties": { - "advanced_config": { - "default": "", - "title": "Advanced Config", - "type": "string" - }, - "forward_host": { - "title": "Forward Host", - "type": "string" - }, - "forward_port": { - "anyOf": [ - { - "maximum": 65535.0, - "minimum": 1.0, - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Forward Port" - }, - "forward_scheme": { - "enum": [ - "http", - "https", - "path", - "empty", - "grpc", - "grpcs" - ], - "title": "Forward Scheme", - "type": "string" - }, - "id": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Id" - }, - "location_type": { - "default": "", - "title": "Location Type", - "type": "string" - }, - "npmplus_auth_request": { - "default": "none", - "enum": [ - "none", - "anubis", - "tinyauth", - "authelia", - "authentik", - "authentik-send-basic-auth" - ], - "title": "Npmplus Auth Request", - "type": "string" - }, - "npmplus_crowdsec_appsec": { - "default": false, - "title": "Npmplus Crowdsec Appsec", - "type": "boolean" - }, - "npmplus_enabled": { - "default": true, - "title": "Npmplus Enabled", - "type": "boolean" - }, - "npmplus_fancyindex": { - "default": false, - "title": "Npmplus Fancyindex", - "type": "boolean" - }, - "npmplus_noindex": { - "default": false, - "title": "Npmplus Noindex", - "type": "boolean" - }, - "npmplus_proxy_request_buffering": { - "default": false, - "title": "Npmplus Proxy Request Buffering", - "type": "boolean" - }, - "npmplus_proxy_response_buffering": { - "default": false, - "title": "Npmplus Proxy Response Buffering", - "type": "boolean" - }, - "npmplus_upstream_compression": { - "default": false, - "title": "Npmplus Upstream Compression", - "type": "boolean" - }, - "npmplus_x_frame_options": { - "default": "SAMEORIGIN", - "enum": [ - "DENY", - "SAMEORIGIN", - "upstream", - "none" - ], - "title": "Npmplus X Frame Options", - "type": "string" - }, - "path": { - "title": "Path", - "type": "string" - } - }, - "required": [ - "path", - "forward_scheme", - "forward_host" - ], - "title": "NpmplusLocationPayload", - "type": "object" - }, - "OdooAddonSettingOverride": { - "additionalProperties": false, - "properties": { - "addon": { - "title": "Addon", - "type": "string" + "reconcile_result": { + "$ref": "#/components/schemas/OdooStableOperationWorkerReconcileResultResponse" }, - "setting": { - "title": "Setting", + "status": { + "const": "ok", + "default": "ok", + "title": "Status", "type": "string" }, - "value": { - "$ref": "#/components/schemas/OdooOverrideValue" - } - }, - "required": [ - "addon", - "setting", - "value" - ], - "title": "OdooAddonSettingOverride", - "type": "object" - }, - "OdooConfigParameterOverride": { - "additionalProperties": false, - "properties": { - "key": { - "title": "Key", + "trace_id": { + "title": "Trace Id", "type": "string" - }, - "value": { - "$ref": "#/components/schemas/OdooOverrideValue" } }, "required": [ - "key", - "value" + "trace_id", + "reconcile_result" ], - "title": "OdooConfigParameterOverride", + "title": "OdooStableOperationWorkerReconcileResponse", "type": "object" }, - "OdooInstanceOverrideRecord": { + "OdooStableOperationWorkerReconcileResultResponse": { "additionalProperties": false, "properties": { - "addon_settings": { - "default": [], + "reconciled_bootstrap_ids": { "items": { - "$ref": "#/components/schemas/OdooAddonSettingOverride" + "type": "string" }, - "title": "Addon Settings", + "title": "Reconciled Bootstrap Ids", "type": "array" }, - "apply_on": { - "default": [ - "deploy", - "promotion" - ], + "reconciled_count": { + "title": "Reconciled Count", + "type": "integer" + }, + "reconciled_replacement_ids": { "items": { - "enum": [ - "restore", - "deploy", - "promotion", - "preview", - "manual" - ], "type": "string" }, - "title": "Apply On", + "title": "Reconciled Replacement Ids", "type": "array" }, - "config_parameters": { - "default": [], + "reconciled_restore_ids": { "items": { - "$ref": "#/components/schemas/OdooConfigParameterOverride" + "type": "string" }, - "title": "Config Parameters", + "title": "Reconciled Restore Ids", "type": "array" - }, - "context": { - "title": "Context", - "type": "string" - }, - "instance": { - "title": "Instance", - "type": "string" - }, - "last_apply": { - "$ref": "#/components/schemas/OdooOverrideApplyResult" - }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" - }, - "source_label": { - "default": "", - "title": "Source Label", - "type": "string" - }, - "updated_at": { - "title": "Updated At", - "type": "string" - }, - "website_bootstrap": { - "anyOf": [ - { - "$ref": "#/components/schemas/OdooWebsiteBootstrapPayload" - }, - { - "type": "null" - } - ] } }, "required": [ - "context", - "instance", - "updated_at" + "reconciled_bootstrap_ids", + "reconciled_restore_ids", + "reconciled_replacement_ids", + "reconciled_count" ], - "title": "OdooInstanceOverrideRecord", + "title": "OdooStableOperationWorkerReconcileResultResponse", "type": "object" }, - "OdooOutgoingEmailResponse": { + "OdooStableOperationWorkerStatusResponse": { "additionalProperties": false, "properties": { - "email": { - "$ref": "#/components/schemas/OutgoingEmailResult" - }, - "query": { - "$ref": "#/components/schemas/OutgoingEmailQuery" - }, - "runtime": { - "$ref": "#/components/schemas/OdooRuntimeEvidence" - }, "status": { "const": "ok", "default": "ok", @@ -18712,94 +18127,74 @@ "trace_id": { "title": "Trace Id", "type": "string" + }, + "worker_status": { + "$ref": "#/components/schemas/OdooStableOperationWorkerStatusResponseModel" } }, "required": [ "trace_id", - "runtime", - "query", - "email" + "worker_status" ], - "title": "OdooOutgoingEmailResponse", + "title": "OdooStableOperationWorkerStatusResponse", "type": "object" }, - "OdooOverrideApplyResult": { + "OdooStableOperationWorkerStatusResponseModel": { "additionalProperties": false, "properties": { - "applied_at": { - "default": "", - "title": "Applied At", - "type": "string" + "counts_by_kind_status": { + "additionalProperties": { + "type": "integer" + }, + "title": "Counts By Kind Status", + "type": "object" }, - "attempted": { - "default": false, - "title": "Attempted", - "type": "boolean" + "operations": { + "items": { + "$ref": "#/components/schemas/OdooStableOperationLeaseSummaryResponse" + }, + "title": "Operations", + "type": "array" }, - "detail": { - "default": "", - "title": "Detail", + "pending_count": { + "title": "Pending Count", + "type": "integer" + }, + "recorded_at": { + "title": "Recorded At", "type": "string" }, + "running_count": { + "title": "Running Count", + "type": "integer" + }, + "stalled_count": { + "title": "Stalled Count", + "type": "integer" + }, "status": { - "default": "skipped", - "enum": [ - "skipped", - "pending", - "pass", - "fail" - ], "title": "Status", "type": "string" - } - }, - "title": "OdooOverrideApplyResult", - "type": "object" - }, - "OdooOverrideValue": { - "additionalProperties": false, - "properties": { - "secret_binding_id": { - "default": "", - "title": "Secret Binding Id", - "type": "string" - }, - "source": { - "enum": [ - "literal", - "secret_binding" - ], - "title": "Source", - "type": "string" }, - "value": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "integer" - }, - { - "type": "number" - }, - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Value" + "terminal_count": { + "title": "Terminal Count", + "type": "integer" } }, "required": [ - "source" + "status", + "recorded_at", + "pending_count", + "running_count", + "stalled_count", + "terminal_count", + "counts_by_kind_status", + "operations" ], - "title": "OdooOverrideValue", + "title": "OdooStableOperationWorkerStatusResponseModel", "type": "object" }, - "OdooProdBackupRestoreOperationStatusResponse": { + "OdooStableTargetReplacementOperationStatusResponse": { "additionalProperties": false, "properties": { "operation": { @@ -18834,554 +18229,133 @@ "trace_id", "operation" ], - "title": "OdooProdBackupRestoreOperationStatusResponse", + "title": "OdooStableTargetReplacementOperationStatusResponse", "type": "object" }, - "OdooProdRetainedVolumeBackupImportOperationStatusResponse": { + "OdooTestingRouteBindingRefreshEnvelope": { "additionalProperties": false, "properties": { - "operation": { - "additionalProperties": true, - "title": "Operation", - "type": "object" + "confirmation": { + "default": "", + "title": "Confirmation", + "type": "string" }, - "result": { - "anyOf": [ - { - "additionalProperties": true, - "type": "object" - }, - { - "type": "null" - } + "mode": { + "default": "dry-run", + "enum": [ + "dry-run", + "apply" ], - "title": "Result" - }, - "status": { - "const": "ok", - "default": "ok", - "title": "Status", + "title": "Mode", "type": "string" }, - "trace_id": { - "title": "Trace Id", + "reason": { + "default": "", + "title": "Reason", "type": "string" + }, + "schema_version": { + "default": 1, + "minimum": 1.0, + "title": "Schema Version", + "type": "integer" } }, - "required": [ - "trace_id", - "operation" - ], - "title": "OdooProdRetainedVolumeBackupImportOperationStatusResponse", + "title": "OdooTestingRouteBindingRefreshEnvelope", "type": "object" }, - "OdooRuntimeEvidence": { + "OdooWebsiteBootstrapPayload": { "additionalProperties": false, "properties": { - "container_id": { - "title": "Container Id", + "canonical_url": { + "default": "", + "title": "Canonical Url", "type": "string" }, - "identity": { - "$ref": "#/components/schemas/RuntimeIdentity" + "company_email": { + "default": "", + "title": "Company Email", + "type": "string" }, - "observed_at": { - "title": "Observed At", + "default_lang": { + "default": "", + "title": "Default Lang", "type": "string" }, - "target_id": { - "title": "Target Id", + "homepage_url": { + "default": "", + "title": "Homepage Url", "type": "string" }, - "target_name": { - "title": "Target Name", + "logo_alt": { + "default": "", + "title": "Logo Alt", + "type": "string" + }, + "logo_path": { + "default": "", + "title": "Logo Path", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "pages_source": { + "additionalProperties": true, + "title": "Pages Source", + "type": "object" + }, + "primary_page_xmlid": { + "default": "", + "title": "Primary Page Xmlid", + "type": "string" + }, + "routes": { + "default": [], + "items": { + "$ref": "#/components/schemas/OdooWebsiteBootstrapRoute" + }, + "title": "Routes", + "type": "array" + }, + "routes_source": { + "additionalProperties": true, + "title": "Routes Source", + "type": "object" + }, + "tenant": { + "default": "", + "title": "Tenant", "type": "string" } }, "required": [ - "identity", - "target_id", - "target_name", - "container_id", - "observed_at" + "name" ], - "title": "OdooRuntimeEvidence", + "title": "OdooWebsiteBootstrapPayload", "type": "object" }, - "OdooRuntimeLogsResponse": { + "OdooWebsiteBootstrapResponse": { "additionalProperties": false, "properties": { - "line_limit": { - "title": "Line Limit", - "type": "integer" - }, - "lines": { + "apply_on": { "items": { + "enum": [ + "restore", + "deploy", + "promotion", + "preview", + "manual" + ], "type": "string" }, - "title": "Lines", + "title": "Apply On", "type": "array" }, - "redacted": { - "default": true, - "title": "Redacted", - "type": "boolean" - }, - "runtime": { - "$ref": "#/components/schemas/OdooRuntimeEvidence" - }, - "search": { - "title": "Search", - "type": "string" - }, - "since": { - "title": "Since", - "type": "string" - }, - "status": { - "const": "ok", - "default": "ok", - "title": "Status", - "type": "string" - }, - "trace_id": { - "title": "Trace Id", - "type": "string" - } - }, - "required": [ - "trace_id", - "runtime", - "lines", - "line_limit", - "since", - "search" - ], - "title": "OdooRuntimeLogsResponse", - "type": "object" - }, - "OdooStableBootstrapOperationStatusResponse": { - "additionalProperties": false, - "properties": { - "operation": { - "additionalProperties": true, - "title": "Operation", - "type": "object" - }, - "result": { - "anyOf": [ - { - "additionalProperties": true, - "type": "object" - }, - { - "type": "null" - } - ], - "title": "Result" - }, - "status": { - "const": "ok", - "default": "ok", - "title": "Status", - "type": "string" - }, - "trace_id": { - "title": "Trace Id", - "type": "string" - } - }, - "required": [ - "trace_id", - "operation" - ], - "title": "OdooStableBootstrapOperationStatusResponse", - "type": "object" - }, - "OdooStableOperationLeaseSummaryResponse": { - "additionalProperties": false, - "properties": { - "attempt": { - "title": "Attempt", - "type": "integer" - }, - "context": { - "title": "Context", - "type": "string" - }, - "heartbeat_age_seconds": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Heartbeat Age Seconds" - }, - "heartbeat_at": { - "title": "Heartbeat At", - "type": "string" - }, - "instance": { - "title": "Instance", - "type": "string" - }, - "lease_expired": { - "title": "Lease Expired", - "type": "boolean" - }, - "lease_expires_at": { - "title": "Lease Expires At", - "type": "string" - }, - "lease_owner": { - "title": "Lease Owner", - "type": "string" - }, - "operation_id": { - "title": "Operation Id", - "type": "string" - }, - "operation_kind": { - "title": "Operation Kind", - "type": "string" - }, - "phase": { - "title": "Phase", - "type": "string" - }, - "product": { - "title": "Product", - "type": "string" - }, - "status": { - "title": "Status", - "type": "string" - } - }, - "required": [ - "operation_kind", - "operation_id", - "product", - "context", - "instance", - "status", - "phase", - "attempt", - "lease_owner", - "lease_expires_at", - "heartbeat_at", - "heartbeat_age_seconds", - "lease_expired" - ], - "title": "OdooStableOperationLeaseSummaryResponse", - "type": "object" - }, - "OdooStableOperationWorkerReconcileResponse": { - "additionalProperties": false, - "properties": { - "reconcile_result": { - "$ref": "#/components/schemas/OdooStableOperationWorkerReconcileResultResponse" - }, - "status": { - "const": "ok", - "default": "ok", - "title": "Status", - "type": "string" - }, - "trace_id": { - "title": "Trace Id", - "type": "string" - } - }, - "required": [ - "trace_id", - "reconcile_result" - ], - "title": "OdooStableOperationWorkerReconcileResponse", - "type": "object" - }, - "OdooStableOperationWorkerReconcileResultResponse": { - "additionalProperties": false, - "properties": { - "reconciled_bootstrap_ids": { - "items": { - "type": "string" - }, - "title": "Reconciled Bootstrap Ids", - "type": "array" - }, - "reconciled_count": { - "title": "Reconciled Count", - "type": "integer" - }, - "reconciled_replacement_ids": { - "items": { - "type": "string" - }, - "title": "Reconciled Replacement Ids", - "type": "array" - }, - "reconciled_restore_ids": { - "items": { - "type": "string" - }, - "title": "Reconciled Restore Ids", - "type": "array" - } - }, - "required": [ - "reconciled_bootstrap_ids", - "reconciled_restore_ids", - "reconciled_replacement_ids", - "reconciled_count" - ], - "title": "OdooStableOperationWorkerReconcileResultResponse", - "type": "object" - }, - "OdooStableOperationWorkerStatusResponse": { - "additionalProperties": false, - "properties": { - "status": { - "const": "ok", - "default": "ok", - "title": "Status", - "type": "string" - }, - "trace_id": { - "title": "Trace Id", - "type": "string" - }, - "worker_status": { - "$ref": "#/components/schemas/OdooStableOperationWorkerStatusResponseModel" - } - }, - "required": [ - "trace_id", - "worker_status" - ], - "title": "OdooStableOperationWorkerStatusResponse", - "type": "object" - }, - "OdooStableOperationWorkerStatusResponseModel": { - "additionalProperties": false, - "properties": { - "counts_by_kind_status": { - "additionalProperties": { - "type": "integer" - }, - "title": "Counts By Kind Status", - "type": "object" - }, - "operations": { - "items": { - "$ref": "#/components/schemas/OdooStableOperationLeaseSummaryResponse" - }, - "title": "Operations", - "type": "array" - }, - "pending_count": { - "title": "Pending Count", - "type": "integer" - }, - "recorded_at": { - "title": "Recorded At", - "type": "string" - }, - "running_count": { - "title": "Running Count", - "type": "integer" - }, - "stalled_count": { - "title": "Stalled Count", - "type": "integer" - }, - "status": { - "title": "Status", - "type": "string" - }, - "terminal_count": { - "title": "Terminal Count", - "type": "integer" - } - }, - "required": [ - "status", - "recorded_at", - "pending_count", - "running_count", - "stalled_count", - "terminal_count", - "counts_by_kind_status", - "operations" - ], - "title": "OdooStableOperationWorkerStatusResponseModel", - "type": "object" - }, - "OdooStableTargetReplacementOperationStatusResponse": { - "additionalProperties": false, - "properties": { - "operation": { - "additionalProperties": true, - "title": "Operation", - "type": "object" - }, - "result": { - "anyOf": [ - { - "additionalProperties": true, - "type": "object" - }, - { - "type": "null" - } - ], - "title": "Result" - }, - "status": { - "const": "ok", - "default": "ok", - "title": "Status", - "type": "string" - }, - "trace_id": { - "title": "Trace Id", - "type": "string" - } - }, - "required": [ - "trace_id", - "operation" - ], - "title": "OdooStableTargetReplacementOperationStatusResponse", - "type": "object" - }, - "OdooTestingRouteBindingRefreshEnvelope": { - "additionalProperties": false, - "properties": { - "confirmation": { - "default": "", - "title": "Confirmation", - "type": "string" - }, - "mode": { - "default": "dry-run", - "enum": [ - "dry-run", - "apply" - ], - "title": "Mode", - "type": "string" - }, - "reason": { - "default": "", - "title": "Reason", - "type": "string" - }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" - } - }, - "title": "OdooTestingRouteBindingRefreshEnvelope", - "type": "object" - }, - "OdooWebsiteBootstrapPayload": { - "additionalProperties": false, - "properties": { - "canonical_url": { - "default": "", - "title": "Canonical Url", - "type": "string" - }, - "company_email": { - "default": "", - "title": "Company Email", - "type": "string" - }, - "default_lang": { - "default": "", - "title": "Default Lang", - "type": "string" - }, - "homepage_url": { - "default": "", - "title": "Homepage Url", - "type": "string" - }, - "logo_alt": { - "default": "", - "title": "Logo Alt", - "type": "string" - }, - "logo_path": { - "default": "", - "title": "Logo Path", - "type": "string" - }, - "name": { - "title": "Name", - "type": "string" - }, - "pages_source": { - "additionalProperties": true, - "title": "Pages Source", - "type": "object" - }, - "primary_page_xmlid": { - "default": "", - "title": "Primary Page Xmlid", - "type": "string" - }, - "routes": { - "default": [], - "items": { - "$ref": "#/components/schemas/OdooWebsiteBootstrapRoute" - }, - "title": "Routes", - "type": "array" - }, - "routes_source": { - "additionalProperties": true, - "title": "Routes Source", - "type": "object" - }, - "tenant": { - "default": "", - "title": "Tenant", - "type": "string" - } - }, - "required": [ - "name" - ], - "title": "OdooWebsiteBootstrapPayload", - "type": "object" - }, - "OdooWebsiteBootstrapResponse": { - "additionalProperties": false, - "properties": { - "apply_on": { - "items": { - "enum": [ - "restore", - "deploy", - "promotion", - "preview", - "manual" - ], - "type": "string" - }, - "title": "Apply On", - "type": "array" - }, - "context": { - "title": "Context", - "type": "string" + "context": { + "title": "Context", + "type": "string" }, "instance": { "title": "Instance", @@ -30517,329 +29491,246 @@ "title": "ProductOverviewResponse", "type": "object" }, - "ProductOwnerActionContext": { + "ProductOwnerProfile": { "additionalProperties": false, + "description": "The person who accepts or rejects what customers see for this product.\n\nAn Owner can veto a change but never merge or deploy one. The numeric GitHub id\nis the identity; the login is for mentions and display and may be renamed.", "properties": { - "action": { - "title": "Action", + "github_id": { + "default": "", + "title": "Github Id", "type": "string" }, - "environment": { - "title": "Environment", + "github_login": { + "default": "", + "title": "Github Login", "type": "string" }, - "product": { - "title": "Product", + "review_label": { + "default": "owner-review", + "title": "Review Label", "type": "string" - }, - "repository_id": { - "title": "Repository Id", + } + }, + "title": "ProductOwnerProfile", + "type": "object" + }, + "ProductOwnerSecretInput": { + "additionalProperties": false, + "properties": { + "instructions": { + "default": "", + "maxLength": 2000, + "title": "Instructions", "type": "string" }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" - }, - "system": { - "title": "System", + "label": { + "maxLength": 120, + "minLength": 1, + "title": "Label", "type": "string" } }, "required": [ - "product", - "system", - "repository_id", - "environment", - "action" + "label" ], - "title": "ProductOwnerActionContext", + "title": "ProductOwnerSecretInput", "type": "object" }, - "ProductOwnerAuthorityEvaluation": { + "ProductPreviewProfile": { "additionalProperties": false, "properties": { - "actor_identity_id": { - "title": "Actor Identity Id", + "app_name_prefix": { + "default": "", + "title": "App Name Prefix", "type": "string" }, - "actor_is_preferred": { - "default": false, - "title": "Actor Is Preferred", - "type": "boolean" - }, "context": { - "$ref": "#/components/schemas/ProductOwnerActionContext" - }, - "decision": { - "enum": [ - "authorized", - "denied", - "not_required", - "unavailable" - ], - "title": "Decision", + "default": "", + "title": "Context", "type": "string" }, - "notify_owner_identity_ids": { + "copied_env_keys": { "default": [], "items": { "type": "string" }, - "title": "Notify Owner Identity Ids", + "title": "Copied Env Keys", "type": "array" }, - "policy_digest": { - "default": "", - "title": "Policy Digest", - "type": "string" - }, - "policy_record_id": { - "default": "", - "title": "Policy Record Id", - "type": "string" - }, - "policy_revision": { - "anyOf": [ - { - "minimum": 1.0, - "type": "integer" - }, - { - "type": "null" - } + "data_transport_mode": { + "default": "none", + "enum": [ + "none", + "clone", + "bootstrap", + "migrate_seed", + "driver" ], - "title": "Policy Revision" - }, - "quorum": { - "const": 1, - "default": 1, - "title": "Quorum", - "type": "integer" - }, - "reason_code": { - "title": "Reason Code", + "title": "Data Transport Mode", "type": "string" }, - "requirement_digest": { - "default": "", - "title": "Requirement Digest", + "domain_certificate_type": { + "default": "none", + "enum": [ + "none", + "letsencrypt" + ], + "title": "Domain Certificate Type", "type": "string" }, - "requirement_record_id": { - "default": "", - "title": "Requirement Record Id", + "enable_label": { + "default": "launchplane-preview", + "title": "Enable Label", "type": "string" }, - "requirement_revision": { - "anyOf": [ - { - "minimum": 1.0, - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Requirement Revision" - }, - "routing_digest": { - "default": "", - "title": "Routing Digest", - "type": "string" + "enabled": { + "default": false, + "title": "Enabled", + "type": "boolean" }, - "routing_record_id": { + "migration_command": { "default": "", - "title": "Routing Record Id", + "title": "Migration Command", "type": "string" }, - "routing_revision": { - "anyOf": [ - { - "minimum": 1.0, - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Routing Revision" - }, - "satisfying_owner_identity_ids": { + "omitted_env_keys": { "default": [], "items": { "type": "string" }, - "title": "Satisfying Owner Identity Ids", + "title": "Omitted Env Keys", "type": "array" }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" - } - }, - "required": [ - "decision", - "reason_code", - "context", - "actor_identity_id" - ], - "title": "ProductOwnerAuthorityEvaluation", - "type": "object" - }, - "ProductOwnerAuthorityEvaluationResponse": { - "additionalProperties": false, - "properties": { - "evaluation": { - "$ref": "#/components/schemas/ProductOwnerAuthorityEvaluation" - }, - "status": { - "const": "ok", - "default": "ok", - "title": "Status", - "type": "string" + "override_env": { + "additionalProperties": { + "type": "string" + }, + "title": "Override Env", + "type": "object" }, - "trace_id": { - "title": "Trace Id", - "type": "string" - } - }, - "required": [ - "trace_id", - "evaluation" - ], - "title": "ProductOwnerAuthorityEvaluationResponse", - "type": "object" - }, - "ProductOwnerGrant": { - "additionalProperties": false, - "properties": { - "environments": { + "preview_domain_env_keys": { + "default": [], "items": { "type": "string" }, - "title": "Environments", + "title": "Preview Domain Env Keys", "type": "array" }, - "grant_id": { - "default": "", - "title": "Grant Id", - "type": "string" + "preview_url_env_keys": { + "default": [], + "items": { + "type": "string" + }, + "title": "Preview Url Env Keys", + "type": "array" }, - "identity": { - "$ref": "#/components/schemas/ProductOwnerIdentity" + "required_provider_fields": { + "default": [], + "items": { + "type": "string" + }, + "title": "Required Provider Fields", + "type": "array" }, - "repository_ids": { + "required_template_env_keys": { + "default": [], "items": { "type": "string" }, - "title": "Repository Ids", + "title": "Required Template Env Keys", "type": "array" }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" - } - }, - "required": [ - "identity", - "repository_ids", - "environments" - ], - "title": "ProductOwnerGrant", - "type": "object" - }, - "ProductOwnerIdentity": { - "additionalProperties": false, - "properties": { - "identity_id": { + "seed_command": { "default": "", - "title": "Identity Id", - "type": "string" - }, - "identity_kind": { - "const": "human", - "default": "human", - "title": "Identity Kind", - "type": "string" - }, - "owner_class": { - "const": "owner", - "default": "owner", - "title": "Owner Class", + "title": "Seed Command", "type": "string" }, - "provider": { - "const": "github", - "title": "Provider", + "slug_template": { + "default": "pr-{number}", + "title": "Slug Template", "type": "string" }, - "provider_subject_id": { - "title": "Provider Subject Id", + "template_instance": { + "default": "testing", + "title": "Template Instance", "type": "string" - }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" } }, - "required": [ - "provider", - "provider_subject_id" - ], - "title": "ProductOwnerIdentity", + "title": "ProductPreviewProfile", "type": "object" }, - "ProductOwnerPolicyApplyEnvelope": { + "ProductPreviewSummary": { "additionalProperties": false, "properties": { - "expected_current_policy_digest": { + "active_count": { + "default": 0, + "minimum": 0.0, + "title": "Active Count", + "type": "integer" + }, + "context": { "default": "", - "title": "Expected Current Policy Digest", + "title": "Context", "type": "string" }, - "expected_current_record_id": { + "enabled": { + "title": "Enabled", + "type": "boolean" + }, + "latest_preview_id": { "default": "", - "title": "Expected Current Record Id", + "title": "Latest Preview Id", "type": "string" }, - "mode": { - "default": "apply", + "provenance": { + "$ref": "#/components/schemas/DataProvenance", + "default": { + "detail": "Product previews are not enabled for this product profile.", + "freshness_status": "unsupported", + "recorded_at": "", + "refreshed_at": "", + "source_kind": "unsupported", + "source_record_id": "", + "stale_after": "" + } + }, + "slug_template": { + "default": "", + "title": "Slug Template", + "type": "string" + }, + "trust_state": { + "default": "unsupported", "enum": [ - "dry_run", - "apply" + "verified", + "recorded", + "stale", + "missing", + "unsupported" ], - "title": "Mode", + "title": "Trust State", "type": "string" - }, - "record": { - "$ref": "#/components/schemas/ProductOwnerPolicyRecord" - }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" } }, "required": [ - "record" + "enabled" ], - "title": "ProductOwnerPolicyApplyEnvelope", + "title": "ProductPreviewSummary", "type": "object" }, - "ProductOwnerPolicyApplyResponse": { + "ProductProfileListResponse": { "additionalProperties": false, "properties": { - "result": { - "$ref": "#/components/schemas/ProductOwnerPolicyApplyResult" + "driver_id": { + "title": "Driver Id", + "type": "string" + }, + "profiles": { + "items": { + "$ref": "#/components/schemas/LaunchplaneProductProfileRecord" + }, + "title": "Profiles", + "type": "array" }, "status": { "const": "ok", @@ -30854,272 +29745,108 @@ }, "required": [ "trace_id", - "result" + "driver_id", + "profiles" ], - "title": "ProductOwnerPolicyApplyResponse", + "title": "ProductProfileListResponse", "type": "object" }, - "ProductOwnerPolicyApplyResult": { + "ProductProfileResponse": { "additionalProperties": false, "properties": { - "record": { - "$ref": "#/components/schemas/ProductOwnerPolicyRecord" - }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" + "profile": { + "$ref": "#/components/schemas/LaunchplaneProductProfileRecord" }, "status": { - "enum": [ - "would_apply", - "would_replay", - "applied", - "replayed" - ], + "const": "ok", + "default": "ok", "title": "Status", "type": "string" + }, + "trace_id": { + "title": "Trace Id", + "type": "string" } }, "required": [ - "status", - "record" + "trace_id", + "profile" ], - "title": "ProductOwnerPolicyApplyResult", + "title": "ProductProfileResponse", "type": "object" }, - "ProductOwnerPolicyRecord": { + "ProductPromotionDryRunEnvelope": { "additionalProperties": false, "properties": { - "effective_at": { - "title": "Effective At", - "type": "string" - }, - "owners": { - "items": { - "$ref": "#/components/schemas/ProductOwnerGrant" - }, - "title": "Owners", - "type": "array" - }, - "policy_digest": { - "default": "", - "title": "Policy Digest", + "bump": { + "default": "patch", + "enum": [ + "patch", + "minor", + "major" + ], + "title": "Bump", "type": "string" }, - "policy_revision": { - "minimum": 1.0, - "title": "Policy Revision", - "type": "integer" - }, - "preview_trust": { - "$ref": "#/components/schemas/ProductOwnerPreviewTrustPolicy" - }, - "product": { - "title": "Product", + "evidence_fingerprint": { + "title": "Evidence Fingerprint", "type": "string" }, - "quorum": { - "const": 1, - "default": 1, - "title": "Quorum", - "type": "integer" - }, "reason": { "title": "Reason", "type": "string" }, - "record_id": { - "default": "", - "title": "Record Id", - "type": "string" - }, - "review_age": { - "$ref": "#/components/schemas/ProductOwnerReviewAgePolicy" - }, "schema_version": { "default": 1, "minimum": 1.0, "title": "Schema Version", "type": "integer" - }, - "self_review": { - "$ref": "#/components/schemas/ProductOwnerSelfReviewPolicy" - }, - "source": { - "title": "Source", - "type": "string" - }, - "status": { - "default": "active", - "enum": [ - "active", - "superseded" - ], - "title": "Status", - "type": "string" - }, - "supersedes_record_id": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Supersedes Record Id" - }, - "system": { - "title": "System", - "type": "string" } }, "required": [ - "product", - "system", - "policy_revision", - "owners", - "effective_at", - "source", - "reason" + "reason", + "evidence_fingerprint" ], - "title": "ProductOwnerPolicyRecord", - "type": "object" - }, - "ProductOwnerPreviewTrustPolicy": { - "additionalProperties": false, - "description": "Minimum provable preview isolation for admissible Owner review evidence.", - "properties": { - "minimum_isolation_class": { - "default": "synthetic_seeded", - "enum": [ - "not_applicable", - "no_product_data", - "synthetic_seeded", - "cloned_product_data", - "unknown" - ], - "title": "Minimum Isolation Class", - "type": "string" - }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" - } - }, - "title": "ProductOwnerPreviewTrustPolicy", - "type": "object" - }, - "ProductOwnerProfile": { - "additionalProperties": false, - "description": "The person who accepts or rejects what customers see for this product.\n\nAn Owner can veto a change but never merge or deploy one. The numeric GitHub id\nis the identity; the login is for mentions and display and may be renamed.", - "properties": { - "github_id": { - "default": "", - "title": "Github Id", - "type": "string" - }, - "github_login": { - "default": "", - "title": "Github Login", - "type": "string" - }, - "review_label": { - "default": "owner-review", - "title": "Review Label", - "type": "string" - } - }, - "title": "ProductOwnerProfile", + "title": "ProductPromotionDryRunEnvelope", "type": "object" }, - "ProductOwnerReadModel": { + "ProductPromotionDryRunResponse": { "additionalProperties": false, "properties": { - "current_policy": { + "original_trace_id": { "anyOf": [ { - "$ref": "#/components/schemas/ProductOwnerPolicyRecord" + "type": "string" }, { "type": "null" } - ] + ], + "title": "Original Trace Id", + "x-launchplane-optional-response": true }, - "current_requirement": { - "anyOf": [ - { - "$ref": "#/components/schemas/ProductOwnerRequirementRecord" - }, - { - "type": "null" - } - ] + "records": { + "$ref": "#/components/schemas/GenericWebProdPromotionRecords" }, - "current_routing": { + "replayed": { "anyOf": [ { - "$ref": "#/components/schemas/ProductOwnerRoutingRecord" + "type": "boolean" }, { "type": "null" } - ] + ], + "title": "Replayed", + "x-launchplane-optional-response": true }, - "policy_history_count": { - "default": 0, - "minimum": 0.0, - "title": "Policy History Count", - "type": "integer" + "result": { + "$ref": "#/components/schemas/ProductPromotionDryRunResult" }, - "product": { - "title": "Product", - "type": "string" - }, - "requirement_history_count": { - "default": 0, - "minimum": 0.0, - "title": "Requirement History Count", - "type": "integer" - }, - "routing_history_count": { - "default": 0, - "minimum": 0.0, - "title": "Routing History Count", - "type": "integer" - }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" - }, - "system": { - "title": "System", - "type": "string" - } - }, - "required": [ - "product", - "system" - ], - "title": "ProductOwnerReadModel", - "type": "object" - }, - "ProductOwnerReadResponse": { - "additionalProperties": false, - "properties": { - "read_model": { - "$ref": "#/components/schemas/ProductOwnerReadModel" - }, - "status": { - "const": "ok", - "default": "ok", - "title": "Status", + "status": { + "const": "accepted", + "default": "accepted", + "title": "Status", "type": "string" }, "trace_id": { @@ -31129,655 +29856,496 @@ }, "required": [ "trace_id", - "read_model" + "records", + "result" ], - "title": "ProductOwnerReadResponse", + "title": "ProductPromotionDryRunResponse", "type": "object" }, - "ProductOwnerRequirement": { + "ProductPromotionDryRunResult": { "additionalProperties": false, "properties": { - "action": { - "title": "Action", + "artifact_id": { + "default": "", + "title": "Artifact Id", "type": "string" }, - "environments": { - "items": { - "type": "string" - }, - "title": "Environments", - "type": "array" + "backup_record_id": { + "default": "", + "title": "Backup Record Id", + "type": "string" }, - "quorum": { - "const": 1, - "default": 1, - "title": "Quorum", - "type": "integer" + "backup_status": { + "default": "skipped", + "enum": [ + "pending", + "pass", + "fail", + "skipped" + ], + "title": "Backup Status", + "type": "string" }, - "repository_ids": { - "items": { - "type": "string" - }, - "title": "Repository Ids", - "type": "array" + "bump": { + "enum": [ + "patch", + "minor", + "major" + ], + "title": "Bump", + "type": "string" }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" - } - }, - "required": [ - "action", - "repository_ids", - "environments" - ], - "title": "ProductOwnerRequirement", - "type": "object" - }, - "ProductOwnerRequirementApplyEnvelope": { - "additionalProperties": false, - "properties": { - "expected_current_record_id": { + "context": { "default": "", - "title": "Expected Current Record Id", + "title": "Context", "type": "string" }, - "expected_current_requirement_digest": { + "deploy_reference": { "default": "", - "title": "Expected Current Requirement Digest", + "title": "Deploy Reference", "type": "string" }, - "mode": { - "default": "apply", + "deployment_record_id": { + "default": "", + "title": "Deployment Record Id", + "type": "string" + }, + "deployment_status": { + "default": "skipped", "enum": [ - "dry_run", - "apply" + "pending", + "pass", + "fail", + "skipped" ], - "title": "Mode", + "title": "Deployment Status", "type": "string" }, - "record": { - "$ref": "#/components/schemas/ProductOwnerRequirementRecord" + "destination_health_status": { + "default": "skipped", + "enum": [ + "pending", + "pass", + "fail", + "skipped" + ], + "title": "Destination Health Status", + "type": "string" }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" - } - }, - "required": [ - "record" - ], - "title": "ProductOwnerRequirementApplyEnvelope", - "type": "object" - }, - "ProductOwnerRequirementApplyResponse": { - "additionalProperties": false, - "properties": { - "result": { - "$ref": "#/components/schemas/ProductOwnerRequirementApplyResult" + "dry_run": { + "default": false, + "title": "Dry Run", + "type": "boolean" }, - "status": { - "const": "ok", - "default": "ok", - "title": "Status", + "error_message": { + "default": "", + "title": "Error Message", "type": "string" }, - "trace_id": { - "title": "Trace Id", + "evidence_fingerprint": { + "title": "Evidence Fingerprint", "type": "string" - } - }, - "required": [ - "trace_id", - "result" - ], - "title": "ProductOwnerRequirementApplyResponse", - "type": "object" - }, - "ProductOwnerRequirementApplyResult": { - "additionalProperties": false, - "properties": { - "record": { - "$ref": "#/components/schemas/ProductOwnerRequirementRecord" - }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" }, - "status": { - "enum": [ - "would_apply", - "would_replay", - "applied", - "replayed" - ], - "title": "Status", + "from_instance": { + "default": "", + "title": "From Instance", "type": "string" - } - }, - "required": [ - "status", - "record" - ], - "title": "ProductOwnerRequirementApplyResult", - "type": "object" - }, - "ProductOwnerRequirementRecord": { - "additionalProperties": false, - "properties": { - "effective_at": { - "title": "Effective At", + }, + "inventory_record_id": { + "default": "", + "title": "Inventory Record Id", "type": "string" }, "product": { + "default": "", "title": "Product", "type": "string" }, - "reason": { - "title": "Reason", + "promotion_record_id": { + "default": "", + "title": "Promotion Record Id", "type": "string" }, - "record_id": { + "promotion_status": { + "default": "pending", + "enum": [ + "pending", + "pass", + "fail" + ], + "title": "Promotion Status", + "type": "string" + }, + "provider_id": { "default": "", - "title": "Record Id", + "title": "Provider Id", "type": "string" }, - "requirement_digest": { + "provider_target_type": { "default": "", - "title": "Requirement Digest", + "title": "Provider Target Type", "type": "string" }, - "requirement_revision": { - "minimum": 1.0, - "title": "Requirement Revision", - "type": "integer" + "release_status": { + "default": "skipped", + "enum": [ + "pending", + "pass", + "fail", + "skipped" + ], + "title": "Release Status", + "type": "string" }, - "requirements": { - "default": [], - "items": { - "$ref": "#/components/schemas/ProductOwnerRequirement" - }, - "title": "Requirements", - "type": "array" + "release_tag": { + "default": "", + "title": "Release Tag", + "type": "string" }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" + "release_url": { + "default": "", + "title": "Release Url", + "type": "string" }, - "source": { - "title": "Source", + "source_git_ref": { + "default": "", + "title": "Source Git Ref", "type": "string" }, - "status": { - "default": "active", + "source_health_status": { + "default": "skipped", "enum": [ - "active", - "superseded" + "pending", + "pass", + "fail", + "skipped" ], - "title": "Status", + "title": "Source Health Status", "type": "string" }, - "supersedes_record_id": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } + "target_category": { + "default": "unknown", + "enum": [ + "application", + "compose", + "container", + "service", + "static", + "unknown" ], - "title": "Supersedes Record Id" + "title": "Target Category", + "type": "string" }, - "system": { - "title": "System", + "target_id": { + "default": "", + "title": "Target Id", + "type": "string" + }, + "target_name": { + "default": "", + "title": "Target Name", + "type": "string" + }, + "to_instance": { + "default": "", + "title": "To Instance", "type": "string" } }, "required": [ - "product", - "system", - "requirement_revision", - "effective_at", - "source", - "reason" + "evidence_fingerprint", + "bump" ], - "title": "ProductOwnerRequirementRecord", + "title": "ProductPromotionDryRunResult", "type": "object" }, - "ProductOwnerReviewAgePolicy": { + "ProductPromotionEvidence": { "additionalProperties": false, - "description": "Finite Owner-review evidence age, scoped to one product Owner policy.\n\n``elevated_max_age_seconds`` covers sensitive, unknown, and\nproduction-affecting changes. Policies may only choose values shorter than the\nmigration defaults, so an operator can tighten but never extend evidence life.", "properties": { - "elevated_max_age_seconds": { - "default": 604800, - "minimum": 1.0, - "title": "Elevated Max Age Seconds", - "type": "integer" - }, - "routine_max_age_seconds": { - "default": 2592000, - "minimum": 1.0, - "title": "Routine Max Age Seconds", - "type": "integer" + "artifact_id": { + "default": "", + "title": "Artifact Id", + "type": "string" }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" - } - }, - "title": "ProductOwnerReviewAgePolicy", - "type": "object" - }, - "ProductOwnerRoutingApplyEnvelope": { - "additionalProperties": false, - "properties": { - "expected_current_record_id": { + "deploy_reference": { "default": "", - "title": "Expected Current Record Id", + "title": "Deploy Reference", "type": "string" }, - "expected_current_routing_digest": { + "deployment_record_id": { "default": "", - "title": "Expected Current Routing Digest", + "title": "Deployment Record Id", "type": "string" }, - "mode": { - "default": "apply", + "deployment_status": { + "default": "skipped", "enum": [ - "dry_run", - "apply" + "pending", + "pass", + "fail", + "skipped" ], - "title": "Mode", + "title": "Deployment Status", "type": "string" }, - "record": { - "$ref": "#/components/schemas/ProductOwnerRoutingRecord" + "environment": { + "title": "Environment", + "type": "string" }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" - } - }, - "required": [ - "record" - ], - "title": "ProductOwnerRoutingApplyEnvelope", - "type": "object" - }, - "ProductOwnerRoutingApplyResponse": { - "additionalProperties": false, - "properties": { - "result": { - "$ref": "#/components/schemas/ProductOwnerRoutingApplyResult" + "health_status": { + "default": "skipped", + "enum": [ + "pending", + "pass", + "fail", + "skipped" + ], + "title": "Health Status", + "type": "string" }, - "status": { - "const": "ok", - "default": "ok", - "title": "Status", + "inventory_stale_after": { + "default": "", + "title": "Inventory Stale After", "type": "string" }, - "trace_id": { - "title": "Trace Id", + "inventory_updated_at": { + "default": "", + "title": "Inventory Updated At", + "type": "string" + }, + "runtime_identity_detail": { + "default": "", + "title": "Runtime Identity Detail", + "type": "string" + }, + "runtime_identity_status": { + "default": "unchecked", + "enum": [ + "unchecked", + "match", + "mismatch", + "missing", + "malformed", + "unverifiable" + ], + "title": "Runtime Identity Status", + "type": "string" + }, + "source_git_ref": { + "default": "", + "title": "Source Git Ref", + "type": "string" + }, + "trust_state": { + "default": "missing", + "enum": [ + "verified", + "recorded", + "stale", + "missing", + "unsupported" + ], + "title": "Trust State", "type": "string" } }, "required": [ - "trace_id", - "result" + "environment" ], - "title": "ProductOwnerRoutingApplyResponse", + "title": "ProductPromotionEvidence", "type": "object" }, - "ProductOwnerRoutingApplyResult": { + "ProductPromotionLiveConfirmations": { "additionalProperties": false, "properties": { - "record": { - "$ref": "#/components/schemas/ProductOwnerRoutingRecord" + "major": { + "title": "Major", + "type": "string" }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" + "minor": { + "title": "Minor", + "type": "string" }, - "status": { - "enum": [ - "would_apply", - "would_replay", - "applied", - "replayed" - ], - "title": "Status", + "patch": { + "title": "Patch", "type": "string" } }, "required": [ - "status", - "record" + "patch", + "minor", + "major" ], - "title": "ProductOwnerRoutingApplyResult", + "title": "ProductPromotionLiveConfirmations", "type": "object" }, - "ProductOwnerRoutingRecord": { + "ProductPromotionOperationAvailability": { "additionalProperties": false, "properties": { - "authoritative": { - "const": false, - "default": false, - "title": "Authoritative", - "type": "boolean" - }, - "effective_at": { - "title": "Effective At", + "authz_action": { + "title": "Authz Action", "type": "string" }, - "preferred_owner_identity_ids": { + "consequences": { "default": [], "items": { "type": "string" }, - "title": "Preferred Owner Identity Ids", + "title": "Consequences", "type": "array" }, - "product": { - "title": "Product", - "type": "string" + "disabled_reasons": { + "default": [], + "items": { + "type": "string" + }, + "title": "Disabled Reasons", + "type": "array" }, - "reason": { - "title": "Reason", - "type": "string" + "enabled": { + "title": "Enabled", + "type": "boolean" }, - "record_id": { - "default": "", - "title": "Record Id", + "operation": { + "enum": [ + "direct_dry_run", + "workflow_dry_run", + "workflow_live" + ], + "title": "Operation", "type": "string" }, - "routing_digest": { - "default": "", - "title": "Routing Digest", - "type": "string" + "requires_confirmation": { + "default": false, + "title": "Requires Confirmation", + "type": "boolean" }, - "routing_revision": { - "minimum": 1.0, - "title": "Routing Revision", - "type": "integer" + "requires_idempotency_key": { + "default": true, + "title": "Requires Idempotency Key", + "type": "boolean" }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" + "requires_matching_direct_dry_run": { + "default": false, + "title": "Requires Matching Direct Dry Run", + "type": "boolean" }, - "source": { - "title": "Source", - "type": "string" + "requires_reason": { + "default": true, + "title": "Requires Reason", + "type": "boolean" }, - "status": { - "default": "active", + "trust_state": { + "default": "recorded", "enum": [ - "active", - "superseded" - ], - "title": "Status", - "type": "string" - }, - "supersedes_record_id": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } + "verified", + "recorded", + "stale", + "missing", + "unsupported" ], - "title": "Supersedes Record Id" - }, - "system": { - "title": "System", - "type": "string" - } - }, - "required": [ - "product", - "system", - "routing_revision", - "effective_at", - "source", - "reason" - ], - "title": "ProductOwnerRoutingRecord", - "type": "object" - }, - "ProductOwnerSecretInput": { - "additionalProperties": false, - "properties": { - "instructions": { - "default": "", - "maxLength": 2000, - "title": "Instructions", - "type": "string" - }, - "label": { - "maxLength": 120, - "minLength": 1, - "title": "Label", + "title": "Trust State", "type": "string" } }, "required": [ - "label" + "operation", + "authz_action", + "enabled" ], - "title": "ProductOwnerSecretInput", - "type": "object" - }, - "ProductOwnerSelfReviewPolicy": { - "additionalProperties": false, - "description": "Self-review denial policy for one product Owner scope.\n\nSelf-review is denied by default. A product policy may permit it only for\nroutine changes, and only through an explicitly revisioned exception. Sensitive,\nunknown, and production-affecting changes are always denied.", - "properties": { - "exception_reason": { - "default": "", - "maxLength": 1000, - "title": "Exception Reason", - "type": "string" - }, - "exception_revision": { - "default": 0, - "minimum": 0.0, - "title": "Exception Revision", - "type": "integer" - }, - "routine_exception_enabled": { - "default": false, - "title": "Routine Exception Enabled", - "type": "boolean" - }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" - } - }, - "title": "ProductOwnerSelfReviewPolicy", + "title": "ProductPromotionOperationAvailability", "type": "object" }, - "ProductPreviewProfile": { + "ProductPromotionStatus": { "additionalProperties": false, "properties": { - "app_name_prefix": { - "default": "", - "title": "App Name Prefix", - "type": "string" - }, - "context": { + "base_driver_id": { "default": "", - "title": "Context", + "title": "Base Driver Id", "type": "string" }, - "copied_env_keys": { - "default": [], + "bump_options": { + "default": [ + "patch", + "minor", + "major" + ], "items": { + "enum": [ + "patch", + "minor", + "major" + ], "type": "string" }, - "title": "Copied Env Keys", + "title": "Bump Options", "type": "array" }, - "data_transport_mode": { - "default": "none", - "enum": [ - "none", - "clone", - "bootstrap", - "migrate_seed", - "driver" - ], - "title": "Data Transport Mode", + "context": { + "title": "Context", "type": "string" }, - "domain_certificate_type": { - "default": "none", + "default_bump": { "enum": [ - "none", - "letsencrypt" + "patch", + "minor", + "major" ], - "title": "Domain Certificate Type", - "type": "string" - }, - "enable_label": { - "default": "launchplane-preview", - "title": "Enable Label", + "title": "Default Bump", "type": "string" }, - "enabled": { - "default": false, - "title": "Enabled", - "type": "boolean" + "destination": { + "$ref": "#/components/schemas/ProductPromotionEvidence" }, - "migration_command": { - "default": "", - "title": "Migration Command", + "destination_environment": { + "default": "prod", + "title": "Destination Environment", "type": "string" }, - "omitted_env_keys": { - "default": [], - "items": { - "type": "string" - }, - "title": "Omitted Env Keys", - "type": "array" - }, - "override_env": { - "additionalProperties": { - "type": "string" - }, - "title": "Override Env", - "type": "object" - }, - "preview_domain_env_keys": { - "default": [], - "items": { - "type": "string" - }, - "title": "Preview Domain Env Keys", - "type": "array" - }, - "preview_url_env_keys": { - "default": [], - "items": { - "type": "string" - }, - "title": "Preview Url Env Keys", - "type": "array" - }, - "required_provider_fields": { - "default": [], - "items": { - "type": "string" - }, - "title": "Required Provider Fields", - "type": "array" - }, - "required_template_env_keys": { - "default": [], - "items": { - "type": "string" - }, - "title": "Required Template Env Keys", - "type": "array" + "direct_dry_run": { + "$ref": "#/components/schemas/ProductPromotionOperationAvailability" }, - "seed_command": { - "default": "", - "title": "Seed Command", + "display_name": { + "title": "Display Name", "type": "string" }, - "slug_template": { - "default": "pr-{number}", - "title": "Slug Template", + "driver_id": { + "title": "Driver Id", "type": "string" }, - "template_instance": { - "default": "testing", - "title": "Template Instance", + "evidence_fingerprint": { + "title": "Evidence Fingerprint", "type": "string" - } - }, - "title": "ProductPreviewProfile", - "type": "object" - }, - "ProductPreviewSummary": { - "additionalProperties": false, - "properties": { - "active_count": { - "default": 0, - "minimum": 0.0, - "title": "Active Count", - "type": "integer" }, - "context": { - "default": "", - "title": "Context", + "live_confirmations": { + "$ref": "#/components/schemas/ProductPromotionLiveConfirmations" + }, + "product": { + "title": "Product", "type": "string" }, - "enabled": { - "title": "Enabled", - "type": "boolean" + "release_review": { + "$ref": "#/components/schemas/ReleaseReviewStatus" }, - "latest_preview_id": { - "default": "", - "title": "Latest Preview Id", + "repository": { + "title": "Repository", "type": "string" }, - "provenance": { - "$ref": "#/components/schemas/DataProvenance", - "default": { - "detail": "Product previews are not enabled for this product profile.", - "freshness_status": "unsupported", - "recorded_at": "", - "refreshed_at": "", - "source_kind": "unsupported", - "source_record_id": "", - "stale_after": "" - } + "schema_version": { + "default": 1, + "minimum": 1.0, + "title": "Schema Version", + "type": "integer" }, - "slug_template": { - "default": "", - "title": "Slug Template", + "source": { + "$ref": "#/components/schemas/ProductPromotionEvidence" + }, + "source_environment": { + "default": "testing", + "title": "Source Environment", "type": "string" }, "trust_state": { - "default": "unsupported", "enum": [ "verified", "recorded", @@ -31787,27 +30355,48 @@ ], "title": "Trust State", "type": "string" + }, + "workflow_dry_run": { + "$ref": "#/components/schemas/ProductPromotionOperationAvailability" + }, + "workflow_id": { + "title": "Workflow Id", + "type": "string" + }, + "workflow_live": { + "$ref": "#/components/schemas/ProductPromotionOperationAvailability" + }, + "workflow_ref": { + "title": "Workflow Ref", + "type": "string" } }, "required": [ - "enabled" + "product", + "display_name", + "driver_id", + "repository", + "workflow_id", + "workflow_ref", + "context", + "source", + "destination", + "evidence_fingerprint", + "default_bump", + "direct_dry_run", + "workflow_dry_run", + "workflow_live", + "live_confirmations", + "trust_state" ], - "title": "ProductPreviewSummary", + "title": "ProductPromotionStatus", "type": "object" }, - "ProductProfileListResponse": { + "ProductPromotionStatusResponse": { "additionalProperties": false, "properties": { - "driver_id": { - "title": "Driver Id", - "type": "string" - }, - "profiles": { - "items": { - "$ref": "#/components/schemas/LaunchplaneProductProfileRecord" - }, - "title": "Profiles", - "type": "array" + "promotion_status": { + "$ref": "#/components/schemas/ProductPromotionStatus" }, "status": { "const": "ok", @@ -31822,17 +30411,93 @@ }, "required": [ "trace_id", - "driver_id", - "profiles" + "promotion_status" ], - "title": "ProductProfileListResponse", + "title": "ProductPromotionStatusResponse", "type": "object" }, - "ProductProfileResponse": { + "ProductPromotionWorkflowDeliveryStatus": { "additionalProperties": false, "properties": { - "profile": { - "$ref": "#/components/schemas/LaunchplaneProductProfileRecord" + "delivery_id": { + "title": "Delivery Id", + "type": "string" + }, + "dispatch_status": { + "enum": [ + "pending", + "dispatched", + "failed", + "reconcile_required" + ], + "title": "Dispatch Status", + "type": "string" + }, + "error_code": { + "default": "", + "title": "Error Code", + "type": "string" + }, + "observed_at": { + "title": "Observed At", + "type": "string" + }, + "run_conclusion": { + "default": "", + "title": "Run Conclusion", + "type": "string" + }, + "run_id": { + "default": 0, + "minimum": 0.0, + "title": "Run Id", + "type": "integer" + }, + "run_observation_status": { + "default": "pending", + "enum": [ + "pending", + "observed" + ], + "title": "Run Observation Status", + "type": "string" + }, + "run_status": { + "default": "pending", + "title": "Run Status", + "type": "string" + }, + "run_url": { + "default": "", + "title": "Run Url", + "type": "string" + }, + "state": { + "enum": [ + "pending", + "running", + "delivered", + "failed", + "reconcile_required" + ], + "title": "State", + "type": "string" + } + }, + "required": [ + "delivery_id", + "state", + "dispatch_status", + "observed_at" + ], + "title": "ProductPromotionWorkflowDeliveryStatus", + "type": "object" + }, + "ProductPromotionWorkflowDeliveryStatusResponse": { + "additionalProperties": false, + "properties": { + "delivery": { + "$ref": "#/components/schemas/ProductPromotionWorkflowDeliveryStatus" }, "status": { "const": "ok", @@ -31847,12 +30512,12 @@ }, "required": [ "trace_id", - "profile" + "delivery" ], - "title": "ProductProfileResponse", + "title": "ProductPromotionWorkflowDeliveryStatusResponse", "type": "object" }, - "ProductPromotionDryRunEnvelope": { + "ProductPromotionWorkflowDispatchEnvelope": { "additionalProperties": false, "properties": { "bump": { @@ -31865,6 +30530,16 @@ "title": "Bump", "type": "string" }, + "confirmation": { + "default": "", + "title": "Confirmation", + "type": "string" + }, + "dry_run": { + "default": true, + "title": "Dry Run", + "type": "boolean" + }, "evidence_fingerprint": { "title": "Evidence Fingerprint", "type": "string" @@ -31884,10 +30559,10 @@ "reason", "evidence_fingerprint" ], - "title": "ProductPromotionDryRunEnvelope", + "title": "ProductPromotionWorkflowDispatchEnvelope", "type": "object" }, - "ProductPromotionDryRunResponse": { + "ProductPromotionWorkflowDispatchResponse": { "additionalProperties": false, "properties": { "original_trace_id": { @@ -31903,7 +30578,11 @@ "x-launchplane-optional-response": true }, "records": { - "$ref": "#/components/schemas/GenericWebProdPromotionRecords" + "additionalProperties": { + "type": "string" + }, + "title": "Records", + "type": "object" }, "replayed": { "anyOf": [ @@ -31918,7 +30597,7 @@ "x-launchplane-optional-response": true }, "result": { - "$ref": "#/components/schemas/ProductPromotionDryRunResult" + "$ref": "#/components/schemas/ProductPromotionWorkflowDispatchResult" }, "status": { "const": "accepted", @@ -31933,37 +30612,20 @@ }, "required": [ "trace_id", - "records", "result" ], - "title": "ProductPromotionDryRunResponse", + "title": "ProductPromotionWorkflowDispatchResponse", "type": "object" }, - "ProductPromotionDryRunResult": { + "ProductPromotionWorkflowDispatchResult": { "additionalProperties": false, "properties": { "artifact_id": { - "default": "", "title": "Artifact Id", "type": "string" }, - "backup_record_id": { - "default": "", - "title": "Backup Record Id", - "type": "string" - }, - "backup_status": { - "default": "skipped", - "enum": [ - "pending", - "pass", - "fail", - "skipped" - ], - "title": "Backup Status", - "type": "string" - }, "bump": { + "default": "patch", "enum": [ "patch", "minor", @@ -31977,36 +30639,17 @@ "title": "Context", "type": "string" }, - "deploy_reference": { - "default": "", - "title": "Deploy Reference", - "type": "string" - }, - "deployment_record_id": { - "default": "", - "title": "Deployment Record Id", - "type": "string" - }, - "deployment_status": { - "default": "skipped", - "enum": [ - "pending", - "pass", - "fail", - "skipped" - ], - "title": "Deployment Status", + "delivery_id": { + "title": "Delivery Id", "type": "string" }, - "destination_health_status": { - "default": "skipped", + "dispatch_status": { + "default": "pending", "enum": [ "pending", - "pass", - "fail", - "skipped" + "dispatched" ], - "title": "Destination Health Status", + "title": "Dispatch Status", "type": "string" }, "dry_run": { @@ -32014,204 +30657,148 @@ "title": "Dry Run", "type": "boolean" }, - "error_message": { - "default": "", - "title": "Error Message", - "type": "string" - }, "evidence_fingerprint": { "title": "Evidence Fingerprint", "type": "string" }, - "from_instance": { + "product": { "default": "", - "title": "From Instance", + "title": "Product", "type": "string" }, - "inventory_record_id": { + "ref": { "default": "", - "title": "Inventory Record Id", + "title": "Ref", "type": "string" }, - "product": { - "default": "", - "title": "Product", - "type": "string" - }, - "promotion_record_id": { - "default": "", - "title": "Promotion Record Id", - "type": "string" - }, - "promotion_status": { - "default": "pending", - "enum": [ - "pending", - "pass", - "fail" - ], - "title": "Promotion Status", - "type": "string" - }, - "provider_id": { + "repository": { "default": "", - "title": "Provider Id", + "title": "Repository", "type": "string" }, - "provider_target_type": { + "run_conclusion": { "default": "", - "title": "Provider Target Type", + "title": "Run Conclusion", "type": "string" }, - "release_status": { - "default": "skipped", - "enum": [ - "pending", - "pass", - "fail", - "skipped" - ], - "title": "Release Status", - "type": "string" + "run_id": { + "default": 0, + "title": "Run Id", + "type": "integer" }, - "release_tag": { - "default": "", - "title": "Release Tag", + "run_status": { + "default": "pending", + "title": "Run Status", "type": "string" }, - "release_url": { + "run_url": { "default": "", - "title": "Release Url", + "title": "Run Url", "type": "string" }, "source_git_ref": { - "default": "", "title": "Source Git Ref", "type": "string" }, - "source_health_status": { - "default": "skipped", - "enum": [ - "pending", - "pass", - "fail", - "skipped" - ], - "title": "Source Health Status", - "type": "string" - }, - "target_category": { - "default": "unknown", - "enum": [ - "application", - "compose", - "container", - "service", - "static", - "unknown" - ], - "title": "Target Category", - "type": "string" - }, - "target_id": { - "default": "", - "title": "Target Id", - "type": "string" - }, - "target_name": { - "default": "", - "title": "Target Name", - "type": "string" - }, - "to_instance": { + "workflow_id": { "default": "", - "title": "To Instance", + "title": "Workflow Id", "type": "string" } }, "required": [ "evidence_fingerprint", - "bump" + "delivery_id", + "artifact_id", + "source_git_ref" ], - "title": "ProductPromotionDryRunResult", + "title": "ProductPromotionWorkflowDispatchResult", "type": "object" }, - "ProductPromotionEvidence": { + "ProductPromotionWorkflowProfile": { "additionalProperties": false, "properties": { - "artifact_id": { - "default": "", - "title": "Artifact Id", + "artifact_id_input": { + "default": "artifact_id", + "title": "Artifact Id Input", "type": "string" }, - "deploy_reference": { - "default": "", - "title": "Deploy Reference", + "bump_input": { + "default": "bump", + "title": "Bump Input", "type": "string" }, - "deployment_record_id": { - "default": "", - "title": "Deployment Record Id", + "default_bump": { + "default": "patch", + "title": "Default Bump", "type": "string" }, - "deployment_status": { - "default": "skipped", - "enum": [ - "pending", - "pass", - "fail", - "skipped" - ], - "title": "Deployment Status", + "deploy_reference_input": { + "default": "deploy_reference", + "title": "Deploy Reference Input", "type": "string" }, - "environment": { - "title": "Environment", + "dry_run_input": { + "default": "dry_run", + "title": "Dry Run Input", "type": "string" }, - "health_status": { - "default": "skipped", - "enum": [ - "pending", - "pass", - "fail", - "skipped" - ], - "title": "Health Status", + "promotion_intent_input": { + "default": "promotion_intent_id", + "title": "Promotion Intent Input", "type": "string" }, - "inventory_stale_after": { - "default": "", - "title": "Inventory Stale After", + "ref": { + "default": "main", + "title": "Ref", "type": "string" }, - "inventory_updated_at": { - "default": "", - "title": "Inventory Updated At", + "source_git_ref_input": { + "default": "source_git_ref", + "title": "Source Git Ref Input", "type": "string" }, - "runtime_identity_detail": { - "default": "", - "title": "Runtime Identity Detail", + "workflow_id": { + "default": "promote-prod.yml", + "title": "Workflow Id", "type": "string" - }, - "runtime_identity_status": { - "default": "unchecked", + } + }, + "title": "ProductPromotionWorkflowProfile", + "type": "object" + }, + "ProductProviderRecordedTopology": { + "additionalProperties": false, + "properties": { + "authority_status": { + "default": "missing", "enum": [ - "unchecked", - "match", - "mismatch", - "missing", - "malformed", - "unverifiable" + "active", + "disabled", + "missing" ], - "title": "Runtime Identity Status", + "title": "Authority Status", "type": "string" }, - "source_git_ref": { - "default": "", - "title": "Source Git Ref", - "type": "string" + "domains": { + "default": [], + "items": { + "$ref": "#/components/schemas/ProductTopologyDomain" + }, + "title": "Domains", + "type": "array" + }, + "ingress": { + "$ref": "#/components/schemas/ProductTopologyIngress" + }, + "placement": { + "$ref": "#/components/schemas/ProductTopologyPlacement" + }, + "provenance": { + "$ref": "#/components/schemas/DataProvenance" + }, + "tls": { + "$ref": "#/components/schemas/ProductTopologyTlsOwnership" }, "trust_state": { "default": "missing", @@ -32226,94 +30813,164 @@ "type": "string" } }, - "required": [ - "environment" - ], - "title": "ProductPromotionEvidence", + "title": "ProductProviderRecordedTopology", "type": "object" }, - "ProductPromotionLiveConfirmations": { + "ProductPublicIngressSummary": { "additionalProperties": false, "properties": { - "major": { - "title": "Major", + "failure_code": { + "default": "", + "title": "Failure Code", "type": "string" }, - "minor": { - "title": "Minor", + "incident_eligible": { + "default": false, + "title": "Incident Eligible", + "type": "boolean" + }, + "incident_id": { + "default": "", + "title": "Incident Id", "type": "string" }, - "patch": { - "title": "Patch", + "incident_last_reminded_at": { + "default": "", + "title": "Incident Last Reminded At", "type": "string" - } - }, - "required": [ - "patch", - "minor", - "major" - ], - "title": "ProductPromotionLiveConfirmations", - "type": "object" - }, - "ProductPromotionOperationAvailability": { - "additionalProperties": false, - "properties": { - "authz_action": { - "title": "Authz Action", + }, + "incident_latest_event": { + "anyOf": [ + { + "enum": [ + "opened", + "updated", + "reminder", + "resolved" + ], + "type": "string" + }, + { + "const": "baseline", + "type": "string" + }, + { + "const": "", + "type": "string" + } + ], + "default": "", + "title": "Incident Latest Event" + }, + "incident_latest_event_at": { + "default": "", + "title": "Incident Latest Event At", "type": "string" }, - "consequences": { - "default": [], - "items": { - "type": "string" - }, - "title": "Consequences", - "type": "array" + "incident_material_fingerprint_sha256": { + "default": "", + "title": "Incident Material Fingerprint Sha256", + "type": "string" }, - "disabled_reasons": { - "default": [], - "items": { - "type": "string" - }, - "title": "Disabled Reasons", - "type": "array" + "incident_next_reminder_at": { + "default": "", + "title": "Incident Next Reminder At", + "type": "string" }, - "enabled": { - "title": "Enabled", - "type": "boolean" + "incident_notification_state": { + "anyOf": [ + { + "enum": [ + "active", + "acknowledged", + "silenced" + ], + "type": "string" + }, + { + "const": "", + "type": "string" + } + ], + "default": "", + "title": "Incident Notification State" }, - "operation": { + "incident_opened_at": { + "default": "", + "title": "Incident Opened At", + "type": "string" + }, + "incident_severity": { + "anyOf": [ + { + "enum": [ + "warning", + "critical" + ], + "type": "string" + }, + { + "const": "", + "type": "string" + } + ], + "default": "", + "title": "Incident Severity" + }, + "incident_status": { + "default": "", + "title": "Incident Status", + "type": "string" + }, + "monitoring_intent": { + "default": "prelaunch", "enum": [ - "direct_dry_run", - "workflow_dry_run", - "workflow_live" + "public", + "private", + "prelaunch" ], - "title": "Operation", + "title": "Monitoring Intent", "type": "string" }, - "requires_confirmation": { + "notification_sent": { "default": false, - "title": "Requires Confirmation", + "title": "Notification Sent", "type": "boolean" }, - "requires_idempotency_key": { - "default": true, - "title": "Requires Idempotency Key", - "type": "boolean" + "observed_at": { + "default": "", + "title": "Observed At", + "type": "string" }, - "requires_matching_direct_dry_run": { - "default": false, - "title": "Requires Matching Direct Dry Run", - "type": "boolean" + "provenance": { + "$ref": "#/components/schemas/DataProvenance", + "default": { + "detail": "Launchplane has not recorded public ingress observations for this lane.", + "freshness_status": "missing", + "recorded_at": "", + "refreshed_at": "", + "source_kind": "record", + "source_record_id": "", + "stale_after": "" + } }, - "requires_reason": { - "default": true, - "title": "Requires Reason", - "type": "boolean" + "record_id": { + "default": "", + "title": "Record Id", + "type": "string" + }, + "status": { + "default": "missing", + "title": "Status", + "type": "string" + }, + "summary": { + "default": "", + "title": "Summary", + "type": "string" }, "trust_state": { - "default": "recorded", + "default": "missing", "enum": [ "verified", "recorded", @@ -32325,725 +30982,732 @@ "type": "string" } }, - "required": [ - "operation", - "authz_action", - "enabled" - ], - "title": "ProductPromotionOperationAvailability", + "title": "ProductPublicIngressSummary", "type": "object" }, - "ProductPromotionStatus": { + "ProductRetirementRequest": { "additionalProperties": false, "properties": { - "base_driver_id": { + "confirmation": { "default": "", - "title": "Base Driver Id", + "title": "Confirmation", "type": "string" }, - "bump_options": { - "default": [ - "patch", - "minor", - "major" - ], - "items": { - "enum": [ - "patch", - "minor", - "major" - ], - "type": "string" - }, - "title": "Bump Options", - "type": "array" + "expected_target_sha256": { + "title": "Expected Target Sha256", + "type": "string" }, - "context": { - "title": "Context", + "instance": { + "title": "Instance", "type": "string" }, - "default_bump": { + "mode": { + "default": "plan", "enum": [ - "patch", - "minor", - "major" + "plan", + "apply" ], - "title": "Default Bump", - "type": "string" - }, - "destination": { - "$ref": "#/components/schemas/ProductPromotionEvidence" - }, - "destination_environment": { - "default": "prod", - "title": "Destination Environment", + "title": "Mode", "type": "string" }, - "direct_dry_run": { - "$ref": "#/components/schemas/ProductPromotionOperationAvailability" - }, - "display_name": { - "title": "Display Name", + "product": { + "title": "Product", "type": "string" }, - "driver_id": { - "title": "Driver Id", + "reason": { + "title": "Reason", "type": "string" }, - "evidence_fingerprint": { - "title": "Evidence Fingerprint", + "related_issue": { + "title": "Related Issue", "type": "string" }, - "live_confirmations": { - "$ref": "#/components/schemas/ProductPromotionLiveConfirmations" - }, - "product": { - "title": "Product", + "reviewed_plan_record_id": { + "default": "", + "title": "Reviewed Plan Record Id", "type": "string" }, - "release_review": { - "$ref": "#/components/schemas/ReleaseReviewStatus" - }, - "repository": { - "title": "Repository", + "reviewed_plan_sha256": { + "default": "", + "title": "Reviewed Plan Sha256", "type": "string" }, "schema_version": { + "const": 1, "default": 1, - "minimum": 1.0, "title": "Schema Version", "type": "integer" - }, - "source": { - "$ref": "#/components/schemas/ProductPromotionEvidence" - }, - "source_environment": { - "default": "testing", - "title": "Source Environment", - "type": "string" - }, - "trust_state": { - "enum": [ - "verified", - "recorded", - "stale", - "missing", - "unsupported" - ], - "title": "Trust State", - "type": "string" - }, - "workflow_dry_run": { - "$ref": "#/components/schemas/ProductPromotionOperationAvailability" - }, - "workflow_id": { - "title": "Workflow Id", - "type": "string" - }, - "workflow_live": { - "$ref": "#/components/schemas/ProductPromotionOperationAvailability" - }, - "workflow_ref": { - "title": "Workflow Ref", - "type": "string" } }, "required": [ "product", - "display_name", - "driver_id", - "repository", - "workflow_id", - "workflow_ref", - "context", - "source", - "destination", - "evidence_fingerprint", - "default_bump", - "direct_dry_run", - "workflow_dry_run", - "workflow_live", - "live_confirmations", - "trust_state" + "instance", + "expected_target_sha256", + "reason", + "related_issue" ], - "title": "ProductPromotionStatus", + "title": "ProductRetirementRequest", "type": "object" }, - "ProductPromotionStatusResponse": { + "ProductReviewDecisionEnvelope": { "additionalProperties": false, "properties": { - "promotion_status": { - "$ref": "#/components/schemas/ProductPromotionStatus" + "decision": { + "enum": [ + "accepted", + "changes_requested" + ], + "title": "Decision", + "type": "string" }, - "status": { - "const": "ok", - "default": "ok", - "title": "Status", + "pull_request": { + "minimum": 1.0, + "title": "Pull Request", + "type": "integer" + }, + "reason": { + "default": "", + "maxLength": 4000, + "title": "Reason", "type": "string" }, - "trace_id": { - "title": "Trace Id", + "repository": { + "maxLength": 256, + "minLength": 3, + "pattern": "^[^/\\s]+/[^/\\s]+$", + "title": "Repository", "type": "string" } }, "required": [ - "trace_id", - "promotion_status" + "repository", + "pull_request", + "decision" ], - "title": "ProductPromotionStatusResponse", + "title": "ProductReviewDecisionEnvelope", "type": "object" }, - "ProductPromotionWorkflowDeliveryStatus": { + "ProductReviewDecisionRecord": { "additionalProperties": false, + "description": "One Owner decision about one pull request preview.\n\nThe decision is a recorded opinion: it never merges or deploys anything.", "properties": { - "delivery_id": { - "title": "Delivery Id", + "decided_at": { + "title": "Decided At", "type": "string" }, - "dispatch_status": { + "decision": { "enum": [ - "pending", - "dispatched", - "failed", - "reconcile_required" + "accepted", + "changes_requested" ], - "title": "Dispatch Status", + "title": "Decision", "type": "string" }, - "error_code": { + "feedback_requested": { + "default": false, + "title": "Feedback Requested", + "type": "boolean" + }, + "feedback_url": { "default": "", - "title": "Error Code", + "title": "Feedback Url", "type": "string" }, - "observed_at": { - "title": "Observed At", + "head_sha": { + "default": "", + "title": "Head Sha", "type": "string" }, - "run_conclusion": { - "default": "", - "title": "Run Conclusion", + "owner_github_id": { + "title": "Owner Github Id", "type": "string" }, - "run_id": { - "default": 0, - "minimum": 0.0, - "title": "Run Id", - "type": "integer" + "owner_github_login": { + "title": "Owner Github Login", + "type": "string" }, - "run_observation_status": { - "default": "pending", - "enum": [ - "pending", - "observed" - ], - "title": "Run Observation Status", + "preview_url": { + "default": "", + "title": "Preview Url", "type": "string" }, - "run_status": { - "default": "pending", - "title": "Run Status", + "product": { + "title": "Product", "type": "string" }, - "run_url": { + "pull_request_number": { + "minimum": 1.0, + "title": "Pull Request Number", + "type": "integer" + }, + "reason": { "default": "", - "title": "Run Url", + "maxLength": 4000, + "title": "Reason", "type": "string" }, - "state": { - "enum": [ - "pending", - "running", - "delivered", - "failed", - "reconcile_required" - ], - "title": "State", + "record_id": { + "title": "Record Id", + "type": "string" + }, + "repository": { + "title": "Repository", "type": "string" + }, + "schema_version": { + "default": 1, + "minimum": 1.0, + "title": "Schema Version", + "type": "integer" } }, "required": [ - "delivery_id", - "state", - "dispatch_status", - "observed_at" + "record_id", + "product", + "repository", + "pull_request_number", + "decision", + "owner_github_id", + "owner_github_login", + "decided_at" ], - "title": "ProductPromotionWorkflowDeliveryStatus", + "title": "ProductReviewDecisionRecord", "type": "object" }, - "ProductPromotionWorkflowDeliveryStatusResponse": { + "ProductReviewFeedbackRetryEnvelope": { "additionalProperties": false, "properties": { - "delivery": { - "$ref": "#/components/schemas/ProductPromotionWorkflowDeliveryStatus" - }, - "status": { - "const": "ok", - "default": "ok", - "title": "Status", + "decision_id": { + "maxLength": 512, + "minLength": 1, + "title": "Decision Id", "type": "string" }, - "trace_id": { - "title": "Trace Id", + "pull_request": { + "minimum": 1.0, + "title": "Pull Request", + "type": "integer" + }, + "repository": { + "maxLength": 256, + "minLength": 3, + "pattern": "^[^/\\s]+/[^/\\s]+$", + "title": "Repository", "type": "string" } }, "required": [ - "trace_id", - "delivery" + "repository", + "pull_request", + "decision_id" ], - "title": "ProductPromotionWorkflowDeliveryStatusResponse", + "title": "ProductReviewFeedbackRetryEnvelope", "type": "object" }, - "ProductPromotionWorkflowDispatchEnvelope": { + "ProductReviewResponse": { "additionalProperties": false, "properties": { - "bump": { - "default": "patch", - "enum": [ - "patch", - "minor", - "major" - ], - "title": "Bump", - "type": "string" + "can_decide": { + "title": "Can Decide", + "type": "boolean" }, - "confirmation": { - "default": "", - "title": "Confirmation", + "cannot_decide_reason": { + "title": "Cannot Decide Reason", "type": "string" }, - "dry_run": { - "default": true, - "title": "Dry Run", - "type": "boolean" - }, - "evidence_fingerprint": { - "title": "Evidence Fingerprint", + "display_name": { + "title": "Display Name", "type": "string" }, - "reason": { - "title": "Reason", + "head_sha": { + "title": "Head Sha", "type": "string" }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" - } - }, - "required": [ - "reason", - "evidence_fingerprint" - ], - "title": "ProductPromotionWorkflowDispatchEnvelope", - "type": "object" - }, - "ProductPromotionWorkflowDispatchResponse": { - "additionalProperties": false, - "properties": { - "original_trace_id": { + "latest_decision": { "anyOf": [ { - "type": "string" + "$ref": "#/components/schemas/ProductReviewDecisionRecord" }, { "type": "null" } - ], - "title": "Original Trace Id", - "x-launchplane-optional-response": true + ] }, - "records": { - "additionalProperties": { - "type": "string" - }, - "title": "Records", - "type": "object" + "owner_github_login": { + "title": "Owner Github Login", + "type": "string" }, - "replayed": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Replayed", - "x-launchplane-optional-response": true + "owner_set": { + "title": "Owner Set", + "type": "boolean" }, - "result": { - "$ref": "#/components/schemas/ProductPromotionWorkflowDispatchResult" + "preview_url": { + "title": "Preview Url", + "type": "string" + }, + "product": { + "title": "Product", + "type": "string" + }, + "pull_request_number": { + "title": "Pull Request Number", + "type": "integer" + }, + "pull_request_url": { + "title": "Pull Request Url", + "type": "string" + }, + "repository": { + "title": "Repository", + "type": "string" }, "status": { - "const": "accepted", - "default": "accepted", + "default": "ok", "title": "Status", "type": "string" }, "trace_id": { "title": "Trace Id", "type": "string" + }, + "viewer_is_owner": { + "title": "Viewer Is Owner", + "type": "boolean" } }, "required": [ "trace_id", - "result" + "product", + "display_name", + "repository", + "pull_request_number", + "pull_request_url", + "preview_url", + "head_sha", + "owner_set", + "owner_github_login", + "viewer_is_owner", + "can_decide", + "cannot_decide_reason", + "latest_decision" ], - "title": "ProductPromotionWorkflowDispatchResponse", + "title": "ProductReviewResponse", "type": "object" }, - "ProductPromotionWorkflowDispatchResult": { + "ProductRuntimeConfigRequirement": { "additionalProperties": false, "properties": { - "artifact_id": { - "title": "Artifact Id", - "type": "string" - }, - "bump": { - "default": "patch", - "enum": [ - "patch", - "minor", - "major" - ], - "title": "Bump", - "type": "string" - }, "context": { "default": "", "title": "Context", "type": "string" }, - "delivery_id": { - "title": "Delivery Id", - "type": "string" - }, - "dispatch_status": { - "default": "pending", - "enum": [ - "pending", - "dispatched" - ], - "title": "Dispatch Status", + "instance": { + "default": "", + "title": "Instance", "type": "string" }, - "dry_run": { - "default": false, - "title": "Dry Run", - "type": "boolean" - }, - "evidence_fingerprint": { - "title": "Evidence Fingerprint", + "key": { + "title": "Key", "type": "string" - }, - "product": { + } + }, + "required": [ + "key" + ], + "title": "ProductRuntimeConfigRequirement", + "type": "object" + }, + "ProductRuntimeConfigStatusItem": { + "additionalProperties": false, + "properties": { + "context": { "default": "", - "title": "Product", + "title": "Context", "type": "string" }, - "ref": { + "instance": { "default": "", - "title": "Ref", + "title": "Instance", "type": "string" }, - "repository": { - "default": "", - "title": "Repository", + "key": { + "title": "Key", "type": "string" }, - "run_conclusion": { + "source_label": { "default": "", - "title": "Run Conclusion", - "type": "string" - }, - "run_id": { - "default": 0, - "title": "Run Id", - "type": "integer" - }, - "run_status": { - "default": "pending", - "title": "Run Status", + "title": "Source Label", "type": "string" }, - "run_url": { - "default": "", - "title": "Run Url", + "status": { + "enum": [ + "configured", + "missing", + "disabled", + "unvalidated", + "stale", + "unsupported" + ], + "title": "Status", "type": "string" }, - "source_git_ref": { - "title": "Source Git Ref", + "trust_state": { + "default": "missing", + "enum": [ + "verified", + "recorded", + "stale", + "missing", + "unsupported" + ], + "title": "Trust State", "type": "string" }, - "workflow_id": { + "updated_at": { "default": "", - "title": "Workflow Id", + "title": "Updated At", "type": "string" } }, "required": [ - "evidence_fingerprint", - "delivery_id", - "artifact_id", - "source_git_ref" + "key", + "status" ], - "title": "ProductPromotionWorkflowDispatchResult", + "title": "ProductRuntimeConfigStatusItem", "type": "object" }, - "ProductPromotionWorkflowProfile": { + "ProductRuntimeSettingSummary": { "additionalProperties": false, "properties": { - "artifact_id_input": { - "default": "artifact_id", - "title": "Artifact Id Input", - "type": "string" - }, - "bump_input": { - "default": "bump", - "title": "Bump Input", + "context": { + "title": "Context", "type": "string" }, - "default_bump": { - "default": "patch", - "title": "Default Bump", - "type": "string" + "env_keys": { + "items": { + "type": "string" + }, + "title": "Env Keys", + "type": "array" }, - "deploy_reference_input": { - "default": "deploy_reference", - "title": "Deploy Reference Input", - "type": "string" + "env_value_count": { + "minimum": 0.0, + "title": "Env Value Count", + "type": "integer" }, - "dry_run_input": { - "default": "dry_run", - "title": "Dry Run Input", + "instance": { + "title": "Instance", "type": "string" }, - "promotion_intent_input": { - "default": "promotion_intent_id", - "title": "Promotion Intent Input", + "scope": { + "title": "Scope", "type": "string" }, - "ref": { - "default": "main", - "title": "Ref", + "source_label": { + "title": "Source Label", "type": "string" }, - "source_git_ref_input": { - "default": "source_git_ref", - "title": "Source Git Ref Input", + "trust_state": { + "default": "recorded", + "enum": [ + "verified", + "recorded", + "stale", + "missing", + "unsupported" + ], + "title": "Trust State", "type": "string" }, - "workflow_id": { - "default": "promote-prod.yml", - "title": "Workflow Id", + "updated_at": { + "title": "Updated At", "type": "string" } }, - "title": "ProductPromotionWorkflowProfile", + "required": [ + "scope", + "context", + "instance", + "env_keys", + "env_value_count", + "updated_at", + "source_label" + ], + "title": "ProductRuntimeSettingSummary", "type": "object" }, - "ProductProviderRecordedTopology": { + "ProductSecretBindingSummary": { "additionalProperties": false, "properties": { - "authority_status": { - "default": "missing", - "enum": [ - "active", - "disabled", - "missing" - ], - "title": "Authority Status", + "binding_id": { + "title": "Binding Id", "type": "string" }, - "domains": { - "default": [], - "items": { - "$ref": "#/components/schemas/ProductTopologyDomain" - }, - "title": "Domains", - "type": "array" + "binding_key": { + "title": "Binding Key", + "type": "string" }, - "ingress": { - "$ref": "#/components/schemas/ProductTopologyIngress" + "binding_type": { + "title": "Binding Type", + "type": "string" }, - "placement": { - "$ref": "#/components/schemas/ProductTopologyPlacement" + "context": { + "title": "Context", + "type": "string" }, - "provenance": { - "$ref": "#/components/schemas/DataProvenance" + "instance": { + "title": "Instance", + "type": "string" }, - "tls": { - "$ref": "#/components/schemas/ProductTopologyTlsOwnership" + "integration": { + "title": "Integration", + "type": "string" + }, + "secret_id": { + "title": "Secret Id", + "type": "string" + }, + "status": { + "title": "Status", + "type": "string" }, "trust_state": { - "default": "missing", - "enum": [ - "verified", - "recorded", - "stale", - "missing", - "unsupported" + "anyOf": [ + { + "enum": [ + "verified", + "recorded", + "stale", + "missing", + "unsupported" + ], + "type": "string" + }, + { + "const": "disabled", + "type": "string" + } ], - "title": "Trust State", + "title": "Trust State" + }, + "updated_at": { + "title": "Updated At", "type": "string" } }, - "title": "ProductProviderRecordedTopology", + "required": [ + "binding_id", + "secret_id", + "integration", + "binding_type", + "binding_key", + "context", + "instance", + "status", + "updated_at", + "trust_state" + ], + "title": "ProductSecretBindingSummary", "type": "object" }, - "ProductPublicIngressSummary": { + "ProductSecretConfigRequirement": { "additionalProperties": false, "properties": { - "failure_code": { - "default": "", - "title": "Failure Code", + "binding_key": { + "title": "Binding Key", "type": "string" }, - "incident_eligible": { - "default": false, - "title": "Incident Eligible", - "type": "boolean" - }, - "incident_id": { + "context": { "default": "", - "title": "Incident Id", + "title": "Context", "type": "string" }, - "incident_last_reminded_at": { + "instance": { "default": "", - "title": "Incident Last Reminded At", + "title": "Instance", "type": "string" }, - "incident_latest_event": { + "integration": { + "default": "runtime_environment", + "title": "Integration", + "type": "string" + }, + "owner_input": { "anyOf": [ { - "enum": [ - "opened", - "updated", - "reminder", - "resolved" - ], - "type": "string" - }, - { - "const": "baseline", - "type": "string" + "$ref": "#/components/schemas/ProductOwnerSecretInput" }, { - "const": "", - "type": "string" + "type": "null" } - ], - "default": "", - "title": "Incident Latest Event" + ] + } + }, + "required": [ + "binding_key" + ], + "title": "ProductSecretConfigRequirement", + "type": "object" + }, + "ProductSiteOverview": { + "additionalProperties": false, + "properties": { + "available_actions": { + "default": [], + "items": { + "$ref": "#/components/schemas/ProductActionAvailability" + }, + "title": "Available Actions", + "type": "array" }, - "incident_latest_event_at": { + "base_driver_id": { "default": "", - "title": "Incident Latest Event At", + "title": "Base Driver Id", "type": "string" }, - "incident_material_fingerprint_sha256": { - "default": "", - "title": "Incident Material Fingerprint Sha256", + "display_name": { + "title": "Display Name", "type": "string" }, - "incident_next_reminder_at": { - "default": "", - "title": "Incident Next Reminder At", + "driver_id": { + "title": "Driver Id", "type": "string" }, - "incident_notification_state": { + "environments": { + "default": [], + "items": { + "$ref": "#/components/schemas/ProductEnvironmentSummary" + }, + "title": "Environments", + "type": "array" + }, + "preview": { + "$ref": "#/components/schemas/ProductPreviewSummary" + }, + "product": { + "title": "Product", + "type": "string" + }, + "provenance": { + "$ref": "#/components/schemas/DataProvenance" + }, + "repository": { + "title": "Repository", + "type": "string" + }, + "schema_version": { + "default": 1, + "minimum": 1.0, + "title": "Schema Version", + "type": "integer" + }, + "trust_state": { + "enum": [ + "verified", + "recorded", + "stale", + "missing", + "unsupported" + ], + "title": "Trust State", + "type": "string" + }, + "warnings": { + "default": [], + "items": { + "type": "string" + }, + "title": "Warnings", + "type": "array" + } + }, + "required": [ + "product", + "display_name", + "repository", + "driver_id", + "preview", + "trust_state", + "provenance" + ], + "title": "ProductSiteOverview", + "type": "object" + }, + "ProductTargetSummary": { + "additionalProperties": false, + "properties": { + "artifact_manifest": { "anyOf": [ { - "enum": [ - "active", - "acknowledged", - "silenced" - ], - "type": "string" + "$ref": "#/components/schemas/ArtifactIdentityManifest" }, { - "const": "", - "type": "string" + "type": "null" } - ], - "default": "", - "title": "Incident Notification State" + ] }, - "incident_opened_at": { - "default": "", - "title": "Incident Opened At", - "type": "string" + "expected_runtime_identity": { + "anyOf": [ + { + "$ref": "#/components/schemas/RuntimeIdentity" + }, + { + "type": "null" + } + ] }, - "incident_severity": { + "observed_runtime_identity": { "anyOf": [ { - "enum": [ - "warning", - "critical" - ], - "type": "string" + "$ref": "#/components/schemas/RuntimeIdentity" }, { - "const": "", - "type": "string" + "type": "null" } - ], + ] + }, + "provider": { "default": "", - "title": "Incident Severity" + "title": "Provider", + "type": "string" }, - "incident_status": { + "provider_target_type": { "default": "", - "title": "Incident Status", + "title": "Provider Target Type", "type": "string" }, - "monitoring_intent": { - "default": "prelaunch", + "runtime_identity_detail": { + "default": "", + "title": "Runtime Identity Detail", + "type": "string" + }, + "runtime_identity_status": { + "default": "unchecked", "enum": [ - "public", - "private", - "prelaunch" + "unchecked", + "match", + "mismatch", + "missing", + "malformed", + "unverifiable" ], - "title": "Monitoring Intent", + "title": "Runtime Identity Status", "type": "string" }, - "notification_sent": { + "target_id_recorded": { "default": false, - "title": "Notification Sent", + "title": "Target Id Recorded", "type": "boolean" }, - "observed_at": { - "default": "", - "title": "Observed At", - "type": "string" - }, - "provenance": { - "$ref": "#/components/schemas/DataProvenance", - "default": { - "detail": "Launchplane has not recorded public ingress observations for this lane.", - "freshness_status": "missing", - "recorded_at": "", - "refreshed_at": "", - "source_kind": "record", - "source_record_id": "", - "stale_after": "" - } - }, - "record_id": { + "target_name": { "default": "", - "title": "Record Id", - "type": "string" - }, - "status": { - "default": "missing", - "title": "Status", + "title": "Target Name", "type": "string" }, - "summary": { + "target_type": { "default": "", - "title": "Summary", + "title": "Target Type", "type": "string" }, "trust_state": { @@ -33059,1301 +31723,1160 @@ "type": "string" } }, - "title": "ProductPublicIngressSummary", + "title": "ProductTargetSummary", "type": "object" }, - "ProductRetirementRequest": { + "ProductTopologyDomain": { "additionalProperties": false, "properties": { - "confirmation": { - "default": "", - "title": "Confirmation", - "type": "string" - }, - "expected_target_sha256": { - "title": "Expected Target Sha256", - "type": "string" - }, - "instance": { - "title": "Instance", + "domain_name": { + "title": "Domain Name", "type": "string" }, - "mode": { - "default": "plan", + "role": { + "default": "primary", "enum": [ - "plan", - "apply" + "primary", + "alias" ], - "title": "Mode", - "type": "string" - }, - "product": { - "title": "Product", - "type": "string" - }, - "reason": { - "title": "Reason", - "type": "string" - }, - "related_issue": { - "title": "Related Issue", - "type": "string" - }, - "reviewed_plan_record_id": { - "default": "", - "title": "Reviewed Plan Record Id", - "type": "string" - }, - "reviewed_plan_sha256": { - "default": "", - "title": "Reviewed Plan Sha256", + "title": "Role", "type": "string" }, - "schema_version": { - "const": 1, - "default": 1, - "title": "Schema Version", - "type": "integer" + "tls_expected": { + "default": false, + "title": "Tls Expected", + "type": "boolean" } }, "required": [ - "product", - "instance", - "expected_target_sha256", - "reason", - "related_issue" + "domain_name" ], - "title": "ProductRetirementRequest", + "title": "ProductTopologyDomain", "type": "object" }, - "ProductReviewDecisionEnvelope": { + "ProductTopologyIngress": { "additionalProperties": false, "properties": { - "decision": { + "endpoint_key": { + "default": "", + "title": "Endpoint Key", + "type": "string" + }, + "path": { + "default": "unknown", "enum": [ - "accepted", - "changes_requested" + "edge_to_provider", + "direct_to_provider", + "none", + "unknown" ], - "title": "Decision", + "title": "Path", "type": "string" }, - "pull_request": { - "minimum": 1.0, - "title": "Pull Request", - "type": "integer" + "provenance": { + "$ref": "#/components/schemas/DataProvenance" }, - "reason": { + "provider": { "default": "", - "maxLength": 4000, - "title": "Reason", + "title": "Provider", "type": "string" }, - "repository": { - "maxLength": 256, - "minLength": 3, - "pattern": "^[^/\\s]+/[^/\\s]+$", - "title": "Repository", + "termination_kind": { + "anyOf": [ + { + "enum": [ + "edge", + "direct", + "none" + ], + "type": "string" + }, + { + "const": "unknown", + "type": "string" + } + ], + "default": "unknown", + "title": "Termination Kind" + }, + "trust_state": { + "default": "missing", + "enum": [ + "verified", + "recorded", + "stale", + "missing", + "unsupported" + ], + "title": "Trust State", "type": "string" } }, - "required": [ - "repository", - "pull_request", - "decision" - ], - "title": "ProductReviewDecisionEnvelope", + "title": "ProductTopologyIngress", "type": "object" }, - "ProductReviewDecisionRecord": { + "ProductTopologyPlacement": { "additionalProperties": false, - "description": "One Owner decision about one pull request preview.\n\nThe decision is a recorded opinion: it never merges or deploys anything.", "properties": { - "decided_at": { - "title": "Decided At", - "type": "string" + "provenance": { + "$ref": "#/components/schemas/DataProvenance" }, - "decision": { - "enum": [ - "accepted", - "changes_requested" - ], - "title": "Decision", + "provider": { + "default": "", + "title": "Provider", "type": "string" }, - "feedback_requested": { + "provider_target_record_present": { "default": false, - "title": "Feedback Requested", + "title": "Provider Target Record Present", "type": "boolean" }, - "feedback_url": { + "provider_target_type": { "default": "", - "title": "Feedback Url", + "title": "Provider Target Type", "type": "string" }, - "head_sha": { + "target_name": { "default": "", - "title": "Head Sha", + "title": "Target Name", "type": "string" }, - "owner_github_id": { - "title": "Owner Github Id", - "type": "string" - }, - "owner_github_login": { - "title": "Owner Github Login", - "type": "string" - }, - "preview_url": { + "target_type": { "default": "", - "title": "Preview Url", + "title": "Target Type", "type": "string" }, - "product": { - "title": "Product", + "trust_state": { + "default": "missing", + "enum": [ + "verified", + "recorded", + "stale", + "missing", + "unsupported" + ], + "title": "Trust State", "type": "string" + } + }, + "title": "ProductTopologyPlacement", + "type": "object" + }, + "ProductTopologyTlsOwnership": { + "additionalProperties": false, + "properties": { + "owner": { + "anyOf": [ + { + "enum": [ + "launchplane", + "provider", + "external", + "none" + ], + "type": "string" + }, + { + "const": "unknown", + "type": "string" + } + ], + "default": "unknown", + "title": "Owner" }, - "pull_request_number": { - "minimum": 1.0, - "title": "Pull Request Number", - "type": "integer" + "provenance": { + "$ref": "#/components/schemas/DataProvenance" }, - "reason": { + "provider": { "default": "", - "maxLength": 4000, - "title": "Reason", + "title": "Provider", "type": "string" }, - "record_id": { - "title": "Record Id", + "terminator": { + "default": "unknown", + "enum": [ + "edge", + "provider", + "external", + "none", + "unknown" + ], + "title": "Terminator", "type": "string" }, - "repository": { - "title": "Repository", + "trust_state": { + "default": "missing", + "enum": [ + "verified", + "recorded", + "stale", + "missing", + "unsupported" + ], + "title": "Trust State", "type": "string" - }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" } }, - "required": [ - "record_id", - "product", - "repository", - "pull_request_number", - "decision", - "owner_github_id", - "owner_github_login", - "decided_at" - ], - "title": "ProductReviewDecisionRecord", + "title": "ProductTopologyTlsOwnership", "type": "object" }, - "ProductReviewFeedbackRetryEnvelope": { + "ProductTopologyWarning": { "additionalProperties": false, "properties": { - "decision_id": { - "maxLength": 512, - "minLength": 1, - "title": "Decision Id", + "code": { + "enum": [ + "missing_route_authority", + "route_authority_disabled", + "stale_route_authority", + "desired_domain_unknown", + "domain_divergence", + "provider_placement_missing", + "placement_divergence", + "external_ingress_internals_unsupported", + "ingress_ownership_unknown", + "ingress_divergence", + "tls_ownership_unknown", + "tls_ownership_divergence", + "tls_observation_missing", + "stale_tls_observation", + "tls_mismatch", + "tls_expiring", + "tls_expired", + "tls_untrusted", + "tls_unavailable", + "tls_unsupported", + "public_ingress_observation_missing", + "stale_public_ingress_observation", + "public_runtime_identity_unverified", + "public_ingress_failure" + ], + "title": "Code", "type": "string" }, - "pull_request": { - "minimum": 1.0, - "title": "Pull Request", - "type": "integer" + "detail": { + "title": "Detail", + "type": "string" }, - "repository": { - "maxLength": 256, - "minLength": 3, - "pattern": "^[^/\\s]+/[^/\\s]+$", - "title": "Repository", + "domain_name": { + "default": "", + "title": "Domain Name", + "type": "string" + }, + "scope": { + "enum": [ + "authority", + "placement", + "domains", + "ingress", + "tls", + "observation" + ], + "title": "Scope", + "type": "string" + }, + "severity": { + "enum": [ + "warning", + "error" + ], + "title": "Severity", "type": "string" } }, "required": [ - "repository", - "pull_request", - "decision_id" + "code", + "scope", + "severity", + "detail" ], - "title": "ProductReviewFeedbackRetryEnvelope", + "title": "ProductTopologyWarning", "type": "object" }, - "ProductReviewResponse": { + "ProductionBackupAuthorityReadModel": { "additionalProperties": false, "properties": { - "can_decide": { - "title": "Can Decide", - "type": "boolean" - }, - "cannot_decide_reason": { - "title": "Cannot Decide Reason", + "context": { + "title": "Context", "type": "string" }, - "display_name": { - "title": "Display Name", + "generated_at": { + "title": "Generated At", "type": "string" }, - "head_sha": { - "title": "Head Sha", + "instance": { + "title": "Instance", "type": "string" }, - "latest_decision": { + "policy": { "anyOf": [ { - "$ref": "#/components/schemas/ProductReviewDecisionRecord" + "$ref": "#/components/schemas/ProductionBackupPolicySummary" }, { "type": "null" } ] }, - "owner_github_login": { - "title": "Owner Github Login", - "type": "string" - }, - "owner_set": { - "title": "Owner Set", - "type": "boolean" - }, - "preview_url": { - "title": "Preview Url", - "type": "string" - }, "product": { "title": "Product", "type": "string" }, - "pull_request_number": { - "title": "Pull Request Number", - "type": "integer" - }, - "pull_request_url": { - "title": "Pull Request Url", + "promotion_action": { + "title": "Promotion Action", "type": "string" }, - "repository": { - "title": "Repository", - "type": "string" + "ready": { + "title": "Ready", + "type": "boolean" }, - "status": { - "default": "ok", - "title": "Status", + "reason_codes": { + "default": [], + "items": { + "type": "string" + }, + "title": "Reason Codes", + "type": "array" + }, + "schema_version": { + "default": 1, + "minimum": 1.0, + "title": "Schema Version", + "type": "integer" + }, + "state": { + "enum": [ + "ready", + "missing", + "invalid", + "stale", + "retired" + ], + "title": "State", "type": "string" }, - "trace_id": { - "title": "Trace Id", + "summary": { + "title": "Summary", "type": "string" }, - "viewer_is_owner": { - "title": "Viewer Is Owner", - "type": "boolean" + "targets": { + "default": [], + "items": { + "$ref": "#/components/schemas/ProductionBackupTargetSummary" + }, + "title": "Targets", + "type": "array" } }, "required": [ - "trace_id", "product", - "display_name", - "repository", - "pull_request_number", - "pull_request_url", - "preview_url", - "head_sha", - "owner_set", - "owner_github_login", - "viewer_is_owner", - "can_decide", - "cannot_decide_reason", - "latest_decision" + "context", + "instance", + "promotion_action", + "state", + "ready", + "summary", + "generated_at" ], - "title": "ProductReviewResponse", + "title": "ProductionBackupAuthorityReadModel", "type": "object" }, - "ProductRuntimeConfigRequirement": { + "ProductionBackupAuthorityReadResponse": { "additionalProperties": false, "properties": { - "context": { - "default": "", - "title": "Context", - "type": "string" + "authority": { + "$ref": "#/components/schemas/ProductionBackupAuthorityReadModel" }, - "instance": { - "default": "", - "title": "Instance", + "status": { + "const": "ok", + "default": "ok", + "title": "Status", "type": "string" }, - "key": { - "title": "Key", + "trace_id": { + "title": "Trace Id", "type": "string" } }, "required": [ - "key" + "trace_id", + "authority" ], - "title": "ProductRuntimeConfigRequirement", + "title": "ProductionBackupAuthorityReadResponse", "type": "object" }, - "ProductRuntimeConfigStatusItem": { + "ProductionBackupAuthorityStatus": { "additionalProperties": false, "properties": { - "context": { - "default": "", - "title": "Context", + "generated_at": { + "title": "Generated At", "type": "string" }, - "instance": { - "default": "", - "title": "Instance", + "promotion_action": { + "title": "Promotion Action", "type": "string" }, - "key": { - "title": "Key", - "type": "string" + "ready": { + "title": "Ready", + "type": "boolean" }, - "source_label": { - "default": "", - "title": "Source Label", - "type": "string" + "reason_codes": { + "default": [], + "items": { + "type": "string" + }, + "title": "Reason Codes", + "type": "array" }, - "status": { - "enum": [ - "configured", - "missing", - "disabled", - "unvalidated", - "stale", - "unsupported" - ], - "title": "Status", - "type": "string" + "schema_version": { + "default": 1, + "minimum": 1.0, + "title": "Schema Version", + "type": "integer" }, - "trust_state": { - "default": "missing", + "state": { "enum": [ - "verified", - "recorded", - "stale", + "ready", "missing", - "unsupported" + "invalid", + "stale", + "retired" ], - "title": "Trust State", + "title": "State", "type": "string" }, - "updated_at": { - "default": "", - "title": "Updated At", + "summary": { + "title": "Summary", "type": "string" } }, "required": [ - "key", - "status" + "promotion_action", + "state", + "ready", + "summary", + "generated_at" ], - "title": "ProductRuntimeConfigStatusItem", + "title": "ProductionBackupAuthorityStatus", "type": "object" }, - "ProductRuntimeSettingSummary": { + "ProductionBackupAuthorityWriteEnvelope": { "additionalProperties": false, "properties": { - "context": { - "title": "Context", + "expected_current_policy_record_id": { + "default": "", + "title": "Expected Current Policy Record Id", "type": "string" }, - "env_keys": { - "items": { + "expected_current_target_record_ids": { + "additionalProperties": { "type": "string" }, - "title": "Env Keys", - "type": "array" - }, - "env_value_count": { - "minimum": 0.0, - "title": "Env Value Count", - "type": "integer" + "title": "Expected Current Target Record Ids", + "type": "object" }, - "instance": { - "title": "Instance", + "mode": { + "enum": [ + "dry_run", + "apply" + ], + "title": "Mode", "type": "string" }, - "scope": { - "title": "Scope", - "type": "string" + "policy": { + "$ref": "#/components/schemas/ProductionBackupPolicyRecord" }, - "source_label": { - "title": "Source Label", + "reviewed_authority_digest": { + "default": "", + "title": "Reviewed Authority Digest", "type": "string" }, - "trust_state": { - "default": "recorded", - "enum": [ - "verified", - "recorded", - "stale", - "missing", - "unsupported" - ], - "title": "Trust State", - "type": "string" + "schema_version": { + "default": 1, + "minimum": 1.0, + "title": "Schema Version", + "type": "integer" }, - "updated_at": { - "title": "Updated At", - "type": "string" + "targets": { + "default": [], + "items": { + "$ref": "#/components/schemas/ProductionBackupTargetRecord" + }, + "title": "Targets", + "type": "array" } }, "required": [ - "scope", - "context", - "instance", - "env_keys", - "env_value_count", - "updated_at", - "source_label" + "mode", + "policy" ], - "title": "ProductRuntimeSettingSummary", + "title": "ProductionBackupAuthorityWriteEnvelope", "type": "object" }, - "ProductSecretBindingSummary": { + "ProductionBackupAuthorityWriteResponse": { "additionalProperties": false, "properties": { - "binding_id": { - "title": "Binding Id", - "type": "string" - }, - "binding_key": { - "title": "Binding Key", - "type": "string" - }, - "binding_type": { - "title": "Binding Type", - "type": "string" - }, - "context": { - "title": "Context", - "type": "string" - }, - "instance": { - "title": "Instance", - "type": "string" - }, - "integration": { - "title": "Integration", - "type": "string" - }, - "secret_id": { - "title": "Secret Id", - "type": "string" - }, - "status": { - "title": "Status", - "type": "string" - }, - "trust_state": { + "original_trace_id": { "anyOf": [ { - "enum": [ - "verified", - "recorded", - "stale", - "missing", - "unsupported" - ], "type": "string" }, { - "const": "disabled", - "type": "string" + "type": "null" } ], - "title": "Trust State" + "title": "Original Trace Id", + "x-launchplane-optional-response": true }, - "updated_at": { - "title": "Updated At", + "replayed": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "title": "Replayed", + "x-launchplane-optional-response": true + }, + "result": { + "$ref": "#/components/schemas/ProductionBackupAuthorityWriteResult" + }, + "status": { + "const": "ok", + "default": "ok", + "title": "Status", + "type": "string" + }, + "trace_id": { + "title": "Trace Id", "type": "string" } }, "required": [ - "binding_id", - "secret_id", - "integration", - "binding_type", - "binding_key", - "context", - "instance", - "status", - "updated_at", - "trust_state" + "trace_id", + "result" ], - "title": "ProductSecretBindingSummary", + "title": "ProductionBackupAuthorityWriteResponse", "type": "object" }, - "ProductSecretConfigRequirement": { + "ProductionBackupAuthorityWriteResult": { "additionalProperties": false, "properties": { - "binding_key": { - "title": "Binding Key", + "authority_digest": { + "title": "Authority Digest", "type": "string" }, - "context": { - "default": "", - "title": "Context", + "mode": { + "enum": [ + "dry_run", + "apply" + ], + "title": "Mode", "type": "string" }, - "instance": { - "default": "", - "title": "Instance", - "type": "string" + "policy": { + "$ref": "#/components/schemas/ProductionBackupPolicySummary" }, - "integration": { - "default": "runtime_environment", - "title": "Integration", + "schema_version": { + "default": 1, + "minimum": 1.0, + "title": "Schema Version", + "type": "integer" + }, + "status": { + "enum": [ + "would_apply", + "applied", + "replayed" + ], + "title": "Status", "type": "string" }, - "owner_input": { - "anyOf": [ - { - "$ref": "#/components/schemas/ProductOwnerSecretInput" - }, - { - "type": "null" - } - ] + "targets": { + "items": { + "$ref": "#/components/schemas/ProductionBackupTargetSummary" + }, + "title": "Targets", + "type": "array" } }, "required": [ - "binding_key" + "mode", + "status", + "authority_digest", + "policy", + "targets" ], - "title": "ProductSecretConfigRequirement", + "title": "ProductionBackupAuthorityWriteResult", "type": "object" }, - "ProductSiteOverview": { + "ProductionBackupGateRequest": { "additionalProperties": false, "properties": { - "available_actions": { - "default": [], - "items": { - "$ref": "#/components/schemas/ProductActionAvailability" - }, - "title": "Available Actions", - "type": "array" - }, - "base_driver_id": { - "default": "", - "title": "Base Driver Id", + "backup_record_id": { + "maxLength": 256, + "minLength": 1, + "title": "Backup Record Id", "type": "string" }, - "display_name": { - "title": "Display Name", + "context": { + "maxLength": 128, + "minLength": 1, + "title": "Context", "type": "string" }, - "driver_id": { - "title": "Driver Id", + "instance": { + "maxLength": 128, + "minLength": 1, + "title": "Instance", "type": "string" }, - "environments": { - "default": [], - "items": { - "$ref": "#/components/schemas/ProductEnvironmentSummary" - }, - "title": "Environments", - "type": "array" - }, - "preview": { - "$ref": "#/components/schemas/ProductPreviewSummary" - }, "product": { + "maxLength": 128, + "minLength": 1, "title": "Product", "type": "string" }, - "provenance": { - "$ref": "#/components/schemas/DataProvenance" - }, - "repository": { - "title": "Repository", + "promotion_action": { + "maxLength": 256, + "minLength": 1, + "title": "Promotion Action", "type": "string" }, "schema_version": { + "const": 1, "default": 1, - "minimum": 1.0, "title": "Schema Version", "type": "integer" }, - "trust_state": { - "enum": [ - "verified", - "recorded", - "stale", - "missing", - "unsupported" - ], - "title": "Trust State", - "type": "string" - }, - "warnings": { - "default": [], - "items": { - "type": "string" - }, - "title": "Warnings", - "type": "array" + "timeout_seconds": { + "default": 1800, + "maximum": 7200.0, + "minimum": 1.0, + "title": "Timeout Seconds", + "type": "integer" } }, "required": [ "product", - "display_name", - "repository", - "driver_id", - "preview", - "trust_state", - "provenance" + "context", + "instance", + "promotion_action", + "backup_record_id" ], - "title": "ProductSiteOverview", + "title": "ProductionBackupGateRequest", "type": "object" }, - "ProductTargetSummary": { + "ProductionBackupGateResponse": { "additionalProperties": false, "properties": { - "artifact_manifest": { - "anyOf": [ - { - "$ref": "#/components/schemas/ArtifactIdentityManifest" - }, - { - "type": "null" - } - ] - }, - "expected_runtime_identity": { - "anyOf": [ - { - "$ref": "#/components/schemas/RuntimeIdentity" - }, - { - "type": "null" - } - ] - }, - "observed_runtime_identity": { - "anyOf": [ - { - "$ref": "#/components/schemas/RuntimeIdentity" - }, - { - "type": "null" - } - ] - }, - "provider": { - "default": "", - "title": "Provider", + "backup_record_id": { + "title": "Backup Record Id", "type": "string" }, - "provider_target_type": { - "default": "", - "title": "Provider Target Type", + "error_code": { + "title": "Error Code", "type": "string" }, - "runtime_identity_detail": { - "default": "", - "title": "Runtime Identity Detail", - "type": "string" + "evidence": { + "additionalProperties": { + "type": "string" + }, + "title": "Evidence", + "type": "object" }, - "runtime_identity_status": { - "default": "unchecked", - "enum": [ - "unchecked", - "match", - "mismatch", - "missing", - "malformed", - "unverifiable" - ], - "title": "Runtime Identity Status", + "operation_id": { + "title": "Operation Id", "type": "string" }, - "target_id_recorded": { - "default": false, - "title": "Target Id Recorded", - "type": "boolean" - }, - "target_name": { - "default": "", - "title": "Target Name", + "operation_status": { + "title": "Operation Status", "type": "string" }, - "target_type": { - "default": "", - "title": "Target Type", + "status": { + "const": "accepted", + "default": "accepted", + "title": "Status", "type": "string" }, - "trust_state": { - "default": "missing", - "enum": [ - "verified", - "recorded", - "stale", - "missing", - "unsupported" - ], - "title": "Trust State", + "trace_id": { + "title": "Trace Id", "type": "string" } }, - "title": "ProductTargetSummary", + "required": [ + "trace_id", + "operation_id", + "operation_status", + "backup_record_id", + "evidence", + "error_code" + ], + "title": "ProductionBackupGateResponse", "type": "object" }, - "ProductTopologyDomain": { + "ProductionBackupPolicyRecord": { "additionalProperties": false, "properties": { - "domain_name": { - "title": "Domain Name", + "context": { + "title": "Context", "type": "string" }, - "role": { - "default": "primary", - "enum": [ - "primary", - "alias" - ], - "title": "Role", + "effective_at": { + "title": "Effective At", "type": "string" }, - "tls_expected": { - "default": false, - "title": "Tls Expected", - "type": "boolean" - } - }, - "required": [ - "domain_name" - ], - "title": "ProductTopologyDomain", - "type": "object" - }, - "ProductTopologyIngress": { - "additionalProperties": false, - "properties": { - "endpoint_key": { + "fast_snapshot": { + "$ref": "#/components/schemas/ProductionFastSnapshotPolicy" + }, + "independent_backup": { + "$ref": "#/components/schemas/ProductionIndependentBackupPolicy" + }, + "instance": { + "title": "Instance", + "type": "string" + }, + "policy_digest": { "default": "", - "title": "Endpoint Key", + "title": "Policy Digest", "type": "string" }, - "path": { - "default": "unknown", - "enum": [ - "edge_to_provider", - "direct_to_provider", - "none", - "unknown" - ], - "title": "Path", + "policy_id": { + "default": "", + "title": "Policy Id", "type": "string" }, - "provenance": { - "$ref": "#/components/schemas/DataProvenance" + "policy_revision": { + "minimum": 1.0, + "title": "Policy Revision", + "type": "integer" }, - "provider": { + "product": { + "title": "Product", + "type": "string" + }, + "promotion_action": { + "title": "Promotion Action", + "type": "string" + }, + "reason": { + "title": "Reason", + "type": "string" + }, + "record_id": { "default": "", - "title": "Provider", + "title": "Record Id", "type": "string" }, - "termination_kind": { + "review_after": { + "title": "Review After", + "type": "string" + }, + "schema_version": { + "default": 1, + "minimum": 1.0, + "title": "Schema Version", + "type": "integer" + }, + "source": { + "title": "Source", + "type": "string" + }, + "status": { + "default": "active", + "enum": [ + "active", + "superseded", + "retired" + ], + "title": "Status", + "type": "string" + }, + "supersedes_record_id": { "anyOf": [ { - "enum": [ - "edge", - "direct", - "none" - ], "type": "string" }, { - "const": "unknown", - "type": "string" + "type": "null" } ], - "default": "unknown", - "title": "Termination Kind" - }, - "trust_state": { - "default": "missing", - "enum": [ - "verified", - "recorded", - "stale", - "missing", - "unsupported" - ], - "title": "Trust State", - "type": "string" + "title": "Supersedes Record Id" } }, - "title": "ProductTopologyIngress", + "required": [ + "product", + "context", + "instance", + "promotion_action", + "policy_revision", + "fast_snapshot", + "independent_backup", + "effective_at", + "review_after", + "source", + "reason" + ], + "title": "ProductionBackupPolicyRecord", "type": "object" }, - "ProductTopologyPlacement": { + "ProductionBackupPolicySummary": { "additionalProperties": false, "properties": { - "provenance": { - "$ref": "#/components/schemas/DataProvenance" + "destination_target_id": { + "title": "Destination Target Id", + "type": "string" }, - "provider": { - "default": "", - "title": "Provider", + "effective_at": { + "title": "Effective At", "type": "string" }, - "provider_target_record_present": { - "default": false, - "title": "Provider Target Record Present", - "type": "boolean" + "policy_id": { + "title": "Policy Id", + "type": "string" }, - "provider_target_type": { - "default": "", - "title": "Provider Target Type", + "policy_revision": { + "minimum": 1.0, + "title": "Policy Revision", + "type": "integer" + }, + "promotion_action": { + "title": "Promotion Action", "type": "string" }, - "target_name": { - "default": "", - "title": "Target Name", + "record_id": { + "title": "Record Id", "type": "string" }, - "target_type": { - "default": "", - "title": "Target Type", + "review_after": { + "title": "Review After", "type": "string" }, - "trust_state": { - "default": "missing", + "source_target_id": { + "title": "Source Target Id", + "type": "string" + }, + "status": { "enum": [ - "verified", - "recorded", - "stale", - "missing", - "unsupported" + "active", + "superseded", + "retired" ], - "title": "Trust State", + "title": "Status", "type": "string" } }, - "title": "ProductTopologyPlacement", + "required": [ + "policy_id", + "record_id", + "policy_revision", + "status", + "promotion_action", + "source_target_id", + "destination_target_id", + "effective_at", + "review_after" + ], + "title": "ProductionBackupPolicySummary", "type": "object" }, - "ProductTopologyTlsOwnership": { + "ProductionBackupTargetRecord": { "additionalProperties": false, "properties": { - "owner": { - "anyOf": [ - { - "enum": [ - "launchplane", - "provider", - "external", - "none" - ], - "type": "string" + "destination": { + "discriminator": { + "mapping": { + "proxmox_guest": "#/components/schemas/ProxmoxGuestBackupDestinationReference", + "proxmox_storage": "#/components/schemas/ProxmoxStorageBackupDestinationReference" + }, + "propertyName": "destination_kind" + }, + "oneOf": [ + { + "$ref": "#/components/schemas/ProxmoxGuestBackupDestinationReference" }, { - "const": "unknown", - "type": "string" + "$ref": "#/components/schemas/ProxmoxStorageBackupDestinationReference" } ], - "default": "unknown", - "title": "Owner" + "title": "Destination" }, - "provenance": { - "$ref": "#/components/schemas/DataProvenance" + "effective_at": { + "title": "Effective At", + "type": "string" }, - "provider": { + "reason": { + "title": "Reason", + "type": "string" + }, + "record_id": { "default": "", - "title": "Provider", + "title": "Record Id", "type": "string" }, - "terminator": { - "default": "unknown", - "enum": [ - "edge", - "provider", - "external", - "none", - "unknown" - ], - "title": "Terminator", + "review_after": { + "title": "Review After", "type": "string" }, - "trust_state": { - "default": "missing", - "enum": [ - "verified", - "recorded", - "stale", - "missing", - "unsupported" - ], - "title": "Trust State", + "schema_version": { + "default": 1, + "minimum": 1.0, + "title": "Schema Version", + "type": "integer" + }, + "source": { + "title": "Source", "type": "string" - } - }, - "title": "ProductTopologyTlsOwnership", - "type": "object" - }, - "ProductTopologyWarning": { - "additionalProperties": false, - "properties": { - "code": { + }, + "status": { + "default": "active", "enum": [ - "missing_route_authority", - "route_authority_disabled", - "stale_route_authority", - "desired_domain_unknown", - "domain_divergence", - "provider_placement_missing", - "placement_divergence", - "external_ingress_internals_unsupported", - "ingress_ownership_unknown", - "ingress_divergence", - "tls_ownership_unknown", - "tls_ownership_divergence", - "tls_observation_missing", - "stale_tls_observation", - "tls_mismatch", - "tls_expiring", - "tls_expired", - "tls_untrusted", - "tls_unavailable", - "tls_unsupported", - "public_ingress_observation_missing", - "stale_public_ingress_observation", - "public_runtime_identity_unverified", - "public_ingress_failure" + "active", + "superseded", + "retired" ], - "title": "Code", + "title": "Status", "type": "string" }, - "detail": { - "title": "Detail", - "type": "string" + "supersedes_record_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Supersedes Record Id" }, - "domain_name": { + "target_digest": { "default": "", - "title": "Domain Name", + "title": "Target Digest", "type": "string" }, - "scope": { - "enum": [ - "authority", - "placement", - "domains", - "ingress", - "tls", - "observation" - ], - "title": "Scope", + "target_id": { + "title": "Target Id", "type": "string" }, - "severity": { - "enum": [ - "warning", - "error" - ], - "title": "Severity", - "type": "string" + "target_revision": { + "minimum": 1.0, + "title": "Target Revision", + "type": "integer" } }, "required": [ - "code", - "scope", - "severity", - "detail" + "target_id", + "target_revision", + "destination", + "effective_at", + "review_after", + "source", + "reason" ], - "title": "ProductTopologyWarning", + "title": "ProductionBackupTargetRecord", "type": "object" }, - "ProductionBackupAuthorityReadModel": { + "ProductionBackupTargetSummary": { "additionalProperties": false, "properties": { - "context": { - "title": "Context", + "destination_kind": { + "enum": [ + "proxmox_guest", + "proxmox_storage" + ], + "title": "Destination Kind", "type": "string" }, - "generated_at": { - "title": "Generated At", + "effective_at": { + "title": "Effective At", "type": "string" }, - "instance": { - "title": "Instance", + "provider_type": { + "const": "proxmox", + "title": "Provider Type", "type": "string" }, - "policy": { - "anyOf": [ - { - "$ref": "#/components/schemas/ProductionBackupPolicySummary" - }, - { - "type": "null" - } - ] - }, - "product": { - "title": "Product", + "record_id": { + "title": "Record Id", "type": "string" }, - "promotion_action": { - "title": "Promotion Action", + "review_after": { + "title": "Review After", "type": "string" }, - "ready": { - "title": "Ready", - "type": "boolean" - }, - "reason_codes": { - "default": [], - "items": { - "type": "string" - }, - "title": "Reason Codes", - "type": "array" - }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" - }, - "state": { + "status": { "enum": [ - "ready", - "missing", - "invalid", - "stale", + "active", + "superseded", "retired" ], - "title": "State", + "title": "Status", "type": "string" }, - "summary": { - "title": "Summary", + "target_id": { + "title": "Target Id", "type": "string" }, - "targets": { - "default": [], - "items": { - "$ref": "#/components/schemas/ProductionBackupTargetSummary" - }, - "title": "Targets", - "type": "array" + "target_revision": { + "minimum": 1.0, + "title": "Target Revision", + "type": "integer" } }, "required": [ - "product", - "context", - "instance", - "promotion_action", - "state", - "ready", - "summary", - "generated_at" + "target_id", + "record_id", + "target_revision", + "status", + "provider_type", + "destination_kind", + "effective_at", + "review_after" ], - "title": "ProductionBackupAuthorityReadModel", + "title": "ProductionBackupTargetSummary", "type": "object" }, - "ProductionBackupAuthorityReadResponse": { + "ProductionFastSnapshotPolicy": { "additionalProperties": false, "properties": { - "authority": { - "$ref": "#/components/schemas/ProductionBackupAuthorityReadModel" + "max_evidence_age_seconds": { + "maximum": 86400.0, + "minimum": 1.0, + "title": "Max Evidence Age Seconds", + "type": "integer" }, - "status": { - "const": "ok", - "default": "ok", - "title": "Status", + "operation": { + "const": "fast_snapshot", + "default": "fast_snapshot", + "title": "Operation", "type": "string" }, - "trace_id": { - "title": "Trace Id", + "retention_count": { + "maximum": 100.0, + "minimum": 0.0, + "title": "Retention Count", + "type": "integer" + }, + "snapshot_prefix": { + "title": "Snapshot Prefix", + "type": "string" + }, + "source_target_id": { + "title": "Source Target Id", "type": "string" } }, "required": [ - "trace_id", - "authority" + "source_target_id", + "snapshot_prefix", + "retention_count", + "max_evidence_age_seconds" ], - "title": "ProductionBackupAuthorityReadResponse", + "title": "ProductionFastSnapshotPolicy", "type": "object" }, - "ProductionBackupAuthorityStatus": { + "ProductionIndependentBackupPolicy": { "additionalProperties": false, "properties": { - "generated_at": { - "title": "Generated At", - "type": "string" - }, - "promotion_action": { - "title": "Promotion Action", + "destination_target_id": { + "title": "Destination Target Id", "type": "string" }, - "ready": { - "title": "Ready", - "type": "boolean" - }, - "reason_codes": { - "default": [], - "items": { - "type": "string" - }, - "title": "Reason Codes", - "type": "array" - }, - "schema_version": { - "default": 1, + "max_evidence_age_seconds": { + "maximum": 604800.0, "minimum": 1.0, - "title": "Schema Version", + "title": "Max Evidence Age Seconds", "type": "integer" }, - "state": { - "enum": [ - "ready", - "missing", - "invalid", - "stale", - "retired" - ], - "title": "State", + "operation": { + "const": "independent_backup", + "default": "independent_backup", + "title": "Operation", "type": "string" }, - "summary": { - "title": "Summary", + "source_target_id": { + "title": "Source Target Id", "type": "string" } }, "required": [ - "promotion_action", - "state", - "ready", - "summary", - "generated_at" + "source_target_id", + "destination_target_id", + "max_evidence_age_seconds" ], - "title": "ProductionBackupAuthorityStatus", + "title": "ProductionIndependentBackupPolicy", "type": "object" }, - "ProductionBackupAuthorityWriteEnvelope": { + "PromotionEvidenceRequest": { "additionalProperties": false, "properties": { - "expected_current_policy_record_id": { - "default": "", - "title": "Expected Current Policy Record Id", - "type": "string" - }, - "expected_current_target_record_ids": { - "additionalProperties": { - "type": "string" - }, - "title": "Expected Current Target Record Ids", - "type": "object" - }, - "mode": { - "enum": [ - "dry_run", - "apply" - ], - "title": "Mode", + "product": { + "title": "Product", "type": "string" }, - "policy": { - "$ref": "#/components/schemas/ProductionBackupPolicyRecord" - }, - "reviewed_authority_digest": { - "default": "", - "title": "Reviewed Authority Digest", - "type": "string" + "promotion": { + "$ref": "#/components/schemas/PromotionRecord-Input" }, "schema_version": { "default": 1, "minimum": 1.0, "title": "Schema Version", "type": "integer" - }, - "targets": { - "default": [], - "items": { - "$ref": "#/components/schemas/ProductionBackupTargetRecord" - }, - "title": "Targets", - "type": "array" } }, "required": [ - "mode", - "policy" + "product", + "promotion" ], - "title": "ProductionBackupAuthorityWriteEnvelope", + "title": "PromotionEvidenceRequest", "type": "object" }, - "ProductionBackupAuthorityWriteResponse": { + "PromotionRecord-Input": { "additionalProperties": false, "properties": { - "original_trace_id": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Original Trace Id", - "x-launchplane-optional-response": true + "artifact_identity": { + "$ref": "#/components/schemas/ArtifactIdentityReference" }, - "replayed": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Replayed", - "x-launchplane-optional-response": true + "backup_gate": { + "$ref": "#/components/schemas/BackupGateEvidence" }, - "result": { - "$ref": "#/components/schemas/ProductionBackupAuthorityWriteResult" + "backup_record_id": { + "default": "", + "title": "Backup Record Id", + "type": "string" }, - "status": { - "const": "ok", - "default": "ok", - "title": "Status", + "context": { + "title": "Context", "type": "string" }, - "trace_id": { - "title": "Trace Id", + "deploy": { + "$ref": "#/components/schemas/DeploymentEvidence-Input" + }, + "deployment_record_id": { + "default": "", + "title": "Deployment Record Id", "type": "string" - } - }, - "required": [ - "trace_id", - "result" - ], - "title": "ProductionBackupAuthorityWriteResponse", - "type": "object" - }, - "ProductionBackupAuthorityWriteResult": { - "additionalProperties": false, - "properties": { - "authority_digest": { - "title": "Authority Digest", + }, + "destination_health": { + "$ref": "#/components/schemas/HealthcheckEvidence" + }, + "from_instance": { + "title": "From Instance", "type": "string" }, - "mode": { - "enum": [ - "dry_run", - "apply" - ], - "title": "Mode", + "post_deploy_update": { + "$ref": "#/components/schemas/PostDeployUpdateEvidence" + }, + "record_id": { + "title": "Record Id", "type": "string" }, - "policy": { - "$ref": "#/components/schemas/ProductionBackupPolicySummary" + "rollback": { + "$ref": "#/components/schemas/RollbackExecutionEvidence" + }, + "rollback_health": { + "$ref": "#/components/schemas/HealthcheckEvidence" }, "schema_version": { "default": 1, @@ -34361,119 +32884,105 @@ "title": "Schema Version", "type": "integer" }, - "status": { - "enum": [ - "would_apply", - "applied", - "replayed" - ], - "title": "Status", - "type": "string" + "source_health": { + "$ref": "#/components/schemas/HealthcheckEvidence" }, - "targets": { - "items": { - "$ref": "#/components/schemas/ProductionBackupTargetSummary" - }, - "title": "Targets", - "type": "array" + "to_instance": { + "title": "To Instance", + "type": "string" } }, "required": [ - "mode", - "status", - "authority_digest", - "policy", - "targets" + "record_id", + "artifact_identity", + "context", + "from_instance", + "to_instance", + "deploy" ], - "title": "ProductionBackupAuthorityWriteResult", + "title": "PromotionRecord", "type": "object" }, - "ProductionBackupGateRequest": { + "PromotionRecord-Output": { "additionalProperties": false, "properties": { + "artifact_identity": { + "$ref": "#/components/schemas/ArtifactIdentityReference" + }, + "backup_gate": { + "$ref": "#/components/schemas/BackupGateEvidence" + }, "backup_record_id": { - "maxLength": 256, - "minLength": 1, + "default": "", "title": "Backup Record Id", "type": "string" }, "context": { - "maxLength": 128, - "minLength": 1, "title": "Context", "type": "string" }, - "instance": { - "maxLength": 128, - "minLength": 1, - "title": "Instance", + "deploy": { + "$ref": "#/components/schemas/DeploymentEvidence-Output" + }, + "deployment_record_id": { + "default": "", + "title": "Deployment Record Id", "type": "string" }, - "product": { - "maxLength": 128, - "minLength": 1, - "title": "Product", + "destination_health": { + "$ref": "#/components/schemas/HealthcheckEvidence" + }, + "from_instance": { + "title": "From Instance", "type": "string" }, - "promotion_action": { - "maxLength": 256, - "minLength": 1, - "title": "Promotion Action", + "post_deploy_update": { + "$ref": "#/components/schemas/PostDeployUpdateEvidence" + }, + "record_id": { + "title": "Record Id", "type": "string" }, + "rollback": { + "$ref": "#/components/schemas/RollbackExecutionEvidence" + }, + "rollback_health": { + "$ref": "#/components/schemas/HealthcheckEvidence" + }, "schema_version": { - "const": 1, "default": 1, + "minimum": 1.0, "title": "Schema Version", "type": "integer" }, - "timeout_seconds": { - "default": 1800, - "maximum": 7200.0, - "minimum": 1.0, - "title": "Timeout Seconds", - "type": "integer" + "source_health": { + "$ref": "#/components/schemas/HealthcheckEvidence" + }, + "to_instance": { + "title": "To Instance", + "type": "string" } }, "required": [ - "product", + "record_id", + "artifact_identity", "context", - "instance", - "promotion_action", - "backup_record_id" + "from_instance", + "to_instance", + "deploy" ], - "title": "ProductionBackupGateRequest", + "title": "PromotionRecord", "type": "object" }, - "ProductionBackupGateResponse": { + "PromotionRecordResponse": { "additionalProperties": false, "properties": { - "backup_record_id": { - "title": "Backup Record Id", - "type": "string" - }, - "error_code": { - "title": "Error Code", - "type": "string" - }, - "evidence": { - "additionalProperties": { - "type": "string" - }, - "title": "Evidence", - "type": "object" - }, - "operation_id": { - "title": "Operation Id", - "type": "string" - }, - "operation_status": { - "title": "Operation Status", - "type": "string" + "record": { + "$ref": "#/components/schemas/PromotionRecord-Output" }, "status": { - "const": "accepted", - "default": "accepted", + "const": "ok", + "default": "ok", "title": "Status", "type": "string" }, @@ -34484,216 +32993,162 @@ }, "required": [ "trace_id", - "operation_id", - "operation_status", - "backup_record_id", - "evidence", - "error_code" + "record" ], - "title": "ProductionBackupGateResponse", + "title": "PromotionRecordResponse", "type": "object" }, - "ProductionBackupPolicyRecord": { + "ProtectedArtifactEntry": { "additionalProperties": false, "properties": { + "anchor_pr_number": { + "default": 0, + "title": "Anchor Pr Number", + "type": "integer" + }, + "anchor_repo": { + "default": "", + "title": "Anchor Repo", + "type": "string" + }, + "artifact_id": { + "default": "", + "title": "Artifact Id", + "type": "string" + }, "context": { "title": "Context", "type": "string" }, - "effective_at": { - "title": "Effective At", + "environment_kind": { + "enum": [ + "stable", + "preview" + ], + "title": "Environment Kind", "type": "string" }, - "fast_snapshot": { - "$ref": "#/components/schemas/ProductionFastSnapshotPolicy" + "image_digest": { + "default": "", + "title": "Image Digest", + "type": "string" }, - "independent_backup": { - "$ref": "#/components/schemas/ProductionIndependentBackupPolicy" + "image_references": { + "default": [], + "items": { + "type": "string" + }, + "title": "Image References", + "type": "array" + }, + "image_repository": { + "default": "", + "title": "Image Repository", + "type": "string" }, "instance": { + "default": "", "title": "Instance", "type": "string" }, - "policy_digest": { + "preview_generation_id": { "default": "", - "title": "Policy Digest", + "title": "Preview Generation Id", "type": "string" }, - "policy_id": { + "preview_id": { "default": "", - "title": "Policy Id", + "title": "Preview Id", "type": "string" }, - "policy_revision": { - "minimum": 1.0, - "title": "Policy Revision", - "type": "integer" - }, "product": { + "default": "", "title": "Product", "type": "string" }, - "promotion_action": { - "title": "Promotion Action", - "type": "string" - }, "reason": { + "enum": [ + "stable-inventory", + "release-tuple", + "active-preview-generation", + "active-preview-feedback" + ], "title": "Reason", "type": "string" }, - "record_id": { - "default": "", - "title": "Record Id", - "type": "string" - }, - "review_after": { - "title": "Review After", - "type": "string" - }, "schema_version": { "default": 1, "minimum": 1.0, "title": "Schema Version", "type": "integer" }, - "source": { - "title": "Source", + "source_git_ref": { + "default": "", + "title": "Source Git Ref", "type": "string" }, - "status": { - "default": "active", - "enum": [ - "active", - "superseded", - "retired" - ], - "title": "Status", + "source_record_id": { + "title": "Source Record Id", "type": "string" }, - "supersedes_record_id": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Supersedes Record Id" + "source_record_type": { + "title": "Source Record Type", + "type": "string" } }, "required": [ - "product", "context", - "instance", - "promotion_action", - "policy_revision", - "fast_snapshot", - "independent_backup", - "effective_at", - "review_after", - "source", - "reason" + "environment_kind", + "reason", + "source_record_type", + "source_record_id" ], - "title": "ProductionBackupPolicyRecord", + "title": "ProtectedArtifactEntry", "type": "object" }, - "ProductionBackupPolicySummary": { + "ProtectedArtifactSet": { "additionalProperties": false, "properties": { - "destination_target_id": { - "title": "Destination Target Id", - "type": "string" - }, - "effective_at": { - "title": "Effective At", - "type": "string" - }, - "policy_id": { - "title": "Policy Id", - "type": "string" - }, - "policy_revision": { - "minimum": 1.0, - "title": "Policy Revision", - "type": "integer" - }, - "promotion_action": { - "title": "Promotion Action", - "type": "string" - }, - "record_id": { - "title": "Record Id", - "type": "string" - }, - "review_after": { - "title": "Review After", - "type": "string" + "artifact_ids": { + "default": [], + "items": { + "type": "string" + }, + "title": "Artifact Ids", + "type": "array" }, - "source_target_id": { - "title": "Source Target Id", + "context": { + "default": "", + "title": "Context", "type": "string" }, - "status": { - "enum": [ - "active", - "superseded", - "retired" - ], - "title": "Status", - "type": "string" - } - }, - "required": [ - "policy_id", - "record_id", - "policy_revision", - "status", - "promotion_action", - "source_target_id", - "destination_target_id", - "effective_at", - "review_after" - ], - "title": "ProductionBackupPolicySummary", - "type": "object" - }, - "ProductionBackupTargetRecord": { - "additionalProperties": false, - "properties": { - "destination": { - "discriminator": { - "mapping": { - "proxmox_guest": "#/components/schemas/ProxmoxGuestBackupDestinationReference", - "proxmox_storage": "#/components/schemas/ProxmoxStorageBackupDestinationReference" - }, - "propertyName": "destination_kind" + "entries": { + "default": [], + "items": { + "$ref": "#/components/schemas/ProtectedArtifactEntry" }, - "oneOf": [ - { - "$ref": "#/components/schemas/ProxmoxGuestBackupDestinationReference" - }, - { - "$ref": "#/components/schemas/ProxmoxStorageBackupDestinationReference" - } - ], - "title": "Destination" + "title": "Entries", + "type": "array" }, - "effective_at": { - "title": "Effective At", - "type": "string" + "image_digests": { + "default": [], + "items": { + "type": "string" + }, + "title": "Image Digests", + "type": "array" }, - "reason": { - "title": "Reason", - "type": "string" + "image_references": { + "default": [], + "items": { + "type": "string" + }, + "title": "Image References", + "type": "array" }, - "record_id": { + "product": { "default": "", - "title": "Record Id", - "type": "string" - }, - "review_after": { - "title": "Review After", + "title": "Product", "type": "string" }, "schema_version": { @@ -34702,637 +33157,105 @@ "title": "Schema Version", "type": "integer" }, - "source": { - "title": "Source", - "type": "string" + "warnings": { + "default": [], + "items": { + "type": "string" + }, + "title": "Warnings", + "type": "array" + } + }, + "title": "ProtectedArtifactSet", + "type": "object" + }, + "ProtectedArtifactsResponse": { + "additionalProperties": false, + "properties": { + "protected_artifacts": { + "$ref": "#/components/schemas/ProtectedArtifactSet" }, "status": { - "default": "active", - "enum": [ - "active", - "superseded", - "retired" - ], + "const": "ok", + "default": "ok", "title": "Status", "type": "string" }, - "supersedes_record_id": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Supersedes Record Id" - }, - "target_digest": { - "default": "", - "title": "Target Digest", - "type": "string" - }, - "target_id": { - "title": "Target Id", + "trace_id": { + "title": "Trace Id", "type": "string" - }, - "target_revision": { - "minimum": 1.0, - "title": "Target Revision", - "type": "integer" } }, "required": [ - "target_id", - "target_revision", - "destination", - "effective_at", - "review_after", - "source", - "reason" + "trace_id", + "protected_artifacts" ], - "title": "ProductionBackupTargetRecord", + "title": "ProtectedArtifactsResponse", "type": "object" }, - "ProductionBackupTargetSummary": { + "ProviderCodeScanningToolExpectationV1": { "additionalProperties": false, "properties": { - "destination_kind": { + "alerts_threshold": { "enum": [ - "proxmox_guest", - "proxmox_storage" + "none", + "errors", + "errors_and_warnings", + "all" ], - "title": "Destination Kind", - "type": "string" - }, - "effective_at": { - "title": "Effective At", - "type": "string" - }, - "provider_type": { - "const": "proxmox", - "title": "Provider Type", - "type": "string" - }, - "record_id": { - "title": "Record Id", - "type": "string" - }, - "review_after": { - "title": "Review After", + "title": "Alerts Threshold", "type": "string" }, - "status": { + "security_alerts_threshold": { "enum": [ - "active", - "superseded", - "retired" + "none", + "critical", + "high_or_higher", + "medium_or_higher", + "all" ], - "title": "Status", + "title": "Security Alerts Threshold", "type": "string" }, - "target_id": { - "title": "Target Id", + "tool": { + "maxLength": 512, + "minLength": 1, + "title": "Tool", "type": "string" - }, - "target_revision": { - "minimum": 1.0, - "title": "Target Revision", - "type": "integer" } }, "required": [ - "target_id", - "record_id", - "target_revision", - "status", - "provider_type", - "destination_kind", - "effective_at", - "review_after" + "tool", + "alerts_threshold", + "security_alerts_threshold" ], - "title": "ProductionBackupTargetSummary", + "title": "ProviderCodeScanningToolExpectationV1", "type": "object" }, - "ProductionFastSnapshotPolicy": { + "ProviderDeliveryProtectionExpectationV1": { "additionalProperties": false, "properties": { - "max_evidence_age_seconds": { - "maximum": 86400.0, - "minimum": 1.0, - "title": "Max Evidence Age Seconds", - "type": "integer" + "allowed_merge_methods": { + "items": { + "enum": [ + "merge", + "squash", + "rebase" + ], + "type": "string" + }, + "maxItems": 3, + "minItems": 1, + "title": "Allowed Merge Methods", + "type": "array" }, - "operation": { - "const": "fast_snapshot", - "default": "fast_snapshot", - "title": "Operation", - "type": "string" - }, - "retention_count": { - "maximum": 100.0, - "minimum": 0.0, - "title": "Retention Count", - "type": "integer" - }, - "snapshot_prefix": { - "title": "Snapshot Prefix", - "type": "string" - }, - "source_target_id": { - "title": "Source Target Id", - "type": "string" - } - }, - "required": [ - "source_target_id", - "snapshot_prefix", - "retention_count", - "max_evidence_age_seconds" - ], - "title": "ProductionFastSnapshotPolicy", - "type": "object" - }, - "ProductionIndependentBackupPolicy": { - "additionalProperties": false, - "properties": { - "destination_target_id": { - "title": "Destination Target Id", - "type": "string" - }, - "max_evidence_age_seconds": { - "maximum": 604800.0, - "minimum": 1.0, - "title": "Max Evidence Age Seconds", - "type": "integer" - }, - "operation": { - "const": "independent_backup", - "default": "independent_backup", - "title": "Operation", - "type": "string" - }, - "source_target_id": { - "title": "Source Target Id", - "type": "string" - } - }, - "required": [ - "source_target_id", - "destination_target_id", - "max_evidence_age_seconds" - ], - "title": "ProductionIndependentBackupPolicy", - "type": "object" - }, - "PromotionEvidenceRequest": { - "additionalProperties": false, - "properties": { - "product": { - "title": "Product", - "type": "string" - }, - "promotion": { - "$ref": "#/components/schemas/PromotionRecord-Input" - }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" - } - }, - "required": [ - "product", - "promotion" - ], - "title": "PromotionEvidenceRequest", - "type": "object" - }, - "PromotionRecord-Input": { - "additionalProperties": false, - "properties": { - "artifact_identity": { - "$ref": "#/components/schemas/ArtifactIdentityReference" - }, - "backup_gate": { - "$ref": "#/components/schemas/BackupGateEvidence" - }, - "backup_record_id": { - "default": "", - "title": "Backup Record Id", - "type": "string" - }, - "context": { - "title": "Context", - "type": "string" - }, - "deploy": { - "$ref": "#/components/schemas/DeploymentEvidence-Input" - }, - "deployment_record_id": { - "default": "", - "title": "Deployment Record Id", - "type": "string" - }, - "destination_health": { - "$ref": "#/components/schemas/HealthcheckEvidence" - }, - "from_instance": { - "title": "From Instance", - "type": "string" - }, - "post_deploy_update": { - "$ref": "#/components/schemas/PostDeployUpdateEvidence" - }, - "record_id": { - "title": "Record Id", - "type": "string" - }, - "rollback": { - "$ref": "#/components/schemas/RollbackExecutionEvidence" - }, - "rollback_health": { - "$ref": "#/components/schemas/HealthcheckEvidence" - }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" - }, - "source_health": { - "$ref": "#/components/schemas/HealthcheckEvidence" - }, - "to_instance": { - "title": "To Instance", - "type": "string" - } - }, - "required": [ - "record_id", - "artifact_identity", - "context", - "from_instance", - "to_instance", - "deploy" - ], - "title": "PromotionRecord", - "type": "object" - }, - "PromotionRecord-Output": { - "additionalProperties": false, - "properties": { - "artifact_identity": { - "$ref": "#/components/schemas/ArtifactIdentityReference" - }, - "backup_gate": { - "$ref": "#/components/schemas/BackupGateEvidence" - }, - "backup_record_id": { - "default": "", - "title": "Backup Record Id", - "type": "string" - }, - "context": { - "title": "Context", - "type": "string" - }, - "deploy": { - "$ref": "#/components/schemas/DeploymentEvidence-Output" - }, - "deployment_record_id": { - "default": "", - "title": "Deployment Record Id", - "type": "string" - }, - "destination_health": { - "$ref": "#/components/schemas/HealthcheckEvidence" - }, - "from_instance": { - "title": "From Instance", - "type": "string" - }, - "post_deploy_update": { - "$ref": "#/components/schemas/PostDeployUpdateEvidence" - }, - "record_id": { - "title": "Record Id", - "type": "string" - }, - "rollback": { - "$ref": "#/components/schemas/RollbackExecutionEvidence" - }, - "rollback_health": { - "$ref": "#/components/schemas/HealthcheckEvidence" - }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" - }, - "source_health": { - "$ref": "#/components/schemas/HealthcheckEvidence" - }, - "to_instance": { - "title": "To Instance", - "type": "string" - } - }, - "required": [ - "record_id", - "artifact_identity", - "context", - "from_instance", - "to_instance", - "deploy" - ], - "title": "PromotionRecord", - "type": "object" - }, - "PromotionRecordResponse": { - "additionalProperties": false, - "properties": { - "record": { - "$ref": "#/components/schemas/PromotionRecord-Output" - }, - "status": { - "const": "ok", - "default": "ok", - "title": "Status", - "type": "string" - }, - "trace_id": { - "title": "Trace Id", - "type": "string" - } - }, - "required": [ - "trace_id", - "record" - ], - "title": "PromotionRecordResponse", - "type": "object" - }, - "ProtectedArtifactEntry": { - "additionalProperties": false, - "properties": { - "anchor_pr_number": { - "default": 0, - "title": "Anchor Pr Number", - "type": "integer" - }, - "anchor_repo": { - "default": "", - "title": "Anchor Repo", - "type": "string" - }, - "artifact_id": { - "default": "", - "title": "Artifact Id", - "type": "string" - }, - "context": { - "title": "Context", - "type": "string" - }, - "environment_kind": { - "enum": [ - "stable", - "preview" - ], - "title": "Environment Kind", - "type": "string" - }, - "image_digest": { - "default": "", - "title": "Image Digest", - "type": "string" - }, - "image_references": { - "default": [], - "items": { - "type": "string" - }, - "title": "Image References", - "type": "array" - }, - "image_repository": { - "default": "", - "title": "Image Repository", - "type": "string" - }, - "instance": { - "default": "", - "title": "Instance", - "type": "string" - }, - "preview_generation_id": { - "default": "", - "title": "Preview Generation Id", - "type": "string" - }, - "preview_id": { - "default": "", - "title": "Preview Id", - "type": "string" - }, - "product": { - "default": "", - "title": "Product", - "type": "string" - }, - "reason": { - "enum": [ - "stable-inventory", - "release-tuple", - "active-preview-generation", - "active-preview-feedback" - ], - "title": "Reason", - "type": "string" - }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" - }, - "source_git_ref": { - "default": "", - "title": "Source Git Ref", - "type": "string" - }, - "source_record_id": { - "title": "Source Record Id", - "type": "string" - }, - "source_record_type": { - "title": "Source Record Type", - "type": "string" - } - }, - "required": [ - "context", - "environment_kind", - "reason", - "source_record_type", - "source_record_id" - ], - "title": "ProtectedArtifactEntry", - "type": "object" - }, - "ProtectedArtifactSet": { - "additionalProperties": false, - "properties": { - "artifact_ids": { - "default": [], - "items": { - "type": "string" - }, - "title": "Artifact Ids", - "type": "array" - }, - "context": { - "default": "", - "title": "Context", - "type": "string" - }, - "entries": { - "default": [], - "items": { - "$ref": "#/components/schemas/ProtectedArtifactEntry" - }, - "title": "Entries", - "type": "array" - }, - "image_digests": { - "default": [], - "items": { - "type": "string" - }, - "title": "Image Digests", - "type": "array" - }, - "image_references": { - "default": [], - "items": { - "type": "string" - }, - "title": "Image References", - "type": "array" - }, - "product": { - "default": "", - "title": "Product", - "type": "string" - }, - "schema_version": { - "default": 1, - "minimum": 1.0, - "title": "Schema Version", - "type": "integer" - }, - "warnings": { - "default": [], - "items": { - "type": "string" - }, - "title": "Warnings", - "type": "array" - } - }, - "title": "ProtectedArtifactSet", - "type": "object" - }, - "ProtectedArtifactsResponse": { - "additionalProperties": false, - "properties": { - "protected_artifacts": { - "$ref": "#/components/schemas/ProtectedArtifactSet" - }, - "status": { - "const": "ok", - "default": "ok", - "title": "Status", - "type": "string" - }, - "trace_id": { - "title": "Trace Id", - "type": "string" - } - }, - "required": [ - "trace_id", - "protected_artifacts" - ], - "title": "ProtectedArtifactsResponse", - "type": "object" - }, - "ProviderCodeScanningToolExpectationV1": { - "additionalProperties": false, - "properties": { - "alerts_threshold": { - "enum": [ - "none", - "errors", - "errors_and_warnings", - "all" - ], - "title": "Alerts Threshold", - "type": "string" - }, - "security_alerts_threshold": { - "enum": [ - "none", - "critical", - "high_or_higher", - "medium_or_higher", - "all" - ], - "title": "Security Alerts Threshold", - "type": "string" - }, - "tool": { - "maxLength": 512, - "minLength": 1, - "title": "Tool", - "type": "string" - } - }, - "required": [ - "tool", - "alerts_threshold", - "security_alerts_threshold" - ], - "title": "ProviderCodeScanningToolExpectationV1", - "type": "object" - }, - "ProviderDeliveryProtectionExpectationV1": { - "additionalProperties": false, - "properties": { - "allowed_merge_methods": { - "items": { - "enum": [ - "merge", - "squash", - "rebase" - ], - "type": "string" - }, - "maxItems": 3, - "minItems": 1, - "title": "Allowed Merge Methods", - "type": "array" - }, - "code_scanning_tools": { - "items": { - "$ref": "#/components/schemas/ProviderCodeScanningToolExpectationV1" - }, - "maxItems": 32, - "title": "Code Scanning Tools", - "type": "array" + "code_scanning_tools": { + "items": { + "$ref": "#/components/schemas/ProviderCodeScanningToolExpectationV1" + }, + "maxItems": 32, + "title": "Code Scanning Tools", + "type": "array" }, "pull_request": { "anyOf": [ @@ -37197,6 +35120,78 @@ "title": "RepositoryInventoryRecord", "type": "object" }, + "RepositoryTarget": { + "additionalProperties": false, + "properties": { + "head_sha": { + "title": "Head Sha", + "type": "string" + }, + "pull_request_number": { + "minimum": 1.0, + "title": "Pull Request Number", + "type": "integer" + }, + "repository": { + "title": "Repository", + "type": "string" + }, + "repository_id": { + "title": "Repository Id", + "type": "string" + }, + "repository_owner_id": { + "title": "Repository Owner Id", + "type": "string" + }, + "schema_version": { + "default": 1, + "minimum": 1.0, + "title": "Schema Version", + "type": "integer" + }, + "tree_sha": { + "title": "Tree Sha", + "type": "string" + } + }, + "required": [ + "repository_id", + "repository_owner_id", + "repository", + "pull_request_number", + "head_sha", + "tree_sha" + ], + "title": "RepositoryTarget", + "type": "object" + }, + "RepositoryTargetReference": { + "additionalProperties": false, + "properties": { + "pull_request_number": { + "minimum": 1.0, + "title": "Pull Request Number", + "type": "integer" + }, + "repository": { + "title": "Repository", + "type": "string" + }, + "schema_version": { + "default": 1, + "minimum": 1.0, + "title": "Schema Version", + "type": "integer" + } + }, + "required": [ + "repository", + "pull_request_number" + ], + "title": "RepositoryTargetReference", + "type": "object" + }, "ResolvedTargetEvidence": { "additionalProperties": false, "properties": { @@ -49595,10 +47590,21 @@ "summary": "Revoke one-time solo-administration confirmation evidence" } }, - "/v1/change-impact/evaluation": { - "post": { - "operationId": "evaluate_change_impact", + "/v1/contexts/{context}/driver-view": { + "get": { + "operationId": "read_driver_context_view", "parameters": [ + { + "in": "path", + "name": "context", + "required": true, + "schema": { + "minLength": 1, + "pattern": "^\\S+$", + "title": "Context", + "type": "string" + } + }, { "in": "header", "name": "Authorization", @@ -49608,50 +47614,30 @@ "title": "Authorization", "type": "string" } + }, + { + "in": "header", + "name": "Cookie", + "required": false, + "schema": { + "default": "", + "title": "Cookie", + "type": "string" + } } ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ChangeImpactEvaluationRequest" - } - } - }, - "required": true - }, "responses": { "200": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ChangeImpactEvaluationResponse" + "$ref": "#/components/schemas/DriverContextViewResponse" } } }, "description": "Successful Response" }, - "403": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LaunchplaneErrorResponse" - } - } - }, - "description": "Forbidden" - }, - "409": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LaunchplaneErrorResponse" - } - } - }, - "description": "Conflict" - }, - "503": { + "401": { "content": { "application/json": { "schema": { @@ -49659,50 +47645,7 @@ } } }, - "description": "Service Unavailable" - } - }, - "summary": "Evaluate", - "tags": [ - "change-impact" - ] - } - }, - "/v1/change-impact/policies/apply": { - "post": { - "operationId": "apply_change_impact_policy", - "parameters": [ - { - "in": "header", - "name": "Authorization", - "required": false, - "schema": { - "default": "", - "title": "Authorization", - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ChangeImpactPolicyApplyEnvelope" - } - } - }, - "required": true - }, - "responses": { - "202": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ChangeImpactPolicyApplyResponse" - } - } - }, - "description": "Successful Response" + "description": "Unauthorized" }, "403": { "content": { @@ -49735,22 +47678,32 @@ "description": "Service Unavailable" } }, - "summary": "Apply Policy", - "tags": [ - "change-impact" - ] + "summary": "Read Launchplane driver view for a context" } }, - "/v1/change-impact/policy": { + "/v1/contexts/{context}/instances/{instance}/driver-view": { "get": { - "operationId": "read_change_impact_policy", + "operationId": "read_driver_instance_view", "parameters": [ { - "in": "query", - "name": "repository_id", + "in": "path", + "name": "context", "required": true, "schema": { - "title": "Repository Id", + "minLength": 1, + "pattern": "^\\S+$", + "title": "Context", + "type": "string" + } + }, + { + "in": "path", + "name": "instance", + "required": true, + "schema": { + "minLength": 1, + "pattern": "^\\S+$", + "title": "Instance", "type": "string" } }, @@ -49780,12 +47733,22 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ChangeImpactPolicyReadResponse" + "$ref": "#/components/schemas/DriverContextViewResponse" } } }, "description": "Successful Response" }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LaunchplaneErrorResponse" + } + } + }, + "description": "Unauthorized" + }, "403": { "content": { "application/json": { @@ -49817,15 +47780,12 @@ "description": "Service Unavailable" } }, - "summary": "Read Policy", - "tags": [ - "change-impact" - ] + "summary": "Read Launchplane driver view for one context instance" } }, - "/v1/contexts/{context}/driver-view": { + "/v1/contexts/{context}/instances/{instance}/logs": { "get": { - "operationId": "read_driver_context_view", + "operationId": "read_tracked_target_logs", "parameters": [ { "in": "path", @@ -49835,260 +47795,67 @@ "minLength": 1, "pattern": "^\\S+$", "title": "Context", - "type": "string" - } - }, - { - "in": "header", - "name": "Authorization", - "required": false, - "schema": { - "default": "", - "title": "Authorization", - "type": "string" - } - }, - { - "in": "header", - "name": "Cookie", - "required": false, - "schema": { - "default": "", - "title": "Cookie", - "type": "string" - } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DriverContextViewResponse" - } - } - }, - "description": "Successful Response" - }, - "401": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LaunchplaneErrorResponse" - } - } - }, - "description": "Unauthorized" - }, - "403": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LaunchplaneErrorResponse" - } - } - }, - "description": "Forbidden" - }, - "409": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LaunchplaneErrorResponse" - } - } - }, - "description": "Conflict" - }, - "503": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LaunchplaneErrorResponse" - } - } - }, - "description": "Service Unavailable" - } - }, - "summary": "Read Launchplane driver view for a context" - } - }, - "/v1/contexts/{context}/instances/{instance}/driver-view": { - "get": { - "operationId": "read_driver_instance_view", - "parameters": [ - { - "in": "path", - "name": "context", - "required": true, - "schema": { - "minLength": 1, - "pattern": "^\\S+$", - "title": "Context", - "type": "string" - } - }, - { - "in": "path", - "name": "instance", - "required": true, - "schema": { - "minLength": 1, - "pattern": "^\\S+$", - "title": "Instance", - "type": "string" - } - }, - { - "in": "header", - "name": "Authorization", - "required": false, - "schema": { - "default": "", - "title": "Authorization", - "type": "string" - } - }, - { - "in": "header", - "name": "Cookie", - "required": false, - "schema": { - "default": "", - "title": "Cookie", - "type": "string" - } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DriverContextViewResponse" - } - } - }, - "description": "Successful Response" - }, - "401": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LaunchplaneErrorResponse" - } - } - }, - "description": "Unauthorized" - }, - "403": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LaunchplaneErrorResponse" - } - } - }, - "description": "Forbidden" - }, - "409": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LaunchplaneErrorResponse" - } - } - }, - "description": "Conflict" - }, - "503": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LaunchplaneErrorResponse" - } - } - }, - "description": "Service Unavailable" - } - }, - "summary": "Read Launchplane driver view for one context instance" - } - }, - "/v1/contexts/{context}/instances/{instance}/logs": { - "get": { - "operationId": "read_tracked_target_logs", - "parameters": [ - { - "in": "path", - "name": "context", - "required": true, - "schema": { - "minLength": 1, - "pattern": "^\\S+$", - "title": "Context", - "type": "string" - } - }, - { - "in": "path", - "name": "instance", - "required": true, - "schema": { - "minLength": 1, - "pattern": "^\\S+$", - "title": "Instance", - "type": "string" - } - }, - { - "in": "query", - "name": "lines", - "required": false, - "schema": { - "default": "200", - "title": "Lines", - "type": "string" - } - }, - { - "in": "query", - "name": "since", - "required": false, - "schema": { - "default": "all", - "title": "Since", - "type": "string" - } - }, - { - "in": "query", - "name": "search", - "required": false, - "schema": { - "default": "", - "title": "Search", - "type": "string" - } - }, - { - "in": "query", - "name": "service", - "required": false, - "schema": { - "default": "", - "title": "Service", - "type": "string" - } - }, - { - "in": "query", - "name": "source", - "required": false, - "schema": { - "default": "runtime", - "title": "Source", + "type": "string" + } + }, + { + "in": "path", + "name": "instance", + "required": true, + "schema": { + "minLength": 1, + "pattern": "^\\S+$", + "title": "Instance", + "type": "string" + } + }, + { + "in": "query", + "name": "lines", + "required": false, + "schema": { + "default": "200", + "title": "Lines", + "type": "string" + } + }, + { + "in": "query", + "name": "since", + "required": false, + "schema": { + "default": "all", + "title": "Since", + "type": "string" + } + }, + { + "in": "query", + "name": "search", + "required": false, + "schema": { + "default": "", + "title": "Search", + "type": "string" + } + }, + { + "in": "query", + "name": "service", + "required": false, + "schema": { + "default": "", + "title": "Service", + "type": "string" + } + }, + { + "in": "query", + "name": "source", + "required": false, + "schema": { + "default": "runtime", + "title": "Source", "type": "string" } }, @@ -69373,7 +67140,7 @@ }, "/v1/manager-preview-approval/github-webhook": { "post": { - "operationId": "handle_manager_preview_approval_github_webhook", + "operationId": "handle_trusted_maintenance_github_webhook", "parameters": [ { "in": "header", @@ -69476,125 +67243,23 @@ "description": "Service Unavailable" } }, - "summary": "Handle manager preview approval GitHub webhook" + "summary": "Capture signed trusted-maintenance GitHub evidence" } }, - "/v1/manager-preview-approval/reconcile": { + "/v1/merge-train/policies/import": { "post": { - "operationId": "reconcile_manager_preview_approval", + "operationId": "import_merge_train_policy", "parameters": [ - { - "in": "header", - "name": "Authorization", - "required": false, - "schema": { - "default": "", - "title": "Authorization", - "type": "string" - } - }, - { - "in": "header", - "name": "Cookie", - "required": false, - "schema": { - "default": "", - "title": "Cookie", - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ManagerPreviewApprovalReconcileEnvelope" - } - } - }, - "required": true - }, - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "additionalProperties": true, - "title": "Response Reconcile Manager Preview Approval", - "type": "object" - } - } - }, - "description": "Successful Response" - }, - "403": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LaunchplaneErrorResponse" - } - } - }, - "description": "Forbidden" - }, - "404": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LaunchplaneErrorResponse" - } - } - }, - "description": "Not Found" - }, - "409": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LaunchplaneErrorResponse" - } - } - }, - "description": "Conflict" - }, - "413": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LaunchplaneErrorResponse" - } - } - }, - "description": "Content Too Large" - }, - "503": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LaunchplaneErrorResponse" - } - } - }, - "description": "Service Unavailable" - } - }, - "summary": "Reconcile manager preview approval projection" - } - }, - "/v1/merge-train/policies/import": { - "post": { - "operationId": "import_merge_train_policy", - "parameters": [ - { - "in": "header", - "name": "Idempotency-Key", - "required": false, - "schema": { - "default": "", - "title": "Idempotency-Key", - "type": "string" - } - }, + { + "in": "header", + "name": "Idempotency-Key", + "required": false, + "schema": { + "default": "", + "title": "Idempotency-Key", + "type": "string" + } + }, { "in": "header", "name": "Authorization", @@ -74936,1299 +72601,516 @@ "title": "Enabled", "type": "boolean" }, - "migration_command": { - "default": "", - "title": "Migration Command", - "type": "string" - }, - "omitted_env_keys": { - "default": [], - "items": { - "type": "string" - }, - "title": "Omitted Env Keys", - "type": "array" - }, - "override_env": { - "additionalProperties": { - "type": "string" - }, - "title": "Override Env", - "type": "object" - }, - "preview_domain_env_keys": { - "default": [], - "items": { - "type": "string" - }, - "title": "Preview Domain Env Keys", - "type": "array" - }, - "preview_url_env_keys": { - "default": [], - "items": { - "type": "string" - }, - "title": "Preview Url Env Keys", - "type": "array" - }, - "required_provider_fields": { - "default": [], - "items": { - "type": "string" - }, - "title": "Required Provider Fields", - "type": "array" - }, - "required_template_env_keys": { - "default": [], - "items": { - "type": "string" - }, - "title": "Required Template Env Keys", - "type": "array" - }, - "seed_command": { - "default": "", - "title": "Seed Command", - "type": "string" - }, - "slug_template": { - "default": "pr-{number}", - "title": "Slug Template", - "type": "string" - }, - "template_instance": { - "default": "testing", - "title": "Template Instance", - "type": "string" - } - }, - "title": "ProductOnboardingPreviewManifest", - "type": "object" - }, - "ProductOnboardingRuntimeEnvironmentManifest": { - "additionalProperties": false, - "properties": { - "context": { - "default": "", - "title": "Context", - "type": "string" - }, - "env": { - "additionalProperties": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "integer" - }, - { - "type": "number" - }, - { - "type": "boolean" - } - ] - }, - "title": "Env", - "type": "object" - }, - "instance": { - "default": "", - "title": "Instance", - "type": "string" - }, - "scope": { - "enum": [ - "global", - "context", - "instance" - ], - "title": "Scope", - "type": "string" - } - }, - "required": [ - "scope", - "env" - ], - "title": "ProductOnboardingRuntimeEnvironmentManifest", - "type": "object" - }, - "ProductOnboardingSecretBindingManifest": { - "additionalProperties": false, - "properties": { - "binding_id": { - "default": "", - "title": "Binding Id", - "type": "string" - }, - "binding_key": { - "title": "Binding Key", - "type": "string" - }, - "context": { - "title": "Context", - "type": "string" - }, - "instance": { - "default": "", - "title": "Instance", - "type": "string" - }, - "integration": { - "default": "runtime_environment", - "title": "Integration", - "type": "string" - }, - "secret_id": { - "default": "", - "title": "Secret Id", - "type": "string" - }, - "status": { - "default": "disabled", - "enum": [ - "configured", - "disabled" - ], - "title": "Status", - "type": "string" - } - }, - "required": [ - "binding_key", - "context" - ], - "title": "ProductOnboardingSecretBindingManifest", - "type": "object" - }, - "ProductOnboardingTargetManifest": { - "additionalProperties": false, - "properties": { - "compose_path": { - "default": "", - "title": "Compose Path", - "type": "string" - }, - "context": { - "title": "Context", - "type": "string" - }, - "custom_git_branch": { - "default": "", - "title": "Custom Git Branch", - "type": "string" - }, - "custom_git_url": { - "default": "", - "title": "Custom Git Url", - "type": "string" - }, - "deploy_timeout_seconds": { - "anyOf": [ - { - "minimum": 1, - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Deploy Timeout Seconds" - }, - "domains": { - "default": [], - "items": { - "type": "string" - }, - "title": "Domains", - "type": "array" - }, - "enable_submodules": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Enable Submodules" - }, - "env": { - "additionalProperties": { - "type": "string" - }, - "title": "Env", - "type": "object" - }, - "healthcheck_enabled": { - "default": true, - "title": "Healthcheck Enabled", - "type": "boolean" - }, - "healthcheck_path": { - "default": "", - "title": "Healthcheck Path", - "type": "string" - }, - "healthcheck_timeout_seconds": { - "anyOf": [ - { - "minimum": 1, - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Healthcheck Timeout Seconds" - }, - "instance": { - "title": "Instance", - "type": "string" - }, - "project_name": { - "default": "", - "title": "Project Name", - "type": "string" - }, - "require_prod_gate": { - "default": false, - "title": "Require Prod Gate", - "type": "boolean" - }, - "require_test_gate": { - "default": false, - "title": "Require Test Gate", - "type": "boolean" - }, - "source_git_ref": { - "default": "origin/main", - "title": "Source Git Ref", - "type": "string" - }, - "source_type": { - "default": "", - "title": "Source Type", - "type": "string" - }, - "target_id": { - "default": "", - "title": "Target Id", - "type": "string" - }, - "target_name": { - "default": "", - "title": "Target Name", - "type": "string" - }, - "target_type": { - "default": "application", - "enum": [ - "compose", - "application" - ], - "title": "Target Type", - "type": "string" - }, - "watch_paths": { - "default": [], - "items": { - "type": "string" - }, - "title": "Watch Paths", - "type": "array" - } - }, - "required": [ - "context", - "instance" - ], - "title": "ProductOnboardingTargetManifest", - "type": "object" - }, - "ProductOwnerSecretInput": { - "additionalProperties": false, - "properties": { - "instructions": { - "default": "", - "maxLength": 2000, - "title": "Instructions", - "type": "string" - }, - "label": { - "maxLength": 120, - "minLength": 1, - "title": "Label", - "type": "string" - } - }, - "required": [ - "label" - ], - "title": "ProductOwnerSecretInput", - "type": "object" - }, - "ProductPromotionWorkflowProfile": { - "additionalProperties": false, - "properties": { - "artifact_id_input": { - "default": "artifact_id", - "title": "Artifact Id Input", - "type": "string" - }, - "bump_input": { - "default": "bump", - "title": "Bump Input", - "type": "string" - }, - "default_bump": { - "default": "patch", - "title": "Default Bump", - "type": "string" - }, - "deploy_reference_input": { - "default": "deploy_reference", - "title": "Deploy Reference Input", - "type": "string" - }, - "dry_run_input": { - "default": "dry_run", - "title": "Dry Run Input", - "type": "string" - }, - "promotion_intent_input": { - "default": "promotion_intent_id", - "title": "Promotion Intent Input", - "type": "string" - }, - "ref": { - "default": "main", - "title": "Ref", - "type": "string" - }, - "source_git_ref_input": { - "default": "source_git_ref", - "title": "Source Git Ref Input", - "type": "string" - }, - "workflow_id": { - "default": "promote-prod.yml", - "title": "Workflow Id", - "type": "string" - } - }, - "title": "ProductPromotionWorkflowProfile", - "type": "object" - }, - "ProductRuntimeConfigRequirement": { - "additionalProperties": false, - "properties": { - "context": { - "default": "", - "title": "Context", - "type": "string" - }, - "instance": { - "default": "", - "title": "Instance", - "type": "string" - }, - "key": { - "title": "Key", - "type": "string" - } - }, - "required": [ - "key" - ], - "title": "ProductRuntimeConfigRequirement", - "type": "object" - }, - "ProductSecretConfigRequirement": { - "additionalProperties": false, - "properties": { - "binding_key": { - "title": "Binding Key", - "type": "string" - }, - "context": { - "default": "", - "title": "Context", - "type": "string" - }, - "instance": { - "default": "", - "title": "Instance", - "type": "string" - }, - "integration": { - "default": "runtime_environment", - "title": "Integration", - "type": "string" - }, - "owner_input": { - "anyOf": [ - { - "$ref": "#/$defs/ProductOwnerSecretInput" - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "binding_key" - ], - "title": "ProductSecretConfigRequirement", - "type": "object" - } - }, - "additionalProperties": false, - "properties": { - "generic_web": { - "anyOf": [ - { - "$ref": "#/$defs/GenericWebOnboardingIntent" - }, - { - "type": "null" - } - ] - }, - "manifest": { - "anyOf": [ - { - "$ref": "#/$defs/ProductOnboardingManifest" - }, - { - "type": "null" - } - ] - }, - "mode": { - "default": "apply", - "enum": [ - "dry_run", - "apply" - ], - "title": "Mode", - "type": "string" - }, - "product": { - "title": "Product", - "type": "string" - }, - "resolved_target_id": { - "default": "", - "title": "Resolved Target Id", - "type": "string" - }, - "reviewed_plan_sha256": { - "default": "", - "title": "Reviewed Plan Sha256", - "type": "string" - }, - "schema_version": { - "default": 1, - "minimum": 1, - "title": "Schema Version", - "type": "integer" - } - }, - "required": [ - "product" - ], - "title": "ProductOnboardingApplyEnvelope", - "type": "object" - } - } - }, - "required": true - }, - "responses": { - "202": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AcceptedEvidenceResponse" - } - } - }, - "description": "Successful Response" - }, - "400": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LaunchplaneErrorResponse" - } - } - }, - "description": "Bad Request" - }, - "401": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LaunchplaneErrorResponse" - } - } - }, - "description": "Unauthorized" - }, - "403": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LaunchplaneErrorResponse" - } - } - }, - "description": "Forbidden" - }, - "409": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LaunchplaneErrorResponse" - } - } - }, - "description": "Conflict" - }, - "503": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LaunchplaneErrorResponse" - } - } - }, - "description": "Service Unavailable" - } - }, - "summary": "Apply product onboarding records" - } - }, - "/v1/product-owner/evaluation": { - "get": { - "operationId": "read_product_owner_authority_evaluation", - "parameters": [ - { - "in": "query", - "name": "product", - "required": true, - "schema": { - "title": "Product", - "type": "string" - } - }, - { - "in": "query", - "name": "system", - "required": true, - "schema": { - "title": "System", - "type": "string" - } - }, - { - "in": "query", - "name": "repository_id", - "required": true, - "schema": { - "title": "Repository Id", - "type": "string" - } - }, - { - "in": "query", - "name": "environment", - "required": true, - "schema": { - "title": "Environment", - "type": "string" - } - }, - { - "in": "query", - "name": "action", - "required": true, - "schema": { - "title": "Action", - "type": "string" - } - }, - { - "in": "query", - "name": "claimed_policy_revision", - "required": false, - "schema": { - "anyOf": [ - { - "minimum": 1, - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Claimed Policy Revision" - } - }, - { - "in": "query", - "name": "claimed_policy_digest", - "required": false, - "schema": { - "default": "", - "title": "Claimed Policy Digest", - "type": "string" - } - }, - { - "in": "query", - "name": "claimed_requirement_revision", - "required": false, - "schema": { - "anyOf": [ - { - "minimum": 1, - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Claimed Requirement Revision" - } - }, - { - "in": "query", - "name": "claimed_requirement_digest", - "required": false, - "schema": { - "default": "", - "title": "Claimed Requirement Digest", - "type": "string" - } - }, - { - "in": "header", - "name": "Authorization", - "required": false, - "schema": { - "default": "", - "title": "Authorization", - "type": "string" - } - }, - { - "in": "header", - "name": "Cookie", - "required": false, - "schema": { - "default": "", - "title": "Cookie", - "type": "string" - } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProductOwnerAuthorityEvaluationResponse" - } - } - }, - "description": "Successful Response" - }, - "400": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LaunchplaneErrorResponse" - } - } - }, - "description": "Bad Request" - }, - "401": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LaunchplaneErrorResponse" - } - } - }, - "description": "Unauthorized" - }, - "403": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LaunchplaneErrorResponse" - } - } - }, - "description": "Forbidden" - }, - "409": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LaunchplaneErrorResponse" - } - } - }, - "description": "Conflict" - }, - "503": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LaunchplaneErrorResponse" - } - } - }, - "description": "Service Unavailable" - } - }, - "summary": "Evaluate current product Owner authority" - } - }, - "/v1/product-owner/policies/apply": { - "post": { - "operationId": "apply_product_owner_policy", - "parameters": [ - { - "in": "header", - "name": "Authorization", - "required": false, - "schema": { - "default": "", - "title": "Authorization", - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProductOwnerPolicyApplyEnvelope" - } - } - }, - "required": true - }, - "responses": { - "202": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProductOwnerPolicyApplyResponse" - } - } - }, - "description": "Successful Response" - }, - "400": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LaunchplaneErrorResponse" - } - } - }, - "description": "Bad Request" - }, - "401": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LaunchplaneErrorResponse" - } - } - }, - "description": "Unauthorized" - }, - "403": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LaunchplaneErrorResponse" - } - } - }, - "description": "Forbidden" - }, - "409": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LaunchplaneErrorResponse" - } - } - }, - "description": "Conflict" - }, - "503": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LaunchplaneErrorResponse" - } - } - }, - "description": "Service Unavailable" - } - }, - "summary": "Apply or dry-run a product Owner policy revision" - } - }, - "/v1/product-owner/policy": { - "get": { - "operationId": "read_product_owner_policy", - "parameters": [ - { - "in": "query", - "name": "product", - "required": true, - "schema": { - "title": "Product", - "type": "string" - } - }, - { - "in": "query", - "name": "system", - "required": true, - "schema": { - "title": "System", - "type": "string" - } - }, - { - "in": "header", - "name": "Authorization", - "required": false, - "schema": { - "default": "", - "title": "Authorization", - "type": "string" - } - }, - { - "in": "header", - "name": "Cookie", - "required": false, - "schema": { - "default": "", - "title": "Cookie", - "type": "string" - } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProductOwnerReadResponse" - } - } - }, - "description": "Successful Response" - }, - "400": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LaunchplaneErrorResponse" - } - } - }, - "description": "Bad Request" - }, - "401": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LaunchplaneErrorResponse" - } - } - }, - "description": "Unauthorized" - }, - "403": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LaunchplaneErrorResponse" - } - } - }, - "description": "Forbidden" - }, - "409": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LaunchplaneErrorResponse" - } - } - }, - "description": "Conflict" - }, - "503": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LaunchplaneErrorResponse" - } - } - }, - "description": "Service Unavailable" - } - }, - "summary": "Read the product Owner policy bundle" - } - }, - "/v1/product-owner/requirement": { - "get": { - "operationId": "read_product_owner_requirement", - "parameters": [ - { - "in": "query", - "name": "product", - "required": true, - "schema": { - "title": "Product", - "type": "string" - } - }, - { - "in": "query", - "name": "system", - "required": true, - "schema": { - "title": "System", - "type": "string" - } - }, - { - "in": "header", - "name": "Authorization", - "required": false, - "schema": { - "default": "", - "title": "Authorization", - "type": "string" - } - }, - { - "in": "header", - "name": "Cookie", - "required": false, - "schema": { - "default": "", - "title": "Cookie", - "type": "string" - } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProductOwnerReadResponse" - } - } - }, - "description": "Successful Response" - }, - "400": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LaunchplaneErrorResponse" - } - } - }, - "description": "Bad Request" - }, - "401": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LaunchplaneErrorResponse" - } - } - }, - "description": "Unauthorized" - }, - "403": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LaunchplaneErrorResponse" - } - } - }, - "description": "Forbidden" - }, - "409": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LaunchplaneErrorResponse" - } - } - }, - "description": "Conflict" - }, - "503": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LaunchplaneErrorResponse" - } - } - }, - "description": "Service Unavailable" - } - }, - "summary": "Read the product Owner requirement bundle" - } - }, - "/v1/product-owner/requirements/apply": { - "post": { - "operationId": "apply_product_owner_requirement", - "parameters": [ - { - "in": "header", - "name": "Authorization", - "required": false, - "schema": { - "default": "", - "title": "Authorization", - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProductOwnerRequirementApplyEnvelope" - } - } - }, - "required": true - }, - "responses": { - "202": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProductOwnerRequirementApplyResponse" - } - } - }, - "description": "Successful Response" - }, - "400": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LaunchplaneErrorResponse" - } - } - }, - "description": "Bad Request" - }, - "401": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LaunchplaneErrorResponse" - } - } - }, - "description": "Unauthorized" - }, - "403": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LaunchplaneErrorResponse" - } - } - }, - "description": "Forbidden" - }, - "409": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LaunchplaneErrorResponse" - } - } - }, - "description": "Conflict" - }, - "503": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LaunchplaneErrorResponse" - } - } - }, - "description": "Service Unavailable" - } - }, - "summary": "Apply or dry-run a product Owner requirement revision" - } - }, - "/v1/product-owner/routing": { - "get": { - "operationId": "read_product_owner_routing", - "parameters": [ - { - "in": "query", - "name": "product", - "required": true, - "schema": { - "title": "Product", - "type": "string" - } - }, - { - "in": "query", - "name": "system", - "required": true, - "schema": { - "title": "System", - "type": "string" - } - }, - { - "in": "header", - "name": "Authorization", - "required": false, - "schema": { - "default": "", - "title": "Authorization", - "type": "string" - } - }, - { - "in": "header", - "name": "Cookie", - "required": false, - "schema": { - "default": "", - "title": "Cookie", - "type": "string" - } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProductOwnerReadResponse" - } - } - }, - "description": "Successful Response" - }, - "400": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LaunchplaneErrorResponse" - } - } - }, - "description": "Bad Request" - }, - "401": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LaunchplaneErrorResponse" - } - } - }, - "description": "Unauthorized" - }, - "403": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LaunchplaneErrorResponse" - } - } - }, - "description": "Forbidden" - }, - "409": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LaunchplaneErrorResponse" - } - } - }, - "description": "Conflict" - }, - "503": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LaunchplaneErrorResponse" - } - } - }, - "description": "Service Unavailable" - } - }, - "summary": "Read non-authoritative product Owner routing" - } - }, - "/v1/product-owner/routing/apply": { - "post": { - "operationId": "apply_product_owner_routing", - "parameters": [ - { - "in": "header", - "name": "Authorization", - "required": false, - "schema": { - "default": "", - "title": "Authorization", - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProductOwnerRoutingApplyEnvelope" + "migration_command": { + "default": "", + "title": "Migration Command", + "type": "string" + }, + "omitted_env_keys": { + "default": [], + "items": { + "type": "string" + }, + "title": "Omitted Env Keys", + "type": "array" + }, + "override_env": { + "additionalProperties": { + "type": "string" + }, + "title": "Override Env", + "type": "object" + }, + "preview_domain_env_keys": { + "default": [], + "items": { + "type": "string" + }, + "title": "Preview Domain Env Keys", + "type": "array" + }, + "preview_url_env_keys": { + "default": [], + "items": { + "type": "string" + }, + "title": "Preview Url Env Keys", + "type": "array" + }, + "required_provider_fields": { + "default": [], + "items": { + "type": "string" + }, + "title": "Required Provider Fields", + "type": "array" + }, + "required_template_env_keys": { + "default": [], + "items": { + "type": "string" + }, + "title": "Required Template Env Keys", + "type": "array" + }, + "seed_command": { + "default": "", + "title": "Seed Command", + "type": "string" + }, + "slug_template": { + "default": "pr-{number}", + "title": "Slug Template", + "type": "string" + }, + "template_instance": { + "default": "testing", + "title": "Template Instance", + "type": "string" + } + }, + "title": "ProductOnboardingPreviewManifest", + "type": "object" + }, + "ProductOnboardingRuntimeEnvironmentManifest": { + "additionalProperties": false, + "properties": { + "context": { + "default": "", + "title": "Context", + "type": "string" + }, + "env": { + "additionalProperties": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + }, + { + "type": "number" + }, + { + "type": "boolean" + } + ] + }, + "title": "Env", + "type": "object" + }, + "instance": { + "default": "", + "title": "Instance", + "type": "string" + }, + "scope": { + "enum": [ + "global", + "context", + "instance" + ], + "title": "Scope", + "type": "string" + } + }, + "required": [ + "scope", + "env" + ], + "title": "ProductOnboardingRuntimeEnvironmentManifest", + "type": "object" + }, + "ProductOnboardingSecretBindingManifest": { + "additionalProperties": false, + "properties": { + "binding_id": { + "default": "", + "title": "Binding Id", + "type": "string" + }, + "binding_key": { + "title": "Binding Key", + "type": "string" + }, + "context": { + "title": "Context", + "type": "string" + }, + "instance": { + "default": "", + "title": "Instance", + "type": "string" + }, + "integration": { + "default": "runtime_environment", + "title": "Integration", + "type": "string" + }, + "secret_id": { + "default": "", + "title": "Secret Id", + "type": "string" + }, + "status": { + "default": "disabled", + "enum": [ + "configured", + "disabled" + ], + "title": "Status", + "type": "string" + } + }, + "required": [ + "binding_key", + "context" + ], + "title": "ProductOnboardingSecretBindingManifest", + "type": "object" + }, + "ProductOnboardingTargetManifest": { + "additionalProperties": false, + "properties": { + "compose_path": { + "default": "", + "title": "Compose Path", + "type": "string" + }, + "context": { + "title": "Context", + "type": "string" + }, + "custom_git_branch": { + "default": "", + "title": "Custom Git Branch", + "type": "string" + }, + "custom_git_url": { + "default": "", + "title": "Custom Git Url", + "type": "string" + }, + "deploy_timeout_seconds": { + "anyOf": [ + { + "minimum": 1, + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Deploy Timeout Seconds" + }, + "domains": { + "default": [], + "items": { + "type": "string" + }, + "title": "Domains", + "type": "array" + }, + "enable_submodules": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "title": "Enable Submodules" + }, + "env": { + "additionalProperties": { + "type": "string" + }, + "title": "Env", + "type": "object" + }, + "healthcheck_enabled": { + "default": true, + "title": "Healthcheck Enabled", + "type": "boolean" + }, + "healthcheck_path": { + "default": "", + "title": "Healthcheck Path", + "type": "string" + }, + "healthcheck_timeout_seconds": { + "anyOf": [ + { + "minimum": 1, + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Healthcheck Timeout Seconds" + }, + "instance": { + "title": "Instance", + "type": "string" + }, + "project_name": { + "default": "", + "title": "Project Name", + "type": "string" + }, + "require_prod_gate": { + "default": false, + "title": "Require Prod Gate", + "type": "boolean" + }, + "require_test_gate": { + "default": false, + "title": "Require Test Gate", + "type": "boolean" + }, + "source_git_ref": { + "default": "origin/main", + "title": "Source Git Ref", + "type": "string" + }, + "source_type": { + "default": "", + "title": "Source Type", + "type": "string" + }, + "target_id": { + "default": "", + "title": "Target Id", + "type": "string" + }, + "target_name": { + "default": "", + "title": "Target Name", + "type": "string" + }, + "target_type": { + "default": "application", + "enum": [ + "compose", + "application" + ], + "title": "Target Type", + "type": "string" + }, + "watch_paths": { + "default": [], + "items": { + "type": "string" + }, + "title": "Watch Paths", + "type": "array" + } + }, + "required": [ + "context", + "instance" + ], + "title": "ProductOnboardingTargetManifest", + "type": "object" + }, + "ProductOwnerSecretInput": { + "additionalProperties": false, + "properties": { + "instructions": { + "default": "", + "maxLength": 2000, + "title": "Instructions", + "type": "string" + }, + "label": { + "maxLength": 120, + "minLength": 1, + "title": "Label", + "type": "string" + } + }, + "required": [ + "label" + ], + "title": "ProductOwnerSecretInput", + "type": "object" + }, + "ProductPromotionWorkflowProfile": { + "additionalProperties": false, + "properties": { + "artifact_id_input": { + "default": "artifact_id", + "title": "Artifact Id Input", + "type": "string" + }, + "bump_input": { + "default": "bump", + "title": "Bump Input", + "type": "string" + }, + "default_bump": { + "default": "patch", + "title": "Default Bump", + "type": "string" + }, + "deploy_reference_input": { + "default": "deploy_reference", + "title": "Deploy Reference Input", + "type": "string" + }, + "dry_run_input": { + "default": "dry_run", + "title": "Dry Run Input", + "type": "string" + }, + "promotion_intent_input": { + "default": "promotion_intent_id", + "title": "Promotion Intent Input", + "type": "string" + }, + "ref": { + "default": "main", + "title": "Ref", + "type": "string" + }, + "source_git_ref_input": { + "default": "source_git_ref", + "title": "Source Git Ref Input", + "type": "string" + }, + "workflow_id": { + "default": "promote-prod.yml", + "title": "Workflow Id", + "type": "string" + } + }, + "title": "ProductPromotionWorkflowProfile", + "type": "object" + }, + "ProductRuntimeConfigRequirement": { + "additionalProperties": false, + "properties": { + "context": { + "default": "", + "title": "Context", + "type": "string" + }, + "instance": { + "default": "", + "title": "Instance", + "type": "string" + }, + "key": { + "title": "Key", + "type": "string" + } + }, + "required": [ + "key" + ], + "title": "ProductRuntimeConfigRequirement", + "type": "object" + }, + "ProductSecretConfigRequirement": { + "additionalProperties": false, + "properties": { + "binding_key": { + "title": "Binding Key", + "type": "string" + }, + "context": { + "default": "", + "title": "Context", + "type": "string" + }, + "instance": { + "default": "", + "title": "Instance", + "type": "string" + }, + "integration": { + "default": "runtime_environment", + "title": "Integration", + "type": "string" + }, + "owner_input": { + "anyOf": [ + { + "$ref": "#/$defs/ProductOwnerSecretInput" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "binding_key" + ], + "title": "ProductSecretConfigRequirement", + "type": "object" + } + }, + "additionalProperties": false, + "properties": { + "generic_web": { + "anyOf": [ + { + "$ref": "#/$defs/GenericWebOnboardingIntent" + }, + { + "type": "null" + } + ] + }, + "manifest": { + "anyOf": [ + { + "$ref": "#/$defs/ProductOnboardingManifest" + }, + { + "type": "null" + } + ] + }, + "mode": { + "default": "apply", + "enum": [ + "dry_run", + "apply" + ], + "title": "Mode", + "type": "string" + }, + "product": { + "title": "Product", + "type": "string" + }, + "resolved_target_id": { + "default": "", + "title": "Resolved Target Id", + "type": "string" + }, + "reviewed_plan_sha256": { + "default": "", + "title": "Reviewed Plan Sha256", + "type": "string" + }, + "schema_version": { + "default": 1, + "minimum": 1, + "title": "Schema Version", + "type": "integer" + } + }, + "required": [ + "product" + ], + "title": "ProductOnboardingApplyEnvelope", + "type": "object" } } }, @@ -76239,7 +73121,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ProductOwnerRoutingApplyResponse" + "$ref": "#/components/schemas/AcceptedEvidenceResponse" } } }, @@ -76296,7 +73178,7 @@ "description": "Service Unavailable" } }, - "summary": "Apply or dry-run non-authoritative product Owner routing" + "summary": "Apply product onboarding records" } }, "/v1/product-profiles": { diff --git a/frontend/generated/openapi-ui.json b/frontend/generated/openapi-ui.json index df58b9c22..cf868974c 100644 --- a/frontend/generated/openapi-ui.json +++ b/frontend/generated/openapi-ui.json @@ -7481,53 +7481,6 @@ "title": "ChangeImpactCoverage", "type": "object" }, - "ChangeImpactTarget": { - "additionalProperties": false, - "properties": { - "head_sha": { - "title": "Head Sha", - "type": "string" - }, - "pull_request_number": { - "minimum": 1, - "title": "Pull Request Number", - "type": "integer" - }, - "repository": { - "title": "Repository", - "type": "string" - }, - "repository_id": { - "title": "Repository Id", - "type": "string" - }, - "repository_owner_id": { - "title": "Repository Owner Id", - "type": "string" - }, - "schema_version": { - "default": 1, - "minimum": 1, - "title": "Schema Version", - "type": "integer" - }, - "tree_sha": { - "title": "Tree Sha", - "type": "string" - } - }, - "required": [ - "head_sha", - "pull_request_number", - "repository", - "repository_id", - "repository_owner_id", - "schema_version", - "tree_sha" - ], - "title": "ChangeImpactTarget", - "type": "object" - }, "DataProvenance": { "additionalProperties": false, "properties": { @@ -9867,7 +9820,7 @@ "type": "integer" }, "target": { - "$ref": "#/components/schemas/ChangeImpactTarget" + "$ref": "#/components/schemas/RepositoryTarget" } }, "required": [ @@ -27811,6 +27764,53 @@ "title": "RepoProductMappingResponse", "type": "object" }, + "RepositoryTarget": { + "additionalProperties": false, + "properties": { + "head_sha": { + "title": "Head Sha", + "type": "string" + }, + "pull_request_number": { + "minimum": 1, + "title": "Pull Request Number", + "type": "integer" + }, + "repository": { + "title": "Repository", + "type": "string" + }, + "repository_id": { + "title": "Repository Id", + "type": "string" + }, + "repository_owner_id": { + "title": "Repository Owner Id", + "type": "string" + }, + "schema_version": { + "default": 1, + "minimum": 1, + "title": "Schema Version", + "type": "integer" + }, + "tree_sha": { + "title": "Tree Sha", + "type": "string" + } + }, + "required": [ + "head_sha", + "pull_request_number", + "repository", + "repository_id", + "repository_owner_id", + "schema_version", + "tree_sha" + ], + "title": "RepositoryTarget", + "type": "object" + }, "ResolvedTargetEvidence": { "additionalProperties": false, "properties": { diff --git a/frontend/src/generated/openapi.ts/index.ts b/frontend/src/generated/openapi.ts/index.ts index 58267e24b..a7aa1fea6 100644 --- a/frontend/src/generated/openapi.ts/index.ts +++ b/frontend/src/generated/openapi.ts/index.ts @@ -1,3 +1,3 @@ // This file is auto-generated by @hey-api/openapi-ts -export type { AcceptedEvidenceResponse, AgentContextEvidence, AgentContextProvenance, ApplyProductEnvironmentConfigData, ApplyProductEnvironmentConfigError, ApplyProductEnvironmentConfigErrors, ApplyProductEnvironmentConfigResponse, ApplyProductEnvironmentConfigResponses, ApplyProductOwnerData, ApplyProductOwnerError, ApplyProductOwnerErrors, ApplyProductOwnerResponse, ApplyProductOwnerResponses, ApproveHumanPrivilegedOperationData, ApproveHumanPrivilegedOperationError, ApproveHumanPrivilegedOperationErrors, ApproveHumanPrivilegedOperationResponse, ApproveHumanPrivilegedOperationResponses, ApproveOrdinaryAgentOperationData, ApproveOrdinaryAgentOperationError, ApproveOrdinaryAgentOperationErrors, ApproveOrdinaryAgentOperationResponse, ApproveOrdinaryAgentOperationResponses, ArtifactAddonSelector, ArtifactAddonSource, ArtifactBaseImageProvenance, ArtifactBuildFlags, ArtifactBuildProvenance, ArtifactBuildToolProvenance, ArtifactDependencyProvenance, ArtifactExternalCompatibilityInput, ArtifactIdentityManifest, ArtifactIdentityReference, ArtifactImageReference, ArtifactOpenUpgradeInputs, ArtifactPythonEnvironmentProvenance, ArtifactPythonPackage, ArtifactPythonPackageSource, ArtifactUvLockProvenance, AuthorizationCandidateInputDiagnostic, AuthorizationCandidateMergePolicyProvenance, AuthorizationCandidatePolicyProvenance, AuthorizationCandidatePrepareEnvelope, AuthorizationCandidatePrepareResponse, AuthSessionRequiredResponse, AuthSessionResponse, AuthzManagedCompatibilityRetirement, AuthzManagedOperationalReadinessBlocker, AuthzManagedPolicyDiff, AuthzManagedPolicySafetyBlocker, AuthzManagedRuleChange, BackupGateEvidence, BackupGateRecord, BootstrapEvidence, CancelOrdinaryAgentOperationData, CancelOrdinaryAgentOperationError, CancelOrdinaryAgentOperationErrors, CancelOrdinaryAgentOperationResponse, CancelOrdinaryAgentOperationResponses, ChangeImpactCoverage, ChangeImpactTarget, ClientOptions, DataProvenance, DeployedTargetReference, DeploymentEvidenceOutput, DeploymentRecordOutput, DisconnectOrdinaryAgentPrincipalData, DisconnectOrdinaryAgentPrincipalError, DisconnectOrdinaryAgentPrincipalErrors, DisconnectOrdinaryAgentPrincipalResponse, DisconnectOrdinaryAgentPrincipalResponses, DispatchProductPromotionWorkflowData, DispatchProductPromotionWorkflowError, DispatchProductPromotionWorkflowErrors, DispatchProductPromotionWorkflowResponse, DispatchProductPromotionWorkflowResponses, DokployTargetIdRecord, DokployTargetPolicies, DokployTargetRecord, DokployTargetShopifyPolicy, DriverActionDescriptor, DriverCapabilityDescriptor, DriverContextView, DriverContextViewResponse, DriverDescriptor, DriverDescriptorsResponse, DriverSettingGroupDescriptor, DriverView, DryRunProductPromotionData, DryRunProductPromotionError, DryRunProductPromotionErrors, DryRunProductPromotionResponse, DryRunProductPromotionResponses, EnvironmentInventory, EveryCodeSummaryReadModel, EveryCodeSummaryResponse, EveryCodeWorkRequestRecord, EveryCodeWorkRequestRecordsResponse, EveryCodeWorkRequestSummary, GenericWebProdPromotionRecords, GitHubHumanIdentityResponse, GitHubIssueInboxIssue, GitHubIssueInboxReadModel, GitHubIssueInboxRepositoryGroup, GovernanceAdvisoryObservation, GovernanceLandingOutcomeFacet, GovernanceMergeAdmissionFacet, GovernanceMergeReadinessFacet, GovernanceOwnerHistoryEntry, GovernanceOwnerJudgmentFacet, GovernanceProjection, GovernanceProjectionResponse, HealthcheckEvidence, InspectionSetupManagedSecretMetadata, InspectionSetupMetadata, InspectionSetupRuntimeMetadata, LaunchplaneAuthzPolicyOutput, LaunchplaneErrorDetail, LaunchplaneErrorResponse, LaunchplaneLaneSummary, LaunchplanePreviewSummary, LaunchplaneProductProfileRecord, ListEveryCodeWorkRequestsData, ListEveryCodeWorkRequestsError, ListEveryCodeWorkRequestsErrors, ListEveryCodeWorkRequestsResponse, ListEveryCodeWorkRequestsResponses, ListHumanPrivilegedOperationsData, ListHumanPrivilegedOperationsError, ListHumanPrivilegedOperationsErrors, ListHumanPrivilegedOperationsResponse, ListHumanPrivilegedOperationsResponses, ListProductEnvironmentPublicIngressIncidentsData, ListProductEnvironmentPublicIngressIncidentsError, ListProductEnvironmentPublicIngressIncidentsErrors, ListProductEnvironmentPublicIngressIncidentsResponse, ListProductEnvironmentPublicIngressIncidentsResponses, ListProductEnvironmentsData, ListProductEnvironmentsError, ListProductEnvironmentsErrors, ListProductEnvironmentsResponse, ListProductEnvironmentsResponses, ListProductProfilesData, ListProductProfilesError, ListProductProfilesErrors, ListProductProfilesResponse, ListProductProfilesResponses, ListProductsData, ListProductsError, ListProductsErrors, ListProductsResponse, ListProductsResponses, ManagedAuthzPolicySetExecutionEvidence, ManagedAuthzPolicySetHumanEvidence, ManagedAuthzPolicySetProposalInputOutput, ManagedMergeTrainPolicyImportExecutionEvidence, ManagedMergeTrainPolicyImportHumanEvidence, ManagedMergeTrainPolicyImportProposalInputOutput, ManagedMergeTrainPolicyPreparationContextOutput, ManagedOrdinaryAgentPolicyPreparationContext, ManagedSecretReencryptionHumanEvidence, ManagedSecretReencryptionPlanInput, MergeAdmissionRecord, MergeBatchNoEffectEvidence, MergeLandingOutcomeRecord, MergeReadinessAdvisoryObservation, MergeReadinessCandidateEvidence, MergeReadinessCandidateFacet, MergeReadinessEngineeringEvidenceReference, MergeReadinessEngineeringReviewFacet, MergeReadinessFenceEvidence, MergeReadinessFenceFacet, MergeReadinessOwnerFacet, MergeReadinessPolicyFacet, MergeReadinessPolicyFingerprintEvidence, MergeReadinessPolicyFingerprints, MergeReadinessResult, MergeReadinessTarget, MergeReadinessTechnicalChecksFacet, MergeTrainAdmissionDecision, MergeTrainControllerLeaseDiagnostics, MergeTrainControllerRecordSummary, MergeTrainControllerStateRecord, MergeTrainControllerStatusReadModel, MergeTrainControllerStatusResponse, MergeTrainDryRunQueueEntrySummary, MergeTrainEnqueuePolicyInput, MergeTrainEnqueuePolicyOutput, MergeTrainGitHubAppSource, MergeTrainGitHubTokenSource, MergeTrainIdentity, MergeTrainLatestDryRunSummary, MergeTrainPolicyOutput, MergeTrainPolicyRecordOutput, MergeTrainPolicySummary, MergeTrainPolicyTarget, MergeTrainPolicyTargetsResponse, MergeTrainReconciliationDiagnostic, MergeTrainRepositoryPolicyOutput, MergeTrainRunRecord, MergeTrainSchedulerPolicy, MergeTrainServiceAuthz, MergeTrainStructuralCandidateResult, OdooAddonSettingOverride, OdooConfigParameterOverride, OdooInstanceOverrideRecord, OdooOverrideApplyResult, OdooOverrideValue, OdooWebsiteBootstrapPayload, OdooWebsiteBootstrapRoute, OrdinaryAgentConnectionView, OrdinaryAgentDeliveryActivationDurationOption, OrdinaryAgentDeliveryActivationExecutionEvidence, OrdinaryAgentDeliveryActivationOptionsResponse, OrdinaryAgentDeliveryActivationPlanEnvelope, OrdinaryAgentDeliveryActivationReference, OrdinaryAgentDeliveryActivationRevokeHumanEvidence, OrdinaryAgentDeliveryActivationRevokeOption, OrdinaryAgentDeliveryActivationRevokeRequest, OrdinaryAgentDeliveryActivationScope, OrdinaryAgentDeliveryActivationSetupHumanEvidence, OrdinaryAgentDeliveryActivationSetupOption, OrdinaryAgentDeliveryActivationSetupRequest, OrdinaryAgentDeliveryAuthorizationCandidateInputsResponse, OrdinaryAgentDeliveryAuthorizationCandidateRepository, OrdinaryAgentDeliveryInventoryReference, OrdinaryAgentDeliveryPolicyIntent, OrdinaryAgentDeliveryPolicyPackageReference, OrdinaryAgentDeliveryPolicyPrepareEnvelope, OrdinaryAgentDeliveryPolicyPrepareResponse, OrdinaryAgentDeliveryRuntimeCapabilityEvidence, OrdinaryAgentDisconnectRequest, OrdinaryAgentJobBinding, OrdinaryAgentJobView, OrdinaryAgentMergeTrainTargetInputsResponse, OrdinaryAgentMergeTrainTargetIntentInput, OrdinaryAgentMergeTrainTargetIntentOutput, OrdinaryAgentMergeTrainTargetInventoryInput, OrdinaryAgentMergeTrainTargetPolicyInput, OrdinaryAgentMergeTrainTargetPrepareEnvelope, OrdinaryAgentMergeTrainTargetPrepareResponse, OrdinaryAgentOperationClientResponse, OrdinaryAgentSessionAttenuation, OrdinaryAgentSessionLeaseSelector, OrdinaryAgentSessionOperationView, OrdinaryAgentTarget, OwnerAcceptanceAuthorization, OwnerAcceptanceBinding, OwnerAcceptanceContributionBinding, OwnerAcceptanceDecision, OwnerAcceptanceEventRecord, OwnerAcceptancePolicyFingerprintBinding, OwnerAcceptancePreviewBinding, OwnerAcceptancePreviewIsolationBinding, OwnerAcceptanceProductDecision, OwnerAcceptanceResolutionEvidence, OwnerAcceptanceReviewContext, OwnerAcceptanceRuntimeIdentityBinding, OwnerSecretInputEnvelope, OwnerSecretInputEnvelopeWritable, OwnerSecretInputField, OwnerSecretInputResponse, PlanOrdinaryAgentDeliveryActivationData, PlanOrdinaryAgentDeliveryActivationError, PlanOrdinaryAgentDeliveryActivationErrors, PlanOrdinaryAgentDeliveryActivationResponse, PlanOrdinaryAgentDeliveryActivationResponses, PostDeployUpdateEvidence, PrepareAuthorizationCandidateData, PrepareAuthorizationCandidateError, PrepareAuthorizationCandidateErrors, PrepareAuthorizationCandidateResponse, PrepareAuthorizationCandidateResponses, PrepareOrdinaryAgentDeliveryPolicyData, PrepareOrdinaryAgentDeliveryPolicyError, PrepareOrdinaryAgentDeliveryPolicyErrors, PrepareOrdinaryAgentDeliveryPolicyResponse, PrepareOrdinaryAgentDeliveryPolicyResponses, PrepareOrdinaryAgentMergeTrainTargetData, PrepareOrdinaryAgentMergeTrainTargetError, PrepareOrdinaryAgentMergeTrainTargetErrors, PrepareOrdinaryAgentMergeTrainTargetResponse, PrepareOrdinaryAgentMergeTrainTargetResponses, PreviewGenerationRecord, PreviewPullRequestSummary, PreviewReadinessItem, PreviewReadinessReadModel, PreviewReadinessResponse, PreviewRecord, PreviewSourceRecord, PrivilegedOperationActor, PrivilegedOperationAgentActor, PrivilegedOperationApproval, PrivilegedOperationApprovalEnvelope, PrivilegedOperationEventRecord, PrivilegedOperationExecutionEvidence, PrivilegedOperationHumanResponse, PrivilegedOperationListResponse, PrivilegedOperationRecord, PrivilegedOperationRevocationEnvelope, PrivilegedOperationSemanticReview, PrivilegedOperationSemanticReviewActivityEntry, PrivilegedOperationSemanticReviewBlastRadius, PrivilegedOperationSemanticReviewBlocker, PrivilegedOperationSemanticReviewChange, PrivilegedOperationSemanticReviewDigest, PrivilegedOperationSemanticReviewEvidence, PrivilegedOperationSemanticReviewLifecycle, PrivilegedOperationSemanticReviewMetric, PrivilegedOperationSemanticReviewResponse, PrivilegedOperationSemanticReviewRollback, ProductActionAvailability, ProductActivityEvent, ProductActivityReadModel, ProductActivityRecordLink, ProductActivityResponse, ProductConfigApplyResponse, ProductConfigApplyResult, ProductConfigApplySummary, ProductConfigInputWriteAvailability, ProductConfigLiveTarget, ProductConfigLiveTargetRuntimeNextAction, ProductConfigLiveTargetRuntimeOperation, ProductConfigOperationAvailability, ProductConfigRuntimeEnvironmentRecordSummary, ProductConfigRuntimeEnvironmentResult, ProductConfigRuntimeKeySafetyResult, ProductConfigSecretResult, ProductConfigWriteAvailability, ProductDesiredTopology, ProductEnvironmentConfigStatus, ProductEnvironmentConfigStatusResponse, ProductEnvironmentDetail, ProductEnvironmentDriverExtensions, ProductEnvironmentIncidentList, ProductEnvironmentIncidentResponse, ProductEnvironmentIncidentsResponse, ProductEnvironmentListResponse, ProductEnvironmentManagedSecretInput, ProductEnvironmentResponse, ProductEnvironmentsResponse, ProductEnvironmentSummary, ProductEnvironmentTopology, ProductExpectedConfigProfile, ProductHealthMonitoringCheckSummary, ProductHealthMonitoringSummary, ProductImageProfile, ProductIncidentDetail, ProductIncidentEventSummary, ProductIncidentMaterialEvidence, ProductIncidentNotificationAttemptSummary, ProductIncidentObservationSummary, ProductIncidentOutboxDeliverySummary, ProductIncidentReminderSummary, ProductIncidentSummary, ProductionBackupAuthorityStatus, ProductLaneHealthCheck, ProductLaneHealthMonitoringPolicy, ProductLaneProfile, ProductManagedSecretConfigStatusItem, ProductObservedIngress, ProductObservedPlacement, ProductObservedTlsDomain, ProductObservedTopology, ProductOdooEnvironmentExtension, ProductOdooLaneDataPolicy, ProductOdooPrelaunchRebuildPolicy, ProductOdooStableBootstrapPolicy, ProductOperationalReadiness, ProductOperationalReadinessAction, ProductOperationalReadinessCaller, ProductOperationalReadinessDimension, ProductOperationalReadinessEvidence, ProductOperationalReadinessRemediation, ProductOperationalReadinessResponse, ProductOverviewResponse, ProductOwnerProfile, ProductOwnerSecretInput, ProductPreviewProfile, ProductPreviewSummary, ProductProfileListResponse, ProductProfileResponse, ProductPromotionDryRunEnvelope, ProductPromotionDryRunResponse, ProductPromotionDryRunResult, ProductPromotionEvidence, ProductPromotionLiveConfirmations, ProductPromotionOperationAvailability, ProductPromotionStatus, ProductPromotionStatusResponse, ProductPromotionWorkflowDeliveryStatus, ProductPromotionWorkflowDeliveryStatusResponse, ProductPromotionWorkflowDispatchEnvelope, ProductPromotionWorkflowDispatchResponse, ProductPromotionWorkflowDispatchResult, ProductPromotionWorkflowProfile, ProductProviderRecordedTopology, ProductPublicIngressSummary, ProductReviewDecisionEnvelope, ProductReviewDecisionRecord, ProductReviewFeedbackRetryEnvelope, ProductReviewResponse, ProductRuntimeConfigRequirement, ProductRuntimeConfigStatusItem, ProductRuntimeSettingSummary, ProductSecretBindingSummary, ProductSecretConfigRequirement, ProductSiteOverview, ProductTargetSummary, ProductTopologyDomain, ProductTopologyIngress, ProductTopologyPlacement, ProductTopologyTlsOwnership, ProductTopologyWarning, PromotionRecordOutput, ProviderCodeScanningToolExpectationV1, ProviderDeliveryProtectionExpectationV1, ProviderPullRequestExpectationV1, ProviderRequiredStatusCheckExpectationV1, ProviderTargetRecord, RankWorkGraphSnapshotData, RankWorkGraphSnapshotError, RankWorkGraphSnapshotErrors, RankWorkGraphSnapshotResponse, RankWorkGraphSnapshotResponses, ReadDriverContextViewData, ReadDriverContextViewError, ReadDriverContextViewErrors, ReadDriverContextViewResponse, ReadDriverContextViewResponses, ReadDriverDescriptorsData, ReadDriverDescriptorsError, ReadDriverDescriptorsErrors, ReadDriverDescriptorsResponse, ReadDriverDescriptorsResponses, ReadDriverInstanceViewData, ReadDriverInstanceViewError, ReadDriverInstanceViewErrors, ReadDriverInstanceViewResponse, ReadDriverInstanceViewResponses, ReadEveryCodeSummaryData, ReadEveryCodeSummaryError, ReadEveryCodeSummaryErrors, ReadEveryCodeSummaryResponse, ReadEveryCodeSummaryResponses, ReadGovernanceProjectionData, ReadGovernanceProjectionError, ReadGovernanceProjectionErrors, ReadGovernanceProjectionResponse, ReadGovernanceProjectionResponses, ReadHumanAuthSessionData, ReadHumanAuthSessionError, ReadHumanAuthSessionErrors, ReadHumanAuthSessionResponse, ReadHumanAuthSessionResponses, ReadHumanOrdinaryAgentJobData, ReadHumanOrdinaryAgentJobError, ReadHumanOrdinaryAgentJobErrors, ReadHumanOrdinaryAgentJobResponse, ReadHumanOrdinaryAgentJobResponses, ReadHumanOrdinaryAgentOperationData, ReadHumanOrdinaryAgentOperationError, ReadHumanOrdinaryAgentOperationErrors, ReadHumanOrdinaryAgentOperationResponse, ReadHumanOrdinaryAgentOperationResponses, ReadHumanPrivilegedOperationData, ReadHumanPrivilegedOperationError, ReadHumanPrivilegedOperationErrors, ReadHumanPrivilegedOperationResponse, ReadHumanPrivilegedOperationResponses, ReadHumanPrivilegedOperationReviewData, ReadHumanPrivilegedOperationReviewError, ReadHumanPrivilegedOperationReviewErrors, ReadHumanPrivilegedOperationReviewResponse, ReadHumanPrivilegedOperationReviewResponses, ReadMergeTrainControllerStatusData, ReadMergeTrainControllerStatusError, ReadMergeTrainControllerStatusErrors, ReadMergeTrainControllerStatusResponse, ReadMergeTrainControllerStatusResponses, ReadMergeTrainPolicyTargetsData, ReadMergeTrainPolicyTargetsError, ReadMergeTrainPolicyTargetsErrors, ReadMergeTrainPolicyTargetsResponse, ReadMergeTrainPolicyTargetsResponses, ReadOrdinaryAgentDeliveryActivationOptionsData, ReadOrdinaryAgentDeliveryActivationOptionsError, ReadOrdinaryAgentDeliveryActivationOptionsErrors, ReadOrdinaryAgentDeliveryActivationOptionsResponse, ReadOrdinaryAgentDeliveryActivationOptionsResponses, ReadOrdinaryAgentDeliveryAuthorizationCandidateInputsData, ReadOrdinaryAgentDeliveryAuthorizationCandidateInputsError, ReadOrdinaryAgentDeliveryAuthorizationCandidateInputsErrors, ReadOrdinaryAgentDeliveryAuthorizationCandidateInputsResponse, ReadOrdinaryAgentDeliveryAuthorizationCandidateInputsResponses, ReadOrdinaryAgentMergeTrainTargetInputsData, ReadOrdinaryAgentMergeTrainTargetInputsError, ReadOrdinaryAgentMergeTrainTargetInputsErrors, ReadOrdinaryAgentMergeTrainTargetInputsResponse, ReadOrdinaryAgentMergeTrainTargetInputsResponses, ReadOwnerSecretInputsData, ReadOwnerSecretInputsError, ReadOwnerSecretInputsErrors, ReadOwnerSecretInputsResponse, ReadOwnerSecretInputsResponses, ReadPreviewReadinessData, ReadPreviewReadinessError, ReadPreviewReadinessErrors, ReadPreviewReadinessResponse, ReadPreviewReadinessResponses, ReadProductActivityData, ReadProductActivityError, ReadProductActivityErrors, ReadProductActivityResponse, ReadProductActivityResponses, ReadProductData, ReadProductEnvironmentConfigStatusData, ReadProductEnvironmentConfigStatusError, ReadProductEnvironmentConfigStatusErrors, ReadProductEnvironmentConfigStatusResponse, ReadProductEnvironmentConfigStatusResponses, ReadProductEnvironmentData, ReadProductEnvironmentError, ReadProductEnvironmentErrors, ReadProductEnvironmentPublicIngressIncidentData, ReadProductEnvironmentPublicIngressIncidentError, ReadProductEnvironmentPublicIngressIncidentErrors, ReadProductEnvironmentPublicIngressIncidentResponse, ReadProductEnvironmentPublicIngressIncidentResponses, ReadProductEnvironmentResponse, ReadProductEnvironmentResponses, ReadProductError, ReadProductErrors, ReadProductOperationalReadinessData, ReadProductOperationalReadinessError, ReadProductOperationalReadinessErrors, ReadProductOperationalReadinessResponse, ReadProductOperationalReadinessResponses, ReadProductProfileData, ReadProductProfileError, ReadProductProfileErrors, ReadProductProfileResponse, ReadProductProfileResponses, ReadProductPromotionStatusData, ReadProductPromotionStatusError, ReadProductPromotionStatusErrors, ReadProductPromotionStatusResponse, ReadProductPromotionStatusResponses, ReadProductPromotionWorkflowDeliveryData, ReadProductPromotionWorkflowDeliveryError, ReadProductPromotionWorkflowDeliveryErrors, ReadProductPromotionWorkflowDeliveryResponse, ReadProductPromotionWorkflowDeliveryResponses, ReadProductResponse, ReadProductResponses, ReadProductReviewData, ReadProductReviewError, ReadProductReviewErrors, ReadProductReviewResponse, ReadProductReviewResponses, ReadReleaseReviewData, ReadReleaseReviewError, ReadReleaseReviewErrors, ReadReleaseReviewResponse, ReadReleaseReviewResponses, ReadRepoProductMappingData, ReadRepoProductMappingError, ReadRepoProductMappingErrors, ReadRepoProductMappingResponse, ReadRepoProductMappingResponses, ReadTenantAdmissionEvaluationData, ReadTenantAdmissionEvaluationError, ReadTenantAdmissionEvaluationErrors, ReadTenantAdmissionEvaluationResponse, ReadTenantAdmissionEvaluationResponses, ReadWorkGraphIssueInboxData, ReadWorkGraphIssueInboxError, ReadWorkGraphIssueInboxErrors, ReadWorkGraphIssueInboxResponse, ReadWorkGraphIssueInboxResponses, ReadWorkGraphSnapshotData, ReadWorkGraphSnapshotError, ReadWorkGraphSnapshotErrors, ReadWorkGraphSnapshotResponse, ReadWorkGraphSnapshotResponses, ReleaseChecklist, ReleaseReviewDecisionEnvelope, ReleaseReviewDecisionRecord, ReleaseReviewItem, ReleaseReviewResponse, ReleaseReviewStatus, ReleaseTupleRecord, ReleaseVersion, RepoProductMapping, RepoProductMappingEntry, RepoProductMappingResponse, ResolvedTargetEvidence, RetryProductReviewFeedbackData, RetryProductReviewFeedbackError, RetryProductReviewFeedbackErrors, RetryProductReviewFeedbackResponse, RetryProductReviewFeedbackResponses, RevokeHumanPrivilegedOperationData, RevokeHumanPrivilegedOperationError, RevokeHumanPrivilegedOperationErrors, RevokeHumanPrivilegedOperationResponse, RevokeHumanPrivilegedOperationResponses, RevokeOrdinaryAgentSessionData, RevokeOrdinaryAgentSessionError, RevokeOrdinaryAgentSessionErrors, RevokeOrdinaryAgentSessionResponse, RevokeOrdinaryAgentSessionResponses, RollbackExecutionEvidence, RuntimeEnvironmentRecord, RuntimeIdentity, RuntimeKeySafetyFinding, RuntimeKeySafetyTarget, SecretBinding, StructuredHealthEvidence, SubmitOwnerSecretInputData, SubmitOwnerSecretInputError, SubmitOwnerSecretInputErrors, SubmitOwnerSecretInputResponse, SubmitOwnerSecretInputResponses, TenantAdmissionControllerRunOnceResult, TenantAdmissionEvaluationReadModel, TenantAdmissionEvaluationReadResponse, TenantAdmissionPathResult, TenantAdmissionPullRequestFacts, TenantAdmissionRequiredTechnicalCheck, TenantAdmissionStatusReadModel, TenantAdmissionTechnicalChecks, TenantAdmissionTechnicalCheckSignal, TenantMergeCandidate, TenantMergeEligibilityDecision, TenantMergeEligibilityEvidenceInputs, TerminalEnrollmentCapabilityReadiness, WorkGraphIssueInboxResponse, WorkGraphIssueSnapshot, WorkGraphQueue, WorkGraphQueueEvidence, WorkGraphQueueItem, WorkGraphRankEnvelope, WorkGraphRankResponse, WorkGraphRankResult, WorkGraphRecommendationReason, WorkGraphRepoSnapshot, WorkGraphSnapshot, WorkGraphSnapshotResponse, WriteProductReviewDecisionData, WriteProductReviewDecisionError, WriteProductReviewDecisionErrors, WriteProductReviewDecisionResponse, WriteProductReviewDecisionResponses, WriteReleaseReviewDecisionData, WriteReleaseReviewDecisionError, WriteReleaseReviewDecisionErrors, WriteReleaseReviewDecisionResponse, WriteReleaseReviewDecisionResponses } from './types.gen'; +export type { AcceptedEvidenceResponse, AgentContextEvidence, AgentContextProvenance, ApplyProductEnvironmentConfigData, ApplyProductEnvironmentConfigError, ApplyProductEnvironmentConfigErrors, ApplyProductEnvironmentConfigResponse, ApplyProductEnvironmentConfigResponses, ApplyProductOwnerData, ApplyProductOwnerError, ApplyProductOwnerErrors, ApplyProductOwnerResponse, ApplyProductOwnerResponses, ApproveHumanPrivilegedOperationData, ApproveHumanPrivilegedOperationError, ApproveHumanPrivilegedOperationErrors, ApproveHumanPrivilegedOperationResponse, ApproveHumanPrivilegedOperationResponses, ApproveOrdinaryAgentOperationData, ApproveOrdinaryAgentOperationError, ApproveOrdinaryAgentOperationErrors, ApproveOrdinaryAgentOperationResponse, ApproveOrdinaryAgentOperationResponses, ArtifactAddonSelector, ArtifactAddonSource, ArtifactBaseImageProvenance, ArtifactBuildFlags, ArtifactBuildProvenance, ArtifactBuildToolProvenance, ArtifactDependencyProvenance, ArtifactExternalCompatibilityInput, ArtifactIdentityManifest, ArtifactIdentityReference, ArtifactImageReference, ArtifactOpenUpgradeInputs, ArtifactPythonEnvironmentProvenance, ArtifactPythonPackage, ArtifactPythonPackageSource, ArtifactUvLockProvenance, AuthorizationCandidateInputDiagnostic, AuthorizationCandidateMergePolicyProvenance, AuthorizationCandidatePolicyProvenance, AuthorizationCandidatePrepareEnvelope, AuthorizationCandidatePrepareResponse, AuthSessionRequiredResponse, AuthSessionResponse, AuthzManagedCompatibilityRetirement, AuthzManagedOperationalReadinessBlocker, AuthzManagedPolicyDiff, AuthzManagedPolicySafetyBlocker, AuthzManagedRuleChange, BackupGateEvidence, BackupGateRecord, BootstrapEvidence, CancelOrdinaryAgentOperationData, CancelOrdinaryAgentOperationError, CancelOrdinaryAgentOperationErrors, CancelOrdinaryAgentOperationResponse, CancelOrdinaryAgentOperationResponses, ChangeImpactCoverage, ClientOptions, DataProvenance, DeployedTargetReference, DeploymentEvidenceOutput, DeploymentRecordOutput, DisconnectOrdinaryAgentPrincipalData, DisconnectOrdinaryAgentPrincipalError, DisconnectOrdinaryAgentPrincipalErrors, DisconnectOrdinaryAgentPrincipalResponse, DisconnectOrdinaryAgentPrincipalResponses, DispatchProductPromotionWorkflowData, DispatchProductPromotionWorkflowError, DispatchProductPromotionWorkflowErrors, DispatchProductPromotionWorkflowResponse, DispatchProductPromotionWorkflowResponses, DokployTargetIdRecord, DokployTargetPolicies, DokployTargetRecord, DokployTargetShopifyPolicy, DriverActionDescriptor, DriverCapabilityDescriptor, DriverContextView, DriverContextViewResponse, DriverDescriptor, DriverDescriptorsResponse, DriverSettingGroupDescriptor, DriverView, DryRunProductPromotionData, DryRunProductPromotionError, DryRunProductPromotionErrors, DryRunProductPromotionResponse, DryRunProductPromotionResponses, EnvironmentInventory, EveryCodeSummaryReadModel, EveryCodeSummaryResponse, EveryCodeWorkRequestRecord, EveryCodeWorkRequestRecordsResponse, EveryCodeWorkRequestSummary, GenericWebProdPromotionRecords, GitHubHumanIdentityResponse, GitHubIssueInboxIssue, GitHubIssueInboxReadModel, GitHubIssueInboxRepositoryGroup, GovernanceAdvisoryObservation, GovernanceLandingOutcomeFacet, GovernanceMergeAdmissionFacet, GovernanceMergeReadinessFacet, GovernanceOwnerHistoryEntry, GovernanceOwnerJudgmentFacet, GovernanceProjection, GovernanceProjectionResponse, HealthcheckEvidence, InspectionSetupManagedSecretMetadata, InspectionSetupMetadata, InspectionSetupRuntimeMetadata, LaunchplaneAuthzPolicyOutput, LaunchplaneErrorDetail, LaunchplaneErrorResponse, LaunchplaneLaneSummary, LaunchplanePreviewSummary, LaunchplaneProductProfileRecord, ListEveryCodeWorkRequestsData, ListEveryCodeWorkRequestsError, ListEveryCodeWorkRequestsErrors, ListEveryCodeWorkRequestsResponse, ListEveryCodeWorkRequestsResponses, ListHumanPrivilegedOperationsData, ListHumanPrivilegedOperationsError, ListHumanPrivilegedOperationsErrors, ListHumanPrivilegedOperationsResponse, ListHumanPrivilegedOperationsResponses, ListProductEnvironmentPublicIngressIncidentsData, ListProductEnvironmentPublicIngressIncidentsError, ListProductEnvironmentPublicIngressIncidentsErrors, ListProductEnvironmentPublicIngressIncidentsResponse, ListProductEnvironmentPublicIngressIncidentsResponses, ListProductEnvironmentsData, ListProductEnvironmentsError, ListProductEnvironmentsErrors, ListProductEnvironmentsResponse, ListProductEnvironmentsResponses, ListProductProfilesData, ListProductProfilesError, ListProductProfilesErrors, ListProductProfilesResponse, ListProductProfilesResponses, ListProductsData, ListProductsError, ListProductsErrors, ListProductsResponse, ListProductsResponses, ManagedAuthzPolicySetExecutionEvidence, ManagedAuthzPolicySetHumanEvidence, ManagedAuthzPolicySetProposalInputOutput, ManagedMergeTrainPolicyImportExecutionEvidence, ManagedMergeTrainPolicyImportHumanEvidence, ManagedMergeTrainPolicyImportProposalInputOutput, ManagedMergeTrainPolicyPreparationContextOutput, ManagedOrdinaryAgentPolicyPreparationContext, ManagedSecretReencryptionHumanEvidence, ManagedSecretReencryptionPlanInput, MergeAdmissionRecord, MergeBatchNoEffectEvidence, MergeLandingOutcomeRecord, MergeReadinessAdvisoryObservation, MergeReadinessCandidateEvidence, MergeReadinessCandidateFacet, MergeReadinessEngineeringEvidenceReference, MergeReadinessEngineeringReviewFacet, MergeReadinessFenceEvidence, MergeReadinessFenceFacet, MergeReadinessOwnerFacet, MergeReadinessPolicyFacet, MergeReadinessPolicyFingerprintEvidence, MergeReadinessPolicyFingerprints, MergeReadinessResult, MergeReadinessTarget, MergeReadinessTechnicalChecksFacet, MergeTrainAdmissionDecision, MergeTrainControllerLeaseDiagnostics, MergeTrainControllerRecordSummary, MergeTrainControllerStateRecord, MergeTrainControllerStatusReadModel, MergeTrainControllerStatusResponse, MergeTrainDryRunQueueEntrySummary, MergeTrainEnqueuePolicyInput, MergeTrainEnqueuePolicyOutput, MergeTrainGitHubAppSource, MergeTrainGitHubTokenSource, MergeTrainIdentity, MergeTrainLatestDryRunSummary, MergeTrainPolicyOutput, MergeTrainPolicyRecordOutput, MergeTrainPolicySummary, MergeTrainPolicyTarget, MergeTrainPolicyTargetsResponse, MergeTrainReconciliationDiagnostic, MergeTrainRepositoryPolicyOutput, MergeTrainRunRecord, MergeTrainSchedulerPolicy, MergeTrainServiceAuthz, MergeTrainStructuralCandidateResult, OdooAddonSettingOverride, OdooConfigParameterOverride, OdooInstanceOverrideRecord, OdooOverrideApplyResult, OdooOverrideValue, OdooWebsiteBootstrapPayload, OdooWebsiteBootstrapRoute, OrdinaryAgentConnectionView, OrdinaryAgentDeliveryActivationDurationOption, OrdinaryAgentDeliveryActivationExecutionEvidence, OrdinaryAgentDeliveryActivationOptionsResponse, OrdinaryAgentDeliveryActivationPlanEnvelope, OrdinaryAgentDeliveryActivationReference, OrdinaryAgentDeliveryActivationRevokeHumanEvidence, OrdinaryAgentDeliveryActivationRevokeOption, OrdinaryAgentDeliveryActivationRevokeRequest, OrdinaryAgentDeliveryActivationScope, OrdinaryAgentDeliveryActivationSetupHumanEvidence, OrdinaryAgentDeliveryActivationSetupOption, OrdinaryAgentDeliveryActivationSetupRequest, OrdinaryAgentDeliveryAuthorizationCandidateInputsResponse, OrdinaryAgentDeliveryAuthorizationCandidateRepository, OrdinaryAgentDeliveryInventoryReference, OrdinaryAgentDeliveryPolicyIntent, OrdinaryAgentDeliveryPolicyPackageReference, OrdinaryAgentDeliveryPolicyPrepareEnvelope, OrdinaryAgentDeliveryPolicyPrepareResponse, OrdinaryAgentDeliveryRuntimeCapabilityEvidence, OrdinaryAgentDisconnectRequest, OrdinaryAgentJobBinding, OrdinaryAgentJobView, OrdinaryAgentMergeTrainTargetInputsResponse, OrdinaryAgentMergeTrainTargetIntentInput, OrdinaryAgentMergeTrainTargetIntentOutput, OrdinaryAgentMergeTrainTargetInventoryInput, OrdinaryAgentMergeTrainTargetPolicyInput, OrdinaryAgentMergeTrainTargetPrepareEnvelope, OrdinaryAgentMergeTrainTargetPrepareResponse, OrdinaryAgentOperationClientResponse, OrdinaryAgentSessionAttenuation, OrdinaryAgentSessionLeaseSelector, OrdinaryAgentSessionOperationView, OrdinaryAgentTarget, OwnerAcceptanceAuthorization, OwnerAcceptanceBinding, OwnerAcceptanceContributionBinding, OwnerAcceptanceDecision, OwnerAcceptanceEventRecord, OwnerAcceptancePolicyFingerprintBinding, OwnerAcceptancePreviewBinding, OwnerAcceptancePreviewIsolationBinding, OwnerAcceptanceProductDecision, OwnerAcceptanceResolutionEvidence, OwnerAcceptanceReviewContext, OwnerAcceptanceRuntimeIdentityBinding, OwnerSecretInputEnvelope, OwnerSecretInputEnvelopeWritable, OwnerSecretInputField, OwnerSecretInputResponse, PlanOrdinaryAgentDeliveryActivationData, PlanOrdinaryAgentDeliveryActivationError, PlanOrdinaryAgentDeliveryActivationErrors, PlanOrdinaryAgentDeliveryActivationResponse, PlanOrdinaryAgentDeliveryActivationResponses, PostDeployUpdateEvidence, PrepareAuthorizationCandidateData, PrepareAuthorizationCandidateError, PrepareAuthorizationCandidateErrors, PrepareAuthorizationCandidateResponse, PrepareAuthorizationCandidateResponses, PrepareOrdinaryAgentDeliveryPolicyData, PrepareOrdinaryAgentDeliveryPolicyError, PrepareOrdinaryAgentDeliveryPolicyErrors, PrepareOrdinaryAgentDeliveryPolicyResponse, PrepareOrdinaryAgentDeliveryPolicyResponses, PrepareOrdinaryAgentMergeTrainTargetData, PrepareOrdinaryAgentMergeTrainTargetError, PrepareOrdinaryAgentMergeTrainTargetErrors, PrepareOrdinaryAgentMergeTrainTargetResponse, PrepareOrdinaryAgentMergeTrainTargetResponses, PreviewGenerationRecord, PreviewPullRequestSummary, PreviewReadinessItem, PreviewReadinessReadModel, PreviewReadinessResponse, PreviewRecord, PreviewSourceRecord, PrivilegedOperationActor, PrivilegedOperationAgentActor, PrivilegedOperationApproval, PrivilegedOperationApprovalEnvelope, PrivilegedOperationEventRecord, PrivilegedOperationExecutionEvidence, PrivilegedOperationHumanResponse, PrivilegedOperationListResponse, PrivilegedOperationRecord, PrivilegedOperationRevocationEnvelope, PrivilegedOperationSemanticReview, PrivilegedOperationSemanticReviewActivityEntry, PrivilegedOperationSemanticReviewBlastRadius, PrivilegedOperationSemanticReviewBlocker, PrivilegedOperationSemanticReviewChange, PrivilegedOperationSemanticReviewDigest, PrivilegedOperationSemanticReviewEvidence, PrivilegedOperationSemanticReviewLifecycle, PrivilegedOperationSemanticReviewMetric, PrivilegedOperationSemanticReviewResponse, PrivilegedOperationSemanticReviewRollback, ProductActionAvailability, ProductActivityEvent, ProductActivityReadModel, ProductActivityRecordLink, ProductActivityResponse, ProductConfigApplyResponse, ProductConfigApplyResult, ProductConfigApplySummary, ProductConfigInputWriteAvailability, ProductConfigLiveTarget, ProductConfigLiveTargetRuntimeNextAction, ProductConfigLiveTargetRuntimeOperation, ProductConfigOperationAvailability, ProductConfigRuntimeEnvironmentRecordSummary, ProductConfigRuntimeEnvironmentResult, ProductConfigRuntimeKeySafetyResult, ProductConfigSecretResult, ProductConfigWriteAvailability, ProductDesiredTopology, ProductEnvironmentConfigStatus, ProductEnvironmentConfigStatusResponse, ProductEnvironmentDetail, ProductEnvironmentDriverExtensions, ProductEnvironmentIncidentList, ProductEnvironmentIncidentResponse, ProductEnvironmentIncidentsResponse, ProductEnvironmentListResponse, ProductEnvironmentManagedSecretInput, ProductEnvironmentResponse, ProductEnvironmentsResponse, ProductEnvironmentSummary, ProductEnvironmentTopology, ProductExpectedConfigProfile, ProductHealthMonitoringCheckSummary, ProductHealthMonitoringSummary, ProductImageProfile, ProductIncidentDetail, ProductIncidentEventSummary, ProductIncidentMaterialEvidence, ProductIncidentNotificationAttemptSummary, ProductIncidentObservationSummary, ProductIncidentOutboxDeliverySummary, ProductIncidentReminderSummary, ProductIncidentSummary, ProductionBackupAuthorityStatus, ProductLaneHealthCheck, ProductLaneHealthMonitoringPolicy, ProductLaneProfile, ProductManagedSecretConfigStatusItem, ProductObservedIngress, ProductObservedPlacement, ProductObservedTlsDomain, ProductObservedTopology, ProductOdooEnvironmentExtension, ProductOdooLaneDataPolicy, ProductOdooPrelaunchRebuildPolicy, ProductOdooStableBootstrapPolicy, ProductOperationalReadiness, ProductOperationalReadinessAction, ProductOperationalReadinessCaller, ProductOperationalReadinessDimension, ProductOperationalReadinessEvidence, ProductOperationalReadinessRemediation, ProductOperationalReadinessResponse, ProductOverviewResponse, ProductOwnerProfile, ProductOwnerSecretInput, ProductPreviewProfile, ProductPreviewSummary, ProductProfileListResponse, ProductProfileResponse, ProductPromotionDryRunEnvelope, ProductPromotionDryRunResponse, ProductPromotionDryRunResult, ProductPromotionEvidence, ProductPromotionLiveConfirmations, ProductPromotionOperationAvailability, ProductPromotionStatus, ProductPromotionStatusResponse, ProductPromotionWorkflowDeliveryStatus, ProductPromotionWorkflowDeliveryStatusResponse, ProductPromotionWorkflowDispatchEnvelope, ProductPromotionWorkflowDispatchResponse, ProductPromotionWorkflowDispatchResult, ProductPromotionWorkflowProfile, ProductProviderRecordedTopology, ProductPublicIngressSummary, ProductReviewDecisionEnvelope, ProductReviewDecisionRecord, ProductReviewFeedbackRetryEnvelope, ProductReviewResponse, ProductRuntimeConfigRequirement, ProductRuntimeConfigStatusItem, ProductRuntimeSettingSummary, ProductSecretBindingSummary, ProductSecretConfigRequirement, ProductSiteOverview, ProductTargetSummary, ProductTopologyDomain, ProductTopologyIngress, ProductTopologyPlacement, ProductTopologyTlsOwnership, ProductTopologyWarning, PromotionRecordOutput, ProviderCodeScanningToolExpectationV1, ProviderDeliveryProtectionExpectationV1, ProviderPullRequestExpectationV1, ProviderRequiredStatusCheckExpectationV1, ProviderTargetRecord, RankWorkGraphSnapshotData, RankWorkGraphSnapshotError, RankWorkGraphSnapshotErrors, RankWorkGraphSnapshotResponse, RankWorkGraphSnapshotResponses, ReadDriverContextViewData, ReadDriverContextViewError, ReadDriverContextViewErrors, ReadDriverContextViewResponse, ReadDriverContextViewResponses, ReadDriverDescriptorsData, ReadDriverDescriptorsError, ReadDriverDescriptorsErrors, ReadDriverDescriptorsResponse, ReadDriverDescriptorsResponses, ReadDriverInstanceViewData, ReadDriverInstanceViewError, ReadDriverInstanceViewErrors, ReadDriverInstanceViewResponse, ReadDriverInstanceViewResponses, ReadEveryCodeSummaryData, ReadEveryCodeSummaryError, ReadEveryCodeSummaryErrors, ReadEveryCodeSummaryResponse, ReadEveryCodeSummaryResponses, ReadGovernanceProjectionData, ReadGovernanceProjectionError, ReadGovernanceProjectionErrors, ReadGovernanceProjectionResponse, ReadGovernanceProjectionResponses, ReadHumanAuthSessionData, ReadHumanAuthSessionError, ReadHumanAuthSessionErrors, ReadHumanAuthSessionResponse, ReadHumanAuthSessionResponses, ReadHumanOrdinaryAgentJobData, ReadHumanOrdinaryAgentJobError, ReadHumanOrdinaryAgentJobErrors, ReadHumanOrdinaryAgentJobResponse, ReadHumanOrdinaryAgentJobResponses, ReadHumanOrdinaryAgentOperationData, ReadHumanOrdinaryAgentOperationError, ReadHumanOrdinaryAgentOperationErrors, ReadHumanOrdinaryAgentOperationResponse, ReadHumanOrdinaryAgentOperationResponses, ReadHumanPrivilegedOperationData, ReadHumanPrivilegedOperationError, ReadHumanPrivilegedOperationErrors, ReadHumanPrivilegedOperationResponse, ReadHumanPrivilegedOperationResponses, ReadHumanPrivilegedOperationReviewData, ReadHumanPrivilegedOperationReviewError, ReadHumanPrivilegedOperationReviewErrors, ReadHumanPrivilegedOperationReviewResponse, ReadHumanPrivilegedOperationReviewResponses, ReadMergeTrainControllerStatusData, ReadMergeTrainControllerStatusError, ReadMergeTrainControllerStatusErrors, ReadMergeTrainControllerStatusResponse, ReadMergeTrainControllerStatusResponses, ReadMergeTrainPolicyTargetsData, ReadMergeTrainPolicyTargetsError, ReadMergeTrainPolicyTargetsErrors, ReadMergeTrainPolicyTargetsResponse, ReadMergeTrainPolicyTargetsResponses, ReadOrdinaryAgentDeliveryActivationOptionsData, ReadOrdinaryAgentDeliveryActivationOptionsError, ReadOrdinaryAgentDeliveryActivationOptionsErrors, ReadOrdinaryAgentDeliveryActivationOptionsResponse, ReadOrdinaryAgentDeliveryActivationOptionsResponses, ReadOrdinaryAgentDeliveryAuthorizationCandidateInputsData, ReadOrdinaryAgentDeliveryAuthorizationCandidateInputsError, ReadOrdinaryAgentDeliveryAuthorizationCandidateInputsErrors, ReadOrdinaryAgentDeliveryAuthorizationCandidateInputsResponse, ReadOrdinaryAgentDeliveryAuthorizationCandidateInputsResponses, ReadOrdinaryAgentMergeTrainTargetInputsData, ReadOrdinaryAgentMergeTrainTargetInputsError, ReadOrdinaryAgentMergeTrainTargetInputsErrors, ReadOrdinaryAgentMergeTrainTargetInputsResponse, ReadOrdinaryAgentMergeTrainTargetInputsResponses, ReadOwnerSecretInputsData, ReadOwnerSecretInputsError, ReadOwnerSecretInputsErrors, ReadOwnerSecretInputsResponse, ReadOwnerSecretInputsResponses, ReadPreviewReadinessData, ReadPreviewReadinessError, ReadPreviewReadinessErrors, ReadPreviewReadinessResponse, ReadPreviewReadinessResponses, ReadProductActivityData, ReadProductActivityError, ReadProductActivityErrors, ReadProductActivityResponse, ReadProductActivityResponses, ReadProductData, ReadProductEnvironmentConfigStatusData, ReadProductEnvironmentConfigStatusError, ReadProductEnvironmentConfigStatusErrors, ReadProductEnvironmentConfigStatusResponse, ReadProductEnvironmentConfigStatusResponses, ReadProductEnvironmentData, ReadProductEnvironmentError, ReadProductEnvironmentErrors, ReadProductEnvironmentPublicIngressIncidentData, ReadProductEnvironmentPublicIngressIncidentError, ReadProductEnvironmentPublicIngressIncidentErrors, ReadProductEnvironmentPublicIngressIncidentResponse, ReadProductEnvironmentPublicIngressIncidentResponses, ReadProductEnvironmentResponse, ReadProductEnvironmentResponses, ReadProductError, ReadProductErrors, ReadProductOperationalReadinessData, ReadProductOperationalReadinessError, ReadProductOperationalReadinessErrors, ReadProductOperationalReadinessResponse, ReadProductOperationalReadinessResponses, ReadProductProfileData, ReadProductProfileError, ReadProductProfileErrors, ReadProductProfileResponse, ReadProductProfileResponses, ReadProductPromotionStatusData, ReadProductPromotionStatusError, ReadProductPromotionStatusErrors, ReadProductPromotionStatusResponse, ReadProductPromotionStatusResponses, ReadProductPromotionWorkflowDeliveryData, ReadProductPromotionWorkflowDeliveryError, ReadProductPromotionWorkflowDeliveryErrors, ReadProductPromotionWorkflowDeliveryResponse, ReadProductPromotionWorkflowDeliveryResponses, ReadProductResponse, ReadProductResponses, ReadProductReviewData, ReadProductReviewError, ReadProductReviewErrors, ReadProductReviewResponse, ReadProductReviewResponses, ReadReleaseReviewData, ReadReleaseReviewError, ReadReleaseReviewErrors, ReadReleaseReviewResponse, ReadReleaseReviewResponses, ReadRepoProductMappingData, ReadRepoProductMappingError, ReadRepoProductMappingErrors, ReadRepoProductMappingResponse, ReadRepoProductMappingResponses, ReadTenantAdmissionEvaluationData, ReadTenantAdmissionEvaluationError, ReadTenantAdmissionEvaluationErrors, ReadTenantAdmissionEvaluationResponse, ReadTenantAdmissionEvaluationResponses, ReadWorkGraphIssueInboxData, ReadWorkGraphIssueInboxError, ReadWorkGraphIssueInboxErrors, ReadWorkGraphIssueInboxResponse, ReadWorkGraphIssueInboxResponses, ReadWorkGraphSnapshotData, ReadWorkGraphSnapshotError, ReadWorkGraphSnapshotErrors, ReadWorkGraphSnapshotResponse, ReadWorkGraphSnapshotResponses, ReleaseChecklist, ReleaseReviewDecisionEnvelope, ReleaseReviewDecisionRecord, ReleaseReviewItem, ReleaseReviewResponse, ReleaseReviewStatus, ReleaseTupleRecord, ReleaseVersion, RepoProductMapping, RepoProductMappingEntry, RepoProductMappingResponse, RepositoryTarget, ResolvedTargetEvidence, RetryProductReviewFeedbackData, RetryProductReviewFeedbackError, RetryProductReviewFeedbackErrors, RetryProductReviewFeedbackResponse, RetryProductReviewFeedbackResponses, RevokeHumanPrivilegedOperationData, RevokeHumanPrivilegedOperationError, RevokeHumanPrivilegedOperationErrors, RevokeHumanPrivilegedOperationResponse, RevokeHumanPrivilegedOperationResponses, RevokeOrdinaryAgentSessionData, RevokeOrdinaryAgentSessionError, RevokeOrdinaryAgentSessionErrors, RevokeOrdinaryAgentSessionResponse, RevokeOrdinaryAgentSessionResponses, RollbackExecutionEvidence, RuntimeEnvironmentRecord, RuntimeIdentity, RuntimeKeySafetyFinding, RuntimeKeySafetyTarget, SecretBinding, StructuredHealthEvidence, SubmitOwnerSecretInputData, SubmitOwnerSecretInputError, SubmitOwnerSecretInputErrors, SubmitOwnerSecretInputResponse, SubmitOwnerSecretInputResponses, TenantAdmissionControllerRunOnceResult, TenantAdmissionEvaluationReadModel, TenantAdmissionEvaluationReadResponse, TenantAdmissionPathResult, TenantAdmissionPullRequestFacts, TenantAdmissionRequiredTechnicalCheck, TenantAdmissionStatusReadModel, TenantAdmissionTechnicalChecks, TenantAdmissionTechnicalCheckSignal, TenantMergeCandidate, TenantMergeEligibilityDecision, TenantMergeEligibilityEvidenceInputs, TerminalEnrollmentCapabilityReadiness, WorkGraphIssueInboxResponse, WorkGraphIssueSnapshot, WorkGraphQueue, WorkGraphQueueEvidence, WorkGraphQueueItem, WorkGraphRankEnvelope, WorkGraphRankResponse, WorkGraphRankResult, WorkGraphRecommendationReason, WorkGraphRepoSnapshot, WorkGraphSnapshot, WorkGraphSnapshotResponse, WriteProductReviewDecisionData, WriteProductReviewDecisionError, WriteProductReviewDecisionErrors, WriteProductReviewDecisionResponse, WriteProductReviewDecisionResponses, WriteReleaseReviewDecisionData, WriteReleaseReviewDecisionError, WriteReleaseReviewDecisionErrors, WriteReleaseReviewDecisionResponse, WriteReleaseReviewDecisionResponses } from './types.gen'; diff --git a/frontend/src/generated/openapi.ts/types.gen.ts b/frontend/src/generated/openapi.ts/types.gen.ts index 73cfef1dd..b8f7f7c47 100644 --- a/frontend/src/generated/openapi.ts/types.gen.ts +++ b/frontend/src/generated/openapi.ts/types.gen.ts @@ -298,16 +298,6 @@ export type ChangeImpactCoverage = { unmatched_path_samples: Array; }; -export type ChangeImpactTarget = { - head_sha: string; - pull_request_number: number; - repository: string; - repository_id: string; - repository_owner_id: string; - schema_version: number; - tree_sha: string; -}; - export type DataProvenance = { detail: string; freshness_status: 'verified' | 'recorded' | 'stale' | 'missing' | 'unsupported'; @@ -711,7 +701,7 @@ export type GovernanceProjection = { mode: 'read_only_projection'; owner_judgment: GovernanceOwnerJudgmentFacet | null; schema_version: 1; - target: ChangeImpactTarget; + target: RepositoryTarget; }; export type GovernanceProjectionResponse = { @@ -3677,6 +3667,16 @@ export type RepoProductMappingResponse = { trace_id: string; }; +export type RepositoryTarget = { + head_sha: string; + pull_request_number: number; + repository: string; + repository_id: string; + repository_owner_id: string; + schema_version: number; + tree_sha: string; +}; + export type ResolvedTargetEvidence = { target_id: string; target_name: string; diff --git a/tests/fixtures/retired-approval/manager-event.json b/tests/fixtures/retired-approval/manager-event.json new file mode 100644 index 000000000..0da109d82 --- /dev/null +++ b/tests/fixtures/retired-approval/manager-event.json @@ -0,0 +1,58 @@ +{ + "schema_version": 1, + "event_id": "manager-preview-approval-event-58ee611edefdafe7ae004607b4ece96d", + "approval_id": "manager-preview-approval-98c7eff04a6a35e937fde2f405e26fb5", + "binding": { + "schema_version": 1, + "product": "atlas-commerce", + "context": "atlas-commerce", + "repository": "atlas-commerce", + "pr_number": 17, + "pr_url": "https://github.com/example/atlas-commerce/pull/17", + "head_sha": "1111111111111111111111111111111111111111", + "preview_id": "preview-atlas-17", + "serving_generation_id": "generation-atlas-17", + "artifact_id": "preview-artifact-atlas-17", + "artifact_image_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "manifest_fingerprint": "manifest-atlas-17", + "preview_url": "https://pr-17.atlas.example.test/", + "runtime_identity": { + "schema_version": 1, + "product": "atlas-commerce", + "context": "atlas-commerce", + "instance": "pr-17", + "environment_kind": "preview", + "deployment_record_id": "deployment-preview-atlas-17", + "artifact_id": "preview-artifact-atlas-17", + "source_git_ref": "1111111111111111111111111111111111111111", + "image_reference": "ghcr.io/example/atlas-commerce@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "release_tuple_id": "", + "preview_id": "preview-atlas-17", + "preview_generation_id": "generation-atlas-17", + "deployed_at": "2026-07-15T08:35:00Z" + }, + "runtime_identity_sha256": "6c084c211e0ecbf1d28fbefbc50490efaef55ec402490a1a83e7ab633c861c19", + "binding_sha256": "98c7eff04a6a35e937fde2f405e26fb509a3bf29d1809da3dbc38a9aa63963aa" + }, + "action": "approved", + "occurred_at": "2026-07-15T08:56:00Z", + "source_event_kind": "github_issue_comment", + "source_event_id": "comment-approval-17", + "reason": "", + "authorization": { + "schema_version": 1, + "role": "manager", + "action": "manager_preview_approval.write", + "manager_github_id": 101, + "manager_login": "manager", + "managed_set_id": "manager.atlas-commerce", + "managed_rule_id": "preview-approval", + "policy_record_id": "launchplane-authz-policy-r00000000000000000001-2d66780bd1bb", + "policy_revision": 1, + "policy_schema_version": 2, + "policy_sha256": "2d66780bd1bbb2e3c56bd4ddfb63f183df7236a7692d4ae53561eccd97ed8666", + "policy_source": "test:manager-preview-promotion", + "role_policy_provenance": null, + "authorized_at": "2026-07-15T08:56:00Z" + } +} diff --git a/tests/support/ordinary_agent_qualification.py b/tests/support/ordinary_agent_qualification.py index c82005164..1cc8f402e 100644 --- a/tests/support/ordinary_agent_qualification.py +++ b/tests/support/ordinary_agent_qualification.py @@ -15,7 +15,7 @@ authz_policy_sha256, build_authz_policy_record_id, ) -from control_plane.contracts.change_impact import ( +from control_plane.contracts.retired_change_impact import ( ChangeImpactComponentRule, ChangeImpactPolicyRecord, ) diff --git a/tests/support/repository_evidence.py b/tests/support/repository_evidence.py index bbf48cdc4..2934034ae 100644 --- a/tests/support/repository_evidence.py +++ b/tests/support/repository_evidence.py @@ -1,13 +1,15 @@ from pathlib import Path -from control_plane.change_impact_service import ChangeImpactRepositoryEvidenceProvider -from control_plane.contracts.change_impact import ( - ChangeImpactAuthorshipEvidence, - ChangeImpactBaseEvidence, - ChangeImpactChangedFileEvidence, - ChangeImpactRepositoryEvidence, - ChangeImpactTarget, - ChangeImpactTargetReference, +from control_plane.contracts.repository_evidence import ( + RepositoryEvidenceProvider, +) +from control_plane.contracts.repository_evidence import ( + RepositoryAuthorshipEvidence, + RepositoryBaseEvidence, + RepositoryChangedFileEvidence, + RepositoryEvidence, + RepositoryTarget, + RepositoryTargetReference, ) from control_plane.service_auth import GitHubHumanIdentity from control_plane.storage.filesystem import FilesystemRecordStore @@ -23,11 +25,11 @@ BASE_REF = "main" -class _EvidenceProvider(ChangeImpactRepositoryEvidenceProvider): - def __init__(self, evidence: ChangeImpactRepositoryEvidence) -> None: +class _EvidenceProvider(RepositoryEvidenceProvider): + def __init__(self, evidence: RepositoryEvidence) -> None: self.evidence = evidence - def resolve(self, target: ChangeImpactTargetReference) -> ChangeImpactRepositoryEvidence: + def resolve(self, target: RepositoryTargetReference) -> RepositoryEvidence: return self.evidence @@ -56,10 +58,10 @@ def _repository_evidence( path: str = "src/runtime/app.py", head: str = HEAD_SHA, base_sha: str = BASE_SHA, - authorship: ChangeImpactAuthorshipEvidence | None = None, -) -> ChangeImpactRepositoryEvidence: - return ChangeImpactRepositoryEvidence( - target=ChangeImpactTarget( + authorship: RepositoryAuthorshipEvidence | None = None, +) -> RepositoryEvidence: + return RepositoryEvidence( + target=RepositoryTarget( repository_id=REPOSITORY_ID, repository_owner_id=REPOSITORY_OWNER_ID, repository=REPOSITORY, @@ -67,10 +69,10 @@ def _repository_evidence( head_sha=head, tree_sha=TREE_SHA, ), - changed_files=(ChangeImpactChangedFileEvidence(path=path),), - base=ChangeImpactBaseEvidence(base_ref=BASE_REF, base_sha=base_sha), + changed_files=(RepositoryChangedFileEvidence(path=path),), + base=RepositoryBaseEvidence(base_ref=BASE_REF, base_sha=base_sha), authorship=authorship - or ChangeImpactAuthorshipEvidence( + or RepositoryAuthorshipEvidence( resolution="resolved", contributor_github_ids=(CONTRIBUTOR_GITHUB_ID,), commit_count=1, diff --git a/tests/support/retired_approval_history.py b/tests/support/retired_approval_history.py new file mode 100644 index 000000000..923f22dc4 --- /dev/null +++ b/tests/support/retired_approval_history.py @@ -0,0 +1,129 @@ +from __future__ import annotations + +from datetime import datetime, timezone +from control_plane.contracts.retired_change_impact_audit import ChangeImpactPolicyAuditRecord + + +from control_plane.contracts.repository_evidence import ( + RepositoryChangedFileEvidence, + RepositoryEvidence, + RepositoryTarget, +) +from control_plane.contracts.retired_change_impact import ( + ChangeImpactComponentRule, + ChangeImpactPolicyRecord, + ChangeImpactProductScope, + ChangeImpactStoredEvidence, +) + + +REPOSITORY_ID = "1001" +REPOSITORY_OWNER_ID = "2001" +REPOSITORY = "example/shared-addons" +HEAD_SHA = "a" * 40 +TREE_SHA = "b" * 40 +MERGE_SHA = "d" * 40 + + +def _product(product: str) -> ChangeImpactProductScope: + return ChangeImpactProductScope(product=product, system="web") + + +def _policy( + *, + revision: int = 1, + supersedes_record_id: str | None = None, + effective_at: str = "2026-08-06T00:00:00Z", +) -> ChangeImpactPolicyRecord: + return ChangeImpactPolicyRecord( + repository_id=REPOSITORY_ID, + repository_owner_id=REPOSITORY_OWNER_ID, + repository=REPOSITORY, + policy_revision=revision, + component_rules=( + ChangeImpactComponentRule( + component="generic-web-runtime", + path_prefixes=("src/runtime",), + affected_products=(_product("generic-web-a"),), + review_tier="routine", + reason="Runtime code reaches one generic web product.", + ), + ChangeImpactComponentRule( + component="odoo-shared-addon", + path_prefixes=("addons/shared",), + affected_products=(_product("cm-odoo"), _product("opw-odoo")), + review_tier="routine", + reason="Shared addon reaches multiple Odoo products.", + ), + ChangeImpactComponentRule( + component="billing-policy", + path_prefixes=("control_plane/billing",), + affected_products=(), + review_tier="sensitive", + reason="Billing/governance policy is sensitive engineering work.", + ), + ChangeImpactComponentRule( + component="engineering-ci", + path_prefixes=(".github/workflows",), + affected_products=(), + review_tier="routine", + reason="CI-only engineering change has no product runtime effect.", + ), + ), + effective_at=effective_at, + source="test", + reason="Exercise change impact policy.", + supersedes_record_id=supersedes_record_id, + ) + + +def _repository_evidence( + *paths: str, + head_sha: str = HEAD_SHA, +) -> RepositoryEvidence: + return RepositoryEvidence( + target=RepositoryTarget( + repository_id=REPOSITORY_ID, + repository_owner_id=REPOSITORY_OWNER_ID, + repository=REPOSITORY, + pull_request_number=20, + head_sha=head_sha, + tree_sha=TREE_SHA, + ), + merge_commit_sha=MERGE_SHA, + changed_files=tuple(RepositoryChangedFileEvidence(path=path) for path in paths), + ) + + +def _stored_evidence( + component: str, + *, + kind: str = "dependency", + products: tuple[ChangeImpactProductScope, ...] = (), + confidence: str = "known", +) -> ChangeImpactStoredEvidence: + return ChangeImpactStoredEvidence.model_validate( + { + "record_id": f"stored-{kind}-{component}", + "component": component, + "kind": kind, + "affected_products": products, + "confidence": confidence, + "reason": "Launchplane storage binds this evidence to the exact target.", + } + ) + + +def _audit(subject: str = "operator:first", revision: int = 1) -> ChangeImpactPolicyAuditRecord: + policy = _policy( + revision=revision, + supersedes_record_id=_policy().record_id if revision > 1 else None, + ) + return ChangeImpactPolicyAuditRecord( + record_id=policy.record_id, + policy_digest=policy.policy_digest, + actor_kind="local_operator", + actor_subject=subject, + trace_id="trace-original", + recorded_at=datetime(2026, 8, 7, tzinfo=timezone.utc), + ) diff --git a/tests/test_agent_operator_contract.py b/tests/test_agent_operator_contract.py index 37d3815d7..c55459d2b 100644 --- a/tests/test_agent_operator_contract.py +++ b/tests/test_agent_operator_contract.py @@ -58,8 +58,6 @@ def test_exact_operations_dependencies_and_workflows(self) -> None: ("GET", "/v1/agent/context"): "read_agent_context", ("POST", "/v1/agent/write-intents/evaluate"): "evaluate_agent_write_intent", ("POST", "/v1/product-config/apply"): "apply_product_config", - ("POST", "/v1/change-impact/policies/apply"): "apply_change_impact_policy", - ("GET", "/v1/change-impact/policy"): "read_change_impact_policy", ( "POST", "/v1/work-graph/merge-train/controller/run-once", @@ -89,8 +87,6 @@ def test_exact_operations_dependencies_and_workflows(self) -> None: "read_agent_context": ["read_identity"], "evaluate_agent_write_intent": ["read_browser_mutation_identity"], "apply_product_config": ["read_browser_mutation_identity"], - "apply_change_impact_policy": ["read_write_identity"], - "read_change_impact_policy": ["read_identity"], "write_merge_train_controller_run_once": ["read_write_identity"], "remediate_preview_pr_feedback": ["read_write_identity"], "execute_product_retirement": ["read_write_identity"], @@ -108,17 +104,6 @@ def test_exact_operations_dependencies_and_workflows(self) -> None: ) self.assertRegex(operation["schema_fingerprint_sha256"], r"^[0-9a-f]{64}$") - change_impact = next( - operation - for operation in operations - if operation["path"] == "/v1/change-impact/policies/apply" - ) - self.assertEqual(change_impact["idempotency"], "none") - self.assertEqual( - change_impact["reviewed_evidence"], - ["reviewed_dry_run", "expected_policy_digest"], - ) - self.assertEqual(artifact["contract"]["protected_workflows"], list(PROTECTED_WORKFLOWS)) self.assertEqual(artifact["contract"]["invariants"], INVARIANTS) @@ -208,8 +193,8 @@ def test_idempotency_metadata_must_match_live_openapi_parameters(self) -> None: build_agent_operator_contract(openapi_document=missing_header) unexpected_header = copy.deepcopy(document) - change_impact = unexpected_header["paths"]["/v1/change-impact/policies/apply"]["post"] - change_impact.setdefault("parameters", []).append( + context = unexpected_header["paths"]["/v1/agent/context"]["get"] + context.setdefault("parameters", []).append( { "in": "header", "name": "Idempotency-Key", diff --git a/tests/test_change_impact.py b/tests/test_change_impact.py deleted file mode 100644 index f13928dae..000000000 --- a/tests/test_change_impact.py +++ /dev/null @@ -1,370 +0,0 @@ -from __future__ import annotations - -from datetime import datetime, timedelta, timezone -from pathlib import Path -from tempfile import TemporaryDirectory -import unittest - -from pydantic import ValidationError - -from control_plane.change_impact_service import ( - ChangeImpactPolicyConflictError, - ChangeImpactPolicySequenceError, - ChangeImpactTrustedCallerBinding, - apply_change_impact_policy, - evaluate_change_impact, -) -from control_plane.contracts.change_impact import ( - ChangeImpactChangedFileEvidence, - ChangeImpactComponentRule, - ChangeImpactPolicyRecord, - ChangeImpactProductScope, - ChangeImpactRepositoryEvidence, - ChangeImpactStoredEvidence, - ChangeImpactTarget, -) -from control_plane.storage.filesystem import FilesystemRecordStore -from control_plane.storage.postgres import PostgresRecordStore - - -REPOSITORY_ID = "1001" -REPOSITORY_OWNER_ID = "2001" -REPOSITORY = "example/shared-addons" -HEAD_SHA = "a" * 40 -TREE_SHA = "b" * 40 -MERGE_SHA = "d" * 40 - - -def _product(product: str) -> ChangeImpactProductScope: - return ChangeImpactProductScope(product=product, system="web") - - -def _policy( - *, - revision: int = 1, - supersedes_record_id: str | None = None, - effective_at: str = "2026-08-06T00:00:00Z", -) -> ChangeImpactPolicyRecord: - return ChangeImpactPolicyRecord( - repository_id=REPOSITORY_ID, - repository_owner_id=REPOSITORY_OWNER_ID, - repository=REPOSITORY, - policy_revision=revision, - component_rules=( - ChangeImpactComponentRule( - component="generic-web-runtime", - path_prefixes=("src/runtime",), - affected_products=(_product("generic-web-a"),), - review_tier="routine", - reason="Runtime code reaches one generic web product.", - ), - ChangeImpactComponentRule( - component="odoo-shared-addon", - path_prefixes=("addons/shared",), - affected_products=(_product("cm-odoo"), _product("opw-odoo")), - review_tier="routine", - reason="Shared addon reaches multiple Odoo products.", - ), - ChangeImpactComponentRule( - component="billing-policy", - path_prefixes=("control_plane/billing",), - affected_products=(), - review_tier="sensitive", - reason="Billing/governance policy is sensitive engineering work.", - ), - ChangeImpactComponentRule( - component="engineering-ci", - path_prefixes=(".github/workflows",), - affected_products=(), - review_tier="routine", - reason="CI-only engineering change has no product runtime effect.", - ), - ), - effective_at=effective_at, - source="test", - reason="Exercise change impact policy.", - supersedes_record_id=supersedes_record_id, - ) - - -def _repository_evidence( - *paths: str, - head_sha: str = HEAD_SHA, -) -> ChangeImpactRepositoryEvidence: - return ChangeImpactRepositoryEvidence( - target=ChangeImpactTarget( - repository_id=REPOSITORY_ID, - repository_owner_id=REPOSITORY_OWNER_ID, - repository=REPOSITORY, - pull_request_number=20, - head_sha=head_sha, - tree_sha=TREE_SHA, - ), - merge_commit_sha=MERGE_SHA, - changed_files=tuple(ChangeImpactChangedFileEvidence(path=path) for path in paths), - ) - - -def _stored_evidence( - component: str, - *, - kind: str = "dependency", - products: tuple[ChangeImpactProductScope, ...] = (), - confidence: str = "known", -) -> ChangeImpactStoredEvidence: - return ChangeImpactStoredEvidence.model_validate( - { - "record_id": f"stored-{kind}-{component}", - "component": component, - "kind": kind, - "affected_products": products, - "confidence": confidence, - "reason": "Launchplane storage binds this evidence to the exact target.", - } - ) - - -class ChangeImpactEvaluationTests(unittest.TestCase): - def test_policy_declared_product_scope_is_authoritative_and_storage_can_extend_it(self) -> None: - policy = _policy() - without_storage = evaluate_change_impact( - repository_evidence=_repository_evidence("src/runtime/app.py"), - policies=(policy,), - evaluated_at="2026-08-06T01:00:00Z", - ) - self.assertEqual(without_storage.status, "success") - self.assertEqual(without_storage.engineering_review_tier, "routine") - self.assertEqual(without_storage.required_engineering_review_count, 1) - self.assertEqual(without_storage.owner_impact, "required") - - evaluation = evaluate_change_impact( - repository_evidence=_repository_evidence("src/runtime/app.py"), - policies=(policy,), - stored_evidence=( - _stored_evidence( - "generic-web-runtime", - products=(_product("generic-web-a"),), - ), - ), - evaluated_at="2026-08-06T01:00:00Z", - ) - self.assertEqual(evaluation.status, "success") - self.assertEqual(evaluation.engineering_review_tier, "routine") - self.assertEqual(evaluation.required_engineering_review_count, 1) - self.assertEqual(evaluation.owner_impact, "required") - self.assertEqual( - tuple(item.product for item in evaluation.affected_products), ("generic-web-a",) - ) - self.assertEqual(evaluation.policy_revision, policy.policy_revision) - self.assertEqual(evaluation.policy_digest, policy.policy_digest) - - def test_multi_product_and_engineering_only_paths_remain_deterministic(self) -> None: - policy = _policy() - multi_product = evaluate_change_impact( - repository_evidence=_repository_evidence("addons/shared/models.py"), - policies=(policy,), - stored_evidence=(_stored_evidence("odoo-shared-addon"),), - evaluated_at="2026-08-06T01:00:00Z", - ) - self.assertEqual(multi_product.status, "success") - self.assertEqual( - tuple(item.product for item in multi_product.affected_products), - ("cm-odoo", "opw-odoo"), - ) - - engineering_only = evaluate_change_impact( - repository_evidence=_repository_evidence(".github/workflows/ci.yml"), - policies=(policy,), - evaluated_at="2026-08-06T01:00:00Z", - ) - self.assertEqual(engineering_only.status, "success") - self.assertEqual(engineering_only.engineering_review_tier, "routine") - self.assertEqual(engineering_only.owner_impact, "not_required") - self.assertIsNotNone(engineering_only.coverage) - assert engineering_only.coverage is not None - self.assertEqual( - engineering_only.coverage.model_dump(), - { - "state": "complete", - "unmatched_path_count": 0, - "unmatched_path_samples": (), - "truncated": False, - }, - ) - - def test_sensitive_path_cannot_be_downgraded_by_stored_evidence(self) -> None: - policy = _policy() - evaluation = evaluate_change_impact( - repository_evidence=_repository_evidence("control_plane/billing/rules.py"), - policies=(policy,), - stored_evidence=( - _stored_evidence("billing-policy", kind="dependency"), - _stored_evidence("billing-policy", kind="reviewer"), - ), - evaluated_at="2026-08-06T01:00:00Z", - ) - self.assertEqual(evaluation.status, "success") - self.assertEqual(evaluation.engineering_review_tier, "sensitive") - self.assertEqual(evaluation.required_engineering_review_count, 2) - self.assertEqual( - tuple(item.source for item in evaluation.matched_evidence), - ("server_diff", "launchplane_dependency", "launchplane_reviewer"), - ) - - def test_reviewer_evidence_cannot_replace_dependency_authority(self) -> None: - policy = _policy() - evaluation = evaluate_change_impact( - repository_evidence=_repository_evidence("src/runtime/app.py"), - policies=(policy,), - stored_evidence=( - _stored_evidence( - "generic-web-runtime", - kind="reviewer", - products=(_product("generic-web-a"),), - ), - ), - evaluated_at="2026-08-06T01:00:00Z", - ) - - self.assertEqual(evaluation.status, "unknown") - self.assertEqual(evaluation.engineering_review_tier, "sensitive") - self.assertEqual(evaluation.required_engineering_review_count, 2) - self.assertIn( - "reviewer evidence lacks trusted dependency evidence", evaluation.unknown_evidence[0] - ) - - def test_unknown_path_and_ambiguous_storage_fail_closed(self) -> None: - policy = _policy() - unknown_path = evaluate_change_impact( - repository_evidence=_repository_evidence("unmapped/file.py"), - policies=(policy,), - evaluated_at="2026-08-06T01:00:00Z", - ) - self.assertEqual(unknown_path.status, "unknown") - self.assertEqual(unknown_path.engineering_review_tier, "sensitive") - - ambiguous = evaluate_change_impact( - repository_evidence=_repository_evidence("src/runtime/app.py"), - policies=(policy,), - stored_evidence=(_stored_evidence("generic-web-runtime", confidence="ambiguous"),), - evaluated_at="2026-08-06T01:00:00Z", - ) - self.assertEqual(ambiguous.status, "unknown") - self.assertEqual(ambiguous.required_engineering_review_count, 2) - - def test_trusted_oidc_repository_and_head_binding_fails_closed(self) -> None: - policy = _policy() - stale_head = evaluate_change_impact( - repository_evidence=_repository_evidence(".github/workflows/ci.yml"), - policies=(policy,), - trusted_caller_binding=ChangeImpactTrustedCallerBinding( - repository=REPOSITORY, - repository_id=REPOSITORY_ID, - repository_owner_id=REPOSITORY_OWNER_ID, - head_sha="c" * 40, - ), - evaluated_at="2026-08-06T01:00:00Z", - ) - self.assertEqual(stale_head.status, "stale_head") - self.assertEqual(stale_head.required_engineering_review_count, 2) - self.assertIsNone(stale_head.coverage) - - merge_ref = evaluate_change_impact( - repository_evidence=_repository_evidence(".github/workflows/ci.yml"), - policies=(policy,), - trusted_caller_binding=ChangeImpactTrustedCallerBinding( - repository=REPOSITORY, - repository_id=REPOSITORY_ID, - repository_owner_id=REPOSITORY_OWNER_ID, - head_sha=MERGE_SHA, - ref="refs/pull/20/merge", - ), - evaluated_at="2026-08-06T01:00:00Z", - ) - self.assertEqual(merge_ref.status, "success") - - wrong_repository = evaluate_change_impact( - repository_evidence=_repository_evidence(".github/workflows/ci.yml"), - policies=(policy,), - trusted_caller_binding=ChangeImpactTrustedCallerBinding( - repository="example/other", - repository_id="9999", - repository_owner_id=REPOSITORY_OWNER_ID, - head_sha=HEAD_SHA, - ), - evaluated_at="2026-08-06T01:00:00Z", - ) - self.assertEqual(wrong_repository.status, "unknown") - self.assertEqual(wrong_repository.reason_code, "caller_repository_mismatch") - - def test_change_impact_outputs_are_frozen(self) -> None: - evaluation = evaluate_change_impact( - repository_evidence=_repository_evidence(".github/workflows/ci.yml"), - policies=(_policy(),), - evaluated_at="2026-08-06T01:00:00Z", - ) - with self.assertRaises(ValidationError): - evaluation.status = "unknown" - - def test_policy_storage_revisions_round_trip_filesystem_and_sqlite(self) -> None: - first = _policy() - second = _policy(revision=2, supersedes_record_id=first.record_id) - with TemporaryDirectory() as directory: - filesystem_store = FilesystemRecordStore(Path(directory) / "records") - self.assertEqual( - apply_change_impact_policy(store=filesystem_store, record=first).status, - "applied", - ) - self.assertEqual( - apply_change_impact_policy( - store=filesystem_store, - record=second, - expected_current_record_id=first.record_id, - expected_current_policy_digest=first.policy_digest, - ).status, - "applied", - ) - self.assertEqual( - filesystem_store.list_change_impact_policy_records(status="active")[0].record_id, - second.record_id, - ) - with self.assertRaises(ChangeImpactPolicyConflictError): - apply_change_impact_policy(store=filesystem_store, record=first) - with self.assertRaises(ChangeImpactPolicyConflictError): - apply_change_impact_policy( - store=filesystem_store, - record=_policy(revision=3, supersedes_record_id=second.record_id), - expected_current_record_id=first.record_id, - expected_current_policy_digest=first.policy_digest, - ) - - with TemporaryDirectory() as directory: - postgres_store = PostgresRecordStore( - database_url=f"sqlite+pysqlite:///{Path(directory) / 'records.db'}" - ) - postgres_store.ensure_schema() - postgres_store.write_change_impact_policy_record(first) - self.assertEqual( - postgres_store.read_change_impact_policy_record(first.record_id).policy_digest, - first.policy_digest, - ) - - def test_dry_run_rejects_future_effective_policy(self) -> None: - future_effective_at = ( - (datetime.now(timezone.utc) + timedelta(days=1)) - .replace(microsecond=0) - .isoformat() - .replace("+00:00", "Z") - ) - with TemporaryDirectory() as directory: - store = FilesystemRecordStore(Path(directory)) - with self.assertRaises(ChangeImpactPolicySequenceError): - apply_change_impact_policy( - store=store, - record=_policy(effective_at=future_effective_at), - mode="dry_run", - ) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_change_impact_coverage.py b/tests/test_change_impact_coverage.py deleted file mode 100644 index 004ee6635..000000000 --- a/tests/test_change_impact_coverage.py +++ /dev/null @@ -1,111 +0,0 @@ -import unittest - -from control_plane.change_impact_service import evaluate_change_impact -from tests.test_change_impact import _policy, _repository_evidence, _stored_evidence - - -class ChangeImpactCoverageTests(unittest.TestCase): - def test_pure_gaps_are_sorted_bounded_and_still_fail_closed(self) -> None: - paths = tuple(f"unmapped/{index:03}.py" for index in range(25)) - forward = evaluate_change_impact( - repository_evidence=_repository_evidence(*paths), - policies=(_policy(),), - evaluated_at="2026-08-06T01:00:00Z", - ) - reversed_paths = evaluate_change_impact( - repository_evidence=_repository_evidence(*reversed(paths)), - policies=(_policy(),), - evaluated_at="2026-08-06T01:00:00Z", - ) - self.assertEqual(forward, reversed_paths) - self.assertEqual(forward.reason_code, "policy_coverage_incomplete") - self.assertEqual( - ( - forward.status, - forward.engineering_review_tier, - forward.required_engineering_review_count, - forward.owner_impact, - forward.affected_products, - ), - ("unknown", "sensitive", 2, "unknown", ()), - ) - assert forward.coverage is not None - self.assertEqual( - forward.coverage.model_dump(), - { - "state": "incomplete", - "unmatched_path_count": 25, - "unmatched_path_samples": paths[:20], - "truncated": True, - }, - ) - self.assertEqual(len(forward.unknown_evidence), 21) - - def test_long_path_sample_is_bounded_without_changing_coverage_count(self) -> None: - path = "unmapped/" + "a" * 300 - result = evaluate_change_impact( - repository_evidence=_repository_evidence(path), - policies=(_policy(),), - evaluated_at="2026-08-06T01:00:00Z", - ) - assert result.coverage is not None - self.assertEqual( - result.coverage.model_dump(), - { - "state": "incomplete", - "unmatched_path_count": 1, - "unmatched_path_samples": (path[:256],), - "truncated": True, - }, - ) - self.assertNotIn(path, " ".join(result.unknown_evidence)) - - def test_mixed_evidence_preserves_contradiction_and_declared_product(self) -> None: - result = evaluate_change_impact( - repository_evidence=_repository_evidence("unmapped/file.py", "src/runtime/app.py"), - policies=(_policy(),), - stored_evidence=(_stored_evidence("generic-web-runtime", confidence="ambiguous"),), - evaluated_at="2026-08-06T01:00:00Z", - ) - self.assertEqual(result.reason_code, "ambiguous_or_missing_evidence") - self.assertEqual((result.status, result.owner_impact), ("unknown", "required")) - self.assertEqual( - tuple(item.product for item in result.affected_products), ("generic-web-a",) - ) - assert result.coverage is not None - self.assertEqual( - result.coverage.model_dump(), - { - "state": "incomplete", - "unmatched_path_count": 1, - "unmatched_path_samples": ("unmapped/file.py",), - "truncated": False, - }, - ) - self.assertEqual( - result.unknown_evidence, - ( - "ambiguous stored evidence for generic-web-runtime", - "no policy rule matched changed path unmapped/file.py", - ), - ) - - def test_complete_path_coverage_does_not_validate_contradictory_evidence(self) -> None: - result = evaluate_change_impact( - repository_evidence=_repository_evidence("src/runtime/app.py"), - policies=(_policy(),), - stored_evidence=(_stored_evidence("generic-web-runtime", confidence="ambiguous"),), - evaluated_at="2026-08-06T01:00:00Z", - ) - self.assertEqual(result.status, "unknown") - self.assertEqual(result.reason_code, "ambiguous_or_missing_evidence") - assert result.coverage is not None - self.assertEqual(result.coverage.state, "complete") - - def test_missing_policy_has_no_path_coverage_claim(self) -> None: - result = evaluate_change_impact( - repository_evidence=_repository_evidence("unmapped/file.py"), - policies=(), - ) - self.assertEqual(result.reason_code, "policy_unavailable") - self.assertIsNone(result.coverage) diff --git a/tests/test_change_impact_decision_digest.py b/tests/test_change_impact_decision_digest.py deleted file mode 100644 index bacb793b0..000000000 --- a/tests/test_change_impact_decision_digest.py +++ /dev/null @@ -1,353 +0,0 @@ -import unittest - -from pydantic import ValidationError - -from control_plane.change_impact_decision_digest import ( - CHANGE_IMPACT_DECISION_FIELDS, - build_change_impact_decision_digest, -) -from control_plane.change_impact_service import evaluate_change_impact -from control_plane.contracts.change_impact import ( - ChangeImpactChangedFileEvidence, - ChangeImpactBaseEvidence, - ChangeImpactComponentRule, - ChangeImpactEvaluation, - ChangeImpactPolicyRecord, -) -from tests.test_change_impact import _product, _stored_evidence -from tests.test_change_impact_generated import _generated -from tests.test_change_impact_rename import _resolve -from tests.test_change_impact_v2 import _repository_evidence, _rule, _v2_policy - - -def _rule_with(rule: ChangeImpactComponentRule, **changes: object) -> ChangeImpactComponentRule: - return ChangeImpactComponentRule.model_validate(rule.model_dump(exclude={"rule_id"}) | changes) - - -def _decision(policy: ChangeImpactPolicyRecord) -> ChangeImpactEvaluation: - return evaluate_change_impact( - repository_evidence=_repository_evidence("src/api/file.py"), policies=(policy,) - ) - - -def _effective(evaluation: ChangeImpactEvaluation) -> object: - return evaluation.model_dump( - include={ - "status", - "reason_code", - "owner_impact", - "engineering_review_tier", - "required_engineering_review_count", - "affected_products", - "production_affecting_products", - "governance_impact", - "coverage", - } - ) - - -class ChangeImpactDecisionDigestTests(unittest.TestCase): - def test_contract_growth_requires_explicit_hash_or_provenance_disposition(self) -> None: - # A new contract field must be reviewed against the frozen v2 projection. - # Target is bound separately; IDs and evidence decorations are provenance; - # the binding pair is the output. Schema versions are bound explicitly. - excluded = { - "target", - "policy_record_id", - "policy_revision", - "policy_digest", - "matched_evidence", - "unknown_evidence", - "binding_hash_version", - "change_impact_decision_digest", - } - self.assertEqual( - set(ChangeImpactEvaluation.model_fields) - | set(ChangeImpactEvaluation.model_computed_fields), - CHANGE_IMPACT_DECISION_FIELDS | excluded, - ) - # Prefixes are scoped to matches, authority fields to their resolved mode. - # Only the content-derived rule ID and explanatory reason are excluded. - scoped = { - "schema_version", - "component", - "path_prefixes", - "affected_products", - "review_tier", - "production_affecting", - "product_impact", - "governance_impact", - "generated_by", - } - self.assertEqual( - set(ChangeImpactComponentRule.model_fields) - | set(ChangeImpactComponentRule.model_computed_fields), - scoped | {"rule_id", "reason"}, - ) - - def test_policy_contract_rejects_duplicate_components_and_routine_unknown_default(self) -> None: - with self.assertRaises(ValidationError): - _v2_policy(_rule("same", "src"), _rule("same", "other")) - with self.assertRaises(ValidationError): - ChangeImpactPolicyRecord.model_validate( - _v2_policy(_rule("source", "src")).model_dump(exclude={"policy_digest"}) - | {"default_unknown_review_tier": "routine"} - ) - - def test_evaluation_schema_version_is_part_of_the_scoped_protocol(self) -> None: - policy = _v2_policy(_rule("source", "src", products=(_product("a"),))) - evidence = _repository_evidence("src/api/file.py") - baseline = evaluate_change_impact(repository_evidence=evidence, policies=(policy,)) - future = ChangeImpactEvaluation.model_validate( - baseline.model_dump() | {"schema_version": 2} - ) - digest = build_change_impact_decision_digest( - repository_evidence=evidence, - policy=policy, - stored_evidence=(), - generated_boundaries={}, - evaluation=future, - ) - self.assertNotEqual(digest, baseline.change_impact_decision_digest) - - def test_head_tree_and_base_identity_each_require_a_new_digest(self) -> None: - policy = _v2_policy(_rule("source", "src", products=(_product("a"),))) - evidence = _repository_evidence("src/api/file.py") - baseline = evaluate_change_impact(repository_evidence=evidence, policies=(policy,)) - for updated in ( - evidence.model_copy( - update={"target": evidence.target.model_copy(update={"head_sha": "c" * 40})} - ), - evidence.model_copy( - update={"target": evidence.target.model_copy(update={"tree_sha": "d" * 40})} - ), - evidence.model_copy( - update={"base": ChangeImpactBaseEvidence(base_ref="main", base_sha="e" * 40)} - ), - ): - result = evaluate_change_impact(repository_evidence=updated, policies=(policy,)) - self.assertEqual(_effective(result), _effective(baseline)) - self.assertNotEqual( - result.change_impact_decision_digest, baseline.change_impact_decision_digest - ) - - def test_mutations_bind_effective_authority_but_ignore_unused_policy_and_prose(self) -> None: - parent = _rule("parent", "src", products=(_product("parent"),)) - child = _rule("child", "src/api", products=(_product("a"),)) - baseline_policy = _v2_policy(parent, child) - baseline = _decision(baseline_policy) - self.assertEqual(baseline.status, "success") - self.assertEqual(baseline.binding_hash_version, 2) - self.assertEqual(ChangeImpactEvaluation.model_validate(baseline.model_dump()), baseline) - cases = [ - ("prose", parent, _rule_with(child, reason="New rationale."), False), - ( - "unused prefix", - parent, - _rule_with(child, path_prefixes=("src/api", "unused")), - False, - ), - ( - "ancestor products", - _rule_with(parent, affected_products=(_product("other"),)), - child, - False, - ), - ("winner component", parent, _rule_with(child, component="other"), True), - ("specificity", parent, _rule_with(child, path_prefixes=("src/api/file.py",)), True), - ("sensitive floor", _rule_with(parent, review_tier="sensitive"), child, True), - ("governance floor", _rule_with(parent, governance_impact=True), child, True), - ("production floor", _rule_with(parent, production_affecting=True), child, True), - ( - "none", - parent, - _rule_with(child, affected_products=(), product_impact="declared_none"), - True, - ), - ] - for field in ("product", "system", "owner_action", "owner_environment"): - scope = _product("a").model_copy(update={field: "changed"}) - cases.append((field, parent, _rule_with(child, affected_products=(scope,)), True)) - for name, ancestor, winner, changed in cases: - with self.subTest(name=name): - result = _decision(_v2_policy(ancestor, winner)) - self.assertEqual(result.status, "success") - self.assertEqual( - result.change_impact_decision_digest != baseline.change_impact_decision_digest, - changed, - ) - if _effective(result) != _effective(baseline): - self.assertNotEqual( - result.change_impact_decision_digest, baseline.change_impact_decision_digest - ) - if not changed: - self.assertEqual(_effective(result), _effective(baseline)) - revised = ChangeImpactPolicyRecord.model_validate( - baseline_policy.model_dump(exclude={"record_id", "policy_digest"}) - | { - "policy_revision": 2, - "supersedes_record_id": baseline_policy.record_id, - "reason": "Unrelated new revision.", - "component_rules": ( - child, - _rule("unrelated", "unrelated", governance=True), - parent, - ), - } - ) - current = _decision(revised) - self.assertNotEqual(current.policy_digest, baseline.policy_digest) - self.assertEqual( - current.change_impact_decision_digest, baseline.change_impact_decision_digest - ) - self.assertEqual(_effective(current), _effective(baseline)) - - def test_trusted_evidence_semantics_cover_authority_without_provenance_churn(self) -> None: - policy = _v2_policy(_rule("child", "src/api", products=(_product("a"),))) - dependency = _stored_evidence("child", products=(_product("b"),)) - reviewer = _stored_evidence("child", kind="reviewer", products=(_product("c"),)) - results = [ - evaluate_change_impact( - repository_evidence=_repository_evidence("src/api/file.py"), - policies=(policy,), - stored_evidence=evidence, - ) - for evidence in ( - (dependency, reviewer), - ( - reviewer, - dependency.model_copy(update={"record_id": "reissued", "reason": "New prose."}), - dependency, - ), - (dependency, reviewer.model_copy(update={"affected_products": (_product("d"),)})), - (reviewer,), - ) - ] - original, reordered, changed, untrusted = results - self.assertEqual(_effective(original), _effective(reordered)) - self.assertEqual( - original.change_impact_decision_digest, reordered.change_impact_decision_digest - ) - self.assertNotEqual(_effective(original), _effective(changed)) - self.assertNotEqual( - original.change_impact_decision_digest, changed.change_impact_decision_digest - ) - self.assertEqual( - ( - untrusted.status, - untrusted.binding_hash_version, - untrusted.change_impact_decision_digest, - ), - ("unknown", None, None), - ) - - def test_generated_authority_and_ancestor_floors_are_covered(self) -> None: - parent = _rule("parent", "src", products=(_product("unused"),), governance=True) - source = _rule("source", "src/api", products=(_product("a"),)) - policies = ( - _v2_policy(parent, source, _generated()), - _v2_policy( - _rule_with(parent, affected_products=(_product("other"),)), source, _generated() - ), - _v2_policy(_rule_with(parent, governance_impact=None), source, _generated()), - _v2_policy( - parent, _rule_with(source, affected_products=(_product("b"),)), _generated() - ), - _v2_policy( - parent, - _rule_with(source, component="replacement"), - _rule_with(_generated(), generated_by=("replacement",)), - ), - ) - results = [ - evaluate_change_impact( - repository_evidence=_repository_evidence("generated/artifact/schema.json"), - policies=(policy,), - ) - for policy in policies - ] - baseline = results[0] - for index, result in enumerate(results[1:], 1): - with self.subTest(index=index): - self.assertEqual(result.status, "success") - self.assertEqual( - result.change_impact_decision_digest == baseline.change_impact_decision_digest, - index == 1, - ) - if index == 1: - self.assertEqual(_effective(result), _effective(baseline)) - - def test_rename_is_distinct_from_remove_add_and_missing_origin_cannot_bind(self) -> None: - policy = _v2_policy(_rule("source", "src", products=(_product("a"),))) - evidence = _repository_evidence("src/old.py", "src/new.py") - results = [] - for changed_files in ( - ( - ChangeImpactChangedFileEvidence(path="src/old.py", change_kind="removed"), - ChangeImpactChangedFileEvidence( - path="src/new.py", change_kind="renamed", previous_path="src/old.py" - ), - ), - ( - ChangeImpactChangedFileEvidence(path="src/old.py", change_kind="removed"), - ChangeImpactChangedFileEvidence(path="src/new.py", change_kind="added"), - ), - (ChangeImpactChangedFileEvidence(path="src/new.py", change_kind="renamed"),), - ( - ChangeImpactChangedFileEvidence( - path="src/new.py", change_kind="renamed", previous_path="src/missing.py" - ), - ), - (ChangeImpactChangedFileEvidence(path="src/new.py"),), - ): - # Validate to canonical path order, like the provider boundary. - current = type(evidence).model_validate( - evidence.model_dump() | {"changed_files": changed_files} - ) - results.append(evaluate_change_impact(repository_evidence=current, policies=(policy,))) - self.assertEqual(_effective(results[0]), _effective(results[1])) - self.assertNotEqual( - results[0].change_impact_decision_digest, results[1].change_impact_decision_digest - ) - for result in results[2:]: - self.assertEqual( - (result.status, result.binding_hash_version, result.change_impact_decision_digest), - ("unknown", None, None), - ) - - def test_real_provider_swap_pairs_bind_independently_of_evidence_order(self) -> None: - evidence = _resolve( - [ - {"filename": "src/a.py", "status": "renamed", "previous_filename": "src/b.py"}, - {"filename": "src/b.py", "status": "renamed", "previous_filename": "src/a.py"}, - ] - ) - policy = _v2_policy(_rule("source", "src", products=(_product("a"),))) - forward = evaluate_change_impact(repository_evidence=evidence, policies=(policy,)) - reversed_evidence = evidence.model_copy( - update={"changed_files": tuple(reversed(evidence.changed_files))} - ) - backward = evaluate_change_impact(repository_evidence=reversed_evidence, policies=(policy,)) - self.assertEqual(forward.status, "success") - self.assertEqual(_effective(forward), _effective(backward)) - self.assertEqual( - forward.change_impact_decision_digest, backward.change_impact_decision_digest - ) - - def test_partial_coverage_retains_known_products_and_sensitive_typed_fallback(self) -> None: - result = evaluate_change_impact( - repository_evidence=_repository_evidence("src/api/file.py", "unmapped/file.py"), - policies=(_v2_policy(_rule("source", "src", products=(_product("a"),))),), - ) - self.assertEqual( - ( - result.reason_code, - result.engineering_review_tier, - result.required_engineering_review_count, - ), - ("policy_coverage_incomplete", "sensitive", 2), - ) - self.assertEqual(tuple(p.product for p in result.affected_products), ("a",)) - self.assertEqual( - (result.binding_hash_version, result.change_impact_decision_digest), (None, None) - ) diff --git a/tests/test_change_impact_generated.py b/tests/test_change_impact_generated.py deleted file mode 100644 index cf373503b..000000000 --- a/tests/test_change_impact_generated.py +++ /dev/null @@ -1,244 +0,0 @@ -from dataclasses import asdict -import unittest - -from pydantic import ValidationError - -from control_plane.change_impact_generated import resolve_generated_boundaries -from control_plane.change_impact_matching import validate_change_impact_v2_policy -from control_plane.change_impact_service import evaluate_change_impact -from control_plane.contracts.change_impact import ( - ChangeImpactComponentRule, - ChangeImpactPolicyRecord, -) -from tests.test_change_impact import _policy, _product, _stored_evidence -from tests.test_change_impact_v2 import _repository_evidence -from tests.test_change_impact_v2 import _rule, _v2_policy - - -def _generated(*generators: str, component: str = "artifact") -> ChangeImpactComponentRule: - return ChangeImpactComponentRule( - component=component, - path_prefixes=(f"generated/{component}",), - generated_by=generators or ("source",), - reason="Generator attribution.", - ) - - -class GeneratedBoundaryTests(unittest.TestCase): - def test_generated_scope_uses_leaves_and_preserves_all_floor_axes(self) -> None: - parent = _rule("parent", "src", products=(_product("unrelated"),), governance=True) - source = _rule("source", "src/api", products=(_product("api"),), production=True) - artifact_parent = _rule("generated", "generated", products=(_product("also-unrelated"),)) - policy = _v2_policy(parent, source, artifact_parent, _generated()) - result = evaluate_change_impact( - repository_evidence=_repository_evidence("generated/artifact/schema.json"), - policies=(policy,), - ) - self.assertEqual( - ( - result.status, - result.owner_impact, - result.required_engineering_review_count, - tuple(p.product for p in result.affected_products), - tuple(p.product for p in result.production_affecting_products), - result.governance_impact, - ), - ("success", "required", 2, ("api",), ("api",), True), - ) - winner = next(e for e in result.matched_evidence if not e.review_floor_only) - self.assertEqual(tuple(p.product for p in winner.affected_products), ("api",)) - - def test_all_none_and_mixed_fanout_keep_distinct_authority(self) -> None: - none = _rule("none", "docs") - direct = _rule("source", "src", products=(_product("api"),)) - for leaves, products, owner in ( - (("none",), (), "not_required"), - (("none", "source"), ("api",), "required"), - ): - with self.subTest(leaves=leaves): - policy = _v2_policy(none, direct, _generated(*leaves)) - boundary = resolve_generated_boundaries(policy)["artifact"] - result = evaluate_change_impact( - repository_evidence=_repository_evidence("generated/artifact/schema.json"), - policies=(policy,), - ) - self.assertEqual( - (boundary.declared_none, result.owner_impact), (not products, owner) - ) - self.assertEqual(tuple(p.product for p in result.affected_products), products) - - def test_narrow_direct_rule_does_not_expand_generated_ancestor(self) -> None: - generated = _generated().model_copy(update={"governance_impact": True, "rule_id": ""}) - policy = _v2_policy( - _rule("source", "src", products=(_product("unused"),), production=True), - generated, - _rule("handwritten", "generated/artifact/manual"), - ) - result = evaluate_change_impact( - repository_evidence=_repository_evidence("generated/artifact/manual/readme.md"), - policies=(policy,), - ) - self.assertEqual( - ( - result.affected_products, - result.production_affecting_products, - result.governance_impact, - result.required_engineering_review_count, - ), - ((), (), True, 2), - ) - - def test_every_generator_prefix_contributes_ancestor_floors(self) -> None: - source = ChangeImpactComponentRule( - component="source", - path_prefixes=("src/api", "contracts/api"), - affected_products=(_product("api"),), - reason="Two generator roots.", - ) - policy = _v2_policy( - source, - _generated(), - _rule("src", "src"), - _rule("contracts", "contracts", production=True, governance=True), - ) - boundary = resolve_generated_boundaries(policy)["artifact"] - self.assertEqual( - tuple((a.component, a.prefix) for a in boundary.generators[0].ancestor_floors), - (("contracts", "contracts"), ("src", "src")), - ) - self.assertEqual( - (boundary.floors.sensitive, boundary.floors.governance, boundary.floors.production), - (True, True, True), - ) - - def test_losing_generated_generator_ancestor_does_not_expand(self) -> None: - policy = _v2_policy( - _generated(), - _generated("unused", component="ancestor").model_copy( - update={"path_prefixes": ("src",), "rule_id": ""} - ), - _rule("source", "src/api", products=(_product("api"),)), - _rule("unused", "other", products=(_product("unused"),), governance=True), - ) - boundary = resolve_generated_boundaries(policy)["artifact"] - self.assertEqual(tuple(p.product for p in boundary.affected_products), ("api",)) - self.assertFalse(boundary.floors.governance) - - def test_mapping_contributions_change_even_when_aggregate_is_identical(self) -> None: - leaves = ( - _rule("a", "a", products=(_product("api"),)), - _rule("b", "b", products=(_product("api"),)), - ) - first = resolve_generated_boundaries(_v2_policy(*leaves, _generated("a")))["artifact"] - second = resolve_generated_boundaries(_v2_policy(*leaves, _generated("b")))["artifact"] - self.assertEqual(first.affected_products, second.affected_products) - self.assertNotEqual(asdict(first), asdict(second)) - - def test_resolution_ignores_prose_and_input_order_and_is_immutable(self) -> None: - leaves = (_rule("a", "a", products=(_product("api"),)), _rule("b", "b")) - policy = _v2_policy(*leaves, _generated("b", "a")) - original = policy.model_dump(mode="json") - resolved = resolve_generated_boundaries(policy) - payload = policy.model_dump(exclude={"policy_digest"}) - payload["component_rules"] = [ - rule.model_copy(update={"reason": "Different prose.", "rule_id": ""}).model_dump() - for rule in reversed(policy.component_rules) - ] - changed = ChangeImpactPolicyRecord.model_validate(payload) - self.assertEqual(resolved, resolve_generated_boundaries(changed)) - self.assertEqual(resolved, resolve_generated_boundaries(policy)) - self.assertEqual(policy.model_dump(mode="json"), original) - with self.assertRaises(TypeError): - resolved["artifact"] = resolved["artifact"] # type: ignore[index] - - def test_generator_edges_do_not_license_unmatched_stored_evidence(self) -> None: - policy = _v2_policy(_rule("source", "src", products=(_product("api"),)), _generated()) - result = evaluate_change_impact( - repository_evidence=_repository_evidence("generated/artifact/schema.json"), - policies=(policy,), - stored_evidence=(_stored_evidence("source", products=(_product("extra"),)),), - ) - self.assertEqual((result.status, result.owner_impact), ("unknown", "required")) - self.assertEqual(tuple(p.product for p in result.affected_products), ("api",)) - - def test_artifact_stored_extension_keeps_generator_production_floor(self) -> None: - policy = _v2_policy( - _rule("source", "src", products=(_product("api"),), production=True, governance=True), - _generated(), - ) - result = evaluate_change_impact( - repository_evidence=_repository_evidence("generated/artifact/schema.json"), - policies=(policy,), - stored_evidence=(_stored_evidence("artifact", products=(_product("extra"),)),), - ) - self.assertEqual(result.status, "success") - self.assertEqual( - tuple(p.product for p in result.production_affecting_products), ("api", "extra") - ) - self.assertEqual( - tuple( - (row.review_tier, row.governance_impact, row.production_affecting) - for row in result.matched_evidence - ), - (("sensitive", True, True), ("sensitive", True, True)), - ) - - def test_invalid_generator_graphs_fail_before_evaluation(self) -> None: - cases = ( - ((_generated("missing"),), "missing"), - ((_generated("artifact"),), "itself"), - ((_generated(), _generated("artifact", component="source")), "terminal"), - ( - ( - _generated(), - ChangeImpactComponentRule( - component="source", path_prefixes=("src",), reason="Implicit." - ), - ), - "implicit", - ), - ) - for rules, message in cases: - with self.subTest(message=message): - policy = _v2_policy(*rules) - with self.assertRaisesRegex(ValueError, message): - resolve_generated_boundaries(policy) - result = evaluate_change_impact( - repository_evidence=_repository_evidence("generated/artifact/file"), - policies=(policy,), - ) - self.assertEqual( - (result.status, result.reason_code), ("unknown", "policy_rules_invalid") - ) - - def test_rule_modes_and_fanout_bounds_are_checked(self) -> None: - payload = _generated().model_dump(exclude={"rule_id"}) - updates: tuple[dict[str, object], ...] = ( - {"generated_by": []}, - {"generated_by": ["source", "source"]}, - {"generated_by": [f"s{i}" for i in range(21)]}, - {"affected_products": [_product("api")]}, - {"product_impact": "declared_none"}, - ) - for update in updates: - with self.subTest(update=update), self.assertRaises(ValidationError): - ChangeImpactComponentRule.model_validate(payload | update) - leaves = tuple(_rule(f"s{i}", f"src/{i}") for i in range(20)) - generators = tuple(rule.component for rule in leaves) - policy = _v2_policy(*leaves, _generated(*generators)) - self.assertEqual(len(resolve_generated_boundaries(policy)["artifact"].generators), 20) - artifacts = tuple(_generated(*generators, component=f"a{i}") for i in range(21)) - validate_change_impact_v2_policy(_v2_policy(*leaves, *artifacts[:20])) - with self.assertRaisesRegex(ValueError, "400 edges"): - validate_change_impact_v2_policy(_v2_policy(*leaves, *artifacts)) - - def test_v1_rejects_generated_authority_and_preserves_legacy_hash(self) -> None: - policy = _policy() - self.assertEqual( - policy.policy_digest, "fec4cc7ea106e369cf2a269d12e4e495a1e0a58d05edffb55a5cc1ab7c81baad" - ) - self.assertNotIn("generated_by", policy.model_dump_json(exclude_none=True)) - payload = policy.model_dump(exclude={"policy_digest"}) - payload["component_rules"] = [_generated()] - with self.assertRaisesRegex(ValidationError, "classification_model v2"): - ChangeImpactPolicyRecord.model_validate(payload) diff --git a/tests/test_change_impact_http.py b/tests/test_change_impact_http.py deleted file mode 100644 index f5b957784..000000000 --- a/tests/test_change_impact_http.py +++ /dev/null @@ -1,543 +0,0 @@ -from __future__ import annotations - -import json -from pathlib import Path -from tempfile import TemporaryDirectory -from typing import cast -import unittest - -from fastapi import FastAPI, HTTPException - -from control_plane.change_impact_github import ( - ChangeImpactRepositoryEvidenceError, - ChangeImpactRepositoryEvidenceStaleError, -) -from control_plane.change_impact_service import ChangeImpactRepositoryEvidenceProvider -from control_plane.contracts.change_impact import ( - ChangeImpactChangedFileEvidence, - ChangeImpactComponentRule, - ChangeImpactPolicyRecord, - ChangeImpactProductScope, - ChangeImpactRepositoryEvidence, - ChangeImpactStoredEvidence, - ChangeImpactTarget, - ChangeImpactTargetReference, -) -from control_plane.http_routes.change_impact import ( - CHANGE_IMPACT_EVALUATION_ROUTE, - CHANGE_IMPACT_POLICY_APPLY_ROUTE, - CHANGE_IMPACT_POLICY_READ_ROUTE, - ChangeImpactReadRouteDependencies, - ChangeImpactWriteRouteDependencies, - register_change_impact_read_routes, - register_change_impact_write_routes, -) -from control_plane.http_app import _BOUNDED_REQUEST_BODY_CONTRACTS -from control_plane.http_routes.support import ApiRouteRegistrar, ReadRouteDependencies -from control_plane.service_auth import ( - GitHubActionsIdentity, - GitHubHumanIdentity, - LaunchplaneIdentity, - LocalOperatorIdentity, - action_safety, -) -from control_plane.storage.filesystem import FilesystemRecordStore -from control_plane.storage.postgres import PostgresRecordStore -from tests.support.http import lifespan_client - - -REPOSITORY_ID = "1001" -REPOSITORY_OWNER_ID = "2001" -REPOSITORY = "example/shared-addons" -HEAD_SHA = "a" * 40 -TREE_SHA = "b" * 40 -MERGE_SHA = "e" * 40 - - -def _human_identity() -> GitHubHumanIdentity: - return GitHubHumanIdentity( - login="human", - github_id=1001, - name="Human", - email="", - organizations=frozenset(), - teams=frozenset(), - role="admin", - ) - - -def _actions_identity( - *, - repository: str = REPOSITORY, - repository_id: str = REPOSITORY_ID, - head_sha: str = MERGE_SHA, -) -> GitHubActionsIdentity: - owner = repository.split("/", maxsplit=1)[0] - return GitHubActionsIdentity( - repository=repository, - repository_owner=owner, - workflow_ref=f"{repository}/.github/workflows/ci.yml@refs/heads/main", - job_workflow_ref="", - ref="refs/pull/2000/merge", - ref_type="branch", - event_name="pull_request", - environment="", - subject=f"repo:{repository}:pull_request", - sha=head_sha, - raw_claims={}, - repository_id=repository_id, - repository_owner_id=REPOSITORY_OWNER_ID, - ) - - -def _policy() -> ChangeImpactPolicyRecord: - return ChangeImpactPolicyRecord( - repository_id=REPOSITORY_ID, - repository_owner_id=REPOSITORY_OWNER_ID, - repository=REPOSITORY, - policy_revision=1, - component_rules=( - ChangeImpactComponentRule( - component="generic-web-runtime", - path_prefixes=("src/runtime",), - affected_products=( - ChangeImpactProductScope(product="generic-web-a", system="web"), - ), - review_tier="routine", - reason="Runtime code reaches product A.", - ), - ChangeImpactComponentRule( - component="sensitive-auth", - path_prefixes=("control_plane/service_auth.py",), - affected_products=(), - review_tier="sensitive", - reason="Service authentication is sensitive engineering work.", - ), - ), - effective_at="2026-08-06T00:00:00Z", - source="test", - reason="HTTP policy contract.", - ) - - -def _repository_evidence(path: str = "src/runtime/app.py") -> ChangeImpactRepositoryEvidence: - return ChangeImpactRepositoryEvidence( - target=ChangeImpactTarget( - repository_id=REPOSITORY_ID, - repository_owner_id=REPOSITORY_OWNER_ID, - repository=REPOSITORY, - pull_request_number=2000, - head_sha=HEAD_SHA, - tree_sha=TREE_SHA, - ), - merge_commit_sha=MERGE_SHA, - changed_files=(ChangeImpactChangedFileEvidence(path=path),), - ) - - -class _EvidenceProvider(ChangeImpactRepositoryEvidenceProvider): - def __init__( - self, - evidence: ChangeImpactRepositoryEvidence | Exception, - ) -> None: - self.evidence = evidence - self.calls: list[ChangeImpactTargetReference] = [] - - def resolve(self, target: ChangeImpactTargetReference) -> ChangeImpactRepositoryEvidence: - self.calls.append(target) - if isinstance(self.evidence, Exception): - raise self.evidence - return self.evidence - - -class _ChangeImpactStore(FilesystemRecordStore): - def __init__( - self, - root: Path, - stored_evidence: tuple[ChangeImpactStoredEvidence, ...] = (), - ) -> None: - super().__init__(root) - self._stored_evidence = stored_evidence - self.last_evidence_lookup: tuple[str, int, str, str] | None = None - - def list_change_impact_stored_evidence( - self, - *, - repository_id: str, - pull_request_number: int, - head_sha: str, - tree_sha: str, - ) -> tuple[ChangeImpactStoredEvidence, ...]: - self.last_evidence_lookup = ( - repository_id, - pull_request_number, - head_sha, - tree_sha, - ) - return self._stored_evidence - - -class _AuditedChangeImpactStore(PostgresRecordStore): - def __init__(self, root: Path) -> None: - super().__init__(database_url=f"sqlite+pysqlite:///{root / 'policy.sqlite'}") - self.ensure_schema() - self.last_evidence_lookup: tuple[str, int, str, str] | None = None - - def list_change_impact_stored_evidence( - self, - *, - repository_id: str, - pull_request_number: int, - head_sha: str, - tree_sha: str, - ) -> tuple[ChangeImpactStoredEvidence, ...]: - self.last_evidence_lookup = (repository_id, pull_request_number, head_sha, tree_sha) - return (_stored_dependency(),) - - -def _stored_dependency() -> ChangeImpactStoredEvidence: - return ChangeImpactStoredEvidence( - record_id="dependency-record-1", - component="generic-web-runtime", - affected_products=(ChangeImpactProductScope(product="generic-web-a", system="web"),), - kind="dependency", - reason="Launchplane storage maps the component to product A.", - ) - - -def _http_error( - *, - status_code: int, - trace_id: str, - code: str, - message: str, - authz: dict[str, object] | None = None, -) -> HTTPException: - detail: dict[str, object] = { - "status_code": status_code, - "trace_id": trace_id, - "code": code, - "message": message, - } - if authz is not None: - detail["authz"] = authz - return HTTPException(status_code=status_code, detail=detail) - - -def _app( - *, - store: object, - provider: ChangeImpactRepositoryEvidenceProvider, - identity: LaunchplaneIdentity | None = None, -) -> FastAPI: - resolved_identity = identity or _human_identity() - common = ReadRouteDependencies( - read_identity=lambda: resolved_identity, - get_record_store=lambda: store, - next_trace_id=lambda: "trace-change-impact", - authorization_allows=lambda **_: True, - http_error=_http_error, - error_response_model=dict, # type: ignore[arg-type] - ) - writes = ChangeImpactWriteRouteDependencies( - read_write_identity=lambda: resolved_identity, - get_record_store=lambda: store, - next_trace_id=lambda: "trace-change-impact-write", - authorization_allows=lambda **_: True, - http_error=_http_error, - error_response_model=dict, # type: ignore[arg-type] - ) - app = FastAPI() - registrar = cast(ApiRouteRegistrar, app) - register_change_impact_read_routes( - registrar, - dependencies=ChangeImpactReadRouteDependencies( - common=common, - read_evaluation_identity=lambda: resolved_identity, - repository_evidence_provider=provider, - ), - ) - register_change_impact_write_routes(registrar, dependencies=writes) - return app - - -class ChangeImpactHttpTests(unittest.IsolatedAsyncioTestCase): - async def test_audited_v2_policy_dry_run_is_available_but_activation_is_blocked(self) -> None: - from tests.test_change_impact_v2 import _rule, _v2_policy - - with TemporaryDirectory() as directory: - store = _AuditedChangeImpactStore(Path(directory)) - self.addCleanup(store.close) - app = _app( - store=store, - provider=_EvidenceProvider(_repository_evidence()), - identity=LocalOperatorIdentity(subject="operator:test", token_label="test"), - ) - policy = _v2_policy(_rule("docs", "docs")) - async with lifespan_client(app) as client: - dry_run = await client.post( - CHANGE_IMPACT_POLICY_APPLY_ROUTE, - json={"mode": "dry_run", "record": policy.model_dump()}, - ) - applied = await client.post( - CHANGE_IMPACT_POLICY_APPLY_ROUTE, - json={"mode": "apply", "record": policy.model_dump()}, - ) - ambiguous = _v2_policy(_rule("first", "docs"), _rule("second", "docs")) - invalid = await client.post( - CHANGE_IMPACT_POLICY_APPLY_ROUTE, - json={"mode": "dry_run", "record": ambiguous.model_dump()}, - ) - self.assertEqual(dry_run.status_code, 202, dry_run.text) - self.assertEqual(dry_run.json()["result"]["attribution_status"], "not_applied") - self.assertIsNone(dry_run.json()["result"]["audit"]) - self.assertEqual(applied.status_code, 409, applied.text) - self.assertEqual(invalid.status_code, 409, invalid.text) - self.assertEqual(store.list_change_impact_policy_records(), ()) - - async def test_evaluation_exposes_server_derived_coverage_gaps(self) -> None: - with TemporaryDirectory() as directory: - store = _ChangeImpactStore(Path(directory)) - store.write_change_impact_policy_record(_policy()) - provider = _EvidenceProvider(_repository_evidence("unmapped/file.py")) - app = _app(store=store, provider=provider) - - async with lifespan_client(app) as client: - response = await client.post( - CHANGE_IMPACT_EVALUATION_ROUTE, - json={"target": {"repository": REPOSITORY, "pull_request_number": 2000}}, - ) - self.assertEqual(response.status_code, 200, response.text) - evaluation = response.json()["evaluation"] - self.assertEqual(evaluation["reason_code"], "policy_coverage_incomplete") - self.assertEqual(evaluation["status"], "unknown") - self.assertEqual(evaluation["owner_impact"], "unknown") - self.assertEqual( - evaluation["coverage"], - { - "state": "incomplete", - "unmatched_path_count": 1, - "unmatched_path_samples": ["unmapped/file.py"], - "truncated": False, - }, - ) - - async def test_apply_read_and_server_derived_evaluate_are_authoritative(self) -> None: - with TemporaryDirectory() as directory: - store = _AuditedChangeImpactStore(Path(directory)) - self.addCleanup(store.close) - provider = _EvidenceProvider(_repository_evidence()) - app = _app( - store=store, - provider=provider, - identity=LocalOperatorIdentity(subject="operator:test", token_label="test"), - ) - policy = _policy() - - async with lifespan_client(app) as client: - applied = await client.post( - CHANGE_IMPACT_POLICY_APPLY_ROUTE, - json={"schema_version": 1, "mode": "apply", "record": policy.model_dump()}, - ) - self.assertEqual(applied.status_code, 202, applied.text) - - read_response = await client.get( - CHANGE_IMPACT_POLICY_READ_ROUTE, - params={"repository_id": REPOSITORY_ID}, - ) - self.assertEqual(read_response.status_code, 200, read_response.text) - self.assertNotIn("authoritative", read_response.json()["read_model"]) - - evaluated = await client.post( - CHANGE_IMPACT_EVALUATION_ROUTE, - json={ - "schema_version": 1, - "target": { - "schema_version": 1, - "repository": REPOSITORY, - "pull_request_number": 2000, - }, - "metadata": {"request_id": "request-1", "reason": "impact check"}, - }, - ) - self.assertEqual(evaluated.status_code, 200, evaluated.text) - evaluation = evaluated.json()["evaluation"] - self.assertEqual(evaluation["status"], "success") - self.assertEqual(evaluation["engineering_review_tier"], "routine") - self.assertEqual(evaluation["required_engineering_review_count"], 1) - self.assertEqual(evaluation["owner_impact"], "required") - self.assertNotIn("authoritative", evaluation) - - self.assertEqual(len(provider.calls), 1) - self.assertEqual( - store.last_evidence_lookup, - (REPOSITORY_ID, 2000, HEAD_SHA, TREE_SHA), - ) - - async def test_callers_cannot_inject_or_omit_changed_file_authority(self) -> None: - with TemporaryDirectory() as directory: - store = _ChangeImpactStore(Path(directory)) - store.write_change_impact_policy_record(_policy()) - provider = _EvidenceProvider(_repository_evidence("control_plane/service_auth.py")) - app = _app(store=store, provider=provider) - - async with lifespan_client(app) as client: - injected = await client.post( - CHANGE_IMPACT_EVALUATION_ROUTE, - json={ - "target": { - "repository": REPOSITORY, - "pull_request_number": 2000, - }, - "changed_files": [{"path": ".github/workflows/ci.yml"}], - "dependency_evidence": [ - { - "source": "reviewer_validation", - "component": "sensitive-auth", - "reason": "caller supplied", - } - ], - }, - ) - self.assertEqual(injected.status_code, 422, injected.text) - self.assertEqual(provider.calls, []) - - evaluated = await client.post( - CHANGE_IMPACT_EVALUATION_ROUTE, - json={ - "target": { - "repository": REPOSITORY, - "pull_request_number": 2000, - } - }, - ) - self.assertEqual(evaluated.status_code, 200, evaluated.text) - evaluation = evaluated.json()["evaluation"] - self.assertEqual(evaluation["status"], "success") - self.assertEqual(evaluation["engineering_review_tier"], "sensitive") - self.assertEqual(evaluation["required_engineering_review_count"], 2) - - async def test_github_actions_callers_are_bound_to_oidc_repo_and_head(self) -> None: - with TemporaryDirectory() as directory: - store = _ChangeImpactStore(Path(directory)) - store.write_change_impact_policy_record(_policy()) - - wrong_repo_provider = _EvidenceProvider(_repository_evidence()) - wrong_repo_app = _app( - store=store, - provider=wrong_repo_provider, - identity=_actions_identity(repository="example/other", repository_id="9999"), - ) - async with lifespan_client(wrong_repo_app) as client: - denied = await client.post( - CHANGE_IMPACT_EVALUATION_ROUTE, - json={ - "target": { - "repository": REPOSITORY, - "pull_request_number": 2000, - } - }, - ) - self.assertEqual(denied.status_code, 403, denied.text) - self.assertEqual(wrong_repo_provider.calls, []) - - stale_head_app = _app( - store=store, - provider=_EvidenceProvider(_repository_evidence("control_plane/service_auth.py")), - identity=_actions_identity(head_sha="c" * 40), - ) - async with lifespan_client(stale_head_app) as client: - stale = await client.post( - CHANGE_IMPACT_EVALUATION_ROUTE, - json={ - "target": { - "repository": REPOSITORY, - "pull_request_number": 2000, - } - }, - ) - self.assertEqual(stale.status_code, 200, stale.text) - self.assertEqual(stale.json()["evaluation"]["status"], "stale_head") - self.assertEqual( - stale.json()["evaluation"]["required_engineering_review_count"], - 2, - ) - - async def test_provider_stale_and_unavailable_fail_closed(self) -> None: - with TemporaryDirectory() as directory: - store = _ChangeImpactStore(Path(directory)) - request = { - "target": { - "repository": REPOSITORY, - "pull_request_number": 2000, - } - } - stale_app = _app( - store=store, - provider=_EvidenceProvider( - ChangeImpactRepositoryEvidenceStaleError("target changed") - ), - ) - async with lifespan_client(stale_app) as client: - stale = await client.post(CHANGE_IMPACT_EVALUATION_ROUTE, json=request) - self.assertEqual(stale.status_code, 409, stale.text) - - unavailable_app = _app( - store=store, - provider=_EvidenceProvider(ChangeImpactRepositoryEvidenceError("provider failed")), - ) - async with lifespan_client(unavailable_app) as client: - unavailable = await client.post(CHANGE_IMPACT_EVALUATION_ROUTE, json=request) - self.assertEqual(unavailable.status_code, 503, unavailable.text) - - def test_openapi_request_has_no_caller_evidence_fields(self) -> None: - with TemporaryDirectory() as directory: - app = _app( - store=_ChangeImpactStore(Path(directory)), - provider=_EvidenceProvider(_repository_evidence()), - ) - schema = app.openapi() - - operation = schema["paths"][CHANGE_IMPACT_EVALUATION_ROUTE]["post"] - request_ref = operation["requestBody"]["content"]["application/json"]["schema"]["$ref"] - request_name = request_ref.rsplit("/", maxsplit=1)[1] - request_schema = schema["components"]["schemas"][request_name] - self.assertEqual( - set(request_schema["properties"]), - {"schema_version", "target", "metadata"}, - ) - target_ref = request_schema["properties"]["target"]["$ref"] - target_name = target_ref.rsplit("/", maxsplit=1)[1] - target_schema = schema["components"]["schemas"][target_name] - self.assertEqual( - set(target_schema["properties"]), - {"schema_version", "repository", "pull_request_number"}, - ) - request_contract = json.dumps(request_schema, sort_keys=True) - for forbidden in ( - "changed_files", - "dependency_evidence", - "expected_head_sha", - "expected_tree_sha", - "sensitive_areas", - "reviewer_validation", - "claimed_policy_revision", - "claimed_policy_digest", - ): - self.assertNotIn(forbidden, request_contract) - - self.assertIn(CHANGE_IMPACT_POLICY_READ_ROUTE, schema["paths"]) - self.assertIn(CHANGE_IMPACT_POLICY_APPLY_ROUTE, schema["paths"]) - self.assertEqual(action_safety("change_impact_policy.write"), "policy_admin") - self.assertEqual(action_safety("change_impact_policy.read"), "read") - self.assertEqual(action_safety("change_impact_evaluation.read"), "read") - - def test_evaluation_route_has_small_json_body_guard(self) -> None: - self.assertEqual( - _BOUNDED_REQUEST_BODY_CONTRACTS[CHANGE_IMPACT_EVALUATION_ROUTE], - ("Change impact evaluation", 16 * 1024, True, True), - ) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_change_impact_policy_audit.py b/tests/test_change_impact_policy_audit.py deleted file mode 100644 index 53610f69d..000000000 --- a/tests/test_change_impact_policy_audit.py +++ /dev/null @@ -1,205 +0,0 @@ -from datetime import datetime, timezone -from pathlib import Path -from tempfile import TemporaryDirectory -import unittest - -from sqlalchemy import event - -from control_plane.change_impact_policy_audit import derive_change_impact_policy_audit -from control_plane.change_impact_service import ChangeImpactPolicyConflictError -from control_plane.contracts.change_impact_audit import ChangeImpactPolicyAuditRecord -from control_plane.service_auth import LocalAdminIdentity, LocalOperatorIdentity -from control_plane.storage.postgres import PostgresRecordStore -from tests.support.http import lifespan_client -from tests.test_change_impact import _policy -from control_plane.http_routes.change_impact import ( - CHANGE_IMPACT_POLICY_APPLY_ROUTE, - CHANGE_IMPACT_POLICY_READ_ROUTE, -) -from tests.test_change_impact_http import ( - _ChangeImpactStore, - _EvidenceProvider, - _actions_identity, - _app, - _human_identity, - _repository_evidence, -) - - -def _audit(subject: str = "operator:first", revision: int = 1) -> ChangeImpactPolicyAuditRecord: - policy = _policy( - revision=revision, - supersedes_record_id=_policy().record_id if revision > 1 else None, - ) - return ChangeImpactPolicyAuditRecord( - record_id=policy.record_id, - policy_digest=policy.policy_digest, - actor_kind="local_operator", - actor_subject=subject, - trace_id="trace-original", - recorded_at=datetime(2026, 8, 7, tzinfo=timezone.utc), - ) - - -class ChangeImpactPolicyAuditTests(unittest.TestCase): - def setUp(self) -> None: - self.directory = TemporaryDirectory() - self.addCleanup(self.directory.cleanup) - self.store = PostgresRecordStore( - database_url=f"sqlite+pysqlite:///{Path(self.directory.name) / 'policy.sqlite'}" - ) - self.store.ensure_schema() - self.addCleanup(self.store.close) - - def test_original_audit_survives_replay_and_supersession(self) -> None: - first = _policy() - original = _audit() - self.store.compare_and_write_change_impact_policy_record_with_audit( - first, audit=original, expected_current_record_id="", expected_current_policy_digest="" - ) - replay = self.store.compare_and_write_change_impact_policy_record_with_audit( - first, - audit=_audit("operator:second"), - expected_current_record_id="", - expected_current_policy_digest="", - ) - self.assertEqual(replay.audit, original) - self.assertEqual(replay.status, "replayed") - self.assertEqual(self.store.write_change_impact_policy_record(first), "replayed") - self.assertEqual(self.store.read_change_impact_policy_audit(first.record_id), original) - second = _policy(revision=2, supersedes_record_id=first.record_id) - self.store.compare_and_write_change_impact_policy_record_with_audit( - second, - audit=_audit("operator:second", revision=2), - expected_current_record_id=first.record_id, - expected_current_policy_digest=first.policy_digest, - ) - self.assertEqual(self.store.read_change_impact_policy_audit(first.record_id), original) - self.assertEqual( - self.store.read_change_impact_policy_record(first.record_id), - first.model_copy(update={"status": "superseded"}), - ) - self.assertEqual(self.store.read_change_impact_policy_record(second.record_id), second) - - def test_legacy_replay_never_backfills_original_writer(self) -> None: - first = _policy() - self.store.write_change_impact_policy_record(first) - result = self.store.compare_and_write_change_impact_policy_record_with_audit( - first, audit=_audit(), expected_current_record_id="", expected_current_policy_digest="" - ) - self.assertEqual(result.status, "replayed") - self.assertIsNone(result.audit) - self.assertIsNone(self.store.read_change_impact_policy_audit(first.record_id)) - - def test_audit_insert_failure_rolls_back_predecessor_update(self) -> None: - first = _policy() - self.store.compare_and_write_change_impact_policy_record_with_audit( - first, audit=_audit(), expected_current_record_id="", expected_current_policy_digest="" - ) - second = _policy(revision=2, supersedes_record_id=first.record_id) - - def reject_insert(_connection: object, _cursor: object, statement: str, *_: object) -> None: - if statement.startswith("INSERT INTO launchplane_change_impact_policies"): - raise RuntimeError("injected policy audit insert failure") - - event.listen(self.store._engine, "before_cursor_execute", reject_insert) - try: - with self.assertRaisesRegex(RuntimeError, "injected policy audit"): - self.store.compare_and_write_change_impact_policy_record_with_audit( - second, - audit=_audit("operator:second", revision=2), - expected_current_record_id=first.record_id, - expected_current_policy_digest=first.policy_digest, - ) - finally: - event.remove(self.store._engine, "before_cursor_execute", reject_insert) - self.assertEqual(self.store.list_change_impact_policy_records(), (first,)) - self.assertEqual(self.store.read_change_impact_policy_audit(first.record_id), _audit()) - - def test_audit_identity_must_match_the_persisted_policy(self) -> None: - with self.assertRaises(ChangeImpactPolicyConflictError): - self.store.compare_and_write_change_impact_policy_record_with_audit( - _policy(), - audit=_audit(revision=2), - expected_current_record_id="", - expected_current_policy_digest="", - ) - self.assertEqual(self.store.list_change_impact_policy_records(), ()) - - -class ChangeImpactPolicyAuditHttpTests(unittest.IsolatedAsyncioTestCase): - async def test_server_actor_dry_run_replay_and_readback(self) -> None: - with TemporaryDirectory() as directory: - store = PostgresRecordStore( - database_url=f"sqlite+pysqlite:///{directory}/policy.sqlite" - ) - store.ensure_schema() - self.addCleanup(store.close) - actor = LocalOperatorIdentity(subject="operator:trusted", token_label="do-not-persist") - app = _app( - store=store, provider=_EvidenceProvider(_repository_evidence()), identity=actor - ) - policy = _policy() - body = {"record": policy.model_dump(mode="json"), "mode": "dry_run"} - async with lifespan_client(app) as client: - dry_run = await client.post(CHANGE_IMPACT_POLICY_APPLY_ROUTE, json=body) - self.assertEqual(dry_run.status_code, 202, dry_run.text) - self.assertEqual(dry_run.json()["result"]["attribution_status"], "not_applied") - self.assertEqual(store.list_change_impact_policy_records(), ()) - forged = await client.post( - CHANGE_IMPACT_POLICY_APPLY_ROUTE, - json={**body, "audit": _audit().model_dump(mode="json")}, - ) - self.assertEqual(forged.status_code, 422) - body["mode"] = "apply" - applied = await client.post(CHANGE_IMPACT_POLICY_APPLY_ROUTE, json=body) - self.assertEqual(applied.status_code, 202, applied.text) - result = applied.json()["result"] - self.assertEqual(result["audit"]["actor_subject"], actor.subject) - self.assertEqual(result["record"], policy.model_dump(mode="json")) - self.assertNotIn(actor.token_label, applied.text) - replay = await client.post(CHANGE_IMPACT_POLICY_APPLY_ROUTE, json=body) - self.assertEqual(replay.json()["result"]["audit"], result["audit"]) - read = await client.get( - CHANGE_IMPACT_POLICY_READ_ROUTE, params={"repository_id": "1001"} - ) - self.assertEqual(read.json()["read_model"]["audit"], result["audit"]) - self.assertEqual(read.json()["read_model"]["attribution_status"], "attributed") - - async def test_incapable_store_and_unsupported_identity_fail_closed(self) -> None: - with TemporaryDirectory() as directory: - store = _ChangeImpactStore(Path(directory)) - for identity, status in ( - (LocalAdminIdentity(subject="admin:test", token_label="test"), 503), - (_human_identity(), 403), - ): - with self.subTest(identity=type(identity).__name__): - app = _app( - store=store, - provider=_EvidenceProvider(_repository_evidence()), - identity=identity, - ) - async with lifespan_client(app) as client: - response = await client.post( - CHANGE_IMPACT_POLICY_APPLY_ROUTE, - json={"record": _policy().model_dump(mode="json")}, - ) - self.assertEqual(response.status_code, status, response.text) - self.assertEqual(store.list_change_impact_policy_records(), ()) - - def test_identity_projection_uses_only_named_verified_fields(self) -> None: - for identity, kind in ( - (LocalAdminIdentity(subject="admin:test", token_label="secret-label"), "local_admin"), - ( - _actions_identity(repository="example/policy-admin", repository_id="9001"), - "github_actions", - ), - ): - with self.subTest(kind=kind): - audit = derive_change_impact_policy_audit( - identity=identity, record=_policy(), trace_id="trace" - ) - self.assertEqual(audit.actor_kind, kind) - self.assertEqual(audit.actor_subject, identity.subject) - self.assertNotIn("raw_claims", audit.model_dump()) - self.assertNotIn("token_label", audit.model_dump()) diff --git a/tests/test_change_impact_policy_selection.py b/tests/test_change_impact_policy_selection.py deleted file mode 100644 index 4ed4980a8..000000000 --- a/tests/test_change_impact_policy_selection.py +++ /dev/null @@ -1,84 +0,0 @@ -import unittest -from unittest.mock import Mock - -from control_plane.change_impact_service import ( - evaluate_change_impact, - get_change_impact_policy_read_model, -) -from tests.test_change_impact import REPOSITORY_ID, _policy, _repository_evidence - - -class ChangeImpactPolicySelectionTests(unittest.TestCase): - def test_superseded_history_cannot_authorize_a_fresh_evaluation(self) -> None: - superseded = _policy().model_copy(update={"status": "superseded"}) - result = evaluate_change_impact( - repository_evidence=_repository_evidence("src/runtime/server.py"), - policies=(superseded,), - evaluated_at="2026-08-07T00:00:00Z", - ) - self.assertEqual( - ( - result.status, - result.reason_code, - result.policy_record_id, - result.owner_impact, - result.affected_products, - result.required_engineering_review_count, - ), - ("unknown", "policy_unavailable", "", "unknown", (), 2), - ) - self.assertIsNone(result.coverage) - - def test_current_read_keeps_history_visible_without_claiming_active_authority(self) -> None: - superseded = _policy().model_copy(update={"status": "superseded"}) - store = Mock() - store.list_change_impact_policy_records.return_value = (superseded,) - result = get_change_impact_policy_read_model(store=store, repository_id=REPOSITORY_ID) - self.assertEqual( - (result.repository_id, result.current_policy, result.policy_history_count), - (REPOSITORY_ID, None, 1), - ) - - def test_active_policy_result_is_identical_with_superseded_history(self) -> None: - previous = _policy(effective_at="2026-08-01T00:00:00Z") - current = _policy(revision=2, supersedes_record_id=previous.record_id) - evidence = _repository_evidence("src/runtime/server.py") - expected = evaluate_change_impact( - repository_evidence=evidence, - policies=(current,), - evaluated_at="2026-08-07T00:00:00Z", - ) - actual = evaluate_change_impact( - repository_evidence=evidence, - policies=(previous.model_copy(update={"status": "superseded"}), current), - evaluated_at="2026-08-07T00:00:00Z", - ) - self.assertEqual(actual, expected) - self.assertEqual((actual.status, actual.policy_digest), ("success", current.policy_digest)) - - def test_timestamp_before_active_policy_does_not_resurrect_superseded_record(self) -> None: - previous = _policy(effective_at="2026-08-01T00:00:00Z") - current = _policy(revision=2, supersedes_record_id=previous.record_id) - for history in ( - (current,), - (previous.model_copy(update={"status": "superseded"}), current), - ): - with self.subTest(history_length=len(history)): - result = evaluate_change_impact( - repository_evidence=_repository_evidence("src/runtime/server.py"), - policies=history, - evaluated_at="2026-08-05T00:00:00Z", - ) - self.assertEqual( - (result.status, result.reason_code), ("unknown", "policy_unavailable") - ) - - def test_multiple_active_policies_remain_an_invalid_history(self) -> None: - first = _policy() - second = _policy(revision=2, supersedes_record_id=first.record_id) - result = evaluate_change_impact( - repository_evidence=_repository_evidence("src/runtime/server.py"), - policies=(first, second), - evaluated_at="2026-08-07T00:00:00Z", - ) - self.assertEqual((result.status, result.reason_code), ("unknown", "policy_history_invalid")) diff --git a/tests/test_change_impact_rename.py b/tests/test_change_impact_rename.py deleted file mode 100644 index a20bba323..000000000 --- a/tests/test_change_impact_rename.py +++ /dev/null @@ -1,119 +0,0 @@ -from pathlib import Path -import unittest - -from control_plane.change_impact_github import ( - ChangeImpactRepositoryEvidenceError, - GitHubChangeImpactRepositoryEvidenceProvider, -) -from control_plane.change_impact_service import evaluate_change_impact -from control_plane.contracts.change_impact import ( - ChangeImpactChangedFileEvidence, - ChangeImpactRepositoryEvidence, - ChangeImpactTargetReference, -) -from tests.test_change_impact import _policy -from tests.test_change_impact_github import REPOSITORY, _GitHubApi - - -def _resolve(files: list[dict[str, object]]) -> ChangeImpactRepositoryEvidence: - class GitHubApi(_GitHubApi): - def __call__(self, *, path: str, token: str) -> object: - if "/files?" in path: - return files - return super().__call__(path=path, token=token) - - return GitHubChangeImpactRepositoryEvidenceProvider( - control_plane_root=Path("."), - github_token=lambda **_: "server-token", - github_api=GitHubApi(), - token_context="launchplane", - ).resolve(ChangeImpactTargetReference(repository=REPOSITORY, pull_request_number=2000)) - - -class ChangeImpactRenameTests(unittest.TestCase): - def test_rename_pair_preserves_both_legacy_product_and_sensitive_boundaries(self) -> None: - evidence = _resolve( - [ - { - "filename": "src/runtime/rules.py", - "previous_filename": "control_plane/billing/rules.py", - "status": "renamed", - } - ] - ) - self.assertEqual( - evidence.changed_files, - ( - ChangeImpactChangedFileEvidence( - path="control_plane/billing/rules.py", change_kind="removed" - ), - ChangeImpactChangedFileEvidence( - path="src/runtime/rules.py", - change_kind="renamed", - previous_path="control_plane/billing/rules.py", - ), - ), - ) - impact = evaluate_change_impact(repository_evidence=evidence, policies=(_policy(),)) - self.assertEqual( - (impact.status, impact.owner_impact, impact.required_engineering_review_count), - ("success", "required", 2), - ) - self.assertEqual(tuple(p.product for p in impact.affected_products), ("generic-web-a",)) - - def test_rename_requires_nonempty_string_origin(self) -> None: - origins: tuple[object, ...] = (None, "", " ", 123, [], {}) - for origin in origins: - with ( - self.subTest(origin=origin), - self.assertRaisesRegex(ChangeImpactRepositoryEvidenceError, "previous_filename"), - ): - _resolve( - [{"filename": "docs/new.md", "status": "renamed", "previous_filename": origin}] - ) - - def test_duplicate_real_destinations_cannot_overwrite_evidence(self) -> None: - renamed: dict[str, object] = { - "filename": "docs/new.md", - "status": "renamed", - "previous_filename": "src/old.py", - } - duplicates: tuple[dict[str, object], ...] = ( - renamed, - {"filename": "docs/new.md", "status": "modified"}, - {"filename": "/docs/new.md", "status": "modified"}, - ) - for duplicate in duplicates: - with ( - self.subTest(duplicate=duplicate), - self.assertRaisesRegex(ChangeImpactRepositoryEvidenceError, "repeats a path"), - ): - _resolve([renamed, duplicate]) - - def test_real_recreated_origin_keeps_its_kind_in_either_payload_order(self) -> None: - renamed: dict[str, object] = { - "filename": "b.py", - "status": "renamed", - "previous_filename": "a.py", - } - for kind in ("added", "modified", "removed"): - real: dict[str, object] = {"filename": "a.py", "status": kind} - for files in ([renamed, real], [real, renamed]): - with self.subTest(kind=kind, files=files): - evidence = _resolve(files) - self.assertEqual( - [(f.path, f.change_kind, f.previous_path) for f in evidence.changed_files], - [("a.py", kind, None), ("b.py", "renamed", "a.py")], - ) - - def test_swap_renames_retain_both_explicit_pairs(self) -> None: - files: list[dict[str, object]] = [ - {"filename": "a.py", "status": "renamed", "previous_filename": "b.py"}, - {"filename": "b.py", "status": "renamed", "previous_filename": "a.py"}, - ] - forward = _resolve(files) - self.assertEqual(forward, _resolve(list(reversed(files)))) - self.assertEqual( - [(f.path, f.change_kind, f.previous_path) for f in forward.changed_files], - [("a.py", "renamed", "b.py"), ("b.py", "renamed", "a.py")], - ) diff --git a/tests/test_change_impact_v2.py b/tests/test_change_impact_v2.py deleted file mode 100644 index b77f30898..000000000 --- a/tests/test_change_impact_v2.py +++ /dev/null @@ -1,254 +0,0 @@ -from pathlib import Path -from tempfile import TemporaryDirectory -import unittest - -from pydantic import ValidationError - -from control_plane.change_impact_matching import select_change_impact_path_rule -from control_plane.change_impact_service import ( - ChangeImpactPolicySequenceError, - apply_change_impact_policy, - evaluate_change_impact, -) -from control_plane.contracts.change_impact import ( - ChangeImpactComponentRule, - ChangeImpactPolicyRecord, - ChangeImpactProductScope, - ChangeImpactRepositoryEvidence, -) -from control_plane.storage.filesystem import FilesystemRecordStore -from tests.test_change_impact import _policy, _product, _stored_evidence -from tests.test_change_impact import _repository_evidence as _legacy_evidence - - -def _repository_evidence(*paths: str) -> ChangeImpactRepositoryEvidence: - evidence = _legacy_evidence(*paths) - return evidence.model_copy( - update={ - "changed_files": tuple( - file.model_copy(update={"change_kind": "modified"}) - for file in evidence.changed_files - ) - } - ) - - -def _v2_policy(*rules: ChangeImpactComponentRule) -> ChangeImpactPolicyRecord: - payload = _policy().model_dump(mode="json", exclude={"policy_digest"}) - payload.update(classification_model="v2", component_rules=list(rules)) - return ChangeImpactPolicyRecord.model_validate(payload) - - -def _rule( - component: str, - prefix: str, - *, - products: tuple[ChangeImpactProductScope, ...] = (), - governance: bool = False, - production: bool = False, -) -> ChangeImpactComponentRule: - return ChangeImpactComponentRule( - component=component, - path_prefixes=(prefix,), - affected_products=products, - product_impact=None if products else "declared_none", - governance_impact=governance, - production_affecting=production, - reason="Explicit test authority.", - ) - - -class ChangeImpactV2Tests(unittest.TestCase): - def test_legacy_stored_policy_digest_and_rule_ids_are_unchanged(self) -> None: - policy = _policy() - self.assertEqual( - policy.policy_digest, - "fec4cc7ea106e369cf2a269d12e4e495a1e0a58d05edffb55a5cc1ab7c81baad", - ) - self.assertEqual( - tuple(rule.rule_id for rule in policy.component_rules), - ( - "change-impact-rule-0f4099000cbeb1f38fe55f12", - "change-impact-rule-5f5187f2ca18348a40c9fbff", - "change-impact-rule-8713eef0d83c98f3c91ab9f3", - "change-impact-rule-c4680340405bc752e3fe684d", - ), - ) - - def test_legacy_nested_rules_still_accumulate_products(self) -> None: - policy = _v2_policy( - _rule("parent", "src", products=(_product("a"),)), - _rule("child", "src/child", products=(_product("b"),)), - ) - payload = policy.model_dump(exclude={"policy_digest", "classification_model"}) - legacy = ChangeImpactPolicyRecord.model_validate(payload) - result = evaluate_change_impact( - repository_evidence=_repository_evidence("src/child/code.py"), policies=(legacy,) - ) - self.assertEqual(tuple(p.product for p in result.affected_products), ("a", "b")) - self.assertIsNone(result.classification_model) - self.assertIsNone(result.governance_impact) - - def test_winner_can_declare_engineering_only_without_lowering_ancestor_floor(self) -> None: - parent = _rule("parent", "src", products=(_product("a"),), governance=True) - child = _rule("docs", "src/docs") - policy = _v2_policy(parent, child) - result = evaluate_change_impact( - repository_evidence=_repository_evidence("src/docs/guide.md"), policies=(policy,) - ) - self.assertEqual( - ( - result.status, - result.owner_impact, - result.affected_products, - result.required_engineering_review_count, - ), - ("success", "not_required", (), 2), - ) - self.assertTrue(result.governance_impact) - self.assertEqual( - [ - (e.component, e.review_floor_only, e.affected_products) - for e in result.matched_evidence - ], - [("parent", True, ()), ("docs", False, ())], - ) - selection = select_change_impact_path_rule( - path="src/docs/guide.md", rules=policy.component_rules - ) - assert selection is not None - self.assertEqual((selection.winner.rule, selection.winner.prefix), (child, "src/docs")) - - def test_legacy_production_extensions_require_a_changed_file_match(self) -> None: - policy = _v2_policy( - _rule("runtime", "src", products=(_product("a"),), production=True), - _rule("docs", "docs", products=(_product("b"),)), - ) - legacy = ChangeImpactPolicyRecord.model_validate( - policy.model_dump(exclude={"policy_digest", "classification_model"}) - ) - for path, status, products in ( - ("src/code.py", "success", ("a", "c")), - ("docs/guide.md", "unknown", ()), - ): - with self.subTest(path=path): - result = evaluate_change_impact( - repository_evidence=_repository_evidence(path), - policies=(legacy,), - stored_evidence=(_stored_evidence("runtime", products=(_product("c"),)),), - ) - self.assertEqual( - (result.status, tuple(p.product for p in result.production_affecting_products)), - (status, products), - ) - - def test_inherited_production_floor_applies_to_winner_and_trusted_extensions(self) -> None: - result = evaluate_change_impact( - repository_evidence=_repository_evidence("src/child/code.py"), - policies=( - _v2_policy( - _rule("parent", "src", products=(_product("a"),), production=True), - _rule("child", "src/child", products=(_product("b"),)), - ), - ), - stored_evidence=(_stored_evidence("child", products=(_product("c"),)),), - ) - self.assertEqual(result.status, "success") - self.assertEqual(tuple(p.product for p in result.affected_products), ("b", "c")) - self.assertEqual(tuple(p.product for p in result.production_affecting_products), ("b", "c")) - - def test_floor_component_cannot_license_stored_product_extension(self) -> None: - result = evaluate_change_impact( - repository_evidence=_repository_evidence("src/docs/guide.md"), - policies=(_v2_policy(_rule("parent", "src"), _rule("docs", "src/docs")),), - stored_evidence=(_stored_evidence("parent", products=(_product("a"),)),), - ) - self.assertEqual( - (result.status, result.owner_impact, result.affected_products), - ("unknown", "unknown", ()), - ) - - def test_old_and_new_rename_paths_union_product_and_governance_impact(self) -> None: - result = evaluate_change_impact( - repository_evidence=_repository_evidence("src/code.py", "docs/code.py"), - policies=( - _v2_policy( - _rule("product", "src", products=(_product("a"),)), - _rule("docs", "docs", governance=True), - ), - ), - ) - self.assertEqual( - (result.owner_impact, result.required_engineering_review_count), ("required", 2) - ) - self.assertEqual(tuple(p.product for p in result.affected_products), ("a",)) - - def test_invalid_v2_rule_sets_fail_dry_run_and_evaluation(self) -> None: - invalid_sets = ( - (_rule("first", "src"), _rule("second", "src")), - (_rule("dot", "src/./docs"),), - ( - ChangeImpactComponentRule( - component="implicit", path_prefixes=("src",), reason="No authority." - ), - ), - ) - for rules in invalid_sets: - with self.subTest(rules=rules), TemporaryDirectory() as directory: - policy = _v2_policy(*rules) - with self.assertRaises(ValueError): - apply_change_impact_policy( - store=FilesystemRecordStore(Path(directory)), record=policy, mode="dry_run" - ) - result = evaluate_change_impact( - repository_evidence=_repository_evidence("src/file.py"), policies=(policy,) - ) - self.assertEqual( - (result.status, result.reason_code), ("unknown", "policy_rules_invalid") - ) - - def test_root_prefix_cannot_become_a_v2_repository_default(self) -> None: - payload = _policy().model_dump(exclude={"policy_digest"}) - payload.update( - classification_model="v2", - component_rules=[ - { - "component": "root", - "path_prefixes": ["/"], - "product_impact": "declared_none", - "reason": "Attempted root default.", - } - ], - ) - policy = ChangeImpactPolicyRecord.model_validate(payload) - result = evaluate_change_impact( - repository_evidence=_repository_evidence("src/file.py"), policies=(policy,) - ) - self.assertEqual((result.status, result.reason_code), ("unknown", "policy_rules_invalid")) - - def test_v2_dry_run_succeeds_but_apply_cannot_activate_incomplete_support(self) -> None: - with TemporaryDirectory() as directory: - store = FilesystemRecordStore(Path(directory)) - policy = _v2_policy(_rule("docs", "docs")) - self.assertEqual( - apply_change_impact_policy(store=store, record=policy, mode="dry_run").status, - "would_apply", - ) - with self.assertRaises(ChangeImpactPolicySequenceError): - apply_change_impact_policy(store=store, record=policy) - self.assertEqual(store.list_change_impact_policy_records(), ()) - - def test_v2_fields_cannot_hide_in_legacy_policy(self) -> None: - payload = _policy().model_dump(mode="json", exclude={"policy_digest"}) - payload["component_rules"] = [_rule("docs", "docs").model_dump()] - with self.assertRaises(ValidationError): - ChangeImpactPolicyRecord.model_validate(payload) - - def test_missing_mapping_stays_unknown_despite_explicit_engineering_rule(self) -> None: - result = evaluate_change_impact( - repository_evidence=_repository_evidence("unknown/file.py"), - policies=(_v2_policy(_rule("docs", "docs")),), - ) - self.assertEqual( - (result.reason_code, result.owner_impact), ("policy_coverage_incomplete", "unknown") - ) diff --git a/tests/test_engineering_review_decision.py b/tests/test_engineering_review_decision.py index db94a7dd6..7454d2d7b 100644 --- a/tests/test_engineering_review_decision.py +++ b/tests/test_engineering_review_decision.py @@ -4,7 +4,9 @@ from typing import Any from unittest.mock import patch -from control_plane.contracts.change_impact import ChangeImpactTargetReference +from control_plane.contracts.repository_evidence import ( + RepositoryTargetReference, +) from control_plane.contracts.engineering_review_decision import ( EngineeringReviewDecisionRecord, EngineeringReviewDecisionRequest, @@ -23,11 +25,10 @@ evaluate_engineering_review_decision, ) from control_plane.engineering_review_service import create_engineering_review_runs -from tests.test_change_impact import ( +from tests.support.retired_approval_history import ( HEAD_SHA, REPOSITORY, TREE_SHA, - _policy, _repository_evidence, ) from tests.test_engineering_review_run import _pending_record @@ -38,7 +39,7 @@ class _Provider: def __init__(self, *paths: str) -> None: self.evidence = _repository_evidence(*paths) - def resolve(self, _target: ChangeImpactTargetReference): # type: ignore[no-untyped-def] + def resolve(self, _target: RepositoryTargetReference): # type: ignore[no-untyped-def] return self.evidence @@ -81,7 +82,7 @@ def read_every_code_work_request_record(self, request_id: str): # type: ignore[ return self.work_request def list_change_impact_policy_records(self, **_filters): # type: ignore[no-untyped-def] - return (_policy(),) + raise AssertionError("Retired approval policy must not influence engineering review.") def list_change_impact_stored_evidence(self, **_filters): # type: ignore[no-untyped-def] return () @@ -145,7 +146,7 @@ def _completed_run( def _request() -> EngineeringReviewDecisionRequest: return EngineeringReviewDecisionRequest( - target=ChangeImpactTargetReference(repository=REPOSITORY, pull_request_number=20), + target=RepositoryTargetReference(repository=REPOSITORY, pull_request_number=20), work_request_id="work-request-20", ) @@ -182,7 +183,7 @@ def test_work_request_result_pr_repository_must_match_target(self) -> None: repository_evidence_provider=_Provider(".github/workflows/ci.yml"), ) - def test_routine_classification_schedules_only_one_review_run(self) -> None: + def test_review_schedules_independent_slots_without_path_classification(self) -> None: store = _Store() target = EngineeringReviewPullRequestTarget( repository=REPOSITORY, @@ -203,10 +204,10 @@ def test_routine_classification_schedules_only_one_review_run(self) -> None: created_at="2026-08-06T00:05:00Z", ) - self.assertEqual(len(result.runs), 1) + self.assertEqual(len(result.runs), 2) self.assertEqual(result.runs[0].review_slot, 1) - def test_routine_change_requires_one_exact_approved_run(self) -> None: + def test_single_review_cannot_bypass_independent_review(self) -> None: store = _Store((_completed_run(slot=1, family="openai"),)) record, created = evaluate_engineering_review_decision( @@ -217,11 +218,13 @@ def test_routine_change_requires_one_exact_approved_run(self) -> None: ) self.assertTrue(created) - self.assertEqual(record.status, "approved") - self.assertEqual(record.required_review_count, 1) + self.assertEqual(record.status, "pending") + self.assertEqual(record.required_review_count, 2) + self.assertEqual(record.schema_version, 2) + self.assertIsNone(record.change_impact_status) self.assertEqual(record.qualifying_run_ids, ("review-run-1-openai",)) - def test_sensitive_change_requires_two_model_families(self) -> None: + def test_two_current_model_families_approve_without_retired_policy(self) -> None: store = _Store((_completed_run(slot=1, family="openai"),)) record, _created = evaluate_engineering_review_decision( @@ -258,6 +261,34 @@ def test_blocking_result_wins_and_stale_run_is_ignored(self) -> None: self.assertEqual(record.status, "blocked") self.assertEqual(record.qualifying_run_ids, ()) + def test_current_decision_round_trip_rejects_retired_classification(self) -> None: + store = _Store( + ( + _completed_run(slot=1, family="openai"), + _completed_run(slot=2, family="anthropic"), + ) + ) + record, _ = evaluate_engineering_review_decision( + store=store, + request=_request(), + repository_evidence_provider=_Provider("src/runtime/app.py"), + ) + self.assertEqual(record.status, "approved") + self.assertEqual( + EngineeringReviewDecisionRecord.model_validate_json(record.model_dump_json()), record + ) + payload = record.model_dump(exclude={"decision_id", "decision_binding_sha256"}) + for changes in ( + {"change_impact_status": "success"}, + {"change_impact_policy_digest": "a" * 64}, + {"required_review_count": 1}, + ): + with ( + self.subTest(changes=changes), + self.assertRaisesRegex(ValueError, "without retired classification"), + ): + EngineeringReviewDecisionRecord.model_validate(payload | changes) + def test_identical_evidence_replays_despite_new_evaluation_time(self) -> None: store = _Store((_completed_run(slot=1, family="openai"),)) first, first_created = evaluate_engineering_review_decision( diff --git a/tests/test_governance_projection.py b/tests/test_governance_projection.py index 7c4b358bb..27918a416 100644 --- a/tests/test_governance_projection.py +++ b/tests/test_governance_projection.py @@ -6,7 +6,9 @@ from unittest.mock import Mock from control_plane.contracts.merge_train_policy import MergeTrainGitHubTokenSource -from control_plane.contracts.change_impact import ChangeImpactTargetReference +from control_plane.contracts.repository_evidence import ( + RepositoryTargetReference, +) from control_plane.contracts.governance_projection import GovernanceMergeReadinessFacet from control_plane.contracts.merge_train_structural_provenance import ( MergeTrainStackCollapseRootProof, @@ -25,7 +27,7 @@ ) -TARGET = ChangeImpactTargetReference(repository=REPOSITORY, pull_request_number=2022) +TARGET = RepositoryTargetReference(repository=REPOSITORY, pull_request_number=2022) NOW = "2026-08-12T05:00:00Z" @@ -422,7 +424,7 @@ class _ChangingProvider: def __init__(self) -> None: self.calls = 0 - def resolve(self, target: ChangeImpactTargetReference) -> object: + def resolve(self, target: RepositoryTargetReference) -> object: self.calls += 1 if self.calls > 1: return _repository_evidence(head="c" * 40) diff --git a/tests/test_governance_projection_http.py b/tests/test_governance_projection_http.py index 6fac66790..febf6a176 100644 --- a/tests/test_governance_projection_http.py +++ b/tests/test_governance_projection_http.py @@ -7,7 +7,9 @@ from fastapi import FastAPI, HTTPException -from control_plane.change_impact_github import ChangeImpactRepositoryEvidenceError +from control_plane.repository_evidence import ( + RepositoryEvidenceError, +) from control_plane.contracts.merge_train_policy import MERGE_TRAIN_POLICY_TARGETS_READ_ACTION from control_plane.contracts.governance_projection import GovernanceMergeReadinessFacet from control_plane.http_routes.governance_projection import ( @@ -230,7 +232,7 @@ async def test_rejects_blank_base_branch_after_normalization(self) -> None: async def test_returns_503_when_repository_evidence_is_unavailable(self) -> None: class _UnavailableProvider: def resolve(self, target: object) -> object: - raise ChangeImpactRepositoryEvidenceError(f"unavailable: {target}") + raise RepositoryEvidenceError(f"unavailable: {target}") with TemporaryDirectory() as directory: store = _configured_store(directory) diff --git a/tests/test_http_app_browser_mutation.py b/tests/test_http_app_browser_mutation.py index f8585e4a6..d07083489 100644 --- a/tests/test_http_app_browser_mutation.py +++ b/tests/test_http_app_browser_mutation.py @@ -45,7 +45,6 @@ def test_cookie_capable_mutation_route_inventory_is_explicit(self) -> None: "/v1/drivers/generic-web/prod-promotion", "/v1/drivers/generic-web/prod-promotion-workflow", "/v1/merge-train/policies/import", - "/v1/manager-preview-approval/reconcile", "/v1/product-config/apply", "/v1/product-profiles/{product}/owner", "/v1/product-review/decisions", diff --git a/tests/test_http_app_driver_odoo.py b/tests/test_http_app_driver_odoo.py index f392f5674..e0067ef6a 100644 --- a/tests/test_http_app_driver_odoo.py +++ b/tests/test_http_app_driver_odoo.py @@ -31,7 +31,6 @@ from control_plane.dokploy import DokploySourceOfTruth, DokployTargetDefinition from control_plane.contracts.product_profile_record import LaunchplaneProductProfileRecord from control_plane.http_app import create_launchplane_fastapi_app, idempotency_scope -from control_plane.manager_preview_approval import build_current_manager_preview_approval_binding from control_plane.odoo_instance_overrides import ( LAUNCHPLANE_INSTANCE_OVERRIDES_REQUIRED_ENV_KEY, LAUNCHPLANE_WEBSITE_BOOTSTRAP_REQUIRED_ENV_KEY, @@ -2261,20 +2260,12 @@ def test_odoo_preview_lifecycle_replay_preserves_destroy_and_generation_evidence "domain_host": "pr-42.cm-preview.example.test", "compose_name": "cm-odoo-preview-pr-42", } - destroy_callback_states: list[str] = [] - - def before_destroy() -> dict[str, object]: - current_preview = store.read_preview_record(str(first_records["preview_id"])) - destroy_callback_states.append(current_preview.state) - return {"manager_preview_invalidation_event_status": "written"} - destroy_records = apply_odoo_preview_lifecycle_evidence( control_plane_root_path=root, record_store=store, profile=profile, issued_plan=destroy_plan, driver_result=destroy_result, - before_destroy=before_destroy, ) delayed_replay = apply_odoo_preview_lifecycle_evidence( @@ -2295,8 +2286,7 @@ def before_destroy() -> dict[str, object]: ) self.assertEqual(delayed_replay["lifecycle_evidence_status"], "stale") - self.assertEqual(destroy_callback_states, ["active"]) - self.assertEqual(destroy_records["manager_preview_invalidation_event_status"], "written") + self.assertEqual(destroy_records["lifecycle_evidence_status"], "applied") self.assertEqual(preview.state, "destroyed") self.assertEqual(preview.serving_generation_id, "") self.assertEqual(error_context.exception.code, "odoo_preview_operation_superseded") @@ -2739,10 +2729,6 @@ async def test_odoo_preview_destroy_apply_allows_missing_image_reference(self) - compose_name="cm-odoo-preview-pr-42", ), ) as apply_driver, - patch( - "control_plane.http_app.record_manager_preview_approval_invalidation_for_pr", - return_value={"required": True, "event_status": "written"}, - ) as record_manager_invalidation, ): response = await _post_odoo_preview_apply( app, @@ -2782,24 +2768,6 @@ async def test_odoo_preview_destroy_apply_allows_missing_image_reference(self) - applied_request = apply_driver.call_args.kwargs["request"] self.assertEqual(applied_request.dry_run_plan.operation, "destroy") self.assertEqual(applied_request.image_reference, "") - record_manager_invalidation.assert_called_once() - invalidation_call = record_manager_invalidation.call_args.kwargs - self.assertEqual(invalidation_call["repository"], "cbusillo/odoo-tenant-cm") - self.assertEqual(invalidation_call["pr_number"], 42) - self.assertEqual(invalidation_call["source_event_kind"], "preview_destroy") - self.assertEqual( - invalidation_call["source_event_id"], - f"odoo-preview-destroy:{plan_id}", - ) - self.assertTrue(invalidation_call["occurred_at"]) - self.assertEqual( - response.json()["records"]["manager_preview_invalidation_event_status"], - "written", - ) - self.assertEqual( - replayed_response.json()["records"]["manager_preview_invalidation_event_status"], - "written", - ) async def test_odoo_preview_recovers_abandoned_refresh_only_after_grace( self, @@ -3090,10 +3058,6 @@ async def test_odoo_preview_apply_replays_non_blocked_idempotency(self) -> None: "compose_name": "cm-odoo-preview-pr-42", }, ) as apply_driver, - patch( - "control_plane.manager_preview_approval_github_webhook" - ".write_manager_preview_approval_projection" - ) as write_manager_status, ): first_response = await _post_odoo_preview_apply( app, @@ -3119,7 +3083,6 @@ async def test_odoo_preview_apply_replays_non_blocked_idempotency(self) -> None: self.assertEqual(conflict_response.status_code, 409) self.assertEqual(conflict_response.json()["error"]["code"], "odoo_preview_plan_mismatch") apply_driver.assert_called_once() - write_manager_status.assert_not_called() previews = store.list_preview_records( context_name="cm", anchor_repo="odoo-tenant-cm", @@ -3150,14 +3113,6 @@ async def test_odoo_preview_apply_replays_non_blocked_idempotency(self) -> None: generation.runtime_identity.image_reference, f"ghcr.io/cbusillo/odoo-tenant-cm@sha256:{image_digest}", ) - binding = build_current_manager_preview_approval_binding( - product="odoo-tenant-cm", - preview=preview, - generation=generation, - ) - self.assertEqual(binding.head_sha, head_sha) - self.assertEqual(binding.preview_url, "https://pr-42.cm-preview.example.test") - self.assertEqual(binding.artifact_image_digest, f"sha256:{image_digest}") async def test_odoo_preview_apply_handler_file_miss_is_not_found(self) -> None: with TemporaryDirectory() as temporary_directory_name: diff --git a/tests/test_manager_preview_approval.py b/tests/test_manager_preview_approval.py deleted file mode 100644 index f5ec8abe0..000000000 --- a/tests/test_manager_preview_approval.py +++ /dev/null @@ -1,814 +0,0 @@ -import json -from pathlib import Path -from tempfile import TemporaryDirectory -import unittest -from typing import Literal - -from control_plane.contracts.authz_policy_record import ( - LaunchplaneAuthzPolicyRecord, - authz_policy_sha256, - build_authz_policy_record_id, -) -from control_plane.contracts.manager_preview_approval import ( - MANAGER_PREVIEW_APPROVAL_READ_ACTION, - MANAGER_PREVIEW_APPROVAL_WRITE_ACTION, - ManagerPreviewApprovalDecision, - ManagerPreviewApprovalEventRecord, -) -from control_plane.contracts.repository_human_admission import ( - RepositoryHumanManagerDelegation, - RepositoryHumanRolePolicyRecord, -) -from control_plane.contracts.preview_generation_record import ( - PreviewGenerationRecord, - PreviewPullRequestSummary, -) -from control_plane.contracts.preview_record import PreviewRecord -from control_plane.contracts.runtime_identity import RuntimeIdentity -from control_plane.manager_preview_approval import ( - ManagerPreviewApprovalAuthorizationError, - ManagerPreviewApprovalEventConflictError, - ManagerPreviewApprovalEventStore, - build_current_manager_preview_approval_binding, - build_manager_preview_approval_system_event, - capture_manager_preview_approval_authorization, - evaluate_manager_preview_approval, - record_manager_preview_approval_event, - manager_preview_approval_required, -) -from control_plane.service_auth import ( - GitHubHumanIdentity, - GitHubHumanPolicyRule, - LaunchplaneAuthzPolicy, -) -from control_plane.storage.filesystem import FilesystemRecordStore -from control_plane.storage.postgres import PostgresRecordStore - - -PRODUCT = "example-site" -CONTEXT = "example-site-testing" -REPOSITORY = "example/example-site" -PR_NUMBER = 17 -HEAD_SHA = "1" * 40 -IMAGE_DIGEST = f"sha256:{'a' * 64}" -OCCURRED_AT = "2026-07-30T12:00:00Z" - - -class ManagerPreviewApprovalTests(unittest.TestCase): - def test_verified_binding_digest_remains_compatible_after_evidence_extraction(self) -> None: - binding = build_current_manager_preview_approval_binding( - product=PRODUCT, - preview=_preview(), - generation=_generation(), - ) - - self.assertEqual( - binding.binding_sha256, - "4484dd9b84b4cd956a23cbfcc335d1ad72fecf0fdd20de9e3097646ea82fa60c", - ) - - def test_legacy_binding_allows_blank_runtime_generation_identity(self) -> None: - binding = build_current_manager_preview_approval_binding( - product=PRODUCT, - preview=_preview(), - generation=_generation(runtime_identity=_runtime_identity(preview_generation_id="")), - ) - - self.assertEqual(binding.serving_generation_id, "generation-17") - self.assertEqual(binding.runtime_identity.preview_generation_id, "") - - def test_records_approval_only_after_exact_manager_authorization(self) -> None: - for version in (2, 3): - with self.subTest(schema_version=version): - with TemporaryDirectory() as temporary_directory_name: - store = FilesystemRecordStore(state_dir=Path(temporary_directory_name)) - result = record_manager_preview_approval_event( - record_store=store, - identity=_manager_identity(login="renamed-manager"), - policy_record=_policy_record(schema_version=version), - product=PRODUCT, - preview=_preview(), - generation=_generation(), - action="approved", - occurred_at=OCCURRED_AT, - source_event_kind="github_issue_comment", - source_event_id="comment-101", - ) - - self.assertEqual(result.status, "written") - self.assertEqual(result.record.manager_github_id, 101) - self.assertEqual(result.record.manager_login, "renamed-manager") - self.assertEqual(result.record.binding.head_sha, HEAD_SHA) - self.assertEqual(result.record.binding.artifact_image_digest, IMAGE_DIGEST) - self.assertEqual( - store.list_manager_preview_approval_event_records( - product=PRODUCT, - context=CONTEXT, - repository=REPOSITORY, - pr_number=PR_NUMBER, - ), - (result.record,), - ) - - policy = _policy_record(schema_version=version) - self.assertTrue( - manager_preview_approval_required( - policy_record=policy, product=PRODUCT, context=CONTEXT - ) - ) - assert result.record.authorization is not None - self.assertEqual( - result.record.authorization.policy_schema_version, policy.policy.schema_version - ) - restored = ManagerPreviewApprovalEventRecord.model_validate_json( - result.record.model_dump_json() - ) - self.assertEqual( - _decision(events=(restored,), policy_record=policy).status, "approved" - ) - if version == 2: - legacy_payload = restored.model_dump(mode="json") - legacy_payload["authorization"].pop("policy_schema_version") - legacy_bytes = json.dumps( - legacy_payload, - separators=(",", ":"), - ).encode("utf-8") - legacy = ManagerPreviewApprovalEventRecord.model_validate_json(legacy_bytes) - self.assertEqual(legacy.model_dump_json(), restored.model_dump_json()) - self.assertEqual( - _decision(events=(legacy,), policy_record=policy).status, - "approved", - ) - - payload = restored.model_dump(mode="json") - payload["authorization"]["policy_schema_version"] = 3 if version == 2 else 2 - mislabelled = ManagerPreviewApprovalEventRecord.model_validate(payload) - self.assertEqual( - _decision(events=(mislabelled,), policy_record=policy).status, "stale" - ) - - def test_rejects_actor_not_named_by_stable_github_id(self) -> None: - with self.assertRaisesRegex( - ManagerPreviewApprovalAuthorizationError, - "exactly one managed policy rule", - ): - capture_manager_preview_approval_authorization( - identity=_manager_identity(github_id=202), - product=PRODUCT, - context=CONTEXT, - policy_record=_policy_record(), - authorized_at=OCCURRED_AT, - ) - - def test_rejects_actor_without_stable_github_numeric_identity(self) -> None: - with self.assertRaisesRegex( - ManagerPreviewApprovalAuthorizationError, - "stable GitHub numeric identity", - ): - capture_manager_preview_approval_authorization( - identity=_manager_identity(github_id=0), - product=PRODUCT, - context=CONTEXT, - policy_record=_policy_record(), - authorized_at=OCCURRED_AT, - ) - - def test_manager_write_path_rejects_system_actions(self) -> None: - with TemporaryDirectory() as temporary_directory_name: - store = FilesystemRecordStore(state_dir=Path(temporary_directory_name)) - with self.assertRaisesRegex(ValueError, "manager action"): - record_manager_preview_approval_event( - record_store=store, - identity=_manager_identity(), - policy_record=_policy_record(), - product=PRODUCT, - preview=_preview(), - generation=_generation(), - action="superseded", - occurred_at=OCCURRED_AT, - source_event_kind="preview_generation", - source_event_id="generation-18", - reason="A new serving generation replaced this preview.", - ) - - def test_requires_current_verified_runtime_identity(self) -> None: - with self.assertRaisesRegex(ValueError, "runtime identity"): - build_current_manager_preview_approval_binding( - product=PRODUCT, - preview=_preview(), - generation=_generation(runtime_identity=None), - ) - - def test_rejects_runtime_identity_for_wrong_source_or_environment(self) -> None: - mismatch_cases = { - "source": _runtime_identity(source_git_ref="2" * 40), - "environment": _runtime_identity(environment_kind="stable"), - "preview_id": _runtime_identity(preview_id="preview-18"), - "generation": _runtime_identity(preview_generation_id="generation-18"), - } - for label, runtime_identity in mismatch_cases.items(): - with self.subTest(label=label): - with self.assertRaisesRegex(ValueError, "runtime identity"): - build_current_manager_preview_approval_binding( - product=PRODUCT, - preview=_preview(), - generation=_generation(runtime_identity=runtime_identity), - ) - - def test_requires_runtime_identity_preview_id_to_match_binding_preview_id(self) -> None: - with self.assertRaisesRegex(ValueError, "runtime identity"): - build_current_manager_preview_approval_binding( - product=PRODUCT, - preview=_preview(), - generation=_generation(runtime_identity=_runtime_identity(preview_id="preview-18")), - ) - - def test_filesystem_replays_identical_event_and_rejects_conflicting_payload(self) -> None: - with TemporaryDirectory() as temporary_directory_name: - store = FilesystemRecordStore(state_dir=Path(temporary_directory_name)) - event = _approved_event(store) - - self.assertEqual(store.write_manager_preview_approval_event_record(event), "replayed") - conflicting = ManagerPreviewApprovalEventRecord.model_validate( - {**event.model_dump(mode="json"), "reason": "Conflicting replay."} - ) - with self.assertRaises(ManagerPreviewApprovalEventConflictError): - store.write_manager_preview_approval_event_record(conflicting) - - def test_sql_store_persists_and_replays_append_only_event(self) -> None: - with TemporaryDirectory() as temporary_directory_name: - database_path = Path(temporary_directory_name) / "launchplane.sqlite3" - store = PostgresRecordStore(database_url=f"sqlite:///{database_path}") - store.ensure_schema() - event = _approved_event(store) - try: - self.assertEqual( - store.write_manager_preview_approval_event_record(event), - "replayed", - ) - self.assertEqual( - store.list_manager_preview_approval_event_records(preview_id="preview-17"), - (event,), - ) - conflicting = ManagerPreviewApprovalEventRecord.model_validate( - {**event.model_dump(mode="json"), "reason": "Conflicting replay."} - ) - with self.assertRaises(ManagerPreviewApprovalEventConflictError): - store.write_manager_preview_approval_event_record(conflicting) - finally: - store.close() - - def test_decision_tracks_changes_requested_revocation_and_invalidation(self) -> None: - with TemporaryDirectory() as temporary_directory_name: - store = FilesystemRecordStore(state_dir=Path(temporary_directory_name)) - approved = _approved_event(store) - changes_requested = record_manager_preview_approval_event( - record_store=store, - identity=_manager_identity(), - policy_record=_policy_record(), - product=PRODUCT, - preview=_preview(), - generation=_generation(), - action="changes_requested", - occurred_at="2026-07-30T12:01:00Z", - source_event_kind="github_issue_comment", - source_event_id="comment-102", - reason="Adjust the primary call to action.", - ).record - decision = _decision(events=(approved, changes_requested)) - self.assertEqual(decision.status, "changes_requested") - - revoked = record_manager_preview_approval_event( - record_store=store, - identity=_manager_identity(), - policy_record=_policy_record(), - product=PRODUCT, - preview=_preview(), - generation=_generation(), - action="revoked", - occurred_at="2026-07-30T12:02:00Z", - source_event_kind="github_issue_comment", - source_event_id="comment-103", - reason="Approval was revoked.", - ).record - decision = _decision(events=(approved, changes_requested, revoked)) - self.assertEqual(decision.status, "revoked") - - invalidated = build_manager_preview_approval_system_event( - binding=approved.binding, - action="invalidated", - occurred_at="2026-07-30T12:03:00Z", - source_event_kind="preview_destroyed", - source_event_id="preview-17-destroyed", - reason="The preview was destroyed.", - ) - decision = _decision(events=(approved, changes_requested, revoked, invalidated)) - self.assertEqual(decision.status, "stale") - - late_approval = record_manager_preview_approval_event( - record_store=store, - identity=_manager_identity(), - policy_record=_policy_record(), - product=PRODUCT, - preview=_preview(), - generation=_generation(), - action="approved", - occurred_at="2026-07-30T12:05:00Z", - source_event_kind="github_issue_comment", - source_event_id="comment-104", - ).record - decision = _decision( - events=(approved, changes_requested, revoked, invalidated, late_approval) - ) - self.assertEqual(decision.status, "stale") - self.assertEqual(decision.event_id, invalidated.event_id) - - superseded = build_manager_preview_approval_system_event( - binding=approved.binding, - action="superseded", - occurred_at="2026-07-30T12:04:00Z", - source_event_kind="preview_generation", - source_event_id="generation-18", - reason="A new serving generation replaced this preview.", - ) - decision = _decision( - events=(approved, changes_requested, revoked, invalidated, superseded) - ) - self.assertEqual(decision.status, "stale") - - def test_exact_approval_becomes_stale_when_preview_identity_changes(self) -> None: - with TemporaryDirectory() as temporary_directory_name: - store = FilesystemRecordStore(state_dir=Path(temporary_directory_name)) - approved = _approved_event(store) - stale_cases = { - "head": ( - _preview(), - _generation( - head_sha="2" * 40, - runtime_identity=_runtime_identity(source_git_ref="2" * 40), - ), - ), - "generation": ( - _preview( - active_generation_id="generation-18", - serving_generation_id="generation-18", - latest_generation_id="generation-18", - ), - _generation( - generation_id="generation-18", - runtime_identity=_runtime_identity(preview_generation_id="generation-18"), - ), - ), - "artifact": ( - _preview(), - _generation( - artifact_id="artifact-18", - runtime_identity=_runtime_identity( - artifact_id="artifact-18", - image_reference=f"ghcr.io/example/site@sha256:{'b' * 64}", - ), - ), - ), - "manifest": ( - _preview(latest_manifest_fingerprint="manifest-18"), - _generation(resolved_manifest_fingerprint="manifest-18"), - ), - "runtime": ( - _preview(), - _generation( - runtime_identity=_runtime_identity(deployment_record_id="deployment-18") - ), - ), - } - for label, (preview, generation) in stale_cases.items(): - with self.subTest(label=label): - decision = _decision( - preview=preview, - generation=generation, - events=(approved,), - ) - self.assertEqual(decision.status, "stale") - - def test_terminal_prior_binding_yields_pending_for_replacement_preview(self) -> None: - with TemporaryDirectory() as temporary_directory_name: - store = FilesystemRecordStore(state_dir=Path(temporary_directory_name)) - approved = _approved_event(store) - replacement_preview = _preview( - active_generation_id="generation-18", - serving_generation_id="generation-18", - latest_generation_id="generation-18", - latest_manifest_fingerprint="manifest-18", - ) - replacement_generation = _generation( - generation_id="generation-18", - sequence=2, - resolved_manifest_fingerprint="manifest-18", - artifact_id="artifact-18", - runtime_identity=_runtime_identity( - deployment_record_id="deployment-18", - artifact_id="artifact-18", - image_reference=f"ghcr.io/example/site@sha256:{'b' * 64}", - preview_generation_id="generation-18", - ), - ) - - for action in ("invalidated", "superseded"): - with self.subTest(action=action): - terminal = build_manager_preview_approval_system_event( - binding=approved.binding, - action=action, - occurred_at="2026-07-30T11:59:00Z", - source_event_kind="preview_lifecycle", - source_event_id=f"generation-17:{action}", - reason="The prior serving generation is no longer current.", - ) - - decision = _decision( - preview=replacement_preview, - generation=replacement_generation, - events=(approved, terminal), - ) - - self.assertEqual(decision.status, "pending") - self.assertEqual(decision.reason_code, "approval_missing") - self.assertNotEqual( - decision.current_binding_sha256, - approved.binding.binding_sha256, - ) - - def test_policy_change_makes_prior_approval_stale(self) -> None: - with TemporaryDirectory() as temporary_directory_name: - store = FilesystemRecordStore(state_dir=Path(temporary_directory_name)) - approved = _approved_event(store) - changed_policy = _policy_record( - revision=2, - extra_actions=("product_profile.read",), - ) - - decision = _decision(events=(approved,), policy_record=changed_policy) - - self.assertEqual(decision.status, "stale") - self.assertEqual(decision.reason_code, "approval_stale") - - wrong_scope = _decision( - events=(approved,), - role_policy_record=_role_policy( - manager_primary_ids=(101,), - context="staging", - ), - ) - self.assertEqual(wrong_scope.status, "stale") - - def test_future_dated_approval_event_is_ignored(self) -> None: - with TemporaryDirectory() as temporary_directory_name: - store = FilesystemRecordStore(state_dir=Path(temporary_directory_name)) - future_approved = record_manager_preview_approval_event( - record_store=store, - identity=_manager_identity(), - policy_record=_policy_record(), - product=PRODUCT, - preview=_preview(), - generation=_generation(), - action="approved", - occurred_at="2026-07-30T12:11:00Z", - source_event_kind="github_issue_comment", - source_event_id="comment-104", - ).record - - decision = _decision(events=(future_approved,)) - - self.assertEqual(decision.status, "pending") - self.assertEqual(decision.reason_code, "approval_missing") - - with self.assertRaisesRegex(ValueError, "recorded before it occurred"): - record_manager_preview_approval_event( - record_store=store, - identity=_manager_identity(), - policy_record=_policy_record(), - product=PRODUCT, - preview=_preview(), - generation=_generation(), - action="approved", - occurred_at="2026-07-30T12:11:00Z", - recorded_at="2026-07-30T12:10:00Z", - source_event_kind="github_issue_comment", - source_event_id="comment-future-rejected", - ) - - def test_role_aware_manager_approval_requires_current_delegation(self) -> None: - active_delegation = RepositoryHumanManagerDelegation( - delegated_manager_github_id=101, - delegated_by_github_id=202, - starts_at="2026-07-30T11:00:00Z", - expires_at="2026-07-30T13:00:00Z", - source_event_kind="github_issue_comment", - source_event_id="delegation-101", - reason="Manager coverage.", - ) - role_policy = _role_policy(manager_delegations=(active_delegation,)) - with TemporaryDirectory() as temporary_directory_name: - store = FilesystemRecordStore(state_dir=Path(temporary_directory_name)) - approved = record_manager_preview_approval_event( - record_store=store, - identity=_manager_identity(), - policy_record=_policy_record(), - product=PRODUCT, - preview=_preview(), - generation=_generation(), - action="approved", - occurred_at=OCCURRED_AT, - source_event_kind="github_issue_comment", - source_event_id="comment-101", - role_policy_record=role_policy, - repository_id="1001", - repository_owner_id="2001", - ).record - - active_decision = _decision(events=(approved,), role_policy_record=role_policy) - self.assertEqual(active_decision.status, "approved") - assert approved.authorization is not None - assert approved.authorization.role_policy_provenance is not None - self.assertEqual( - approved.authorization.role_policy_provenance.authority_kind, - "manager_delegated", - ) - - revoked_policy = _role_policy( - manager_delegations=( - active_delegation.model_copy(update={"revoked_at": "2026-07-30T12:01:00Z"}), - ) - ) - stale_decision = _decision(events=(approved,), role_policy_record=revoked_policy) - self.assertEqual(stale_decision.status, "stale") - self.assertEqual(stale_decision.reason_code, "approval_stale") - - def test_role_policy_rejects_legacy_approval_without_provenance(self) -> None: - with TemporaryDirectory() as temporary_directory_name: - store = FilesystemRecordStore(state_dir=Path(temporary_directory_name)) - legacy_approval = _approved_event(store) - - decision = _decision( - events=(legacy_approval,), - role_policy_record=_role_policy(manager_primary_ids=(101,)), - ) - - self.assertEqual(decision.status, "stale") - self.assertEqual(decision.reason_code, "approval_stale") - - def test_same_timestamp_revocation_wins_over_approval(self) -> None: - with TemporaryDirectory() as temporary_directory_name: - store = FilesystemRecordStore(state_dir=Path(temporary_directory_name)) - approved = _approved_event(store) - revoked = record_manager_preview_approval_event( - record_store=store, - identity=_manager_identity(), - policy_record=_policy_record(), - product=PRODUCT, - preview=_preview(), - generation=_generation(), - action="revoked", - occurred_at=OCCURRED_AT, - source_event_kind="github_issue_comment", - source_event_id="comment-revoked-same-time", - reason="Manager revoked approval.", - ).record - - decision = _decision(events=(approved, revoked)) - - self.assertEqual(decision.status, "revoked") - self.assertEqual(decision.reason_code, "approval_revoked") - - def test_missing_policy_makes_approval_unavailable(self) -> None: - decision = evaluate_manager_preview_approval( - product=PRODUCT, - preview=_preview(), - generation=_generation(), - policy_record=None, - events=(), - evaluated_at="2026-07-30T12:10:00Z", - ) - - self.assertEqual(decision.status, "unavailable") - self.assertEqual(decision.reason_code, "policy_unavailable") - - def test_destroyed_or_failed_preview_is_unavailable_without_gating_cleanup(self) -> None: - destroyed = _decision( - preview=_preview(state="destroyed"), - events=(), - ) - failed = _decision( - generation=_generation(verify_status="fail"), - events=(), - ) - - self.assertEqual(destroyed.status, "unavailable") - self.assertEqual(destroyed.reason_code, "preview_inactive") - self.assertEqual(failed.status, "unavailable") - self.assertEqual(failed.reason_code, "generation_verification_failed") - - def test_empty_github_ids_do_not_change_existing_policy_serialization(self) -> None: - policy = LaunchplaneAuthzPolicy( - schema_version=2, - github_humans=( - GitHubHumanPolicyRule( - logins=("example",), - roles=("read_only",), - actions=(MANAGER_PREVIEW_APPROVAL_READ_ACTION,), - ), - ), - ) - - payload = json.loads(policy.model_dump_json()) - - self.assertNotIn("github_ids", payload["github_humans"][0]) - - -def _approved_event( - record_store: ManagerPreviewApprovalEventStore, -) -> ManagerPreviewApprovalEventRecord: - return record_manager_preview_approval_event( - record_store=record_store, - identity=_manager_identity(), - policy_record=_policy_record(), - product=PRODUCT, - preview=_preview(), - generation=_generation(), - action="approved", - occurred_at=OCCURRED_AT, - source_event_kind="github_issue_comment", - source_event_id="comment-101", - ).record - - -def _decision( - *, - preview: PreviewRecord | None = None, - generation: PreviewGenerationRecord | None = None, - policy_record: LaunchplaneAuthzPolicyRecord | None = None, - role_policy_record: RepositoryHumanRolePolicyRecord | None = None, - events: tuple[ManagerPreviewApprovalEventRecord, ...], -) -> ManagerPreviewApprovalDecision: - return evaluate_manager_preview_approval( - product=PRODUCT, - preview=preview or _preview(), - generation=generation or _generation(), - policy_record=policy_record or _policy_record(), - events=events, - evaluated_at="2026-07-30T12:10:00Z", - role_policy_record=role_policy_record, - ) - - -def _manager_identity(*, github_id: int = 101, login: str = "manager") -> GitHubHumanIdentity: - return GitHubHumanIdentity( - login=login, - github_id=github_id, - name="Example Manager", - email="manager@example.com", - organizations=frozenset(), - teams=frozenset(), - role="read_only", - ) - - -def _policy_record( - *, - schema_version: Literal[2, 3] = 2, - revision: int = 1, - extra_actions: tuple[str, ...] = (), -) -> LaunchplaneAuthzPolicyRecord: - policy = LaunchplaneAuthzPolicy( - schema_version=schema_version, - github_humans=( - GitHubHumanPolicyRule( - managed_set_id="manager.example-site", - managed_rule_id="preview-approval", - github_ids=(101,), - roles=("read_only",), - products=(PRODUCT,), - contexts=(CONTEXT,), - actions=( - MANAGER_PREVIEW_APPROVAL_READ_ACTION, - MANAGER_PREVIEW_APPROVAL_WRITE_ACTION, - *extra_actions, - ), - ), - ), - ) - policy_sha256 = authz_policy_sha256(policy) - return LaunchplaneAuthzPolicyRecord( - record_id=build_authz_policy_record_id( - revision=revision, - policy_sha256=policy_sha256, - ), - revision=revision, - status="active", - source="test:manager-preview-approval", - updated_at=OCCURRED_AT, - policy_sha256=policy_sha256, - policy=policy, - ) - - -def _role_policy( - *, - manager_primary_ids: tuple[int, ...] = (202,), - manager_backup_ids: tuple[int, ...] = (), - manager_delegations: tuple[RepositoryHumanManagerDelegation, ...] = (), - context: str = CONTEXT, -) -> RepositoryHumanRolePolicyRecord: - return RepositoryHumanRolePolicyRecord( - repository_id="1001", - repository_owner_id="2001", - repository=REPOSITORY, - product=PRODUCT, - context=context, - role_policy_revision=1, - repository_owner_github_ids=(301,), - manager_primary_github_ids=manager_primary_ids, - manager_backup_github_ids=manager_backup_ids, - manager_delegations=manager_delegations, - effective_at="2026-07-30T11:00:00Z", - source="test:manager-preview-role-policy", - reason="test manager role policy", - ) - - -def _preview(**updates: object) -> PreviewRecord: - payload = { - "preview_id": "preview-17", - "context": CONTEXT, - "anchor_repo": REPOSITORY, - "anchor_pr_number": PR_NUMBER, - "anchor_pr_url": f"https://github.com/{REPOSITORY}/pull/{PR_NUMBER}", - "preview_label": "launchplane-preview", - "canonical_url": "https://preview-17.example.com/", - "state": "active", - "created_at": "2026-07-30T11:00:00Z", - "updated_at": "2026-07-30T11:30:00Z", - "eligible_at": "2026-07-30T11:00:00Z", - "active_generation_id": "generation-17", - "serving_generation_id": "generation-17", - "latest_generation_id": "generation-17", - "latest_manifest_fingerprint": "manifest-17", - } - payload.update(updates) - return PreviewRecord.model_validate(payload) - - -def _generation(**updates: object) -> PreviewGenerationRecord: - payload = { - "generation_id": "generation-17", - "preview_id": "preview-17", - "sequence": 1, - "state": "ready", - "requested_reason": "Preview requested.", - "requested_at": "2026-07-30T11:00:00Z", - "started_at": "2026-07-30T11:01:00Z", - "ready_at": "2026-07-30T11:30:00Z", - "finished_at": "2026-07-30T11:30:00Z", - "resolved_manifest_fingerprint": "manifest-17", - "artifact_id": "artifact-17", - "anchor_summary": PreviewPullRequestSummary( - repo=REPOSITORY, - pr_number=PR_NUMBER, - head_sha=HEAD_SHA, - pr_url=f"https://github.com/{REPOSITORY}/pull/{PR_NUMBER}", - ), - "deploy_status": "pass", - "verify_status": "pass", - "overall_health_status": "pass", - "runtime_identity": _runtime_identity(), - } - head_sha = updates.pop("head_sha", None) - if head_sha is not None: - payload["anchor_summary"] = PreviewPullRequestSummary( - repo=REPOSITORY, - pr_number=PR_NUMBER, - head_sha=str(head_sha), - pr_url=f"https://github.com/{REPOSITORY}/pull/{PR_NUMBER}", - ) - payload.update(updates) - return PreviewGenerationRecord.model_validate(payload) - - -def _runtime_identity(**updates: object) -> RuntimeIdentity: - payload: dict[str, object] = { - "product": PRODUCT, - "context": CONTEXT, - "instance": "preview-17", - "environment_kind": "preview", - "deployment_record_id": "deployment-17", - "artifact_id": "artifact-17", - "source_git_ref": HEAD_SHA, - "image_reference": f"ghcr.io/example/site@{IMAGE_DIGEST}", - "preview_id": "preview-17", - "preview_generation_id": "generation-17", - "deployed_at": "2026-07-30T11:20:00Z", - } - payload.update(updates) - return RuntimeIdentity.model_validate(payload) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_manager_preview_approval_github_webhook.py b/tests/test_manager_preview_approval_github_webhook.py deleted file mode 100644 index af689d617..000000000 --- a/tests/test_manager_preview_approval_github_webhook.py +++ /dev/null @@ -1,1105 +0,0 @@ -import hashlib -import json -from pathlib import Path -import unittest -from unittest.mock import ANY, patch - -import click - -from control_plane.contracts.authz_policy_record import ( - LaunchplaneAuthzPolicyRecord, - authz_policy_sha256, - build_authz_policy_record_id, -) -from control_plane.contracts.manager_preview_approval import ( - MANAGER_PREVIEW_APPROVAL_READ_ACTION, - MANAGER_PREVIEW_APPROVAL_WRITE_ACTION, - ManagerPreviewApprovalEventRecord, - ManagerPreviewApprovalEventWriteStatus, -) -from control_plane.contracts.preview_generation_record import ( - PreviewGenerationRecord, - PreviewPullRequestSummary, -) -from control_plane.contracts.preview_record import PreviewRecord -from control_plane.contracts.product_profile_record import ( - LaunchplaneProductProfileRecord, - ProductImageProfile, - ProductPreviewProfile, -) -from control_plane.contracts.runtime_identity import RuntimeIdentity -from control_plane.manager_preview_approval import ( - ManagerPreviewApprovalEventConflictError, - build_current_manager_preview_approval_binding, - build_manager_preview_approval_system_event, -) -from control_plane.manager_preview_approval_github_webhook import ( - ManagerPreviewApprovalGitHubDependencies, - handle_manager_preview_approval_github_webhook_request, - invalidate_manager_preview_approval_for_pr, - parse_manager_preview_approval_command, - reconcile_manager_preview_approval_for_pr, - reconcile_manager_preview_approval_for_pr_best_effort, -) -from control_plane.manager_preview_approval_projection import ( - build_manager_preview_approval_projection, - write_manager_preview_approval_projection, -) -from control_plane.service_auth import GitHubHumanPolicyRule, LaunchplaneAuthzPolicy -from control_plane.trusted_maintenance_github_webhook import ( - TrustedMaintenanceGitHubWebhookResult, -) - - -PRODUCT = "example-site" -CONTEXT = "example-site-preview" -REPOSITORY = "example/example-site" -ANCHOR_REPO = "example-site" -PR_NUMBER = 17 -HEAD_SHA = "1" * 40 -IMAGE_DIGEST = f"sha256:{'a' * 64}" -NOW = "2026-07-31T12:00:00Z" - - -class _Store: - def __init__(self) -> None: - self.profile = _profile() - self.preview = _preview() - self.generation = _generation() - self.policy = _policy_record() - self.events: dict[str, ManagerPreviewApprovalEventRecord] = {} - - def list_product_profile_records( - self, *, driver_id: str = "" - ) -> tuple[LaunchplaneProductProfileRecord, ...]: - if driver_id and driver_id != self.profile.driver_id: - return () - return (self.profile,) - - def list_preview_records( - self, - *, - context_name: str = "", - anchor_repo: str = "", - anchor_pr_number: int | None = None, - limit: int | None = None, - ) -> tuple[PreviewRecord, ...]: - records = ( - (self.preview,) - if ( - (not context_name or context_name == self.preview.context) - and (not anchor_repo or anchor_repo == self.preview.anchor_repo) - and (anchor_pr_number is None or anchor_pr_number == self.preview.anchor_pr_number) - ) - else () - ) - return records[:limit] if limit is not None else records - - def read_preview_generation_record(self, generation_id: str) -> PreviewGenerationRecord: - if generation_id != self.generation.generation_id: - raise FileNotFoundError(generation_id) - return self.generation - - def list_manager_preview_approval_event_records( - self, - *, - product: str = "", - context: str = "", - repository: str = "", - pr_number: int | None = None, - preview_id: str = "", - action: str = "", - limit: int | None = None, - ) -> tuple[ManagerPreviewApprovalEventRecord, ...]: - records = tuple( - event - for event in self.events.values() - if (not product or event.binding.product == product) - and (not context or event.binding.context == context) - and (not repository or event.binding.repository == repository) - and (pr_number is None or event.binding.pr_number == pr_number) - and (not preview_id or event.binding.preview_id == preview_id) - and (not action or event.action == action) - ) - records = tuple(sorted(records, key=lambda event: (event.occurred_at, event.event_id))) - return records[:limit] if limit is not None else records - - def write_manager_preview_approval_event_record( - self, record: ManagerPreviewApprovalEventRecord - ) -> ManagerPreviewApprovalEventWriteStatus: - existing = self.events.get(record.event_id) - if existing is None: - self.events[record.event_id] = record - return "written" - if existing == record: - return "replayed" - raise ManagerPreviewApprovalEventConflictError( - "Manager preview approval event id conflicts with existing evidence." - ) - - def list_authz_policy_records( - self, - *, - status: str = "", - limit: int | None = None, - ) -> tuple[LaunchplaneAuthzPolicyRecord, ...]: - records = (self.policy,) if not status or status == self.policy.status else () - return records[:limit] if limit is not None else records - - -class _GitHubApi: - def __init__(self) -> None: - self.comment_body = "" - self.comment_id = 501 - self.comment_created_at = NOW - self.comment_actor_id = 101 - self.comment_actor_login = "manager" - self.head_sha = HEAD_SHA - self.pr_state = "open" - self.projection_actor_id = 9001 - self.comments: list[dict[str, object]] = [] - self.statuses: list[dict[str, object]] = [] - self.calls: list[tuple[str, str, object]] = [] - self.fail_projection_writes = False - - def __call__(self, **kwargs: object) -> object: - path = str(kwargs.get("path") or "") - method = str(kwargs.get("method") or "GET") - body = kwargs.get("body") - self.calls.append((method, path, body)) - if ( - self.fail_projection_writes - and method in {"PATCH", "POST"} - and ( - "/issues/comments/" in path - or path.endswith(f"/issues/{PR_NUMBER}/comments") - or "/statuses/" in path - ) - ): - raise click.ClickException("GitHub projection is temporarily unavailable.") - if path == "/user": - return {"id": self.projection_actor_id, "login": "launchplane"} - if path == f"/repos/{REPOSITORY}/issues/comments/{self.comment_id}" and method == "GET": - return { - "id": self.comment_id, - "body": self.comment_body, - "created_at": self.comment_created_at, - "user": { - "id": self.comment_actor_id, - "login": self.comment_actor_login, - "type": "User", - }, - } - if path == f"/repos/{REPOSITORY}/pulls/{PR_NUMBER}": - return { - "number": PR_NUMBER, - "state": self.pr_state, - "html_url": f"https://github.com/{REPOSITORY}/pull/{PR_NUMBER}", - "head": {"sha": self.head_sha}, - } - if path == f"/users/{self.comment_actor_login}": - return { - "id": self.comment_actor_id, - "login": self.comment_actor_login, - "name": "Example Manager", - } - if path == (f"/repos/{REPOSITORY}/issues/{PR_NUMBER}/comments?per_page=100"): - return list(self.comments) - if path == f"/repos/{REPOSITORY}/issues/{PR_NUMBER}/comments" and method == "POST": - assert isinstance(body, dict) - comment = { - "id": 700 + len(self.comments), - "html_url": f"https://github.com/{REPOSITORY}/pull/{PR_NUMBER}#issuecomment-700", - "body": body["body"], - "user": {"id": self.projection_actor_id}, - } - self.comments.append(comment) - return comment - if path.startswith(f"/repos/{REPOSITORY}/issues/comments/") and method == "PATCH": - assert isinstance(body, dict) - comment_id = int(path.rsplit("/", 1)[1]) - comment = next(comment for comment in self.comments if comment["id"] == comment_id) - comment["body"] = body["body"] - return comment - if path == f"/repos/{REPOSITORY}/statuses/{self.head_sha}" and method == "POST": - assert isinstance(body, dict) - self.statuses.append(body) - return {"id": len(self.statuses), **body} - raise AssertionError(f"Unexpected GitHub API request: {method} {path}") - - -class ManagerPreviewApprovalGitHubWebhookTests(unittest.TestCase): - def test_parses_only_exact_single_line_commands(self) -> None: - fingerprint = "a" * 64 - - approved = parse_manager_preview_approval_command(f"/preview approve {fingerprint}") - changes = parse_manager_preview_approval_command( - f"/preview changes {fingerprint} Please revise the hero" - ) - - self.assertIsNotNone(approved) - assert approved is not None - self.assertEqual(approved.action, "approved") - self.assertIsNotNone(changes) - assert changes is not None - self.assertEqual(changes.action, "changes_requested") - self.assertIsNone( - parse_manager_preview_approval_command(f"Looks good\n/preview approve {fingerprint}") - ) - self.assertIsNone(parse_manager_preview_approval_command(f"/preview revoke {fingerprint}")) - - def test_projection_updates_only_credential_owned_comment(self) -> None: - store = _Store() - github = _GitHubApi() - github.comments = [ - { - "id": 41, - "html_url": "https://example.test/attacker", - "body": "", - "user": {"id": 1234}, - }, - { - "id": 42, - "html_url": "https://example.test/owned", - "body": "", - "user": {"id": github.projection_actor_id}, - }, - ] - projection = build_manager_preview_approval_projection( - record_store=store, - repository=REPOSITORY, - pr_number=PR_NUMBER, - pr_url=f"https://github.com/{REPOSITORY}/pull/{PR_NUMBER}", - pr_state="open", - current_head_sha=HEAD_SHA, - evaluated_at=NOW, - ) - - result = write_manager_preview_approval_projection( - projection=projection, - token="managed-token", - api_request=github, - ) - - self.assertEqual(result["check_state"], "pending") - self.assertIn( - ("PATCH", f"/repos/{REPOSITORY}/issues/comments/42", ANY), - github.calls, - ) - self.assertEqual(github.statuses[-1]["context"], "manager-preview-approval") - self.assertEqual(github.statuses[-1]["state"], "pending") - - def test_signed_pull_request_delegates_to_trusted_maintenance_after_signature( - self, - ) -> None: - store = _Store() - github = _GitHubApi() - payload = _pull_request_payload(action="synchronize") - body = json.dumps(payload).encode() - with patch( - "control_plane.manager_preview_approval_github_webhook." - "handle_trusted_maintenance_github_webhook", - return_value=TrustedMaintenanceGitHubWebhookResult( - status="skipped", - reason="test", - ), - ) as trusted_handler: - status_code, response = handle_manager_preview_approval_github_webhook_request( - body, - "pull_request", - "delivery-trusted-preview", - "sha256=test", - store, - Path("/tmp/launchplane"), - "trace-trusted-preview", - dependencies=_dependencies(github), - ) - - self.assertEqual(status_code, 202) - self.assertEqual(response["status"], "accepted") - trusted_handler.assert_called_once_with( - event_name="pull_request", - delivery_id="delivery-trusted-preview", - signed_payload_sha256=hashlib.sha256(body).hexdigest(), - payload=payload, - record_store=store, - control_plane_root=Path("/tmp/launchplane"), - dependencies=ANY, - ) - - def test_invalid_signature_never_delegates_to_trusted_maintenance(self) -> None: - def reject_signature(**_kwargs: object) -> None: - raise click.ClickException("invalid signature") - - dependencies = ManagerPreviewApprovalGitHubDependencies( - webhook_secret=lambda: "secret", - verify_signature=reject_signature, - github_api=_GitHubApi(), - github_token=lambda **_kwargs: "managed-token", - ) - with patch( - "control_plane.manager_preview_approval_github_webhook." - "handle_trusted_maintenance_github_webhook" - ) as trusted_handler: - status_code, response = handle_manager_preview_approval_github_webhook_request( - json.dumps(_pull_request_payload(action="synchronize")).encode(), - "pull_request", - "delivery-invalid-signature", - "sha256=invalid", - _Store(), - Path("/tmp/launchplane"), - "trace-invalid-signature", - dependencies=dependencies, - ) - - self.assertEqual(status_code, 401) - error = response["error"] - assert isinstance(error, dict) - self.assertEqual(error["code"], "invalid_signature") - trusted_handler.assert_not_called() - - def test_signed_comment_approves_exact_preview_and_replays(self) -> None: - store = _Store() - github = _GitHubApi() - fingerprint = build_current_manager_preview_approval_binding( - product=PRODUCT, - preview=store.preview, - generation=store.generation, - ).binding_sha256 - github.comment_body = f"/preview approve {fingerprint}" - payload = _issue_comment_payload() - dependencies = _dependencies(github) - - first_status, first = handle_manager_preview_approval_github_webhook_request( - json.dumps(payload).encode(), - "issue_comment", - "delivery-1", - "sha256=test", - store, - Path("/tmp/launchplane"), - "trace-1", - dependencies=dependencies, - ) - replay_status, replay = handle_manager_preview_approval_github_webhook_request( - json.dumps(payload).encode(), - "issue_comment", - "delivery-1", - "sha256=test", - store, - Path("/tmp/launchplane"), - "trace-2", - dependencies=_dependencies(github, now="2026-07-31T12:05:00Z"), - ) - - self.assertEqual(first_status, 202) - first_result = first["result"] - replay_result = replay["result"] - assert isinstance(first_result, dict) - assert isinstance(replay_result, dict) - self.assertEqual(first_result["event_status"], "written") - self.assertEqual(first_result["approval_status"], "approved") - self.assertEqual(replay_status, 202) - self.assertEqual(replay_result["event_status"], "replayed") - self.assertEqual(len(store.events), 1) - self.assertEqual(github.statuses, []) - self.assertEqual(github.comments, []) - - def test_exact_current_command_replaces_stale_prior_binding(self) -> None: - store = _Store() - github = _GitHubApi() - dependencies = _dependencies(github) - prior_fingerprint = build_current_manager_preview_approval_binding( - product=PRODUCT, - preview=store.preview, - generation=store.generation, - ).binding_sha256 - github.comment_body = f"/preview approve {prior_fingerprint}" - payload = json.dumps(_issue_comment_payload()).encode() - handle_manager_preview_approval_github_webhook_request( - payload, - "issue_comment", - "delivery-prior", - "sha256=test", - store, - Path("/tmp/launchplane"), - "trace-prior", - dependencies=dependencies, - ) - current_head_sha = "2" * 40 - _replace_serving_generation(store, head_sha=current_head_sha) - github.head_sha = current_head_sha - github.comment_id = 502 - github.comment_created_at = NOW - current_fingerprint = build_current_manager_preview_approval_binding( - product=PRODUCT, - preview=store.preview, - generation=store.generation, - ).binding_sha256 - github.comment_body = f"/preview approve {current_fingerprint}" - current_payload = json.dumps(_issue_comment_payload(comment_id=502)).encode() - - status, response = handle_manager_preview_approval_github_webhook_request( - current_payload, - "issue_comment", - "delivery-current", - "sha256=test", - store, - Path("/tmp/launchplane"), - "trace-current", - dependencies=dependencies, - ) - - self.assertEqual(status, 202) - result = response["result"] - assert isinstance(result, dict) - self.assertEqual(result["event_status"], "written") - self.assertEqual(result["approval_status"], "approved") - self.assertEqual(len(store.events), 2) - self.assertEqual(github.statuses, []) - self.assertEqual(github.comments, []) - - replay_status, replay_response = handle_manager_preview_approval_github_webhook_request( - current_payload, - "issue_comment", - "delivery-current", - "sha256=test", - store, - Path("/tmp/launchplane"), - "trace-current-replay", - dependencies=dependencies, - ) - - self.assertEqual(replay_status, 202) - replay_result = replay_response["result"] - assert isinstance(replay_result, dict) - self.assertEqual(replay_result["event_status"], "replayed") - self.assertEqual(replay_result["approval_status"], "approved") - self.assertEqual(len(store.events), 2) - - def test_exact_current_command_replaces_policy_stale_approval(self) -> None: - store = _Store() - github = _GitHubApi() - fingerprint = build_current_manager_preview_approval_binding( - product=PRODUCT, - preview=store.preview, - generation=store.generation, - ).binding_sha256 - github.comment_body = f"/preview approve {fingerprint}" - github.comment_created_at = "2026-07-31T11:50:00Z" - handle_manager_preview_approval_github_webhook_request( - json.dumps(_issue_comment_payload()).encode(), - "issue_comment", - "delivery-policy-1", - "sha256=test", - store, - Path("/tmp/launchplane"), - "trace-policy-1", - dependencies=_dependencies(github, now="2026-07-31T11:50:00Z"), - ) - store.policy = _policy_record( - revision=2, - extra_actions=("product_profile.read",), - ) - github.comment_id = 502 - github.comment_created_at = "2026-07-31T12:00:00Z" - - status, response = handle_manager_preview_approval_github_webhook_request( - json.dumps(_issue_comment_payload(comment_id=502)).encode(), - "issue_comment", - "delivery-policy-2", - "sha256=test", - store, - Path("/tmp/launchplane"), - "trace-policy-2", - dependencies=_dependencies(github, now="2026-07-31T12:00:00Z"), - ) - - self.assertEqual(status, 202) - result = response["result"] - assert isinstance(result, dict) - self.assertEqual(result["event_status"], "written") - self.assertEqual(result["approval_status"], "approved") - self.assertEqual(len(store.events), 2) - self.assertEqual(github.statuses, []) - self.assertEqual(github.comments, []) - latest_event = max( - store.events.values(), - key=lambda event: (event.occurred_at, event.event_id), - ) - assert latest_event.authorization is not None - self.assertEqual(latest_event.authorization.policy_revision, 2) - - def test_binding_change_during_command_evaluation_rejects_stale_fingerprint(self) -> None: - class _SameHeadBindingRaceStore(_Store): - def __init__(self) -> None: - super().__init__() - self.replace_after_read = True - - def read_preview_generation_record(self, generation_id: str) -> PreviewGenerationRecord: - generation = super().read_preview_generation_record(generation_id) - if self.replace_after_read: - self.replace_after_read = False - _replace_serving_generation(self, head_sha=HEAD_SHA) - return generation - - store = _SameHeadBindingRaceStore() - github = _GitHubApi() - prior_fingerprint = build_current_manager_preview_approval_binding( - product=PRODUCT, - preview=store.preview, - generation=store.generation, - ).binding_sha256 - github.comment_body = f"/preview approve {prior_fingerprint}" - - status, response = handle_manager_preview_approval_github_webhook_request( - json.dumps(_issue_comment_payload()).encode(), - "issue_comment", - "delivery-binding-race", - "sha256=test", - store, - Path("/tmp/launchplane"), - "trace-binding-race", - dependencies=_dependencies(github), - ) - - self.assertEqual(status, 202) - self.assertEqual(response["reason"], "stale_fingerprint") - self.assertEqual(store.events, {}) - self.assertEqual(github.statuses, []) - self.assertEqual(github.comments, []) - - def test_exact_terminal_binding_remains_stale(self) -> None: - for action in ("invalidated", "superseded"): - with self.subTest(action=action): - store = _Store() - binding = build_current_manager_preview_approval_binding( - product=PRODUCT, - preview=store.preview, - generation=store.generation, - ) - store.write_manager_preview_approval_event_record( - build_manager_preview_approval_system_event( - binding=binding, - action=action, - occurred_at="2026-07-31T11:45:00Z", - source_event_kind="preview_lifecycle", - source_event_id=f"generation-17:{action}", - reason="The exact serving preview binding is terminal.", - ) - ) - github = _GitHubApi() - github.comment_body = f"/preview approve {binding.binding_sha256}" - - status, response = handle_manager_preview_approval_github_webhook_request( - json.dumps(_issue_comment_payload()).encode(), - "issue_comment", - f"delivery-{action}", - "sha256=test", - store, - Path("/tmp/launchplane"), - f"trace-{action}", - dependencies=_dependencies(github), - ) - - self.assertEqual(status, 202) - self.assertEqual(response["reason"], "preview_evidence_not_current") - self.assertEqual(len(store.events), 1) - self.assertEqual(github.statuses, []) - self.assertEqual(github.comments, []) - - def test_closed_pr_and_mismatched_head_reject_exact_current_command(self) -> None: - for stale_source in ("closed_pr", "mismatched_head"): - with self.subTest(stale_source=stale_source): - store = _Store() - github = _GitHubApi() - binding = build_current_manager_preview_approval_binding( - product=PRODUCT, - preview=store.preview, - generation=store.generation, - ) - github.comment_body = f"/preview approve {binding.binding_sha256}" - if stale_source == "closed_pr": - github.pr_state = "closed" - else: - github.head_sha = "2" * 40 - - status, response = handle_manager_preview_approval_github_webhook_request( - json.dumps(_issue_comment_payload()).encode(), - "issue_comment", - f"delivery-{stale_source}", - "sha256=test", - store, - Path("/tmp/launchplane"), - f"trace-{stale_source}", - dependencies=_dependencies(github), - ) - - self.assertEqual(status, 202) - self.assertIn( - response["reason"], - {"preview_evidence_not_current", "stale_head"}, - ) - self.assertEqual(store.events, {}) - self.assertEqual(github.statuses, []) - self.assertEqual(github.comments, []) - - def test_stale_fingerprint_and_unauthorized_actor_do_not_write_evidence(self) -> None: - store = _Store() - github = _GitHubApi() - dependencies = _dependencies(github) - payload = json.dumps(_issue_comment_payload()).encode() - github.comment_body = f"/preview approve {'0' * 64}" - - stale_status, stale = handle_manager_preview_approval_github_webhook_request( - payload, - "issue_comment", - "delivery-stale", - "sha256=test", - store, - Path("/tmp/launchplane"), - "trace-stale", - dependencies=dependencies, - ) - fingerprint = build_current_manager_preview_approval_binding( - product=PRODUCT, - preview=store.preview, - generation=store.generation, - ).binding_sha256 - github.comment_body = f"/preview approve {fingerprint}" - github.comment_actor_id = 202 - github.comment_actor_login = "not-manager" - unauthorized_status, unauthorized = handle_manager_preview_approval_github_webhook_request( - payload, - "issue_comment", - "delivery-unauthorized", - "sha256=test", - store, - Path("/tmp/launchplane"), - "trace-unauthorized", - dependencies=dependencies, - ) - - self.assertEqual(stale_status, 202) - self.assertEqual(stale["reason"], "stale_fingerprint") - self.assertEqual(unauthorized_status, 202) - self.assertEqual(unauthorized["reason"], "unauthorized_actor") - self.assertEqual(store.events, {}) - - def test_invalid_signature_is_rejected_before_payload_processing(self) -> None: - def reject_signature(**_kwargs: object) -> None: - raise click.ClickException("signature mismatch") - - dependencies = ManagerPreviewApprovalGitHubDependencies( - webhook_secret=lambda: "secret", - verify_signature=reject_signature, - ) - - status, response = handle_manager_preview_approval_github_webhook_request( - b"{}", - "issue_comment", - "delivery-invalid", - "sha256=bad", - _Store(), - Path("/tmp/launchplane"), - "trace-invalid", - dependencies=dependencies, - ) - - self.assertEqual(status, 401) - error = response["error"] - assert isinstance(error, dict) - self.assertEqual(error["code"], "invalid_signature") - self.assertEqual(error["message"], "GitHub webhook signature is invalid.") - self.assertNotIn("signature mismatch", json.dumps(response)) - - def test_unavailable_evidence_does_not_expose_internal_exception(self) -> None: - dependencies = ManagerPreviewApprovalGitHubDependencies( - webhook_secret=lambda: "secret", - verify_signature=lambda **_kwargs: None, - github_token=lambda **_kwargs: (_ for _ in ()).throw( - click.ClickException("private credential path") - ), - ) - - status, response = handle_manager_preview_approval_github_webhook_request( - json.dumps(_issue_comment_payload()).encode(), - "issue_comment", - "delivery-unavailable", - "sha256=test", - _Store(), - Path("/tmp/launchplane"), - "trace-unavailable", - dependencies=dependencies, - ) - - self.assertEqual(status, 503) - error = response["error"] - assert isinstance(error, dict) - self.assertEqual(error["code"], "manager_preview_approval_unavailable") - self.assertEqual( - error["message"], - "Manager preview approval is temporarily unavailable.", - ) - self.assertNotIn("private credential path", json.dumps(response)) - - def test_preview_label_removal_invalidates_current_approval(self) -> None: - store = _Store() - github = _GitHubApi() - dependencies = _dependencies(github) - fingerprint = build_current_manager_preview_approval_binding( - product=PRODUCT, - preview=store.preview, - generation=store.generation, - ).binding_sha256 - github.comment_body = f"/preview approve {fingerprint}" - handle_manager_preview_approval_github_webhook_request( - json.dumps(_issue_comment_payload()).encode(), - "issue_comment", - "delivery-approve", - "sha256=test", - store, - Path("/tmp/launchplane"), - "trace-approve", - dependencies=dependencies, - ) - payload = { - "action": "unlabeled", - "number": PR_NUMBER, - "label": {"name": "launchplane-preview"}, - "repository": {"full_name": REPOSITORY}, - } - - status, response = handle_manager_preview_approval_github_webhook_request( - json.dumps(payload).encode(), - "pull_request", - "delivery-unlabeled", - "sha256=test", - store, - Path("/tmp/launchplane"), - "trace-unlabeled", - dependencies=dependencies, - ) - - self.assertEqual(status, 202) - result = response["result"] - assert isinstance(result, dict) - self.assertEqual(result["approval_status"], "stale") - self.assertEqual(len(store.events), 2) - self.assertEqual(github.statuses, []) - self.assertEqual(github.comments, []) - - def test_destroyed_preview_records_invalidation_from_captured_binding(self) -> None: - store = _Store() - binding = build_current_manager_preview_approval_binding( - product=PRODUCT, - preview=store.preview, - generation=store.generation, - ) - store.preview = store.preview.model_copy(update={"state": "destroyed"}) - github = _GitHubApi() - - result = invalidate_manager_preview_approval_for_pr( - repository=REPOSITORY, - pr_number=PR_NUMBER, - reason="The serving preview was destroyed.", - source_event_kind="preview_destroy", - source_event_id="destroy-17", - record_store=store, - control_plane_root=Path("/tmp/launchplane"), - occurred_at=NOW, - binding=binding, - dependencies=_dependencies(github), - ) - - self.assertEqual(result["event_status"], "written") - self.assertEqual(len(store.events), 1) - event = next(iter(store.events.values())) - self.assertEqual(event.action, "invalidated") - self.assertEqual(event.binding.binding_sha256, binding.binding_sha256) - self.assertEqual(github.statuses, []) - - def test_reconcile_recovers_pending_projection_after_destroy_and_replacement(self) -> None: - store = _Store() - prior_binding = build_current_manager_preview_approval_binding( - product=PRODUCT, - preview=store.preview, - generation=store.generation, - ) - store.write_manager_preview_approval_event_record( - build_manager_preview_approval_system_event( - binding=prior_binding, - action="invalidated", - occurred_at="2026-07-31T11:45:00Z", - source_event_kind="preview_destroy", - source_event_id="destroy-generation-17", - reason="The prior serving preview was destroyed.", - ) - ) - assert store.generation.runtime_identity is not None - store.preview = store.preview.model_copy( - update={ - "updated_at": "2026-07-31T11:50:00Z", - "active_generation_id": "generation-18", - "serving_generation_id": "generation-18", - "latest_generation_id": "generation-18", - "latest_manifest_fingerprint": "manifest-18", - } - ) - store.generation = store.generation.model_copy( - update={ - "generation_id": "generation-18", - "sequence": 2, - "requested_at": "2026-07-31T11:46:00Z", - "started_at": "2026-07-31T11:47:00Z", - "ready_at": "2026-07-31T11:50:00Z", - "finished_at": "2026-07-31T11:50:00Z", - "resolved_manifest_fingerprint": "manifest-18", - "artifact_id": "artifact-18", - "runtime_identity": store.generation.runtime_identity.model_copy( - update={ - "deployment_record_id": "deployment-18", - "artifact_id": "artifact-18", - "image_reference": f"ghcr.io/example/site@sha256:{'b' * 64}", - "preview_generation_id": "generation-18", - } - ), - } - ) - github = _GitHubApi() - dependencies = _dependencies(github) - github.fail_projection_writes = True - - self.assertFalse( - reconcile_manager_preview_approval_for_pr_best_effort( - repository=REPOSITORY, - pr_number=PR_NUMBER, - record_store=store, - control_plane_root=Path("/tmp/launchplane"), - dependencies=dependencies, - ) - ) - self.assertEqual(github.statuses, []) - github.fail_projection_writes = False - - result = reconcile_manager_preview_approval_for_pr( - repository=REPOSITORY, - pr_number=PR_NUMBER, - record_store=store, - control_plane_root=Path("/tmp/launchplane"), - dependencies=dependencies, - ) - replayed_result = reconcile_manager_preview_approval_for_pr( - repository=REPOSITORY, - pr_number=PR_NUMBER, - record_store=store, - control_plane_root=Path("/tmp/launchplane"), - dependencies=dependencies, - ) - - self.assertEqual(result["status"], "pending") - self.assertEqual(result["check_state"], "pending") - self.assertNotEqual(result["fingerprint"], prior_binding.binding_sha256) - self.assertEqual(replayed_result["fingerprint"], result["fingerprint"]) - self.assertEqual(replayed_result["status"], "pending") - self.assertEqual(len(store.events), 1) - self.assertEqual(len(github.comments), 1) - self.assertEqual(github.statuses[-1]["state"], "pending") - self.assertIn(str(result["fingerprint"]), str(github.comments[-1]["body"])) - - -def _dependencies( - github: _GitHubApi, - *, - now: str = NOW, -) -> ManagerPreviewApprovalGitHubDependencies: - return ManagerPreviewApprovalGitHubDependencies( - webhook_secret=lambda: "secret", - verify_signature=lambda **_kwargs: None, - now_timestamp=lambda: now, - github_api=github, - github_token=lambda **_kwargs: "managed-token", - ) - - -def _issue_comment_payload(*, comment_id: int = 501) -> dict[str, object]: - return { - "action": "created", - "repository": {"full_name": REPOSITORY}, - "issue": { - "number": PR_NUMBER, - "pull_request": {"url": f"https://api.github.com/repos/{REPOSITORY}/pulls/{PR_NUMBER}"}, - }, - "comment": {"id": comment_id}, - } - - -def _pull_request_payload(*, action: str = "synchronize") -> dict[str, object]: - return { - "action": action, - "number": PR_NUMBER, - "repository": {"full_name": REPOSITORY}, - "pull_request": { - "number": PR_NUMBER, - "html_url": f"https://github.com/{REPOSITORY}/pull/{PR_NUMBER}", - "head": {"sha": HEAD_SHA}, - "user": {"id": 301, "type": "Bot", "login": "automation"}, - }, - "sender": {"id": 301, "type": "Bot", "login": "automation"}, - } - - -def _replace_serving_generation(store: _Store, *, head_sha: str) -> None: - assert store.generation.runtime_identity is not None - store.preview = store.preview.model_copy( - update={ - "updated_at": "2026-07-31T11:50:00Z", - "active_generation_id": "generation-18", - "serving_generation_id": "generation-18", - "latest_generation_id": "generation-18", - "latest_manifest_fingerprint": "manifest-18", - } - ) - store.generation = store.generation.model_copy( - update={ - "generation_id": "generation-18", - "sequence": 2, - "requested_at": "2026-07-31T11:46:00Z", - "started_at": "2026-07-31T11:47:00Z", - "ready_at": "2026-07-31T11:50:00Z", - "finished_at": "2026-07-31T11:50:00Z", - "resolved_manifest_fingerprint": "manifest-18", - "artifact_id": "artifact-18", - "anchor_summary": store.generation.anchor_summary.model_copy( - update={"head_sha": head_sha} - ), - "runtime_identity": store.generation.runtime_identity.model_copy( - update={ - "deployment_record_id": "deployment-18", - "artifact_id": "artifact-18", - "source_git_ref": head_sha, - "image_reference": f"ghcr.io/example/site@sha256:{'b' * 64}", - "preview_generation_id": "generation-18", - } - ), - } - ) - - -def _profile() -> LaunchplaneProductProfileRecord: - return LaunchplaneProductProfileRecord( - product=PRODUCT, - display_name="Example Site", - repository=REPOSITORY, - driver_id="generic-web", - image=ProductImageProfile(repository="ghcr.io/example/site"), - runtime_port=8069, - health_path="/web/health", - preview=ProductPreviewProfile( - enabled=True, - context=CONTEXT, - enable_label="launchplane-preview", - ), - updated_at=NOW, - source="test", - ) - - -def _policy_record( - *, - revision: int = 1, - extra_actions: tuple[str, ...] = (), -) -> LaunchplaneAuthzPolicyRecord: - policy = LaunchplaneAuthzPolicy( - schema_version=2, - github_humans=( - GitHubHumanPolicyRule( - managed_set_id="manager.example-site", - managed_rule_id="preview-approval", - github_ids=(101,), - roles=("read_only",), - products=(PRODUCT,), - contexts=(CONTEXT,), - actions=( - MANAGER_PREVIEW_APPROVAL_READ_ACTION, - MANAGER_PREVIEW_APPROVAL_WRITE_ACTION, - *extra_actions, - ), - ), - ), - ) - policy_sha256 = authz_policy_sha256(policy) - return LaunchplaneAuthzPolicyRecord( - record_id=build_authz_policy_record_id( - revision=revision, - policy_sha256=policy_sha256, - ), - revision=revision, - status="active", - source="test:manager-preview-approval", - updated_at=NOW, - policy_sha256=policy_sha256, - policy=policy, - ) - - -def _preview() -> PreviewRecord: - return PreviewRecord( - preview_id="preview-17", - context=CONTEXT, - anchor_repo=ANCHOR_REPO, - anchor_pr_number=PR_NUMBER, - anchor_pr_url=f"https://github.com/{REPOSITORY}/pull/{PR_NUMBER}", - preview_label="launchplane-preview", - canonical_url="https://preview-17.example.test/", - state="active", - created_at="2026-07-31T11:00:00Z", - updated_at="2026-07-31T11:30:00Z", - eligible_at="2026-07-31T11:00:00Z", - active_generation_id="generation-17", - serving_generation_id="generation-17", - latest_generation_id="generation-17", - latest_manifest_fingerprint="manifest-17", - ) - - -def _generation() -> PreviewGenerationRecord: - runtime_identity = RuntimeIdentity( - product=PRODUCT, - context=CONTEXT, - instance="preview-17", - environment_kind="preview", - deployment_record_id="deployment-17", - artifact_id="artifact-17", - source_git_ref=HEAD_SHA, - image_reference=f"ghcr.io/example/site@{IMAGE_DIGEST}", - preview_id="preview-17", - preview_generation_id="generation-17", - deployed_at="2026-07-31T11:20:00Z", - ) - return PreviewGenerationRecord( - generation_id="generation-17", - preview_id="preview-17", - sequence=1, - state="ready", - requested_reason="Preview requested.", - requested_at="2026-07-31T11:00:00Z", - started_at="2026-07-31T11:01:00Z", - ready_at="2026-07-31T11:30:00Z", - finished_at="2026-07-31T11:30:00Z", - resolved_manifest_fingerprint="manifest-17", - artifact_id="artifact-17", - anchor_summary=PreviewPullRequestSummary( - repo=ANCHOR_REPO, - pr_number=PR_NUMBER, - head_sha=HEAD_SHA, - pr_url=f"https://github.com/{REPOSITORY}/pull/{PR_NUMBER}", - ), - deploy_status="pass", - verify_status="pass", - overall_health_status="pass", - runtime_identity=runtime_identity, - ) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_merge_admission_evidence_failures.py b/tests/test_merge_admission_evidence_failures.py index e0f5cf4c4..ed590429b 100644 --- a/tests/test_merge_admission_evidence_failures.py +++ b/tests/test_merge_admission_evidence_failures.py @@ -3,10 +3,10 @@ import unittest from unittest.mock import patch -from control_plane.change_impact_github import ( - ChangeImpactRepositoryEvidenceError, - ChangeImpactRepositoryEvidenceStaleError, - GitHubChangeImpactRepositoryEvidenceProvider, +from control_plane.repository_evidence import ( + RepositoryEvidenceError, + RepositoryEvidenceStaleError, + GitHubRepositoryEvidenceProvider, ) from control_plane.http_app import create_launchplane_fastapi_app from control_plane.merge_admission import MergeAdmissionDeniedError @@ -23,7 +23,10 @@ _merge_train_service_policy, _seed_merge_train_policy, ) -from tests.test_change_impact_github import _commit_payload, _pull_request_payload +from tests.test_repository_evidence import ( + _commit_payload, + _pull_request_payload, +) from tests.test_merge_admission_live import ( _queued_pull_request, _StaticSnapshotReader, @@ -62,8 +65,8 @@ def __call__(self, *, path: str, token: str) -> object: return payload return {"id": 1001, "full_name": repository, "owner": {"id": 2001}} - def provider(self) -> GitHubChangeImpactRepositoryEvidenceProvider: - return GitHubChangeImpactRepositoryEvidenceProvider( + def provider(self) -> GitHubRepositoryEvidenceProvider: + return GitHubRepositoryEvidenceProvider( control_plane_root=Path("."), github_token=lambda **_: "test-token", github_api=self, @@ -74,7 +77,7 @@ def provider(self) -> GitHubChangeImpactRepositoryEvidenceProvider: class MergeAdmissionEvidenceFailureTests(unittest.TestCase): def test_live_evaluator_denies_malformed_or_stale_real_provider_evidence(self) -> None: cases: tuple[ - tuple[list[dict[str, object]], bool, str, type[ChangeImpactRepositoryEvidenceError]], + tuple[list[dict[str, object]], bool, str, type[RepositoryEvidenceError]], ..., ] = ( ( @@ -84,19 +87,19 @@ def test_live_evaluator_denies_malformed_or_stale_real_provider_evidence(self) - ], False, "repository_evidence_unavailable", - ChangeImpactRepositoryEvidenceError, + RepositoryEvidenceError, ), ( [{"filename": "private/path.py", "status": "renamed"}], False, "repository_evidence_unavailable", - ChangeImpactRepositoryEvidenceError, + RepositoryEvidenceError, ), ( [{"filename": "private/path.py", "status": "modified"}], True, "repository_evidence_stale", - ChangeImpactRepositoryEvidenceStaleError, + RepositoryEvidenceStaleError, ), ) candidate, landing, controller, _ = _guard_records() @@ -170,7 +173,7 @@ async def test_duplicate_evidence_blocks_controller_without_effect_or_reconcilia verifier=_StubVerifier(_merge_train_service_identity()), authz_policy=_merge_train_service_policy(), record_store_factory=lambda: store, - change_impact_repository_evidence_provider=api.provider(), + repository_evidence_provider=api.provider(), ) with ( patch( diff --git a/tests/test_merge_admission_live.py b/tests/test_merge_admission_live.py index b8c36caea..2c0f5b733 100644 --- a/tests/test_merge_admission_live.py +++ b/tests/test_merge_admission_live.py @@ -4,9 +4,9 @@ from unittest.mock import patch from control_plane.contracts.authz_policy_record import LaunchplaneAuthzPolicyRecord -from control_plane.contracts.change_impact import ( - ChangeImpactRepositoryEvidence, - ChangeImpactTargetReference, +from control_plane.contracts.repository_evidence import ( + RepositoryEvidence, + RepositoryTargetReference, ) from control_plane.contracts.merge_train_batch import ( MergeTrainBatchCandidateRecord, @@ -74,8 +74,8 @@ def read_merge_train_snapshot( class _UnusedRepositoryEvidenceProvider: def resolve( self, - target: ChangeImpactTargetReference, - ) -> ChangeImpactRepositoryEvidence: + target: RepositoryTargetReference, + ) -> RepositoryEvidence: raise AssertionError(f"queue drift should fail before repository evidence: {target}") @@ -244,7 +244,7 @@ def _owner_repository_evidence( *, head_sha: str = OWNER_HEAD_SHA, tree_sha: str = OWNER_TREE_SHA, -) -> ChangeImpactRepositoryEvidence: +) -> RepositoryEvidence: evidence = _repository_evidence(head=head_sha) return evidence.model_copy( update={ @@ -257,7 +257,7 @@ def _evaluate_live( *, store: FilesystemRecordStore, provider: _EvidenceProvider, - evidence: ChangeImpactRepositoryEvidence, + evidence: RepositoryEvidence, ) -> MergeAdmissionEvaluation: policy_record = build_test_merge_train_policy_record(repository=OWNER_REPOSITORY) candidate_record, landing_record, controller_state, _ = _guard_records( diff --git a/tests/test_merge_readiness.py b/tests/test_merge_readiness.py index fe814289e..c75352449 100644 --- a/tests/test_merge_readiness.py +++ b/tests/test_merge_readiness.py @@ -4,7 +4,9 @@ from pydantic import ValidationError -from control_plane.contracts.change_impact import ChangeImpactTarget +from control_plane.contracts.repository_evidence import ( + RepositoryTarget, +) from control_plane.contracts.engineering_review_decision import EngineeringReviewDecisionRecord from control_plane.contracts.engineering_review_run import EngineeringReviewRunRecord from control_plane.contracts.merge_admission_record import ( @@ -178,7 +180,7 @@ def _engineering_decision( "unknown": "review_authority_unavailable", "stale": "review_authority_stale", }[status], - "target": ChangeImpactTarget( + "target": RepositoryTarget( repository_id="101", repository_owner_id="202", repository=REPOSITORY, diff --git a/tests/test_ordinary_agent_landing_evidence.py b/tests/test_ordinary_agent_landing_evidence.py index 5b93d7e6a..841ccd0a4 100644 --- a/tests/test_ordinary_agent_landing_evidence.py +++ b/tests/test_ordinary_agent_landing_evidence.py @@ -15,7 +15,9 @@ OrdinaryAgentLandingRepositoryEvidenceProvider, OrdinaryAgentLandingEvidenceMismatch, ) -from control_plane.contracts.change_impact import ChangeImpactTargetReference +from control_plane.contracts.repository_evidence import ( + RepositoryTargetReference, +) from control_plane.tenant_admission_controller import TenantAdmissionTechnicalChecks from control_plane.merge_train import MergeTrainDryRunSnapshot, MergeTrainPullRequestSnapshot from tests.support.repository_evidence import _repository_evidence @@ -130,7 +132,7 @@ def test_previous_entry_resolves_without_putting_it_back_in_the_queue(self) -> N provider = OrdinaryAgentLandingRepositoryEvidenceProvider(observed) self.assertEqual( provider.resolve( - ChangeImpactTargetReference( + RepositoryTargetReference( repository=previous.target.repository, pull_request_number=previous.target.pull_request_number, ) @@ -140,7 +142,7 @@ def test_previous_entry_resolves_without_putting_it_back_in_the_queue(self) -> N self.assertEqual(observed.snapshot, evidence.snapshot) with self.assertRaises(OrdinaryAgentLandingEvidenceMismatch): provider.resolve( - ChangeImpactTargetReference( + RepositoryTargetReference( repository=previous.target.repository, pull_request_number=previous.target.pull_request_number + 1, ) diff --git a/tests/test_ordinary_agent_landing_storage.py b/tests/test_ordinary_agent_landing_storage.py index 6ededc9f6..381097290 100644 --- a/tests/test_ordinary_agent_landing_storage.py +++ b/tests/test_ordinary_agent_landing_storage.py @@ -15,11 +15,11 @@ OrdinaryAgentProviderRequestCounts, OrdinaryAgentRequiredCheck, ) -from control_plane.contracts.change_impact import ( - ChangeImpactRepositoryEvidence, - ChangeImpactTarget, - ChangeImpactBaseEvidence, - ChangeImpactChangedFileEvidence, +from control_plane.contracts.repository_evidence import ( + RepositoryEvidence, + RepositoryTarget, + RepositoryBaseEvidence, + RepositoryChangedFileEvidence, ) from control_plane.tenant_admission_controller import ( TenantAdmissionTechnicalChecks, @@ -279,8 +279,8 @@ def evidence( ), evaluated_at=datetime.fromtimestamp(now - check_age_seconds, timezone.utc).isoformat(), ) - repository = ChangeImpactRepositoryEvidence( - target=ChangeImpactTarget( + repository = RepositoryEvidence( + target=RepositoryTarget( repository_id=str(preparation.target.repository_id), repository_owner_id="202", repository=preparation.target.repository, @@ -288,8 +288,8 @@ def evidence( head_sha=preparation.entry.expected_head_sha, tree_sha=preparation.entry.expected_head_tree_sha, ), - base=ChangeImpactBaseEvidence(base_ref="main", base_sha=preparation.expected_base_sha), - changed_files=(ChangeImpactChangedFileEvidence(path="control_plane/example.py"),), + base=RepositoryBaseEvidence(base_ref="main", base_sha=preparation.expected_base_sha), + changed_files=(RepositoryChangedFileEvidence(path="control_plane/example.py"),), ) evidence = OrdinaryAgentLandingEvidence( repository_id=preparation.target.repository_id, diff --git a/tests/test_postgres_integration.py b/tests/test_postgres_integration.py index b785495f3..001ce6158 100644 --- a/tests/test_postgres_integration.py +++ b/tests/test_postgres_integration.py @@ -33,9 +33,11 @@ ORDINARY_AGENT_DELIVERY_ADMINISTRATION_MANAGED_SET_ID, ) from control_plane.contracts.canonical_json import canonical_json_sha256 -from tests.test_change_impact import _policy as _change_impact_policy -from tests.test_change_impact_policy_audit import _audit as _change_impact_audit -from control_plane.contracts.change_impact_audit import ChangeImpactPolicyAuditedWriteResult +from tests.support.retired_approval_history import _policy as _change_impact_policy +from tests.support.retired_approval_history import _audit as _change_impact_audit +from control_plane.contracts.retired_change_impact_audit import ( + ChangeImpactPolicyAuditedWriteResult, +) from control_plane.contracts.idempotency_record import ( LaunchplaneIdempotencyRecord, build_launchplane_idempotency_record_id, @@ -69,7 +71,7 @@ _provider_responses as _historical_provider_responses, seed_crowded_scoped_history, ) -from control_plane.contracts.manager_preview_approval import ( +from control_plane.contracts.retired_manager_preview_approval import ( ManagerPreviewApprovalAuthorization, ManagerPreviewApprovalBinding, ManagerPreviewApprovalEventRecord, @@ -175,7 +177,9 @@ TenantTechnicalHumanWaiverBinding, TenantTechnicalHumanWaiverEventRecord, ) -from control_plane.manager_preview_approval import ManagerPreviewApprovalEventConflictError +from control_plane.contracts.retired_manager_preview_approval import ( + ManagerPreviewApprovalEventConflictError, +) from control_plane.contracts.product_owner import ( PRODUCT_OWNER_ROUTINE_REVIEW_MAX_AGE_SECONDS, ) diff --git a/tests/test_preview_lifecycle_cleanup.py b/tests/test_preview_lifecycle_cleanup.py index e412daea5..66677f3a8 100644 --- a/tests/test_preview_lifecycle_cleanup.py +++ b/tests/test_preview_lifecycle_cleanup.py @@ -3,7 +3,7 @@ from tempfile import TemporaryDirectory from unittest.mock import patch -from control_plane.contracts.manager_preview_approval import ( +from control_plane.contracts.retired_manager_preview_approval import ( ManagerPreviewApprovalEventRecord, ManagerPreviewApprovalEventWriteStatus, ) diff --git a/tests/test_product_owner_http.py b/tests/test_product_owner_http.py deleted file mode 100644 index 2d054d480..000000000 --- a/tests/test_product_owner_http.py +++ /dev/null @@ -1,363 +0,0 @@ -from __future__ import annotations - -from pathlib import Path -from tempfile import TemporaryDirectory -from typing import cast -import unittest - -from fastapi import FastAPI, HTTPException - -from control_plane.contracts.product_owner import ( - ProductOwnerGrant, - ProductOwnerIdentity, - ProductOwnerPolicyRecord, - ProductOwnerRequirement, - ProductOwnerRequirementRecord, - ProductOwnerRoutingRecord, -) -from control_plane.http_routes.product_owner import ( - PRODUCT_OWNER_POLICY_APPLY_ROUTE, - PRODUCT_OWNER_POLICY_READ_ROUTE, - PRODUCT_OWNER_REQUIREMENT_APPLY_ROUTE, - PRODUCT_OWNER_ROUTING_APPLY_ROUTE, - PRODUCT_OWNER_AUTHORITY_EVALUATION_ROUTE, - ProductOwnerWriteRouteDependencies, - register_product_owner_read_routes, - register_product_owner_write_routes, -) -from control_plane.http_routes.support import ApiRouteRegistrar, ReadRouteDependencies -from control_plane.service_auth import GitHubHumanIdentity, action_safety -from control_plane.service_auth import ( - GitHubActionsIdentity, - LaunchplaneIdentity, - LocalAdminIdentity, - LocalOperatorIdentity, - TerminalAgentIdentity, -) -from control_plane.storage.filesystem import FilesystemRecordStore -from tests.support.http import lifespan_client - - -PRODUCT = "product-alpha" -SYSTEM = "system-web" -REPOSITORY_ID = "101" -ENVIRONMENT = "prod" -ACTION = "production.authorize" - - -def _human(github_id: int, *, role: str = "read_only") -> GitHubHumanIdentity: - return GitHubHumanIdentity( - login=f"human-{github_id}", - github_id=github_id, - name="Test Human", - email="", - organizations=frozenset(), - teams=frozenset(), - role=role, # type: ignore[arg-type] - ) - - -def _policy( - *, - revision: int = 1, - subjects: tuple[str, ...] = ("1001", "1002"), - supersedes_record_id: str | None = None, -) -> ProductOwnerPolicyRecord: - return ProductOwnerPolicyRecord( - product=PRODUCT, - system=SYSTEM, - policy_revision=revision, - owners=tuple( - ProductOwnerGrant( - identity=ProductOwnerIdentity(provider="github", provider_subject_id=subject), - repository_ids=(REPOSITORY_ID,), - environments=(ENVIRONMENT,), - ) - for subject in subjects - ), - effective_at="2026-08-05T00:00:00Z", - source="test", - reason="Create two current Owners with quorum one.", - supersedes_record_id=supersedes_record_id, - ) - - -def _requirement() -> ProductOwnerRequirementRecord: - return ProductOwnerRequirementRecord( - product=PRODUCT, - system=SYSTEM, - requirement_revision=1, - requirements=( - ProductOwnerRequirement( - action=ACTION, - repository_ids=(REPOSITORY_ID,), - environments=(ENVIRONMENT,), - ), - ), - effective_at="2026-08-05T00:00:00Z", - source="test", - reason="Require an Owner only for the explicit production action.", - ) - - -class ProductOwnerHttpTests(unittest.IsolatedAsyncioTestCase): - async def test_apply_read_and_authority_routes_share_current_records(self) -> None: - with TemporaryDirectory() as directory: - store = FilesystemRecordStore(Path(directory)) - identity_holder: list[LaunchplaneIdentity] = [_human(1001)] - - def identity() -> LaunchplaneIdentity: - return identity_holder[0] - - def http_error( - *, - status_code: int, - trace_id: str, - code: str, - message: str, - authz: dict[str, object] | None = None, - ) -> HTTPException: - return HTTPException( - status_code=status_code, - detail={"trace_id": trace_id, "code": code, "message": message}, - ) - - app = FastAPI() - read_dependencies = ReadRouteDependencies( - read_identity=identity, - get_record_store=lambda: store, - next_trace_id=lambda: "trace-owner-read", - authorization_allows=lambda **_: True, - http_error=http_error, - error_response_model=dict, # type: ignore[arg-type] - ) - write_dependencies = ProductOwnerWriteRouteDependencies( - read_write_identity=identity, - get_record_store=lambda: store, - next_trace_id=lambda: "trace-owner-write", - authorization_allows=lambda **_: True, - http_error=http_error, - error_response_model=dict, # type: ignore[arg-type] - ) - registrar = cast(ApiRouteRegistrar, app) - register_product_owner_read_routes(registrar, dependencies=read_dependencies) - register_product_owner_write_routes(registrar, dependencies=write_dependencies) - - policy = _policy() - requirement = _requirement() - routing = ProductOwnerRoutingRecord( - product=PRODUCT, - system=SYSTEM, - routing_revision=1, - preferred_owner_identity_ids=( - ProductOwnerIdentity( - provider="github", - provider_subject_id="1001", - ).identity_id, - ), - effective_at="2026-08-05T00:00:00Z", - source="test", - reason="Prefer one Owner without changing authority.", - ) - - async with lifespan_client(app) as client: - for path, record in ( - (PRODUCT_OWNER_POLICY_APPLY_ROUTE, policy), - (PRODUCT_OWNER_REQUIREMENT_APPLY_ROUTE, requirement), - (PRODUCT_OWNER_ROUTING_APPLY_ROUTE, routing), - ): - response = await client.post( - path, - json={"schema_version": 1, "mode": "apply", "record": record.model_dump()}, - ) - self.assertEqual(response.status_code, 202, response.text) - - invalid_owner_payload = policy.model_dump() - invalid_owner_payload["owners"][0]["identity"]["provider"] = "github-actions" - invalid_owner = await client.post( - PRODUCT_OWNER_POLICY_APPLY_ROUTE, - json={ - "schema_version": 1, - "mode": "apply", - "record": invalid_owner_payload, - }, - ) - self.assertEqual(invalid_owner.status_code, 422, invalid_owner.text) - - read_response = await client.get( - PRODUCT_OWNER_POLICY_READ_ROUTE, - params={"product": PRODUCT, "system": SYSTEM}, - ) - self.assertEqual(read_response.status_code, 200, read_response.text) - read_model = read_response.json()["read_model"] - self.assertNotIn("mode", read_model) - self.assertNotIn("authoritative", read_model) - self.assertNotIn("enforcement_effect", read_model) - - evaluation_params: dict[str, str | int] = { - "product": PRODUCT, - "system": SYSTEM, - "repository_id": REPOSITORY_ID, - "environment": ENVIRONMENT, - "action": ACTION, - "claimed_policy_revision": policy.policy_revision, - "claimed_policy_digest": policy.policy_digest, - "claimed_requirement_revision": requirement.requirement_revision, - "claimed_requirement_digest": requirement.requirement_digest, - } - preferred = await client.get( - PRODUCT_OWNER_AUTHORITY_EVALUATION_ROUTE, - params=evaluation_params, - ) - self.assertEqual(preferred.status_code, 200, preferred.text) - preferred_evaluation = preferred.json()["evaluation"] - self.assertEqual(preferred_evaluation["decision"], "authorized") - self.assertTrue(preferred_evaluation["actor_is_preferred"]) - self.assertNotIn("authoritative", preferred_evaluation) - - identity_holder[0] = _human(1002) - non_preferred = await client.get( - PRODUCT_OWNER_AUTHORITY_EVALUATION_ROUTE, - params=evaluation_params, - ) - self.assertEqual(non_preferred.status_code, 200, non_preferred.text) - self.assertEqual(non_preferred.json()["evaluation"]["decision"], "authorized") - self.assertFalse(non_preferred.json()["evaluation"]["actor_is_preferred"]) - - identity_holder[0] = _human(9999, role="admin") - admin = await client.get( - PRODUCT_OWNER_AUTHORITY_EVALUATION_ROUTE, - params=evaluation_params, - ) - self.assertEqual(admin.status_code, 200, admin.text) - self.assertEqual( - admin.json()["evaluation"]["reason_code"], - "actor_not_current_owner", - ) - - non_human_identities: tuple[LaunchplaneIdentity, ...] = ( - GitHubActionsIdentity( - repository="owner/repository", - repository_owner="owner", - workflow_ref="owner/repository/.github/workflows/test.yml@refs/heads/main", - job_workflow_ref="", - ref="refs/heads/main", - ref_type="branch", - event_name="workflow_dispatch", - environment="prod", - subject="repo:owner/repository:ref:refs/heads/main", - sha="a" * 40, - raw_claims={}, - repository_id=REPOSITORY_ID, - ), - TerminalAgentIdentity(subject="1001", token_label="terminal"), - LocalOperatorIdentity(subject="1001", token_label="operator"), - LocalAdminIdentity(subject="1001", token_label="admin"), - ) - for non_human_identity in non_human_identities: - with self.subTest(identity=type(non_human_identity).__name__): - identity_holder[0] = non_human_identity - rejected = await client.get( - PRODUCT_OWNER_AUTHORITY_EVALUATION_ROUTE, - params=evaluation_params, - ) - self.assertEqual(rejected.status_code, 403, rejected.text) - self.assertEqual( - rejected.json()["detail"]["code"], - "product_owner_actor_identity_required", - ) - - identity_holder[0] = _human(1001, role="admin") - listed_admin = await client.get( - PRODUCT_OWNER_AUTHORITY_EVALUATION_ROUTE, - params=evaluation_params, - ) - self.assertEqual(listed_admin.status_code, 200, listed_admin.text) - self.assertEqual(listed_admin.json()["evaluation"]["decision"], "authorized") - - conflicting = await client.post( - PRODUCT_OWNER_POLICY_APPLY_ROUTE, - json={ - "schema_version": 1, - "mode": "apply", - "record": _policy(subjects=("1001",)).model_dump(), - }, - ) - self.assertEqual(conflicting.status_code, 409, conflicting.text) - self.assertEqual( - conflicting.json()["detail"]["code"], - "product_owner_revision_conflict", - ) - - non_linear = _policy( - revision=3, - subjects=("1001",), - supersedes_record_id=policy.record_id, - ) - sequence_error = await client.post( - PRODUCT_OWNER_POLICY_APPLY_ROUTE, - json={ - "schema_version": 1, - "mode": "apply", - "expected_current_record_id": policy.record_id, - "expected_current_policy_digest": policy.policy_digest, - "record": non_linear.model_dump(), - }, - ) - self.assertEqual(sequence_error.status_code, 409, sequence_error.text) - self.assertEqual( - sequence_error.json()["detail"]["code"], - "product_owner_revision_sequence_error", - ) - - self.assertEqual(len(store.list_product_owner_policy_records()), 1) - self.assertEqual(len(store.list_product_owner_requirement_records()), 1) - self.assertEqual(len(store.list_product_owner_routing_records()), 1) - - def test_openapi_and_policy_admin_action_safety(self) -> None: - app = FastAPI() - - def identity() -> GitHubHumanIdentity: - return _human(1001) - - def http_error(**kwargs: object) -> HTTPException: - return HTTPException( - status_code=int(str(kwargs["status_code"])), - detail=kwargs, - ) - - with TemporaryDirectory() as directory: - store = FilesystemRecordStore(Path(directory)) - common = ReadRouteDependencies( - read_identity=identity, - get_record_store=lambda: store, - next_trace_id=lambda: "trace", - authorization_allows=lambda **_: True, - http_error=http_error, - error_response_model=dict, # type: ignore[arg-type] - ) - writes = ProductOwnerWriteRouteDependencies( - read_write_identity=identity, - get_record_store=lambda: store, - next_trace_id=lambda: "trace", - authorization_allows=lambda **_: True, - http_error=http_error, - error_response_model=dict, # type: ignore[arg-type] - ) - registrar = cast(ApiRouteRegistrar, app) - register_product_owner_read_routes(registrar, dependencies=common) - register_product_owner_write_routes(registrar, dependencies=writes) - schema = app.openapi() - self.assertIn(PRODUCT_OWNER_POLICY_READ_ROUTE, schema["paths"]) - self.assertIn(PRODUCT_OWNER_POLICY_APPLY_ROUTE, schema["paths"]) - self.assertIn(PRODUCT_OWNER_AUTHORITY_EVALUATION_ROUTE, schema["paths"]) - self.assertEqual(action_safety("product_owner_policy.write"), "policy_admin") - self.assertEqual(action_safety("product_owner_requirement.write"), "policy_admin") - self.assertEqual(action_safety("product_owner_routing.write"), "policy_admin") - self.assertEqual(action_safety("product_owner_policy.read"), "read") - self.assertEqual(action_safety("product_owner_requirement.read"), "read") - self.assertEqual(action_safety("product_owner_routing.read"), "read") - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_product_owner_policy.py b/tests/test_product_owner_policy.py deleted file mode 100644 index 120d618aa..000000000 --- a/tests/test_product_owner_policy.py +++ /dev/null @@ -1,674 +0,0 @@ -from __future__ import annotations - -import unittest -from tempfile import TemporaryDirectory -from pathlib import Path - -from pydantic import ValidationError - -from control_plane.contracts.product_owner import ( - ProductOwnerActionContext, - ProductOwnerActorIdentity, - ProductOwnerGrant, - ProductOwnerIdentity, - ProductOwnerPolicyRecord, - ProductOwnerRequirement, - ProductOwnerRequirementRecord, - ProductOwnerRoutingRecord, - build_product_owner_policy_record_id, -) -from control_plane.product_owner_service import ( - ProductOwnerPolicyConflictError, - ProductOwnerPolicySequenceError, - ProductOwnerRequirementConflictError, - ProductOwnerRequirementSequenceError, - ProductOwnerRoutingConflictError, - ProductOwnerRoutingSequenceError, - apply_product_owner_policy, - apply_product_owner_requirement, - apply_product_owner_routing, - evaluate_product_owner_authority, - get_product_owner_read_model, -) -from control_plane.storage.filesystem import FilesystemRecordStore -from control_plane.storage.postgres import PostgresRecordStore - - -PRODUCT = "product-alpha" -SYSTEM = "system-web" -REPOSITORY_ID = "101" -ENVIRONMENT = "prod" -ACTION = "production.authorize" - - -def _identity(subject_id: str) -> ProductOwnerIdentity: - return ProductOwnerIdentity(provider="github", provider_subject_id=subject_id) - - -def _grant(subject_id: str) -> ProductOwnerGrant: - return ProductOwnerGrant( - identity=_identity(subject_id), - repository_ids=(REPOSITORY_ID,), - environments=(ENVIRONMENT,), - ) - - -def _policy( - *, - product: str = PRODUCT, - revision: int = 1, - subjects: tuple[str, ...] = ("1001", "1002"), - supersedes_record_id: str | None = None, -) -> ProductOwnerPolicyRecord: - return ProductOwnerPolicyRecord( - record_id=build_product_owner_policy_record_id( - product=product, - system=SYSTEM, - policy_revision=revision, - ), - product=product, - system=SYSTEM, - policy_revision=revision, - owners=tuple(_grant(subject) for subject in subjects), - quorum=1, - effective_at="2026-08-05T00:00:00Z", - source="test", - reason="Exercise the additive owner policy.", - supersedes_record_id=supersedes_record_id, - ) - - -def _requirement(*, product: str = PRODUCT) -> ProductOwnerRequirementRecord: - return ProductOwnerRequirementRecord( - product=product, - system=SYSTEM, - requirement_revision=1, - requirements=( - ProductOwnerRequirement( - action=ACTION, - repository_ids=(REPOSITORY_ID,), - environments=(ENVIRONMENT,), - ), - ), - effective_at="2026-08-05T00:00:00Z", - source="test", - reason="Require one Owner for production authorization.", - ) - - -def _context() -> ProductOwnerActionContext: - return ProductOwnerActionContext( - product=PRODUCT, - system=SYSTEM, - repository_id=REPOSITORY_ID, - environment=ENVIRONMENT, - action=ACTION, - ) - - -class ProductOwnerPolicyTests(unittest.TestCase): - def test_filesystem_storage_keeps_product_revision_streams_independent(self) -> None: - with TemporaryDirectory() as directory: - store = FilesystemRecordStore(Path(directory)) - second_product = "product-beta" - - self.assertEqual(store.write_product_owner_policy_record(_policy()), "written") - self.assertEqual( - store.write_product_owner_policy_record(_policy(product=second_product)), - "written", - ) - self.assertEqual( - store.write_product_owner_requirement_record(_requirement()), - "written", - ) - self.assertEqual( - store.write_product_owner_requirement_record(_requirement(product=second_product)), - "written", - ) - - self.assertEqual( - { - record.product - for record in store.list_product_owner_policy_records(status="active") - }, - {PRODUCT, second_product}, - ) - self.assertEqual( - { - record.product - for record in store.list_product_owner_requirement_records(status="active") - }, - {PRODUCT, second_product}, - ) - - def test_policy_requires_human_immutable_identity_and_quorum_one(self) -> None: - policy = _policy() - self.assertEqual(policy.quorum, 1) - self.assertEqual(len(policy.owners), 2) - self.assertEqual(policy.owners[0].identity.owner_class, "owner") - self.assertEqual(_identity("001").provider_subject_id, "1") - with self.assertRaises(ValidationError): - ProductOwnerIdentity(provider="github", provider_subject_id="") - with self.assertRaises(ValidationError): - ProductOwnerIdentity(provider="launchplane", provider_subject_id="1001") # type: ignore[arg-type] - with self.assertRaises(ValidationError): - ProductOwnerIdentity(provider="github", provider_subject_id="human-login") - with self.assertRaises(ValidationError): - ProductOwnerActorIdentity( - provider="github-actions", # type: ignore[arg-type] - provider_subject_id="1001", - ) - with self.assertRaises(ValidationError): - ProductOwnerActorIdentity.model_validate( - { - "provider": "github", - "provider_subject_id": "1001", - "administrative_roles": ["global_admin"], - } - ) - identity = _identity("1001") - grant = _grant("1001") - with self.assertRaises(ValidationError): - identity.provider_subject_id = "1002" - with self.assertRaises(ValidationError): - grant.environments = ("testing",) - copied_identity = identity.model_copy(update={"provider_subject_id": "1002"}) - self.assertEqual(copied_identity, _identity("1002")) - copied_actor = ProductOwnerActorIdentity( - provider="github", - provider_subject_id="1001", - ).model_copy(update={"provider_subject_id": "1002"}) - self.assertEqual(copied_actor.provider_subject_id, "1002") - self.assertEqual(copied_actor.identity_id, _identity("1002").identity_id) - copied_grant = grant.model_copy(update={"environments": ("testing",)}) - self.assertEqual(copied_grant.environments, ("testing",)) - self.assertNotEqual(copied_grant.grant_id, grant.grant_id) - with self.assertRaises(ValidationError): - identity.model_copy(update={"identity_id": "stale-owner-identity"}) - with self.assertRaises(ValidationError): - _policy(subjects=("1001", "1001")) - with self.assertRaises(ValidationError): - ProductOwnerPolicyRecord.model_validate( - { - **_policy().model_dump(exclude={"quorum"}), - "quorum": 2, - } - ) - - def test_policy_sequence_removes_prior_owner_and_rejects_stale_tip(self) -> None: - with TemporaryDirectory() as directory: - store = FilesystemRecordStore(Path(directory)) - first = _policy() - applied = apply_product_owner_policy(store=store, record=first) - self.assertEqual(applied.status, "applied") - second = _policy( - revision=2, - subjects=("1002",), - supersedes_record_id=first.record_id, - ) - applied_second = apply_product_owner_policy( - store=store, - record=second, - expected_current_record_id=first.record_id, - expected_current_policy_digest=first.policy_digest, - ) - self.assertEqual(applied_second.status, "applied") - with self.assertRaises(ProductOwnerPolicyConflictError): - apply_product_owner_policy( - store=store, - record=_policy( - revision=3, - subjects=("1001",), - supersedes_record_id=second.record_id, - ), - expected_current_record_id=first.record_id, - expected_current_policy_digest=first.policy_digest, - ) - with self.assertRaises(ProductOwnerPolicySequenceError): - apply_product_owner_policy( - store=store, - record=_policy().model_copy(update={"status": "superseded"}), - ) - - def test_requirement_is_separate_and_routing_is_not_authority(self) -> None: - policy = _policy() - actor = ProductOwnerActorIdentity(provider="github", provider_subject_id="1001") - not_required = evaluate_product_owner_authority( - context=_context(), - actor=actor, - policies=(policy,), - requirements=(), - routings=( - ProductOwnerRoutingRecord( - product=PRODUCT, - system=SYSTEM, - routing_revision=1, - preferred_owner_identity_ids=(actor.identity_id,), - effective_at="2026-08-05T00:00:00Z", - source="test", - reason="Prefer one Owner for routing only.", - ), - ), - ) - self.assertEqual(not_required.decision, "not_required") - - routed_non_owner = evaluate_product_owner_authority( - context=_context(), - actor=ProductOwnerActorIdentity(provider="github", provider_subject_id="9999"), - policies=(policy,), - requirements=(_requirement(),), - routings=( - ProductOwnerRoutingRecord( - product=PRODUCT, - system=SYSTEM, - routing_revision=1, - preferred_owner_identity_ids=( - ProductOwnerIdentity( - provider="github", provider_subject_id="9999" - ).identity_id, - ), - effective_at="2026-08-05T00:00:00Z", - source="test", - reason="Routing cannot create authority.", - ), - ), - ) - self.assertEqual(routed_non_owner.decision, "denied") - self.assertEqual(routed_non_owner.reason_code, "actor_not_current_owner") - - missing_provenance = evaluate_product_owner_authority( - context=_context(), - actor=actor, - policies=(policy,), - requirements=(_requirement(),), - routings=(), - ) - self.assertEqual(missing_provenance.decision, "denied") - self.assertEqual(missing_provenance.reason_code, "missing_authority_provenance") - - def test_stale_removed_cross_product_and_admin_are_denied(self) -> None: - first = _policy() - second = _policy( - revision=2, - subjects=("1002",), - supersedes_record_id=first.record_id, - ) - current = second.model_copy(update={"status": "active"}) - history = first.model_copy(update={"status": "superseded"}) - - removed = evaluate_product_owner_authority( - context=_context(), - actor=ProductOwnerActorIdentity(provider="github", provider_subject_id="1001"), - policies=(history, current), - requirements=(_requirement(),), - routings=(), - claimed_policy_revision=2, - claimed_policy_digest=current.policy_digest, - claimed_requirement_revision=1, - claimed_requirement_digest=_requirement().requirement_digest, - ) - self.assertEqual(removed.reason_code, "actor_not_current_owner") - - stale = evaluate_product_owner_authority( - context=_context(), - actor=ProductOwnerActorIdentity(provider="github", provider_subject_id="1002"), - policies=(history, current), - requirements=(_requirement(),), - routings=(), - claimed_policy_revision=1, - claimed_policy_digest=history.policy_digest, - claimed_requirement_revision=1, - claimed_requirement_digest=_requirement().requirement_digest, - ) - self.assertEqual(stale.reason_code, "stale_policy") - - stale_requirement = evaluate_product_owner_authority( - context=_context(), - actor=ProductOwnerActorIdentity(provider="github", provider_subject_id="1002"), - policies=(history, current), - requirements=(_requirement(),), - routings=(), - claimed_policy_revision=2, - claimed_policy_digest=current.policy_digest, - claimed_requirement_revision=1, - claimed_requirement_digest="f" * 64, - ) - self.assertEqual(stale_requirement.reason_code, "stale_requirement") - - cross_product = evaluate_product_owner_authority( - context=_context().model_copy(update={"product": "product-beta"}), - actor=ProductOwnerActorIdentity(provider="github", provider_subject_id="1002"), - policies=(history, current), - requirements=(_requirement(product="product-beta"),), - routings=(), - ) - self.assertEqual(cross_product.reason_code, "owner_policy_unavailable") - - unlisted_human = evaluate_product_owner_authority( - context=_context(), - actor=ProductOwnerActorIdentity(provider="github", provider_subject_id="9999"), - policies=(history, current), - requirements=(_requirement(),), - routings=(), - ) - self.assertEqual(unlisted_human.reason_code, "actor_not_current_owner") - self.assertEqual(unlisted_human.decision, "denied") - - def test_policy_scope_without_current_owner_is_unavailable(self) -> None: - uncovered_policy = ProductOwnerPolicyRecord( - product=PRODUCT, - system=SYSTEM, - policy_revision=1, - owners=( - ProductOwnerGrant( - identity=_identity("1001"), - repository_ids=("202",), - environments=(ENVIRONMENT,), - ), - ), - effective_at="2026-08-05T00:00:00Z", - source="test", - reason="Leave the evaluated repository outside the policy scope.", - ) - evaluation = evaluate_product_owner_authority( - context=_context(), - actor=ProductOwnerActorIdentity(provider="github", provider_subject_id="1001"), - policies=(uncovered_policy,), - requirements=(_requirement(),), - routings=(), - ) - self.assertEqual(evaluation.decision, "unavailable") - self.assertEqual(evaluation.reason_code, "policy_scope_not_covered") - - def test_mutated_record_cannot_reuse_stale_authority_digest(self) -> None: - policy = _policy(subjects=("1001",)) - original_digest = policy.policy_digest - policy.owners = (_grant("9999"),) - actor = ProductOwnerActorIdentity(provider="github", provider_subject_id="9999") - evaluation = evaluate_product_owner_authority( - context=_context(), - actor=actor, - policies=(policy,), - requirements=(_requirement(),), - routings=(), - claimed_policy_revision=1, - claimed_policy_digest=original_digest, - claimed_requirement_revision=1, - claimed_requirement_digest=_requirement().requirement_digest, - ) - self.assertEqual(evaluation.decision, "unavailable") - self.assertEqual(evaluation.reason_code, "owner_policy_unavailable") - - with TemporaryDirectory() as directory: - store = FilesystemRecordStore(Path(directory)) - with self.assertRaises(ProductOwnerPolicyConflictError): - apply_product_owner_policy(store=store, record=policy) - with self.assertRaises(ProductOwnerPolicyConflictError): - store.write_product_owner_policy_record(policy) - - def test_postgres_storage_round_trips_separate_revision_streams(self) -> None: - with TemporaryDirectory() as directory: - store = PostgresRecordStore( - database_url=f"sqlite+pysqlite:///{Path(directory) / 'launchplane.sqlite3'}" - ) - store.ensure_schema() - policy = _policy() - requirement = _requirement() - routing = ProductOwnerRoutingRecord( - product=PRODUCT, - system=SYSTEM, - routing_revision=1, - preferred_owner_identity_ids=(policy.owners[0].identity.identity_id,), - effective_at="2026-08-05T00:00:00Z", - source="test", - reason="Keep preferred routing separate from authority.", - ) - self.assertEqual(store.write_product_owner_policy_record(policy), "written") - self.assertEqual( - store.write_product_owner_requirement_record(requirement), - "written", - ) - self.assertEqual(store.write_product_owner_routing_record(routing), "written") - self.assertEqual(store.read_product_owner_policy_record(policy.record_id), policy) - self.assertEqual( - store.read_product_owner_requirement_record(requirement.record_id), - requirement, - ) - self.assertEqual(store.read_product_owner_routing_record(routing.record_id), routing) - successor_policy = _policy( - revision=2, - subjects=("1002",), - supersedes_record_id=policy.record_id, - ) - successor_requirement = ProductOwnerRequirementRecord( - product=PRODUCT, - system=SYSTEM, - requirement_revision=2, - requirements=(), - effective_at="2026-08-05T01:00:00Z", - source="test", - reason="Disable the requirement in a successor revision.", - supersedes_record_id=requirement.record_id, - ) - successor_routing = ProductOwnerRoutingRecord( - product=PRODUCT, - system=SYSTEM, - routing_revision=2, - preferred_owner_identity_ids=(), - effective_at="2026-08-05T01:00:00Z", - source="test", - reason="Clear preferred routing in a successor revision.", - supersedes_record_id=routing.record_id, - ) - self.assertEqual(store.write_product_owner_policy_record(successor_policy), "written") - self.assertEqual( - store.write_product_owner_requirement_record(successor_requirement), - "written", - ) - self.assertEqual( - store.write_product_owner_routing_record(successor_routing), - "written", - ) - self.assertEqual( - [record.status for record in store.list_product_owner_policy_records()], - ["active", "superseded"], - ) - self.assertEqual( - [record.status for record in store.list_product_owner_requirement_records()], - ["active", "superseded"], - ) - self.assertEqual( - [record.status for record in store.list_product_owner_routing_records()], - ["active", "superseded"], - ) - future_payload = _policy( - revision=3, - subjects=("1002",), - supersedes_record_id=successor_policy.record_id, - ).model_dump(exclude={"effective_at", "policy_digest"}) - future_policy = ProductOwnerPolicyRecord( - **future_payload, - effective_at="2999-01-01T00:00:00Z", - ) - with self.assertRaises(ProductOwnerPolicySequenceError): - store.compare_and_write_product_owner_policy_record( - future_policy, - expected_current_record_id=successor_policy.record_id, - expected_current_policy_digest=successor_policy.policy_digest, - ) - self.assertEqual( - store.list_product_owner_policy_records(status="active"), - (successor_policy,), - ) - store.close() - - def test_requirement_and_routing_streams_use_independent_linear_tips(self) -> None: - with TemporaryDirectory() as directory: - store = FilesystemRecordStore(Path(directory)) - requirement = _requirement() - routing = ProductOwnerRoutingRecord( - product=PRODUCT, - system=SYSTEM, - routing_revision=1, - preferred_owner_identity_ids=(_identity("1001").identity_id,), - effective_at="2026-08-05T00:00:00Z", - source="test", - reason="Initial advisory routing.", - ) - self.assertEqual( - apply_product_owner_requirement(store=store, record=requirement).status, - "applied", - ) - self.assertEqual( - apply_product_owner_routing(store=store, record=routing).status, - "applied", - ) - with self.assertRaises(ProductOwnerRequirementConflictError): - apply_product_owner_requirement( - store=store, - record=ProductOwnerRequirementRecord( - product=PRODUCT, - system=SYSTEM, - requirement_revision=2, - requirements=(), - effective_at="2026-08-05T01:00:00Z", - source="test", - reason="Disable the explicit requirement.", - supersedes_record_id=requirement.record_id, - ), - expected_current_record_id="stale-tip", - expected_current_requirement_digest=requirement.requirement_digest, - ) - with self.assertRaises(ProductOwnerRoutingConflictError): - apply_product_owner_routing( - store=store, - record=ProductOwnerRoutingRecord( - product=PRODUCT, - system=SYSTEM, - routing_revision=2, - preferred_owner_identity_ids=(), - effective_at="2026-08-05T01:00:00Z", - source="test", - reason="Clear preferred routing without changing authority.", - supersedes_record_id=routing.record_id, - ), - expected_current_record_id=routing.record_id, - expected_current_routing_digest="f" * 64, - ) - - def test_authority_evaluation_rejects_noncurrent_or_future_authority(self) -> None: - requirement = _requirement() - gap_policy = _policy( - revision=2, - subjects=("1001",), - supersedes_record_id=_policy().record_id, - ) - gap = evaluate_product_owner_authority( - context=_context(), - actor=ProductOwnerActorIdentity(provider="github", provider_subject_id="1001"), - policies=(gap_policy,), - requirements=(requirement,), - routings=(), - evaluated_at="2026-08-05T12:00:00Z", - ) - self.assertEqual(gap.decision, "unavailable") - self.assertEqual(gap.reason_code, "owner_policy_unavailable") - - future_requirement = ProductOwnerRequirementRecord( - product=PRODUCT, - system=SYSTEM, - requirement_revision=1, - requirements=requirement.requirements, - effective_at="2999-01-01T00:00:00Z", - source="test", - reason="Future requirement must not become current early.", - ) - future = evaluate_product_owner_authority( - context=_context(), - actor=ProductOwnerActorIdentity(provider="github", provider_subject_id="1001"), - policies=(_policy(),), - requirements=(future_requirement,), - routings=(), - evaluated_at="2026-08-05T12:00:00Z", - ) - self.assertEqual(future.decision, "unavailable") - self.assertEqual(future.reason_code, "requirement_history_invalid") - - with TemporaryDirectory() as directory: - store = FilesystemRecordStore(Path(directory)) - future_policy_payload = _policy().model_dump(exclude={"effective_at", "policy_digest"}) - future_policy = ProductOwnerPolicyRecord( - **future_policy_payload, - effective_at="2999-01-01T00:00:00Z", - ) - future_routing = ProductOwnerRoutingRecord( - product=PRODUCT, - system=SYSTEM, - routing_revision=1, - effective_at="2999-01-01T00:00:00Z", - source="test", - reason="Future routing is not current yet.", - ) - with self.assertRaises(ProductOwnerPolicySequenceError): - store.write_product_owner_policy_record(future_policy) - with self.assertRaises(ProductOwnerRequirementSequenceError): - store.write_product_owner_requirement_record(future_requirement) - with self.assertRaises(ProductOwnerRoutingSequenceError): - store.write_product_owner_routing_record(future_routing) - read_model = get_product_owner_read_model( - store=store, - product=PRODUCT, - system=SYSTEM, - ) - self.assertIsNone(read_model.current_policy) - self.assertIsNone(read_model.current_requirement) - self.assertIsNone(read_model.current_routing) - - def test_successor_effective_at_is_monotonic_and_current(self) -> None: - with TemporaryDirectory() as directory: - store = FilesystemRecordStore(Path(directory)) - first = _policy() - self.assertEqual( - apply_product_owner_policy(store=store, record=first).status, "applied" - ) - - regressed_payload = _policy( - revision=2, - subjects=("1002",), - supersedes_record_id=first.record_id, - ).model_dump(exclude={"effective_at", "policy_digest"}) - regressed = ProductOwnerPolicyRecord( - **regressed_payload, - effective_at="2026-08-04T23:59:59Z", - ) - with self.assertRaises(ProductOwnerPolicySequenceError): - apply_product_owner_policy( - store=store, - record=regressed, - expected_current_record_id=first.record_id, - expected_current_policy_digest=first.policy_digest, - ) - - future_payload = _policy( - revision=2, - subjects=("1002",), - supersedes_record_id=first.record_id, - ).model_dump(exclude={"effective_at", "policy_digest"}) - future = ProductOwnerPolicyRecord( - **future_payload, - effective_at="2999-01-01T00:00:00Z", - ) - with self.assertRaises(ProductOwnerPolicySequenceError): - store.compare_and_write_product_owner_policy_record( - future, - expected_current_record_id=first.record_id, - expected_current_policy_digest=first.policy_digest, - ) - self.assertEqual( - store.list_product_owner_policy_records(status="active"), - (first,), - ) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_product_promotion_http.py b/tests/test_product_promotion_http.py index bba85f86e..9205a2e4d 100644 --- a/tests/test_product_promotion_http.py +++ b/tests/test_product_promotion_http.py @@ -21,7 +21,7 @@ from control_plane.contracts.environment_inventory import EnvironmentInventory from control_plane.contracts.idempotency_record import LaunchplaneIdempotencyRecord from control_plane.contracts.lane_summary import LaunchplaneLaneSummary -from control_plane.contracts.manager_preview_approval import ( +from control_plane.contracts.retired_manager_preview_approval import ( MANAGER_PREVIEW_APPROVAL_READ_ACTION, MANAGER_PREVIEW_APPROVAL_WRITE_ACTION, ManagerPreviewApprovalEventRecord, @@ -46,10 +46,8 @@ ) from control_plane.contracts.runtime_identity import RuntimeIdentity from control_plane.contracts.ship_request import ShipRequest -from control_plane.manager_preview_approval import record_manager_preview_approval_event from control_plane.service_auth import ( GitHubActionsIdentity, - GitHubHumanIdentity, GitHubHumanPolicyRule, LaunchplaneAuthzPolicy, ) @@ -448,27 +446,12 @@ def _manager_generation() -> PreviewGenerationRecord: def _approve_manager_preview(store: _ManagerPromotionStore) -> None: - assert store.policy is not None - record_manager_preview_approval_event( - record_store=store, - identity=GitHubHumanIdentity( - login="manager", - github_id=101, - name="Example Manager", - email="", - organizations=frozenset(), - teams=frozenset(), - role="read_only", - ), - policy_record=store.policy, - product="atlas-commerce", - preview=store.preview, - generation=store.generation, - action="approved", - occurred_at="2026-07-15T08:56:00Z", - source_event_kind="github_issue_comment", - source_event_id="comment-approval-17", + # Serialized by the pre-retirement implementation; a historical approval + # must never satisfy the release gate. + record = ManagerPreviewApprovalEventRecord.model_validate_json( + (Path(__file__).parent / "fixtures/retired-approval/manager-event.json").read_text() ) + store.write_manager_preview_approval_event_record(record) class ProductPromotionStatusTests(unittest.TestCase): diff --git a/tests/test_change_impact_github.py b/tests/test_repository_evidence.py similarity index 88% rename from tests/test_change_impact_github.py rename to tests/test_repository_evidence.py index cacaad59e..bb21f1339 100644 --- a/tests/test_change_impact_github.py +++ b/tests/test_repository_evidence.py @@ -3,12 +3,14 @@ from pathlib import Path import unittest -from control_plane.change_impact_github import ( - ChangeImpactRepositoryEvidenceError, - ChangeImpactRepositoryEvidenceStaleError, - GitHubChangeImpactRepositoryEvidenceProvider, +from control_plane.repository_evidence import ( + RepositoryEvidenceError, + RepositoryEvidenceStaleError, + GitHubRepositoryEvidenceProvider, +) +from control_plane.contracts.repository_evidence import ( + RepositoryTargetReference, ) -from control_plane.contracts.change_impact import ChangeImpactTargetReference REPOSITORY = "example/shared-addons" @@ -147,7 +149,7 @@ def __call__(self, *, path: str, token: str) -> object: class ChangeImpactGitHubEvidenceProviderTests(unittest.TestCase): def test_lists_bounded_open_pull_requests(self) -> None: github_api = _GitHubApi() - provider = GitHubChangeImpactRepositoryEvidenceProvider( + provider = GitHubRepositoryEvidenceProvider( control_plane_root=Path("."), github_token=lambda **_: "server-token", github_api=github_api, @@ -164,7 +166,7 @@ def test_lists_bounded_open_pull_requests(self) -> None: def test_resolves_repository_head_tree_and_complete_changed_paths(self) -> None: github_api = _GitHubApi() - provider = GitHubChangeImpactRepositoryEvidenceProvider( + provider = GitHubRepositoryEvidenceProvider( control_plane_root=Path("."), github_token=lambda **_: "server-token", github_api=github_api, @@ -172,7 +174,7 @@ def test_resolves_repository_head_tree_and_complete_changed_paths(self) -> None: ) evidence = provider.resolve( - ChangeImpactTargetReference( + RepositoryTargetReference( repository=REPOSITORY, pull_request_number=2000, ) @@ -209,7 +211,7 @@ def test_non_human_commit_actor_keeps_authorship_unresolved(self) -> None: ), ) ) - provider = GitHubChangeImpactRepositoryEvidenceProvider( + provider = GitHubRepositoryEvidenceProvider( control_plane_root=Path("."), github_token=lambda **_: "server-token", github_api=github_api, @@ -217,7 +219,7 @@ def test_non_human_commit_actor_keeps_authorship_unresolved(self) -> None: ) evidence = provider.resolve( - ChangeImpactTargetReference( + RepositoryTargetReference( repository=REPOSITORY, pull_request_number=2000, ) @@ -228,7 +230,7 @@ def test_non_human_commit_actor_keeps_authorship_unresolved(self) -> None: self.assertEqual(evidence.authorship.contributor_github_ids, ()) def test_non_human_pull_request_author_keeps_authorship_unresolved(self) -> None: - provider = GitHubChangeImpactRepositoryEvidenceProvider( + provider = GitHubRepositoryEvidenceProvider( control_plane_root=Path("."), github_token=lambda **_: "server-token", github_api=_GitHubApi(pull_request_author_type="Bot"), @@ -236,7 +238,7 @@ def test_non_human_pull_request_author_keeps_authorship_unresolved(self) -> None ) evidence = provider.resolve( - ChangeImpactTargetReference( + RepositoryTargetReference( repository=REPOSITORY, pull_request_number=2000, ) @@ -247,7 +249,7 @@ def test_non_human_pull_request_author_keeps_authorship_unresolved(self) -> None self.assertEqual(evidence.authorship.contributor_github_ids, ()) def test_bot_authored_commit_pushed_by_human_resolves_to_human(self) -> None: - provider = GitHubChangeImpactRepositoryEvidenceProvider( + provider = GitHubRepositoryEvidenceProvider( control_plane_root=Path("."), github_token=lambda **_: "server-token", github_api=_GitHubApi( @@ -264,7 +266,7 @@ def test_bot_authored_commit_pushed_by_human_resolves_to_human(self) -> None: ) evidence = provider.resolve( - ChangeImpactTargetReference( + RepositoryTargetReference( repository=REPOSITORY, pull_request_number=2000, ) @@ -278,48 +280,48 @@ def test_bot_authored_commit_pushed_by_human_resolves_to_human(self) -> None: ) def test_head_change_during_resolution_is_stale(self) -> None: - provider = GitHubChangeImpactRepositoryEvidenceProvider( + provider = GitHubRepositoryEvidenceProvider( control_plane_root=Path("."), github_token=lambda **_: "server-token", github_api=_GitHubApi(confirmed_head_sha="c" * 40), token_context="launchplane", ) - with self.assertRaises(ChangeImpactRepositoryEvidenceStaleError): + with self.assertRaises(RepositoryEvidenceStaleError): provider.resolve( - ChangeImpactTargetReference( + RepositoryTargetReference( repository=REPOSITORY, pull_request_number=2000, ) ) def test_pull_request_update_during_resolution_is_stale(self) -> None: - provider = GitHubChangeImpactRepositoryEvidenceProvider( + provider = GitHubRepositoryEvidenceProvider( control_plane_root=Path("."), github_token=lambda **_: "server-token", github_api=_GitHubApi(confirmed_updated_at="2026-08-06T01:00:01Z"), token_context="launchplane", ) - with self.assertRaises(ChangeImpactRepositoryEvidenceStaleError): + with self.assertRaises(RepositoryEvidenceStaleError): provider.resolve( - ChangeImpactTargetReference( + RepositoryTargetReference( repository=REPOSITORY, pull_request_number=2000, ) ) def test_missing_server_credentials_fail_closed(self) -> None: - provider = GitHubChangeImpactRepositoryEvidenceProvider( + provider = GitHubRepositoryEvidenceProvider( control_plane_root=Path("."), github_token=lambda **_: "", github_api=_GitHubApi(), token_context="launchplane", ) - with self.assertRaises(ChangeImpactRepositoryEvidenceError): + with self.assertRaises(RepositoryEvidenceError): provider.resolve( - ChangeImpactTargetReference( + RepositoryTargetReference( repository=REPOSITORY, pull_request_number=2000, ) @@ -335,7 +337,7 @@ def __call__(self, *, path: str, token: str) -> object: ] return super().__call__(path=path, token=token) - provider = GitHubChangeImpactRepositoryEvidenceProvider( + provider = GitHubRepositoryEvidenceProvider( control_plane_root=Path("."), github_token=lambda **_: "server-token", github_api=FullPagesGitHubApi(), @@ -343,9 +345,9 @@ def __call__(self, *, path: str, token: str) -> object: max_file_pages=1, ) - with self.assertRaises(ChangeImpactRepositoryEvidenceError): + with self.assertRaises(RepositoryEvidenceError): provider.resolve( - ChangeImpactTargetReference( + RepositoryTargetReference( repository=REPOSITORY, pull_request_number=2000, ) @@ -364,7 +366,7 @@ def __call__(self, *, path: str, token: str) -> object: return super().__call__(path=path, token=token) github_api = FullPagesGitHubApi() - provider = GitHubChangeImpactRepositoryEvidenceProvider( + provider = GitHubRepositoryEvidenceProvider( control_plane_root=Path("."), github_token=lambda **_: "server-token", github_api=github_api, @@ -372,9 +374,9 @@ def __call__(self, *, path: str, token: str) -> object: max_file_pages=30, ) - with self.assertRaises(ChangeImpactRepositoryEvidenceError): + with self.assertRaises(RepositoryEvidenceError): provider.resolve_current_item( - ChangeImpactTargetReference( + RepositoryTargetReference( repository=REPOSITORY, pull_request_number=2000, ) diff --git a/tests/test_change_impact_binding_versions.py b/tests/test_retired_approval_bindings.py similarity index 100% rename from tests/test_change_impact_binding_versions.py rename to tests/test_retired_approval_bindings.py diff --git a/tests/test_retired_approval_routes.py b/tests/test_retired_approval_routes.py new file mode 100644 index 000000000..32dab0833 --- /dev/null +++ b/tests/test_retired_approval_routes.py @@ -0,0 +1,101 @@ +import hashlib +import hmac +import unittest +from pathlib import Path +from unittest.mock import patch + +from control_plane.http_app import create_launchplane_fastapi_app +from control_plane.service_auth import LaunchplaneAuthzPolicy +from control_plane.trusted_maintenance_github_webhook import ( + TrustedMaintenanceGitHubWebhookDependencies, + TrustedMaintenanceGitHubWebhookResult, + handle_trusted_maintenance_github_webhook_request, +) +from tests.support.auth import StubVerifier, identity +from tests.support.http import lifespan_client + + +class RetiredApprovalRoutesTests(unittest.IsolatedAsyncioTestCase): + async def test_retired_approval_routes_cannot_read_evaluate_or_write(self) -> None: + app = create_launchplane_fastapi_app( + verifier=StubVerifier(identity()), + authz_policy=LaunchplaneAuthzPolicy(), + record_store_factory=object, + ) + routes = ( + ("GET", "/v1/change-impact/policy"), + ("POST", "/v1/change-impact/evaluation"), + ("POST", "/v1/change-impact/policies/apply"), + ("GET", "/v1/product-owner/policy"), + ("GET", "/v1/product-owner/requirement"), + ("GET", "/v1/product-owner/routing"), + ("GET", "/v1/product-owner/evaluation"), + ("POST", "/v1/product-owner/policies/apply"), + ("POST", "/v1/product-owner/requirements/apply"), + ("POST", "/v1/product-owner/routing/apply"), + ("POST", "/v1/manager-preview-approval/reconcile"), + ) + async with lifespan_client(app) as client: + for method, path in routes: + with self.subTest(method=method, path=path): + response = await client.request( + method, path, json={}, headers={"Authorization": "Bearer test"} + ) + self.assertEqual(response.status_code, 404, response.text) + + +class MaintenanceWebhookTransportTests(unittest.TestCase): + def setUp(self) -> None: + self.secret = "test-webhook-secret" + self.dependencies = TrustedMaintenanceGitHubWebhookDependencies( + webhook_secret=lambda: self.secret + ) + + def receive( + self, payload: bytes, *, event: str = "pull_request", signature: str | None = None + ) -> tuple[int, dict[str, object]]: + signature = ( + signature + if signature is not None + else "sha256=" + hmac.new(self.secret.encode(), payload, hashlib.sha256).hexdigest() + ) + return handle_trusted_maintenance_github_webhook_request( + payload, + event, + "delivery-1", + signature, + object(), + Path("."), + "trace-test", + dependencies=self.dependencies, + ) + + def test_signed_manager_command_is_ignored_without_record_access(self) -> None: + payload = b'{"action":"created","comment":{"body":"/preview approve aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}}' + status, body = self.receive(payload, event="issue_comment") + self.assertEqual(status, 202) + self.assertEqual(body["result"]["reason"], "unsupported_event") # type: ignore[index] + + def test_signature_failure_cannot_reach_maintenance_capture(self) -> None: + with patch( + "control_plane.trusted_maintenance_github_webhook.handle_trusted_maintenance_github_webhook" + ) as capture: + status, _ = self.receive(b"{}", signature="sha256=" + "0" * 64) + self.assertEqual(status, 401) + capture.assert_not_called() + + def test_verified_delivery_preserves_digest_and_retryable_capture_failure(self) -> None: + payload = b'{"action":"opened"}' + with patch( + "control_plane.trusted_maintenance_github_webhook.handle_trusted_maintenance_github_webhook", + return_value=TrustedMaintenanceGitHubWebhookResult( + status="retryable_error", reason="database_unavailable" + ), + ) as capture: + status, body = self.receive(payload) + self.assertEqual(status, 503) + self.assertEqual(body["error"]["code"], "trusted_maintenance_unavailable") # type: ignore[index] + self.assertEqual( + capture.call_args.kwargs["signed_payload_sha256"], hashlib.sha256(payload).hexdigest() + ) + self.assertEqual(capture.call_args.kwargs["delivery_id"], "delivery-1") diff --git a/tests/test_schema_migration.py b/tests/test_schema_migration.py index d1eeb1ef0..0146c1fba 100644 --- a/tests/test_schema_migration.py +++ b/tests/test_schema_migration.py @@ -14,12 +14,9 @@ from control_plane.contracts.authz_policy_record import LaunchplaneAuthzPolicyRecord from control_plane.contracts.product_owner import ( - ProductOwnerActionContext, - ProductOwnerActorIdentity, ProductOwnerRequirement, ProductOwnerRequirementRecord, ) -from control_plane.product_owner_service import evaluate_product_owner_authority from control_plane.service_auth import LaunchplaneAuthzPolicy from control_plane.storage.postgres import PostgresRecordStore from control_plane.storage.schema_adoption import ( @@ -1793,24 +1790,6 @@ def test_owner_authority_migration_archives_legacy_requirements(self) -> None: self.assertIsNone(current_record.supersedes_record_id) self.assertEqual(current_record.source, "migration:owner-authority-cutover") self.assertNotIn(current["requirement_digest"], {"a" * 64, "b" * 64}) - evaluation = evaluate_product_owner_authority( - context=ProductOwnerActionContext( - product="example-site", - system="web", - repository_id="101", - environment="preview", - action="pull_request.owner_acceptance", - ), - actor=ProductOwnerActorIdentity( - provider="github", - provider_subject_id="1001", - ), - policies=(), - requirements=(current_record,), - routings=(), - ) - self.assertEqual(evaluation.decision, "not_required") - self.assertEqual(evaluation.reason_code, "owner_action_not_required") self.assertEqual(successor_write, "written") self.assertEqual(len(active_after_write), 1) self.assertEqual(active_after_write[0].requirement_revision, 2) From 70dc08d8a4be8d65c18a17d3daf55883663d6a98 Mon Sep 17 00:00:00 2001 From: shiny-code-bot Date: Sun, 27 Sep 2026 13:53:08 -0400 Subject: [PATCH 2/3] test: remove retired evaluation route from browser boundary inventory --- tests/test_http_app_browser_mutation.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_http_app_browser_mutation.py b/tests/test_http_app_browser_mutation.py index d07083489..eaec1da0d 100644 --- a/tests/test_http_app_browser_mutation.py +++ b/tests/test_http_app_browser_mutation.py @@ -73,7 +73,6 @@ def test_cookie_capable_mutation_route_inventory_is_explicit(self) -> None: } expected_bearer_only_routes = { "/v1/authz-diagnostics/github-actions/evaluate", - "/v1/change-impact/evaluation", "/v1/agent/privileged-operations/plans", "/v1/production-backup-authority/apply", "/v1/production-backup-authority/legacy-runtime-migration", From a8aa8b0013ad9596b3e0f628192e3fe2ff20daf6 Mon Sep 17 00:00:00 2001 From: shiny-code-bot Date: Sun, 27 Sep 2026 14:41:08 -0400 Subject: [PATCH 3/3] chore: resolve diagnostics in approval retirement changes --- control_plane/contracts/repository_evidence.py | 6 +++--- control_plane/http_app.py | 2 +- control_plane/trusted_maintenance_github_webhook.py | 2 +- tests/support/retired_approval_history.py | 3 --- tests/test_engineering_review_decision.py | 6 ++++-- tests/test_governance_projection.py | 8 +++++--- tests/test_governance_projection_http.py | 10 +++++++--- 7 files changed, 21 insertions(+), 16 deletions(-) diff --git a/control_plane/contracts/repository_evidence.py b/control_plane/contracts/repository_evidence.py index deb7fbb25..29527eb3e 100644 --- a/control_plane/contracts/repository_evidence.py +++ b/control_plane/contracts/repository_evidence.py @@ -195,7 +195,7 @@ class RepositoryEvidence(BaseModel): @model_validator(mode="after") def _validate_evidence(self) -> "RepositoryEvidence": if self.schema_version != 1: - raise ValueError("Unsupported repository repository evidence schema version.") + raise ValueError("Unsupported repository evidence schema version.") if self.merge_commit_sha: object.__setattr__( self, @@ -203,10 +203,10 @@ def _validate_evidence(self) -> "RepositoryEvidence": _normalize_git_sha(self.merge_commit_sha, "merge_commit_sha"), ) if not self.changed_files: - raise ValueError("repository repository evidence requires changed files") + raise ValueError("repository evidence requires changed files") paths = tuple(file.path for file in self.changed_files) if len(paths) != len(set(paths)): - raise ValueError("repository repository evidence paths must be unique") + raise ValueError("repository evidence paths must be unique") object.__setattr__( self, "changed_files", diff --git a/control_plane/http_app.py b/control_plane/http_app.py index 2f73ab440..2708b061a 100644 --- a/control_plane/http_app.py +++ b/control_plane/http_app.py @@ -9795,7 +9795,7 @@ def checkpoint_landing_mutation( admission_store=admission_store, admission_evaluator=LiveMergeAdmissionEvaluator( store=record_store, - repository_evidence_provider=(resolved_repository_evidence_provider), + repository_evidence_provider=resolved_repository_evidence_provider, technical_check_client=GitHubMergeTrainClient( transport=UrllibMergeTrainGitHubTransport( token=token, diff --git a/control_plane/trusted_maintenance_github_webhook.py b/control_plane/trusted_maintenance_github_webhook.py index 74e3f2073..78af18c7a 100644 --- a/control_plane/trusted_maintenance_github_webhook.py +++ b/control_plane/trusted_maintenance_github_webhook.py @@ -110,7 +110,7 @@ def error(status: int, code: str, message: str) -> tuple[int, dict[str, object]] except click.ClickException: return error(401, "invalid_signature", "GitHub webhook signature is invalid.") try: - payload = json.loads(payload_bytes.decode("utf-8")) + payload = json.loads(payload_bytes.decode()) except (UnicodeDecodeError, json.JSONDecodeError): return error(400, "invalid_payload", "GitHub webhook body is invalid JSON.") if not isinstance(payload, dict): diff --git a/tests/support/retired_approval_history.py b/tests/support/retired_approval_history.py index 923f22dc4..eaba1239e 100644 --- a/tests/support/retired_approval_history.py +++ b/tests/support/retired_approval_history.py @@ -45,14 +45,12 @@ def _policy( component="generic-web-runtime", path_prefixes=("src/runtime",), affected_products=(_product("generic-web-a"),), - review_tier="routine", reason="Runtime code reaches one generic web product.", ), ChangeImpactComponentRule( component="odoo-shared-addon", path_prefixes=("addons/shared",), affected_products=(_product("cm-odoo"), _product("opw-odoo")), - review_tier="routine", reason="Shared addon reaches multiple Odoo products.", ), ChangeImpactComponentRule( @@ -66,7 +64,6 @@ def _policy( component="engineering-ci", path_prefixes=(".github/workflows",), affected_products=(), - review_tier="routine", reason="CI-only engineering change has no product runtime effect.", ), ), diff --git a/tests/test_engineering_review_decision.py b/tests/test_engineering_review_decision.py index 7454d2d7b..fae77591d 100644 --- a/tests/test_engineering_review_decision.py +++ b/tests/test_engineering_review_decision.py @@ -5,6 +5,8 @@ from unittest.mock import patch from control_plane.contracts.repository_evidence import ( + RepositoryEvidence, + RepositoryEvidenceProvider, RepositoryTargetReference, ) from control_plane.contracts.engineering_review_decision import ( @@ -35,11 +37,11 @@ from tests.test_engineering_review_run import _authority -class _Provider: +class _Provider(RepositoryEvidenceProvider): def __init__(self, *paths: str) -> None: self.evidence = _repository_evidence(*paths) - def resolve(self, _target: RepositoryTargetReference): # type: ignore[no-untyped-def] + def resolve(self, target: RepositoryTargetReference) -> RepositoryEvidence: return self.evidence diff --git a/tests/test_governance_projection.py b/tests/test_governance_projection.py index 27918a416..1e1c43750 100644 --- a/tests/test_governance_projection.py +++ b/tests/test_governance_projection.py @@ -7,6 +7,8 @@ from control_plane.contracts.merge_train_policy import MergeTrainGitHubTokenSource from control_plane.contracts.repository_evidence import ( + RepositoryEvidence, + RepositoryEvidenceProvider, RepositoryTargetReference, ) from control_plane.contracts.governance_projection import GovernanceMergeReadinessFacet @@ -420,11 +422,11 @@ def test_live_readiness_provider_treats_terminal_lineage_as_inactive(self) -> No self.assertEqual(result.availability, "not_active") def test_projection_reuses_one_repository_evidence_snapshot(self) -> None: - class _ChangingProvider: + class _ChangingProvider(RepositoryEvidenceProvider): def __init__(self) -> None: self.calls = 0 - def resolve(self, target: RepositoryTargetReference) -> object: + def resolve(self, target: RepositoryTargetReference) -> RepositoryEvidence: self.calls += 1 if self.calls > 1: return _repository_evidence(head="c" * 40) @@ -436,7 +438,7 @@ def resolve(self, target: RepositoryTargetReference) -> object: projection = build_governance_projection( store=store, - repository_evidence_provider=provider, # type: ignore[arg-type] + repository_evidence_provider=provider, current_readiness_provider=_not_active_readiness, target=TARGET, base_branch="main", diff --git a/tests/test_governance_projection_http.py b/tests/test_governance_projection_http.py index febf6a176..df35ec398 100644 --- a/tests/test_governance_projection_http.py +++ b/tests/test_governance_projection_http.py @@ -10,6 +10,10 @@ from control_plane.repository_evidence import ( RepositoryEvidenceError, ) +from control_plane.contracts.repository_evidence import ( + RepositoryEvidence, + RepositoryTargetReference, +) from control_plane.contracts.merge_train_policy import MERGE_TRAIN_POLICY_TARGETS_READ_ACTION from control_plane.contracts.governance_projection import GovernanceMergeReadinessFacet from control_plane.http_routes.governance_projection import ( @@ -231,8 +235,8 @@ async def test_rejects_blank_base_branch_after_normalization(self) -> None: async def test_returns_503_when_repository_evidence_is_unavailable(self) -> None: class _UnavailableProvider: - def resolve(self, target: object) -> object: - raise RepositoryEvidenceError(f"unavailable: {target}") + def resolve(self, target: RepositoryTargetReference) -> RepositoryEvidence: + raise RepositoryEvidenceError(f"unavailable: {target.repository}") with TemporaryDirectory() as directory: store = _configured_store(directory) @@ -249,7 +253,7 @@ def resolve(self, target: object) -> object: cast(ApiRouteRegistrar, app), dependencies=GovernanceProjectionRouteDependencies( common=common, - repository_evidence_provider=_UnavailableProvider(), # type: ignore[arg-type] + repository_evidence_provider=_UnavailableProvider(), current_readiness_provider=_readiness, now=lambda: "2026-08-12T05:00:00Z", ),