diff --git a/.github/workflows/_ci-p1-windows-build-test.yml b/.github/workflows/_ci-p1-windows-build-test.yml index 73d326fe0..2a2bcad4d 100644 --- a/.github/workflows/_ci-p1-windows-build-test.yml +++ b/.github/workflows/_ci-p1-windows-build-test.yml @@ -177,6 +177,7 @@ jobs: 'quality-browser' { 'FullyQualifiedName~QualityInvalidAttentionTests|FullyQualifiedName~IedBrowserPresentationTests|FullyQualifiedName~M2TypedQualityEvidenceTests|FullyQualifiedName~P0ReadEvidenceCsvBehaviorTests|FullyQualifiedName~P0SiebcuReadAllAndLayoutRegressionTests' } 'smv' { 'FullyQualifiedName~SmvArstack|FullyQualifiedName~SmvLiveCaptureTests|FullyQualifiedName~SmvObservedTimebaseTests|FullyQualifiedName~SmvDigSubPortTests' } 'command-read' { 'FullyQualifiedName~ModelDrivenAutoCommandDockTests|FullyQualifiedName~P109UStableCommandProjectionTests|FullyQualifiedName~P109WCommandDockLifecycleArchitectureTests|FullyQualifiedName~P0CswiCrossDataSetCommandDiscoveryTests|FullyQualifiedName~P0GrxCswiSelectionVisibilityTests|FullyQualifiedName~P0GrxSclLiveCswiParityTests|FullyQualifiedName~P0ReadEvidenceCsvBehaviorTests|FullyQualifiedName~P109KGenericControlSafetyTests|FullyQualifiedName~ZivBscStepControlRegressionTests|FullyQualifiedName~G1ControlCorrectnessRegressionTests' } + 'reporting' { 'FullyQualifiedName~Report|FullyQualifiedName~Rcb|FullyQualifiedName~Sg7|FullyQualifiedName~StaticDataSet|FullyQualifiedName~M217K|FullyQualifiedName~IedBrowser|FullyQualifiedName~Mms' } 'all' { 'FullyQualifiedName~QualityInvalidAttentionTests|FullyQualifiedName~SmvArstack|FullyQualifiedName~P0CswiCrossDataSetCommandDiscoveryTests|FullyQualifiedName~P0ReadEvidenceCsvBehaviorTests' } default { throw 'Unknown FAST DEV scope; refusing to silently skip tests.' } } @@ -204,8 +205,8 @@ jobs: } Write-Host "Exact-source regression results: executed=$($counts.executed) passed=$($counts.passed) failed=$($counts.failed)." - - name: Build locked ArdIrec bridge and native regression for SG7 BCU trial - if: ${{ inputs.fast_dev && inputs.engine_trial_sha != '' && inputs.focus == 'command-read' }} + - name: Build locked ArdIrec bridge and native regression for nonpromotion trial + if: ${{ inputs.fast_dev && inputs.engine_trial_sha != '' && (inputs.focus == 'command-read' || inputs.focus == 'reporting') }} shell: pwsh run: | $ErrorActionPreference = 'Stop' @@ -220,8 +221,8 @@ jobs: } "SG7_NATIVE_BRIDGE=$bridge" | Out-File $env:GITHUB_ENV -Append -Encoding utf8 - - name: Publish actual self-contained SG7 BCU Ed2 trial Windows EXE - if: ${{ inputs.fast_dev && inputs.engine_trial_sha != '' && inputs.focus == 'command-read' }} + - name: Publish self-contained exact-SHA nonpromotion Windows EXE + if: ${{ inputs.fast_dev && inputs.engine_trial_sha != '' && (inputs.focus == 'command-read' || inputs.focus == 'reporting') }} shell: pwsh env: ARSAS_NONPROMOTION_ENGINE_TRIAL: 'true' @@ -232,8 +233,9 @@ jobs: -not (Test-Path $env:SG7_NATIVE_BRIDGE -PathType Leaf)) { throw 'Engine/ArdIrec exact-SHA trial packaging prerequisites missing.' } + $trialVersion = if ('${{ inputs.focus }}' -eq 'reporting') { '1.6.42-reportingtrial' } else { '1.6.40-sg7bcued2trial' } .\ARSAS\scripts\publish-windows-portable.ps1 ` - -Version '1.6.40-sg7bcued2trial' ` + -Version $trialVersion ` -Runtime win-x64 ` -SingleFile $true ` -SelfContained $true ` @@ -243,18 +245,20 @@ jobs: -EngineValidationLockPath $temporary if ($LASTEXITCODE -ne 0) { throw 'Single EXE trial publish failed.' } - - name: Execute portable EXE smoke and seal physical FAT trial identity - if: ${{ inputs.fast_dev && inputs.engine_trial_sha != '' && inputs.focus == 'command-read' }} + - name: Execute portable EXE smoke and seal nonpromotion trial identity + if: ${{ inputs.fast_dev && inputs.engine_trial_sha != '' && (inputs.focus == 'command-read' || inputs.focus == 'reporting') }} shell: pwsh env: SOURCE_SHA: ${{ inputs.source_sha }} EXPERIMENTAL_ENGINE: ${{ inputs.engine_trial_sha }} BASELINE_ENGINE: ${{ steps.engine.outputs.commit }} + FOCUS: ${{ inputs.focus }} run: | $ErrorActionPreference = 'Stop' $root = '.\ARSAS\dist' - $source = Join-Path $root 'ARSAS-1.6.40-sg7bcued2trial-win-x64-portable.exe' - $identityPath = Join-Path $root 'ARSAS-1.6.40-sg7bcued2trial-win-x64-portable-build-identity.json' + $version = if ($env:FOCUS -eq 'reporting') { '1.6.42-reportingtrial' } else { '1.6.40-sg7bcued2trial' } + $source = Join-Path $root "ARSAS-$version-win-x64-portable.exe" + $identityPath = Join-Path $root "ARSAS-$version-win-x64-portable-build-identity.json" if (-not (Test-Path $source -PathType Leaf) -or -not (Test-Path $identityPath -PathType Leaf)) { throw 'Single file EXE / portable identity missing.' } @@ -283,7 +287,7 @@ jobs: if ($proc.ExitCode -ne 0) { throw "Portable EXE smoke failed: $($proc.ExitCode)" } $out = '.\ARSAS\TestResults\sg7-bcu-fat-trial' New-Item -ItemType Directory -Path $out -Force | Out-Null - $name = 'ARSAS-SG7_BCU-ED2-FAT-TRIAL-' + $engineSha.Substring(0,8) + '-win-x64.exe' + $name = if ($env:FOCUS -eq 'reporting') { 'ARSAS-REPORTING-P8-TRIAL-' + $engineSha.Substring(0,8) + '-win-x64.exe' } else { 'ARSAS-SG7_BCU-ED2-FAT-TRIAL-' + $engineSha.Substring(0,8) + '-win-x64.exe' } $exe = Join-Path $out $name Copy-Item $source $exe -Force if ((Get-FileHash $exe -Algorithm SHA256).Hash.ToLowerInvariant() -cne $exeHash) { @@ -293,7 +297,8 @@ jobs: Copy-Item (Join-Path $env:RUNNER_TEMP 'sg7-ardirec-ctest.xml') (Join-Path $out 'native-ardirec-ctest.xml') -Force [ordered]@{ schemaVersion = 1 - kind = 'arsas-nonrelease-supervised-iec61850-ed2-fat-trial' + kind = if ($env:FOCUS -eq 'reporting') { 'arsas-nonrelease-reporting-engine-trial' } else { 'arsas-nonrelease-supervised-iec61850-ed2-fat-trial' } + focus = $env:FOCUS sourceSha = $sourceSha engineTrialSha = $engineSha sourceApprovedEngineSha = $lock.commit @@ -307,6 +312,17 @@ jobs: externalRelayTestPerformed = $false productionReleaseAuthority = $false } | ConvertTo-Json -Depth 6 | Set-Content (Join-Path $out 'FAT-TRIAL-SHA256.json') -Encoding utf8 + if ($env:FOCUS -eq 'reporting') { + @' + ARSAS REPORTING P8 | NONRELEASE ENGINE TRIAL + ------------------------------------------------- + Exact-SHA isolated Windows x64 candidate; NOT the stable release. + Approved engine pin has NOT changed. Verify SHA-256 in the manifest. + Qualification scope: READ-ONLY multi-RCB / structured FCD reporting. + No cyclic MMS fallback, no automatic RCB mutation and no controls. + CI PASS does not prove physical 77/77, 4/4, or interoperability. + '@ | Set-Content (Join-Path $out 'READ-ME-FIRST.txt') -Encoding utf8 + } else { @' ARSAS SG7_BCU IEC 61850 EDITION 2 | SUPERVISED FAT TRIAL ------------------------------------------------------- @@ -325,6 +341,7 @@ jobs: An automation PASS does not prove physical command success. The repository production engine lock remains the qualified #184. '@ | Set-Content (Join-Path $out 'READ-ME-FIRST.txt') -Encoding utf8 + } Write-Host "EXE SMOKE SUCCESS: $name SHA256 $exeHash" Write-Host 'SUPERVISED FAT candidate only. No release or physical command authority.' @@ -386,10 +403,12 @@ jobs: run: | $trial = ($env:FAST_DEV -eq 'true' -and $env:TRIAL_SHA -match '^[0-9a-f]{40}$' -and - $env:FOCUS -eq 'command-read') + $env:FOCUS -in @('command-read', 'reporting')) if ($trial) { - "artifact_name=ARSAS-SG7-BCU-FAT-TRIAL-$env:GITHUB_RUN_ID.exe" | Out-File $env:GITHUB_OUTPUT -Append -Encoding utf8 - "artifact_path=ARSAS/TestResults/sg7-bcu-fat-trial/ARSAS-SG7_BCU-ED2-FAT-TRIAL-*-win-x64.exe" | Out-File $env:GITHUB_OUTPUT -Append -Encoding utf8 + $artifactPrefix = if ($env:FOCUS -eq 'reporting') { 'ARSAS-REPORTING-P8-TRIAL' } else { 'ARSAS-SG7-BCU-FAT-TRIAL' } + $filePrefix = if ($env:FOCUS -eq 'reporting') { 'ARSAS-REPORTING-P8-TRIAL' } else { 'ARSAS-SG7_BCU-ED2-FAT-TRIAL' } + "artifact_name=$artifactPrefix-$env:GITHUB_RUN_ID.exe" | Out-File $env:GITHUB_OUTPUT -Append -Encoding utf8 + "artifact_path=ARSAS/TestResults/sg7-bcu-fat-trial/$filePrefix-*-win-x64.exe" | Out-File $env:GITHUB_OUTPUT -Append -Encoding utf8 "artifact_archive=false" | Out-File $env:GITHUB_OUTPUT -Append -Encoding utf8 } else { "artifact_name=ci-p1-shadow-$env:GITHUB_RUN_ID-$env:GITHUB_RUN_ATTEMPT" | Out-File $env:GITHUB_OUTPUT -Append -Encoding utf8 diff --git a/.github/workflows/ci-p0-workflow-integrity.yml b/.github/workflows/ci-p0-workflow-integrity.yml index 14af8bf9c..a4fe7d83d 100644 --- a/.github/workflows/ci-p0-workflow-integrity.yml +++ b/.github/workflows/ci-p0-workflow-integrity.yml @@ -86,7 +86,9 @@ jobs: run: python3 scripts/test-p7-dual-ingress-diagnostic.py - name: Run conservative CI P1 classifier fixtures - run: python3 scripts/test-classify-ci-changes.py + run: | + python3 scripts/test-classify-ci-changes.py + python3 scripts/test-resolve-ariec-trial.py - name: Run canonical CI P2 evidence safety fixtures run: python3 scripts/test-ci-canonical-reuse.py diff --git a/.github/workflows/ci-p1-shadow.yml b/.github/workflows/ci-p1-shadow.yml index 65dac337b..ca512759a 100644 --- a/.github/workflows/ci-p1-shadow.yml +++ b/.github/workflows/ci-p1-shadow.yml @@ -30,7 +30,7 @@ on: focus: description: 'Focused regression group' type: choice - options: [quality-browser, smv, command-read, all] + options: [quality-browser, smv, command-read, reporting, all] default: quality-browser engine_trial_sha: description: 'Optional exact 40-char experimental ARIEC SHA; FAST DEV only, never FAT' @@ -58,6 +58,7 @@ jobs: ci: ${{ steps.changed.outputs.ci }} risk: ${{ steps.changed.outputs.risk }} engine_trial_sha: ${{ steps.trial.outputs.sha }} + engine_trial_focus: ${{ steps.trial.outputs.focus }} steps: - name: Checkout PR merge source uses: actions/checkout@v7 @@ -85,28 +86,12 @@ jobs: fi - id: trial - name: Resolve explicit nonrelease engine trial (label-gated) + name: Resolve exact nonpromotion engine trial (label-gated) if: ${{ github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'ci-p1-shadow') }} shell: bash run: | - if [[ -f engines/ARIEC61850.trial.json ]]; then - python3 - <<'PY' >> "$GITHUB_OUTPUT" - import json - import re - - with open("engines/ARIEC61850.lock.json", encoding="utf-8") as handle: - production = json.load(handle) - with open("engines/ARIEC61850.trial.json", encoding="utf-8") as handle: - trial = json.load(handle) - sha = str(trial.get("commit", "")) - if (not re.fullmatch(r"[0-9a-f]{40}", sha) - or trial.get("runMode") != "fast-dev-nonpromotion" - or trial.get("scope") != "command-read" - or sha == production.get("commit")): - raise SystemExit("Invalid or non-isolated engine trial manifest") - print("sha=" + sha) - PY - fi + python3 scripts/test-resolve-ariec-trial.py + python3 scripts/resolve-ariec-trial.py --output "$GITHUB_OUTPUT" windows-shadow: name: Compare reusable build/test with legacy authority @@ -128,6 +113,6 @@ jobs: source_sha: ${{ github.event.pull_request.head.sha || github.sha }} shadow_mode: true fast_dev: ${{ (github.event_name == 'workflow_dispatch' && inputs.fast_dev == true) || (github.event_name == 'pull_request' && needs.classify.outputs.engine_trial_sha != '') }} - focus: ${{ github.event_name == 'workflow_dispatch' && inputs.fast_dev == true && inputs.focus || needs.classify.outputs.engine_trial_sha != '' && 'command-read' || 'all' }} + focus: ${{ github.event_name == 'workflow_dispatch' && inputs.fast_dev == true && inputs.focus || needs.classify.outputs.engine_trial_focus || 'all' }} engine_trial_sha: ${{ github.event_name == 'workflow_dispatch' && inputs.fast_dev == true && inputs.engine_trial_sha || needs.classify.outputs.engine_trial_sha || '' }} secrets: {} diff --git a/engines/ARIEC61850.historical-trials.json b/engines/ARIEC61850.historical-trials.json new file mode 100644 index 000000000..ec6197b81 --- /dev/null +++ b/engines/ARIEC61850.historical-trials.json @@ -0,0 +1,320 @@ +{ + "schemaVersion": 1, + "authority": "immutable-code-trial-history-not-physical-approval", + "approvedEngineSha": "f75e323e9f8443aa20e0a3818a1d5d77255b2c0f", + "contracts": { + "smartInteroperabilityCodeTrial": { + "status": "code-verified-not-physical", + "issue": 144, + "integrationPullRequest": 148, + "exactCommit": "9c5292570f55dd81be1b3a6b56f937e5ba1ed276", + "ciRun": 759, + "consumerIssue": 446, + "consumerBasePullRequest": 425, + "fullDiscoveryFallbackForbidden": true, + "vendorBranchingForbidden": true, + "physicalPromotionRequired": true + }, + "sclServerAtTrial": { + "status": "code-verified-candidate-not-physical", + "sourcePullRequest": 153, + "baseEngineCommit": "9c5292570f55dd81be1b3a6b56f937e5ba1ed276", + "exactCommit": "352c81e6a798635c6addcee0683235ca87ad416d", + "consumerIssue": 446, + "consumerMilestone": "P7.5", + "realStationSclNotCommitted": true, + "fixture": "sanitized J Server / F ServerAt multi-AP", + "noAutomaticMmsFailover": true, + "physicalQualificationRequired": true + }, + "datasetFirstDiscoveryTrial": { + "status": "code-trial-not-physical", + "issue": 495, + "enginePullRequest": 157, + "baseEngineCommit": "352c81e6a798635c6addcee0683235ca87ad416d", + "exactCommit": "8b0c0f7e695b016834e40a6f068994dcc945cb1a", + "consumerPullRequest": 497, + "fullNameListFallbackForbidden": true, + "cyclicMmsProcessPollingForbidden": true, + "physicalQualificationRequired": true + }, + "zivSclBscInteroperabilityTrial": { + "status": "code-verified-not-physical", + "issue": 538, + "enginePullRequest": 167, + "baseEngineCommit": "9bd01908ebd6fdf50a74f0a421b4e79613b878d4", + "exactCommit": "67c6635b7e16139c0661e559a2e6946527c1442a", + "engineCiRun": 38219429896, + "sclApTitlePackedPrefixOnly": true, + "bscLiveSemanticGateRequired": true, + "physicalQualificationRequired": true, + "mainlinePromotionForbidden": true, + "fullDiscoveryFallbackForbidden": true, + "cyclicMmsProcessPollingForbidden": true + }, + "canonicalReportIngressIntegrityTrial": { + "status": "code-verified-not-physical", + "issue": 536, + "enginePullRequest": 166, + "baseEngineCommit": "d700b0df741b466afb8abe6cee1d70b1619d7b4d", + "exactCommit": "9bd01908ebd6fdf50a74f0a421b4e79613b878d4", + "engineCiRun": 38214165576, + "physicalQualificationRequired": true, + "mainlinePromotionForbidden": true, + "fullDiscoveryFallbackForbidden": true, + "cyclicMmsProcessPollingForbidden": true + }, + "reportSubscriberIntegrityTrial": { + "status": "code-verified-not-physical", + "issue": 532, + "enginePullRequest": 165, + "baseEngineCommit": "fa93c9fdc7cd98fd3624227ee21c3eefc68bcac7", + "exactCommit": "d700b0df741b466afb8abe6cee1d70b1619d7b4d", + "engineCiRun": 38180732078, + "physicalQualificationRequired": true, + "mainlinePromotionForbidden": true, + "fullDiscoveryFallbackForbidden": true, + "cyclicMmsProcessPollingForbidden": true + }, + "coreReportRoutingTrial": { + "status": "code-verified-not-physical", + "issue": 531, + "simulatorPullRequest": 163, + "corePullRequest": 164, + "baseEngineCommit": "00ad2819b99ffe6b3f885e59aa24a6afb2b4d8e1", + "simulatorFifoCommit": "ef0fe44f3b40e47dc18b1aac4476b635e67d531e", + "exactCommit": "fa93c9fdc7cd98fd3624227ee21c3eefc68bcac7", + "simulatorCiRun": 38141582981, + "coreCiRun": 38142115574, + "physicalQualificationRequired": true, + "mainlinePromotionForbidden": true, + "fullDiscoveryFallbackForbidden": true, + "cyclicMmsProcessPollingForbidden": true + }, + "p10IdentityTrial": { + "status": "code-trial-not-physical", + "enginePullRequest": 161, + "baseEngineCommit": "8b0c0f7e695b016834e40a6f068994dcc945cb1a", + "exactCommit": "00ad2819b99ffe6b3f885e59aa24a6afb2b4d8e1", + "physicalQualificationRequired": true, + "fullNameListFallbackForbidden": true, + "cyclicMmsProcessPollingForbidden": true, + "identitySource": "TrustedSclExactDomainMatch" + }, + "genericControlInteroperabilityTrial": { + "status": "code-verified-not-physical", + "issue": 540, + "enginePullRequest": 168, + "baseEngineCommit": "67c6635b7e16139c0661e559a2e6946527c1442a", + "exactCommit": "a43b9b4864a9565c02f544d4135fe157c2fac577", + "engineCiRun": 38231327892, + "typedSemanticGateRequired": true, + "associationScopedInventoryRequired": true, + "vendorDispatchForbidden": true, + "physicalQualificationRequired": true, + "mainlinePromotionForbidden": true, + "fullDiscoveryFallbackForbidden": true, + "cyclicMmsProcessPollingForbidden": true + }, + "p109LControlRecoveryTrial": { + "status": "code-trial-not-physical", + "issue": 525, + "enginePullRequest": 169, + "baseEngineCommit": "a43b9b4864a9565c02f544d4135fe157c2fac577", + "exactCommit": "5e17da5f673eedce125b5c66e7dc2fa0abef2851", + "readOnlyCtlModelProbeRequired": true, + "noControlWriteOnProbe": true, + "physicalQualificationRequired": true, + "mainlinePromotionForbidden": true, + "fullDiscoveryFallbackForbidden": true, + "cyclicMmsProcessPollingForbidden": true, + "engineCiRun": 38239451501 + }, + "p109QControlModelCommissioningTrial": { + "status": "code-trial-not-physical", + "enginePullRequest": 169, + "engineCiRun": 38257606517, + "baseEngineCommit": "5e17da5f673eedce125b5c66e7dc2fa0abef2851", + "exactCommit": "f7a4e3ba2f9771e34e04073c0db0900112aa55fa", + "authorizedConfirmationRequired": true, + "exactLiveCompareRequired": true, + "singleWriteNoRetry": true, + "verifiedReadbackRequired": true, + "commandOperateForbidden": true, + "physicalQualificationRequired": true, + "mainlinePromotionForbidden": true, + "fullDiscoveryFallbackForbidden": true, + "cyclicMmsProcessPollingForbidden": true + }, + "p109RSimpleCtlModelEditorTrial": { + "status": "code-trial-not-physical", + "enginePullRequest": 169, + "engineCiRun": 38311424952, + "baseEngineCommit": "f7a4e3ba2f9771e34e04073c0db0900112aa55fa", + "exactCommit": "d1dd8fc14e79c5de15fbc52d77aea48a14c7c02b", + "simpleSelectWriteUi": true, + "statusOnlyZeroSupported": true, + "exactLiveCompareRequired": true, + "singleWriteNoRetry": true, + "verifiedReadbackRequired": true, + "commandOperateForbidden": true, + "physicalQualificationRequired": true, + "mainlinePromotionForbidden": true, + "fullDiscoveryFallbackForbidden": true, + "cyclicMmsProcessPollingForbidden": true + }, + "p109TPositionControlInteroperabilityTrial": { + "status": "code-trial-not-physical", + "enginePullRequest": 169, + "engineCiRun": 38320987660, + "baseEngineCommit": "d1dd8fc14e79c5de15fbc52d77aea48a14c7c02b", + "exactCommit": "9d82523978c29dcbf38f60cbfb384c3b050eae7c", + "structuredStatusRequiresTypedMmsRead": true, + "optionalCancelNotRequiredForNormalSbo": true, + "liveTypedControlVerificationRequired": true, + "commandOperateNeverAutoDispatched": true, + "physicalQualificationRequired": true, + "mainlinePromotionForbidden": true, + "fullDiscoveryFallbackForbidden": true, + "cyclicMmsProcessPollingForbidden": true + }, + "p109UPositionStatusNameListRecoveryTrial": { + "status": "code-trial-not-physical", + "enginePullRequest": 169, + "engineCiRun": 38324547203, + "baseEngineCommit": "9d82523978c29dcbf38f60cbfb384c3b050eae7c", + "exactCommit": "ad43399ed411cc72e6eb68b67e198db5e7b95381", + "truncatedNameListSupported": true, + "standardPositionOnlyProbe": true, + "singleScalarReadOnly": true, + "bitStringWidthTwoRequired": true, + "typedControlSemanticVerificationRequired": true, + "operateRequiresExplicitConfirmation": true, + "physicalQualificationRequired": true, + "mainlinePromotionForbidden": true, + "fullDiscoveryFallbackForbidden": true, + "cyclicMmsProcessPollingForbidden": true + }, + "m217KTypedStatusReportTrial": { + "status": "engine-ci-passed-consumer-ci-pending-not-physical", + "sourcePullRequest": 173, + "consumerPullRequest": 618, + "previousConsumerPin": "036e2c00d83a0815b6160147b14ea05975057965", + "exactCommit": "51c2eefd61d6bcbe1116fda6b781b74bb9cf06da", + "engineCiRun": 38997437784, + "physicalSource": "C264_LEGREK_SCC_CSWI3_RPTFIX.iid", + "typedStatusEnumProjection": true, + "strictFcAndTupleEvidenceRequired": true, + "noTextStructureParsing": true, + "noSyntheticReportValues": true, + "physicalQualificationRequired": true, + "mainlinePromotionForbidden": true, + "cyclicMmsProcessPollingForbidden": true + }, + "p109VLiveBscAuthorityTrial": { + "status": "engine-ci-success-consumer-ci-pending-not-physical", + "enginePullRequest": 169, + "consumerPullRequest": 543, + "baseEngineCommit": "ad43399ed411cc72e6eb68b67e198db5e7b95381", + "exactCommit": "036e2c00d83a0815b6160147b14ea05975057965", + "engineCiRun": 38328335496, + "staticSpcNeverOverridesExactLiveBsc": true, + "typedTwoBitCtlValAndExactStepStatusRequired": true, + "wrongOrCrossObjectStatusFailsClosed": true, + "physicalQualificationRequired": true, + "mainlinePromotionForbidden": true, + "cyclicMmsProcessPollingForbidden": true + }, + "p0AIdentityTrial": { + "status": "code-verified-engine-consumer-trial-not-physical", + "enginePullRequest": 177, + "consumerPullRequest": 625, + "baseEngineCommit": "51c2eefd61d6bcbe1116fda6b781b74bb9cf06da", + "exactCommit": "ccf31f60095ea65f9095514f0fa0102060962049", + "engineCiRun": 39228439974, + "fullDomainSetRequired": true, + "sclSha256Required": true, + "endpointAndAccessPointRequired": true, + "provisionalHeuristicOnly": true, + "physicalQualificationRequired": true, + "mainlinePromotionForbidden": true + }, + "p0BRcbPlannerTrial": { + "status": "code-trial-not-physical", + "enginePullRequest": 179, + "consumerPullRequest": 626, + "baseEngineCommit": "ccf31f60095ea65f9095514f0fa0102060962049", + "exactCommit": "d9e795275447ea1dcc3bea435b57f948595de310", + "targetScopedOnly": true, + "globalCandidateAbortForbidden": true, + "writeFreeReconciliation": true, + "boundedContinuationRequired": true, + "foreignReservationProtectionRequired": true, + "physicalQualificationRequired": true, + "mainlinePromotionForbidden": true, + "occupiedForeignRcbRegression": true, + "exactNameContradictoryDatasetBlocked": true, + "indexedInstanceReadPriorityOnly": true, + "exactTargetAvailabilityPages": true + }, + "p0CTypedReadTrial": { + "status": "code-trial-not-physical", + "enginePullRequest": 181, + "consumerPullRequest": 627, + "baseEngineCommit": "d9e795275447ea1dcc3bea435b57f948595de310", + "exactCommit": "ba21d882f67de22c10d3155b449aac77aa8b4899", + "engineCiRun": 39244846312, + "strictInvokeId": true, + "strictAccessOrdinal": true, + "malformedNumericsNeverZero": true, + "typedGvaRequired": true, + "perReferenceFailureEvidence": true, + "sgcbDeferredSeparated": true, + "observationsSingleSource": true, + "readOnly": true, + "physicalQualificationRequired": true, + "mainlinePromotionForbidden": true + }, + "controlReportIsolationTrial": { + "status": "code-trial-not-physical", + "baseEngineCommit": "ba21d882f67de22c10d3155b449aac77aa8b4899", + "exactCommit": "c85d4e20db722bd0c2729f2533b323b13d3ef735", + "enginePullRequest": 183, + "consumerPullRequest": 635, + "physicalQualificationRequired": true, + "mainlinePromotionForbidden": true, + "controlChecksUnchanged": true, + "controlTimeoutsUnchanged": true, + "preservesTypedReadAuthority": true + }, + "p0CStageObservationTrial": { + "status": "engine-ci-success-consumer-ci-pending-not-physical", + "baseEngineCommit": "c85d4e20db722bd0c2729f2533b323b13d3ef735", + "exactCommit": "9c7cb40364023a7b217e5ec31ab3105ba90142eb", + "enginePullRequest": 184, + "engineCiOnlyPullRequest": 185, + "engineCiRun": 39505819831, + "engineTestsPassed": 1182, + "consumerIntegrationPullRequest": 655, + "sbo400msSafetyGatePreserved": true, + "negativeAndDelayedLastApplErrorRegression": true, + "noUnqualifiedImmediateOperate": true, + "physicalLatencyQualificationRequired": true, + "mainlinePromotionForbidden": true + }, + "globalTypedControlStageTrial": { + "status": "engine-ci-success-consumer-trial-not-physical", + "baseEngineCommit": "9c7cb40364023a7b217e5ec31ab3105ba90142eb", + "exactCommit": "f75e323e9f8443aa20e0a3818a1d5d77255b2c0f", + "enginePullRequest": 192, + "engineCiRun": 39909260816, + "typedStatusReadRequired": true, + "sameDataObjectFeedbackRequired": true, + "ctlModelAndOperShapeRequired": true, + "sbowEnhancedSafetyRequired": true, + "noAutomaticOperate": true, + "physicalQualificationRequired": true, + "mainlinePromotionForbidden": true + } + } +} diff --git a/engines/ARIEC61850.reporting-trial.json b/engines/ARIEC61850.reporting-trial.json new file mode 100644 index 000000000..246b9f4c1 --- /dev/null +++ b/engines/ARIEC61850.reporting-trial.json @@ -0,0 +1,18 @@ +{ + "schemaVersion": 1, + "repository": "masarray/ARIEC61850", + "commit": "ae1ce23dbc181c2a17aef20802f7517892fe4264", + "approvedBaselineCommit": "f75e323e9f8443aa20e0a3818a1d5d77255b2c0f", + "runMode": "fast-dev-nonpromotion", + "scope": "reporting", + "enginePullRequests": [ + 198, + 199 + ], + "engineCiRun": 40870337847, + "mainlinePromotionForbidden": true, + "physicalQualificationRequired": true, + "automaticRcbMutationForbidden": true, + "cyclicMmsProcessPollingForbidden": true, + "purpose": "Exact source/engine nonrelease reporting multi-RCB + structured FCD trial; approved lock and stable release unchanged." +} diff --git a/scripts/resolve-ariec-lock.ps1 b/scripts/resolve-ariec-lock.ps1 index 5f4f4e6f1..87c61a23c 100644 --- a/scripts/resolve-ariec-lock.ps1 +++ b/scripts/resolve-ariec-lock.ps1 @@ -47,6 +47,37 @@ function Assert-Sha { Assert-Sha -Value ([string]$lock.commit) -Name 'commit' +# A later (code-only) stage trial must not let historical evidence regress. +# The active typed-control trial can be valid while an earlier independent +# canonical/reporting lineage was silently edited; validate *all* recorded +# immutable contracts before considering the active trial selector. +if ($null -ne $lock.globalTypedControlStageTrial) { + $baselinePath = Join-Path $PSScriptRoot '..\engines\ARIEC61850.historical-trials.json' + if (-not (Test-Path -LiteralPath $baselinePath -PathType Leaf)) { + throw 'Historical physical-tested trial anchor file is missing.' + } + $history = Get-Content -LiteralPath $baselinePath -Raw -Encoding UTF8 | + ConvertFrom-Json -Depth 50 + if ([int]$history.schemaVersion -ne 1 -or + [string]$history.approvedEngineSha -cne 'f75e323e9f8443aa20e0a3818a1d5d77255b2c0f') { + throw 'Historical physical-tested trial anchor identity disagrees.' + } + foreach ($contract in $history.contracts.PSObject.Properties) { + $actualContract = $lock.PSObject.Properties[$contract.Name] + if ($null -eq $actualContract -or $null -eq $actualContract.Value) { + throw "Historical physical-tested trial contract missing: $($contract.Name)" + } + foreach ($expectedField in $contract.Value.PSObject.Properties) { + $actualField = $actualContract.Value.PSObject.Properties[$expectedField.Name] + if ($null -eq $actualField -or + [string]$actualField.Value -cne [string]$expectedField.Value) { + throw "Historical physical-tested trial authority changed: $($contract.Name).$($expectedField.Name)" + } + } + } +} + + if ($null -ne $lock.physicalTestedCommit) { Assert-Sha -Value ([string]$lock.physicalTestedCommit) -Name 'physicalTestedCommit' } diff --git a/scripts/resolve-ariec-trial.py b/scripts/resolve-ariec-trial.py new file mode 100644 index 000000000..5fb29a376 --- /dev/null +++ b/scripts/resolve-ariec-trial.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +"""Select an exact-SHA nonpromotion engine trial without altering ARSAS's production pin.""" +import argparse +import json +import re +import subprocess +from pathlib import Path + +CMD = "engines/ARIEC61850.trial.json" +REPORT = "engines/ARIEC61850.reporting-trial.json" +SHA = re.compile(r"^[0-9a-f]{40}$") + + +def resolve(root, changed): + if CMD in changed and REPORT in changed: + raise ValueError("competing engine trial manifests") + focus = "reporting" if REPORT in changed else "command-read" + manifest_path = REPORT if focus == "reporting" else CMD + lock = json.loads((root / "engines/ARIEC61850.lock.json").read_text(encoding="utf-8")) + trial = json.loads((root / manifest_path).read_text(encoding="utf-8")) + baseline, candidate = lock.get("commit"), trial.get("commit") + if not SHA.fullmatch(str(baseline)) or not SHA.fullmatch(str(candidate)): + raise ValueError("invalid immutable engine SHA") + if lock.get("repository") != "masarray/ARIEC61850" or lock.get("ref") != "main": + raise ValueError("approved engine provenance mismatch") + if trial.get("runMode") != "fast-dev-nonpromotion" or trial.get("scope") != focus: + raise ValueError("trial runMode/scope mismatch") + if candidate == baseline: + raise ValueError("trial engine equals approved production pin") + if focus == "reporting" and ( + trial.get("approvedBaselineCommit") != baseline + or trial.get("repository") != lock["repository"] + or any(trial.get(key) is not True for key in + ("mainlinePromotionForbidden", "physicalQualificationRequired", + "cyclicMmsProcessPollingForbidden", "automaticRcbMutationForbidden")) + ): + raise ValueError("reporting safety/provenance guard violation") + return candidate, focus + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--output", type=Path, required=True) + args = parser.parse_args() + diff = set(subprocess.check_output( + ["git", "diff", "--name-only", "HEAD^1", "HEAD"], text=True).splitlines()) + sha, focus = resolve(Path.cwd(), diff) + with args.output.open("a", encoding="utf-8") as f: + f.write(f"sha={sha}\nfocus={focus}\n") + print(f"CI-only {focus} trial engine: {sha}") diff --git a/scripts/test-resolve-ariec-trial.py b/scripts/test-resolve-ariec-trial.py new file mode 100644 index 000000000..b227a7199 --- /dev/null +++ b/scripts/test-resolve-ariec-trial.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +"""Pure synthetic negative/positive tests for isolated reporting and legacy trials.""" +import importlib.util +import json +import tempfile +import unittest +from pathlib import Path + +spec = importlib.util.spec_from_file_location("trial_resolver", Path(__file__).with_name("resolve-ariec-trial.py")) +module = importlib.util.module_from_spec(spec) +spec.loader.exec_module(module) + + +class TrialTest(unittest.TestCase): + def setUp(self): + self.temp = tempfile.TemporaryDirectory() + self.addCleanup(self.temp.cleanup) + self.root = Path(self.temp.name) + (self.root / "engines").mkdir() + self.pin = {"repository":"masarray/ARIEC61850","ref":"main","commit":"a"*40} + self.legacy = {"commit":"b"*40,"runMode":"fast-dev-nonpromotion","scope":"command-read"} + self.report = {"schemaVersion":1,"repository":"masarray/ARIEC61850", + "commit":"c"*40,"approvedBaselineCommit":"a"*40, + "runMode":"fast-dev-nonpromotion","scope":"reporting", + "mainlinePromotionForbidden":True,"physicalQualificationRequired":True, + "cyclicMmsProcessPollingForbidden":True,"automaticRcbMutationForbidden":True} + self.write() + + def write(self): + for name, data in (("ARIEC61850.lock.json", self.pin), + ("ARIEC61850.trial.json", self.legacy), + ("ARIEC61850.reporting-trial.json", self.report)): + (self.root / "engines" / name).write_text(json.dumps(data), encoding="utf-8") + + def test_selected_reporting_and_existing_command(self): + self.assertEqual(module.resolve(self.root, {module.REPORT}), ("c"*40,"reporting")) + self.assertEqual(module.resolve(self.root, set()), ("b"*40,"command-read")) + + def test_competing_manifests_rejected(self): + with self.assertRaises(ValueError): + module.resolve(self.root, {module.CMD,module.REPORT}) + + def test_wrong_baseline_or_unsafe_policy_rejected(self): + for key,value in (("approvedBaselineCommit","d"*40),("commit","a"*40), + ("mainlinePromotionForbidden",False), + ("physicalQualificationRequired",False), + ("cyclicMmsProcessPollingForbidden",False), + ("automaticRcbMutationForbidden",False), + ("scope","command-read"),("commit","staging")): + original = self.report[key] + self.report[key] = value + self.write() + with self.assertRaises(ValueError): + module.resolve(self.root, {module.REPORT}) + self.report[key] = original + self.write() + + +if __name__ == "__main__": + unittest.main()