From 2ab95244940a7ad29be05b01452722a86eaf8205 Mon Sep 17 00:00:00 2001 From: MrFr3di Date: Thu, 3 Sep 2026 00:10:15 +0500 Subject: [PATCH 1/6] docs(release): define current alpha gate catalog --- docs/release/alpha-gates-v1.json | 42 ++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 docs/release/alpha-gates-v1.json diff --git a/docs/release/alpha-gates-v1.json b/docs/release/alpha-gates-v1.json new file mode 100644 index 000000000..75601adb2 --- /dev/null +++ b/docs/release/alpha-gates-v1.json @@ -0,0 +1,42 @@ +{ + "schemaVersion": 1, + "releaseTrack": "0.1.0-alpha", + "gates": [ + { "id": "scope.alpha_dependencies", "requiredByDefault": true, "availabilityConditional": false }, + { "id": "truth.version", "requiredByDefault": true, "availabilityConditional": false }, + { "id": "truth.clean_tree", "requiredByDefault": true, "availabilityConditional": false }, + + { "id": "build.release_assemble", "requiredByDefault": true, "availabilityConditional": false }, + { "id": "build.r8_shrinking", "requiredByDefault": true, "availabilityConditional": false }, + { "id": "build.baseline_profile_packaged", "requiredByDefault": true, "availabilityConditional": false }, + { "id": "build.signing", "requiredByDefault": true, "availabilityConditional": false }, + { "id": "build.sbom", "requiredByDefault": true, "availabilityConditional": false }, + { "id": "build.dependency_report", "requiredByDefault": true, "availabilityConditional": false }, + + { "id": "virtual.old_edge", "requiredByDefault": true, "availabilityConditional": false }, + { "id": "virtual.current", "requiredByDefault": true, "availabilityConditional": false }, + { "id": "virtual.low_ram", "requiredByDefault": true, "availabilityConditional": false }, + + { "id": "data.room_current_schema", "requiredByDefault": true, "availabilityConditional": false }, + { "id": "data.upgrade_previous_supported", "requiredByDefault": true, "availabilityConditional": false }, + { "id": "security.keystore_persistence", "requiredByDefault": true, "availabilityConditional": false }, + { "id": "security.keystore_reset", "requiredByDefault": true, "availabilityConditional": false }, + { "id": "security.redaction", "requiredByDefault": true, "availabilityConditional": false }, + + { "id": "recovery.tv_path", "requiredByDefault": true, "availabilityConditional": false }, + { "id": "recovery.failure_previous_good", "requiredByDefault": true, "availabilityConditional": false }, + { "id": "recovery.user_docs", "requiredByDefault": true, "availabilityConditional": false }, + + { "id": "player.core_journey", "requiredByDefault": true, "availabilityConditional": false }, + { "id": "player.transport", "requiredByDefault": true, "availabilityConditional": false }, + { "id": "diagnostics.export", "requiredByDefault": true, "availabilityConditional": false }, + + { "id": "physical.current_android_tv", "requiredByDefault": true, "availabilityConditional": false }, + { "id": "physical.constrained_tv", "requiredByDefault": true, "availabilityConditional": false }, + { "id": "physical.fire_tv", "requiredByDefault": false, "availabilityConditional": true }, + + { "id": "perf.baseline_profile_effect", "requiredByDefault": true, "availabilityConditional": false }, + { "id": "perf.startup_frames_memory", "requiredByDefault": true, "availabilityConditional": false }, + { "id": "perf.measurement_provenance", "requiredByDefault": true, "availabilityConditional": false } + ] +} From 68de0064a7e63938982fb54589e5eb3598e6dd1f Mon Sep 17 00:00:00 2001 From: MrFr3di Date: Thu, 3 Sep 2026 00:10:33 +0500 Subject: [PATCH 2/6] docs(release): add alpha evidence manifest schema --- .../alpha-evidence-manifest.schema.json | 169 ++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 docs/release/schemas/alpha-evidence-manifest.schema.json diff --git a/docs/release/schemas/alpha-evidence-manifest.schema.json b/docs/release/schemas/alpha-evidence-manifest.schema.json new file mode 100644 index 000000000..cc89a6fcd --- /dev/null +++ b/docs/release/schemas/alpha-evidence-manifest.schema.json @@ -0,0 +1,169 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://muxtv.app/schemas/alpha-evidence-manifest.schema.json", + "title": "MuxTV alpha release evidence manifest", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaVersion", + "repository", + "commit", + "releaseVersion", + "generatedAtUtc", + "claimEligible", + "gates", + "artifacts", + "knownLimitations" + ], + "properties": { + "schemaVersion": { "const": 1 }, + "repository": { "const": "MuxTV/Muxtv" }, + "commit": { "type": "string", "pattern": "^[0-9a-f]{40}$" }, + "sourceRef": { "type": ["string", "null"], "maxLength": 200 }, + "releaseVersion": { + "type": "string", + "pattern": "^0\\.1\\.0-alpha(?:[.+-][0-9A-Za-z.-]+)?$" + }, + "generatedAtUtc": { "type": "string", "format": "date-time" }, + "claimEligible": { "type": "boolean" }, + "gates": { + "type": "object", + "minProperties": 1, + "additionalProperties": { "$ref": "#/$defs/gate" }, + "propertyNames": { + "pattern": "^[a-z][a-z0-9_]*(?:\\.[a-z][a-z0-9_]*)+$" + } + }, + "artifacts": { + "type": "array", + "items": { "$ref": "#/$defs/artifact" } + }, + "knownLimitations": { + "type": "array", + "items": { "type": "string", "minLength": 1, "maxLength": 1000 } + } + }, + "$defs": { + "status": { + "type": "string", + "enum": ["PENDING", "PASSED", "FAILED", "BLOCKED", "DEFERRED"] + }, + "evidenceReference": { + "type": "object", + "additionalProperties": false, + "required": ["kind", "value"], + "properties": { + "kind": { + "type": "string", + "enum": [ + "WORKFLOW_RUN_ID", + "WORKFLOW_JOB_ID", + "CI_ARTIFACT_NAME", + "REPOSITORY_PATH", + "DEVICE_REPORT", + "MEASUREMENT_REPORT", + "OTHER" + ] + }, + "value": { + "type": "string", + "minLength": 1, + "maxLength": 500, + "description": "Safe evidence reference only. Secret-bearing URLs and credentials are rejected by the executable finalizer." + } + } + }, + "defer": { + "type": "object", + "additionalProperties": false, + "required": ["issueNumber", "rationale", "scopeEffect"], + "properties": { + "issueNumber": { "type": "integer", "minimum": 1 }, + "rationale": { "type": "string", "minLength": 1, "maxLength": 2000 }, + "scopeEffect": { "type": "string", "minLength": 1, "maxLength": 2000 } + } + }, + "gate": { + "type": "object", + "additionalProperties": false, + "required": ["required", "status", "evidence"], + "properties": { + "required": { "type": "boolean" }, + "status": { "$ref": "#/$defs/status" }, + "evidenceCommit": { + "type": ["string", "null"], + "pattern": "^[0-9a-f]{40}$" + }, + "evidence": { + "type": "array", + "items": { "$ref": "#/$defs/evidenceReference" } + }, + "facts": { + "type": "object", + "additionalProperties": { + "type": ["string", "number", "integer", "boolean", "null"] + } + }, + "defer": { "$ref": "#/$defs/defer" }, + "note": { "type": ["string", "null"], "maxLength": 2000 } + }, + "allOf": [ + { + "if": { + "properties": { "status": { "const": "PASSED" } }, + "required": ["status"] + }, + "then": { + "required": ["evidenceCommit"], + "properties": { + "evidenceCommit": { + "type": "string", + "pattern": "^[0-9a-f]{40}$" + }, + "evidence": { "minItems": 1 } + } + } + }, + { + "if": { + "properties": { "status": { "const": "DEFERRED" } }, + "required": ["status"] + }, + "then": { "required": ["defer"] } + } + ] + }, + "artifact": { + "type": "object", + "additionalProperties": false, + "required": ["kind", "name", "sourceCommit"], + "properties": { + "kind": { + "type": "string", + "enum": [ + "APK", + "AAB", + "SBOM", + "DEPENDENCY_REPORT", + "BASELINE_PROFILE_EVIDENCE", + "TEST_EVIDENCE", + "MEASUREMENT_EVIDENCE", + "OTHER" + ] + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 300, + "description": "Safe repository-relative or CI artifact name only." + }, + "sha256": { + "type": ["string", "null"], + "pattern": "^[0-9a-f]{64}$" + }, + "byteCount": { "type": ["integer", "null"], "minimum": 0 }, + "sourceCommit": { "type": "string", "pattern": "^[0-9a-f]{40}$" } + } + } + } +} From 9a05796bee4562625908bdbeea2ec3562d2c92c1 Mon Sep 17 00:00:00 2001 From: MrFr3di Date: Thu, 3 Sep 2026 00:10:58 +0500 Subject: [PATCH 3/6] docs(release): normalize alpha evidence contract --- docs/release/alpha-evidence-contract.md | 110 ++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 docs/release/alpha-evidence-contract.md diff --git a/docs/release/alpha-evidence-contract.md b/docs/release/alpha-evidence-contract.md new file mode 100644 index 000000000..9fd04e192 --- /dev/null +++ b/docs/release/alpha-evidence-contract.md @@ -0,0 +1,110 @@ +# MuxTV 0.1.0-alpha evidence contract + +Status: release-gate contract, not release evidence +Issue: #31 + +## Purpose + +Issue #31 requires a reproducible `0.1.0-alpha` whose compatibility and quality claims are bounded by evidence collected on the exact release commit. This contract and `alpha-gates-v1.json` define the canonical gate inventory; `schemas/alpha-evidence-manifest.schema.json` defines the structural manifest format; the executable finalizer enforces cross-field provenance, gate ownership, claim eligibility and conservative redaction rules. + +Authoring or validating this contract does not mark any release gate `PASSED`, enable R8, change the app version, create signing material, generate an SBOM, or claim device compatibility. + +## Core rule + +An alpha manifest is claim-eligible only when every gate marked `required=true` is `PASSED` on the exact manifest commit and every passed-gate/artifact provenance record belongs to that same commit. + +`DEFERRED` is an explicit product decision, not success. A deferred item requires an issue number, rationale and scope effect. A required gate cannot be deferred while `claimEligible=true`. + +## Canonical virtual-device policy + +Repository-owned persistent Android TV AVD identities are exactly: + +- `MuxTV_TV_OLD_API26`; +- `MuxTV_TV_CURRENT_API36`. + +The release contract must not introduce `virtual.mainstream` or any third persistent AVD. `virtual.low_ram` is a constrained runtime/device configuration applied to one of the canonical devices where representable; it is not a separate AVD identity. API37 and hardware-specific behavior are ephemeral/physical release evidence under #31. + +## Manifest identity + +Every manifest records the schema version, repository, exact lowercase 40-character source commit, optional source ref, requested release version, UTC generation timestamp, claim-eligibility flag, canonical gate map, artifact provenance and known limitations. + +Paths, workflow IDs/URLs and artifact names are references only. They are never interpreted as proof unless the owning gate is `PASSED` and its `evidenceCommit` equals the manifest commit. + +## Gate states + +- `PENDING` — not executed or evidence not reviewed; +- `PASSED` — exact-head evidence exists and acceptance checks are satisfied; +- `FAILED` — executed and did not satisfy the gate; +- `BLOCKED` — cannot execute because of an external dependency/environment; +- `DEFERRED` — explicitly removed from current scope with issue+rationale+scope effect. + +Never use `PASSED` for static review when a gate requires execution. + +## Gate groups + +The executable inventory is `alpha-gates-v1.json`. Its groups are: + +- scope/truth: dependency scope, version identity, clean source tree; +- release build: release assembly, R8/resource shrinking, Baseline Profile packaging, signing, SBOM and dependency report; +- virtual correctness: API26 old edge, API36 current, and constrained low-RAM mode on a canonical device; +- data/security: Room schema/upgrade, Keystore persistence/reset and redaction; +- recovery: TV-operable recovery, previous-good preservation and user recovery docs; +- player/diagnostics: core TV journey, transport behavior and diagnostic export; +- physical qualification: current Android/Google TV, constrained TV and availability-conditional Fire TV; +- performance: Baseline Profile effect, startup/frame/memory evidence and measurement provenance. + +Physical observations remain device-scoped. Emulator evidence cannot certify vendor codec, HDR/Dolby Vision, passthrough, weak-ARM, Fire OS, thermal or absolute performance behavior. + +## Canonical gate ownership + +Every gate from `alpha-gates-v1.json`, including optional/availability-conditional gates, must be present in the manifest so unavailable work cannot disappear silently. The finalizer rejects unknown gates and missing canonical gates. A manifest cannot weaken a gate whose catalog entry has `requiredByDefault=true` by setting `required=false`. + +Optional gates may remain non-required with an explicit non-passed status while `claimEligible=true`; their state and limitations must still be represented truthfully. + +## Exact-commit provenance + +For every `PASSED` gate: + +- `evidenceCommit` is required by schema; +- the finalizer requires `evidenceCommit == manifest.commit`. + +For every artifact: + +- `sourceCommit` must equal `manifest.commit`. + +A claim-eligible manifest must include at least one APK or AAB with a non-null SHA-256 digest and exact source-commit provenance. + +## Claim eligibility + +`claimEligible=true` is valid only when: + +1. schema validation succeeds; +2. manifest gate names exactly match the canonical catalog; +3. no required-by-default gate is weakened; +4. every `required=true` gate is `PASSED`; +5. every passed gate belongs to the manifest commit; +6. every artifact belongs to the manifest commit; +7. at least one APK or AAB has SHA-256 provenance; +8. `security.redaction` is passed as part of the required gate set; +9. known limitations remain explicit rather than hidden through omitted gates. + +The finalizer validates eligibility; it does not generate evidence or turn pending gates into passed ones. + +## Redaction boundary + +Evidence references and artifact names are public-safe metadata only. The finalizer conservatively rejects values shaped like: + +- Authorization/Cookie headers; +- credential/signature query parameters such as token/password/signature values; +- URI user-info credentials; +- private-machine absolute filesystem paths. + +Diagnostics for rejected metadata must not echo the rejected value. The manifest must never contain playlist/provider credentials, signing secrets, temporary signed download URLs, private keystore paths, Authorization/Cookie material or raw secret-bearing locators. + +## Release artifact and performance policy + +Release hardening must operate on the minified release-shaped artifact. R8 keep-rule changes need analyzer/runtime evidence rather than broad defensive keep rules. Baseline Profile packaging is a separate gate from measured effect; emulator-generated profiles or correctness runs are not public performance claims. Physical-device before/after evidence remains required for release-facing startup/frame performance claims. + +## Qualification boundary + +The canonical API26/API36 matrix proves bounded platform correctness. Android 17/API37 Local Network Protection, route-dependent IPv6, vendor codecs/HDR/passthrough and Fire TV behavior require appropriate ephemeral or physical evidence. Lack of such evidence must remain visible in the manifest/known limitations rather than being inferred from emulator success. From 23ac0b8d18ae86842a279b4c8b9d52b69cfef20f Mon Sep 17 00:00:00 2001 From: MrFr3di Date: Thu, 3 Sep 2026 00:11:37 +0500 Subject: [PATCH 4/6] test(release): define alpha evidence finalizer contract --- tools/release/Test-AlphaEvidenceValidator.ps1 | 278 ++++++++++++++++++ 1 file changed, 278 insertions(+) create mode 100644 tools/release/Test-AlphaEvidenceValidator.ps1 diff --git a/tools/release/Test-AlphaEvidenceValidator.ps1 b/tools/release/Test-AlphaEvidenceValidator.ps1 new file mode 100644 index 000000000..6bcd0ecc1 --- /dev/null +++ b/tools/release/Test-AlphaEvidenceValidator.ps1 @@ -0,0 +1,278 @@ +[CmdletBinding()] +param() + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repositoryRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).Path +$validatorPath = Join-Path $PSScriptRoot "Validate-AlphaEvidenceManifest.ps1" +$schemaPath = Join-Path $repositoryRoot "docs\release\schemas\alpha-evidence-manifest.schema.json" +$gateCatalogPath = Join-Path $repositoryRoot "docs\release\alpha-gates-v1.json" + +if (-not (Test-Path $validatorPath -PathType Leaf)) { + throw "Alpha evidence validator entry point is missing." +} +if (-not (Test-Path $schemaPath -PathType Leaf)) { + throw "Alpha evidence schema is missing." +} +if (-not (Test-Path $gateCatalogPath -PathType Leaf)) { + throw "Alpha gate catalog is missing." +} + +$gateCatalog = Get-Content -LiteralPath $gateCatalogPath -Raw -Encoding utf8 | ConvertFrom-Json -Depth 100 +$tempRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("muxtv-alpha-validator-" + [Guid]::NewGuid().ToString("N")) +New-Item -ItemType Directory -Path $tempRoot | Out-Null + +$passed = 0 +$failures = [System.Collections.Generic.List[string]]::new() +$canonicalCommit = "1111111111111111111111111111111111111111" +$otherCommit = "2222222222222222222222222222222222222222" +$canonicalSha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + +function New-EligibleManifest { + $gates = [ordered]@{} + foreach ($gateDefinition in @($gateCatalog.gates)) { + $id = [string]$gateDefinition.id + if ([bool]$gateDefinition.requiredByDefault) { + $gates[$id] = [ordered]@{ + required = $true + status = "PASSED" + evidenceCommit = $canonicalCommit + evidence = @( + [ordered]@{ + kind = "WORKFLOW_RUN_ID" + value = "123456" + } + ) + } + } else { + $gates[$id] = [ordered]@{ + required = $false + status = "PENDING" + evidence = @() + } + } + } + + return [ordered]@{ + schemaVersion = 1 + repository = "MuxTV/Muxtv" + commit = $canonicalCommit + sourceRef = "release/0.1.0-alpha" + releaseVersion = "0.1.0-alpha" + generatedAtUtc = "2026-09-02T00:00:00Z" + claimEligible = $true + gates = $gates + artifacts = @( + [ordered]@{ + kind = "APK" + name = "muxtv-0.1.0-alpha.apk" + sha256 = $canonicalSha256 + byteCount = 1024 + sourceCommit = $canonicalCommit + } + ) + knownLimitations = @("Validation fixture; no compatibility claim.") + } +} + +function Write-FixtureManifest { + param( + [Parameter(Mandatory)][string]$Name, + [Parameter(Mandatory)]$Manifest + ) + + $path = Join-Path $tempRoot ($Name + ".json") + $Manifest | ConvertTo-Json -Depth 100 | Set-Content -LiteralPath $path -Encoding utf8NoBOM + return $path +} + +function Invoke-Validator { + param([Parameter(Mandatory)][string]$ManifestPath) + + & $validatorPath ` + -ManifestPath $ManifestPath ` + -SchemaPath $schemaPath ` + -GateCatalogPath $gateCatalogPath +} + +function Assert-ValidatorSuccess { + param( + [Parameter(Mandatory)][string]$Name, + [Parameter(Mandatory)]$Manifest + ) + + try { + $path = Write-FixtureManifest -Name $Name -Manifest $Manifest + $null = Invoke-Validator -ManifestPath $path + $script:passed += 1 + } catch { + $script:failures.Add("$Name expected success but failed: $($_.Exception.Message)") + } +} + +function Assert-ValidatorFailure { + param( + [Parameter(Mandatory)][string]$Name, + [Parameter(Mandatory)]$Manifest, + [string]$ForbiddenEcho + ) + + try { + $path = Write-FixtureManifest -Name $Name -Manifest $Manifest + $null = Invoke-Validator -ManifestPath $path + $script:failures.Add("$Name expected failure but validator accepted the manifest.") + } catch { + if ($ForbiddenEcho -and $_.Exception.Message.Contains($ForbiddenEcho, [StringComparison]::Ordinal)) { + $script:failures.Add("$Name leaked a forbidden value in the diagnostic.") + } else { + $script:passed += 1 + } + } +} + +try { + Assert-ValidatorSuccess -Name "eligible" -Manifest (New-EligibleManifest) + + $safeReferences = New-EligibleManifest + $safeReferences.gates["security.redaction"].evidence = @( + [ordered]@{ + kind = "OTHER" + value = "https://github.com/MuxTV/Muxtv/actions/runs/123456" + }, + [ordered]@{ + kind = "REPOSITORY_PATH" + value = ".work/evidence/release/alpha-redaction.json" + } + ) + Assert-ValidatorSuccess -Name "safe-references" -Manifest $safeReferences + + $notYetEligible = New-EligibleManifest + $notYetEligible.claimEligible = $false + $pendingGate = $notYetEligible.gates["scope.alpha_dependencies"] + $pendingGate.status = "PENDING" + $pendingGate.evidence = @() + $pendingGate.Remove("evidenceCommit") + Assert-ValidatorSuccess -Name "noneligible-with-pending-required" -Manifest $notYetEligible + + $missingRequired = New-EligibleManifest + $missingRequired.gates.Remove("scope.alpha_dependencies") + Assert-ValidatorFailure -Name "missing-required-gate" -Manifest $missingRequired + + $weakenedRequired = New-EligibleManifest + $weakenedRequired.gates["scope.alpha_dependencies"].required = $false + Assert-ValidatorFailure -Name "weakened-required-gate" -Manifest $weakenedRequired + + $unknownGate = New-EligibleManifest + $unknownGate.gates["future.typo_gate"] = [ordered]@{ + required = $false + status = "PENDING" + evidence = @() + } + Assert-ValidatorFailure -Name "unknown-gate" -Manifest $unknownGate + + $obsoleteMainstreamGate = New-EligibleManifest + $obsoleteMainstreamGate.gates["virtual.mainstream"] = [ordered]@{ + required = $false + status = "PENDING" + evidence = @() + } + Assert-ValidatorFailure -Name "obsolete-mainstream-avd-gate" -Manifest $obsoleteMainstreamGate + + $passedCommitMismatch = New-EligibleManifest + $passedCommitMismatch.claimEligible = $false + $passedCommitMismatch.gates["security.redaction"].evidenceCommit = $otherCommit + Assert-ValidatorFailure -Name "passed-commit-mismatch" -Manifest $passedCommitMismatch + + $artifactCommitMismatch = New-EligibleManifest + $artifactCommitMismatch.claimEligible = $false + $artifactCommitMismatch.artifacts[0].sourceCommit = $otherCommit + Assert-ValidatorFailure -Name "artifact-commit-mismatch" -Manifest $artifactCommitMismatch + + $pendingRequired = New-EligibleManifest + $eligiblePendingGate = $pendingRequired.gates["scope.alpha_dependencies"] + $eligiblePendingGate.status = "PENDING" + $eligiblePendingGate.evidence = @() + $eligiblePendingGate.Remove("evidenceCommit") + Assert-ValidatorFailure -Name "eligible-with-pending-required" -Manifest $pendingRequired + + $missingReleaseArtifact = New-EligibleManifest + $missingReleaseArtifact.artifacts = @( + [ordered]@{ + kind = "SBOM" + name = "sbom.cdx.json" + sha256 = $canonicalSha256 + byteCount = 256 + sourceCommit = $canonicalCommit + } + ) + Assert-ValidatorFailure -Name "eligible-without-apk-or-aab" -Manifest $missingReleaseArtifact + + $releaseArtifactWithoutDigest = New-EligibleManifest + $releaseArtifactWithoutDigest.artifacts[0].sha256 = $null + Assert-ValidatorFailure -Name "eligible-release-artifact-without-digest" -Manifest $releaseArtifactWithoutDigest + + $tokenSecret = "super-secret-token" + $tokenReference = New-EligibleManifest + $tokenReference.gates["security.redaction"].evidence[0].value = + "https://example.invalid/evidence?token=$tokenSecret" + Assert-ValidatorFailure ` + -Name "token-query-secret" ` + -Manifest $tokenReference ` + -ForbiddenEcho $tokenSecret + + $authorizationSecret = "Bearer abc.def.ghi" + $authorizationReference = New-EligibleManifest + $authorizationReference.gates["security.redaction"].evidence[0].value = + "Authorization: $authorizationSecret" + Assert-ValidatorFailure ` + -Name "authorization-secret" ` + -Manifest $authorizationReference ` + -ForbiddenEcho $authorizationSecret + + $cookieSecret = "session=private-cookie-value" + $cookieReference = New-EligibleManifest + $cookieReference.gates["security.redaction"].evidence[0].value = "Cookie: $cookieSecret" + Assert-ValidatorFailure ` + -Name "cookie-secret" ` + -Manifest $cookieReference ` + -ForbiddenEcho $cookieSecret + + $userInfoSecret = "private-password" + $userInfoReference = New-EligibleManifest + $userInfoReference.gates["security.redaction"].evidence[0].value = + "https://release-user:$userInfoSecret@example.invalid/artifact" + Assert-ValidatorFailure ` + -Name "uri-userinfo-secret" ` + -Manifest $userInfoReference ` + -ForbiddenEcho $userInfoSecret + + $privateMachinePath = New-EligibleManifest + $privateMachinePath.artifacts[0].name = "C:\\Users\\private-user\\Desktop\\muxtv.apk" + Assert-ValidatorFailure -Name "private-windows-absolute-path" -Manifest $privateMachinePath + + $privateUnixPath = New-EligibleManifest + $privateUnixPath.artifacts[0].name = "/home/private-user/muxtv.apk" + Assert-ValidatorFailure -Name "private-unix-absolute-path" -Manifest $privateUnixPath + + $signedQuerySecret = "signed-value" + $signedQueryReference = New-EligibleManifest + $signedQueryReference.gates["security.redaction"].evidence[0].value = + "https://example.invalid/evidence?X-Amz-Signature=$signedQuerySecret" + Assert-ValidatorFailure ` + -Name "signed-query-secret" ` + -Manifest $signedQueryReference ` + -ForbiddenEcho $signedQuerySecret +} finally { + Remove-Item -LiteralPath $tempRoot -Recurse -Force -ErrorAction SilentlyContinue +} + +if ($failures.Count -gt 0) { + foreach ($failure in $failures) { + Write-Host $failure + } + throw "Alpha evidence validator contract failed." +} + +Write-Host "Alpha evidence validator contract passed." +Write-Host "cases=$passed" From ea2cd6d3fd9621afca2c6d043562cf8df1a3e063 Mon Sep 17 00:00:00 2001 From: MrFr3di Date: Thu, 3 Sep 2026 00:13:11 +0500 Subject: [PATCH 5/6] test(release): run alpha evidence contract in host validation --- tools/verify-local.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/verify-local.ps1 b/tools/verify-local.ps1 index f3ca69177..a10eb2f7e 100644 --- a/tools/verify-local.ps1 +++ b/tools/verify-local.ps1 @@ -31,6 +31,7 @@ if ($Mode -ne "DeviceOnly") { & (Join-Path $repositoryRoot "tools\ci\Test-RepositoryTruthContract.ps1") -RepositoryRoot $repositoryRoot & (Join-Path $repositoryRoot "tools\ci\Test-FeatureAdapterBoundaryContract.ps1") -RepositoryRoot $repositoryRoot & (Join-Path $repositoryRoot "tools\ci\Test-ProcessAsyncOwnershipContract.ps1") -RepositoryRoot $repositoryRoot + & (Join-Path $repositoryRoot "tools\release\Test-AlphaEvidenceValidator.ps1") if ($Mode -eq "Full") { & (Join-Path $repositoryRoot "tools\ci\Test-RepositoryTruthContractShallowClone.ps1") -RepositoryRoot $repositoryRoot } From b1b10c38ef6f8accc4734b39c6e7e74909856d4f Mon Sep 17 00:00:00 2001 From: MrFr3di Date: Thu, 3 Sep 2026 00:14:59 +0500 Subject: [PATCH 6/6] feat(release): implement alpha evidence manifest finalizer --- .../Validate-AlphaEvidenceManifest.ps1 | 189 ++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100644 tools/release/Validate-AlphaEvidenceManifest.ps1 diff --git a/tools/release/Validate-AlphaEvidenceManifest.ps1 b/tools/release/Validate-AlphaEvidenceManifest.ps1 new file mode 100644 index 000000000..59b2bfc53 --- /dev/null +++ b/tools/release/Validate-AlphaEvidenceManifest.ps1 @@ -0,0 +1,189 @@ +[CmdletBinding()] +param( + [Parameter(Mandatory)][string]$ManifestPath, + [Parameter(Mandatory)][string]$SchemaPath, + [Parameter(Mandatory)][string]$GateCatalogPath +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +function Fail-Validation { + param([Parameter(Mandatory)][string]$Message) + throw $Message +} + +function Read-JsonDocument { + param( + [Parameter(Mandatory)][string]$Path, + [Parameter(Mandatory)][string]$FailureMessage + ) + + if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) { + Fail-Validation $FailureMessage + } + + try { + return Get-Content -LiteralPath $Path -Raw -Encoding utf8 | ConvertFrom-Json -Depth 100 + } catch { + Fail-Validation $FailureMessage + } +} + +function Assert-PublicSafeValue { + param( + [Parameter(Mandatory)][string]$Value, + [Parameter(Mandatory)][bool]$RejectAbsolutePath + ) + + $sensitivePatterns = @( + '(?i)\b(?:authorization|cookie|set-cookie)\s*:', + '(?i)^[a-z][a-z0-9+.-]*://[^/?#\s]*@', + '(?i)[?&#](?:access[_-]?token|token|api[_-]?key|password|passwd|signature|sig|x-amz-signature|x-goog-signature|credential|auth)\s*=', + '(?i)^file://' + ) + + foreach ($pattern in $sensitivePatterns) { + if ($Value -match $pattern) { + Fail-Validation "Alpha evidence metadata failed redaction policy." + } + } + + if ($RejectAbsolutePath) { + if ($Value -match '^[A-Za-z]:[\\/]' -or $Value -match '^[/\\]{1,2}(?![/\\])') { + Fail-Validation "Alpha evidence metadata contains a private absolute path." + } + } +} + +foreach ($requiredFile in @($ManifestPath, $SchemaPath, $GateCatalogPath)) { + if (-not (Test-Path -LiteralPath $requiredFile -PathType Leaf)) { + Fail-Validation "Alpha evidence validator input is missing." + } +} + +try { + $testJsonCommand = Get-Command Test-Json -ErrorAction Stop +} catch { + Fail-Validation "PowerShell Test-Json support is required." +} +if (-not $testJsonCommand.Parameters.ContainsKey("SchemaFile")) { + Fail-Validation "PowerShell Test-Json -SchemaFile support is required." +} + +try { + $manifestJson = Get-Content -LiteralPath $ManifestPath -Raw -Encoding utf8 + $schemaValid = $manifestJson | Test-Json -SchemaFile $SchemaPath -ErrorAction Stop +} catch { + Fail-Validation "Alpha evidence manifest schema validation failed." +} +if (-not $schemaValid) { + Fail-Validation "Alpha evidence manifest schema validation failed." +} + +$manifest = Read-JsonDocument -Path $ManifestPath -FailureMessage "Alpha evidence manifest JSON is invalid." +$catalog = Read-JsonDocument -Path $GateCatalogPath -FailureMessage "Alpha gate catalog JSON is invalid." + +if ($catalog.schemaVersion -ne 1 -or [string]$catalog.releaseTrack -cne "0.1.0-alpha") { + Fail-Validation "Alpha gate catalog identity is invalid." +} + +$catalogById = [System.Collections.Generic.Dictionary[string, object]]::new([System.StringComparer]::Ordinal) +foreach ($definition in @($catalog.gates)) { + $id = [string]$definition.id + if ([string]::IsNullOrWhiteSpace($id) -or $catalogById.ContainsKey($id)) { + Fail-Validation "Alpha gate catalog contains an invalid or duplicate gate id." + } + $catalogById.Add($id, $definition) +} +if ($catalogById.Count -eq 0) { + Fail-Validation "Alpha gate catalog is empty." +} + +$manifestById = [System.Collections.Generic.Dictionary[string, object]]::new([System.StringComparer]::Ordinal) +foreach ($property in @($manifest.gates.PSObject.Properties)) { + if ($manifestById.ContainsKey($property.Name)) { + Fail-Validation "Alpha evidence manifest contains duplicate gates." + } + $manifestById.Add($property.Name, $property.Value) +} + +if ($manifestById.Count -ne $catalogById.Count) { + Fail-Validation "Alpha evidence manifest does not contain the canonical gate set." +} + +foreach ($entry in $catalogById.GetEnumerator()) { + $id = $entry.Key + $definition = $entry.Value + if (-not $manifestById.ContainsKey($id)) { + Fail-Validation "Alpha evidence manifest is missing a canonical gate." + } + + $gate = $manifestById[$id] + if ([bool]$definition.requiredByDefault -and -not [bool]$gate.required) { + Fail-Validation "Alpha evidence manifest weakens a required gate." + } +} + +foreach ($id in $manifestById.Keys) { + if (-not $catalogById.ContainsKey($id)) { + Fail-Validation "Alpha evidence manifest contains an unknown gate." + } +} + +$manifestCommit = [string]$manifest.commit +foreach ($gate in $manifestById.Values) { + if ([string]$gate.status -ceq "PASSED") { + if ([string]$gate.evidenceCommit -cne $manifestCommit) { + Fail-Validation "Passed alpha evidence does not belong to the manifest commit." + } + } + + foreach ($reference in @($gate.evidence)) { + Assert-PublicSafeValue -Value ([string]$reference.value) -RejectAbsolutePath $true + } + + if ($null -ne $gate.PSObject.Properties["note"] -and $null -ne $gate.note) { + Assert-PublicSafeValue -Value ([string]$gate.note) -RejectAbsolutePath $false + } + + if ($null -ne $gate.PSObject.Properties["facts"] -and $null -ne $gate.facts) { + foreach ($factProperty in @($gate.facts.PSObject.Properties)) { + if ($factProperty.Value -is [string]) { + Assert-PublicSafeValue -Value ([string]$factProperty.Value) -RejectAbsolutePath $false + } + } + } +} + +foreach ($artifact in @($manifest.artifacts)) { + if ([string]$artifact.sourceCommit -cne $manifestCommit) { + Fail-Validation "Alpha release artifact does not belong to the manifest commit." + } + Assert-PublicSafeValue -Value ([string]$artifact.name) -RejectAbsolutePath $true +} + +foreach ($limitation in @($manifest.knownLimitations)) { + Assert-PublicSafeValue -Value ([string]$limitation) -RejectAbsolutePath $false +} + +if ([bool]$manifest.claimEligible) { + foreach ($gate in $manifestById.Values) { + if ([bool]$gate.required -and [string]$gate.status -cne "PASSED") { + Fail-Validation "Claim-eligible alpha evidence has a required gate that is not passed." + } + } + + $qualifiedReleaseArtifacts = @( + @($manifest.artifacts) | Where-Object { + ([string]$_.kind -in @("APK", "AAB")) -and + ($null -ne $_.sha256) -and + ([string]$_.sha256 -cmatch '^[0-9a-f]{64}$') + } + ) + if ($qualifiedReleaseArtifacts.Count -lt 1) { + Fail-Validation "Claim-eligible alpha evidence requires an APK or AAB with SHA-256 provenance." + } +} + +Write-Host "Alpha evidence manifest validation passed."