diff --git a/README.md b/README.md index 9771cc5..e0195f1 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,9 @@ In this revision: compare runtime logic into the template - the checked-in governance proof contract for those surfaces lives in `docs/policy/docker-execution-profile-governance-surface.json` +- the checked-in capability-manifest schema for descendant-facing CompareVI + contract proof lives in + `docs/schemas/labview-template-comparevi-capabilities-v1.schema.json` - the checked-in Docker lane-policy schema for descendant-facing policy proof lives in `docs/schemas/labview-template-docker-lane-policy-v1.schema.json` - the checked-in Docker receipt schema for descendant-facing proof lives in @@ -129,6 +132,9 @@ Generated repositories now include: - `.github/workflows/vi-history.yml` - `docs/VI_HISTORY_CAPABILITY.md` +The checked-in schema contract for `.github/comparevi/capabilities.json` lives +at `docs/schemas/labview-template-comparevi-capabilities-v1.schema.json`. + Generated `validate.yml` now fails closed unless the pinned CompareVI.Tools bundle exposes `consumerContract.capabilities.viHistory`, then runs the hosted consumer smoke against the same released producer-native pin. diff --git a/docs/COMPAREVI_PLATFORM_INTEGRATION.md b/docs/COMPAREVI_PLATFORM_INTEGRATION.md index 2032d0b..394e0a6 100644 --- a/docs/COMPAREVI_PLATFORM_INTEGRATION.md +++ b/docs/COMPAREVI_PLATFORM_INTEGRATION.md @@ -43,6 +43,8 @@ Generated `validate.yml` should stay lightweight while still consuming the released CompareVI.Tools bundle through the distributed capability manifest: 1. read `.github/comparevi/capabilities.json` + - canonical capability-manifest schema source: + `LabVIEW-Community-CI-CD/LabviewGitHubCiTemplate/docs/schemas/labview-template-comparevi-capabilities-v1.schema.json` 2. resolve `authoritativeConsumerPin` 3. download `CompareVI.Tools-$pin.zip` from the released compare bundle 4. fail closed unless the manifest exposes `consumerContract.capabilities.viHistory` diff --git a/docs/CONSUMER_PROVING_RAIL.md b/docs/CONSUMER_PROVING_RAIL.md index 96791b5..1cafba9 100644 --- a/docs/CONSUMER_PROVING_RAIL.md +++ b/docs/CONSUMER_PROVING_RAIL.md @@ -45,6 +45,8 @@ standing-priority work. adoption - the checked-in execution-profile governance proof contract lives in `docs/policy/docker-execution-profile-governance-surface.json` + - the checked-in CompareVI capability-manifest schema lives in + `docs/schemas/labview-template-comparevi-capabilities-v1.schema.json` - the checked-in Docker lane-policy schema for that scaffold lives in `docs/schemas/labview-template-docker-lane-policy-v1.schema.json` - the checked-in Docker receipt schema for that scaffold lives in diff --git a/docs/VI_HISTORY_CAPABILITY_DISTRIBUTION.md b/docs/VI_HISTORY_CAPABILITY_DISTRIBUTION.md index 9aee537..ea0bac6 100644 --- a/docs/VI_HISTORY_CAPABILITY_DISTRIBUTION.md +++ b/docs/VI_HISTORY_CAPABILITY_DISTRIBUTION.md @@ -24,6 +24,8 @@ Generated repositories receive: - `.github/comparevi/lineage.json` - `.github/workflows/vi-history.yml` - `docs/VI_HISTORY_CAPABILITY.md` +- canonical capability-manifest schema source: + `LabVIEW-Community-CI-CD/LabviewGitHubCiTemplate/docs/schemas/labview-template-comparevi-capabilities-v1.schema.json` For `docker` and `mixed` renders, `.github/comparevi/capabilities.json` also records the Producer-published Docker capability contract pointer: diff --git a/docs/schemas/labview-template-comparevi-capabilities-v1.schema.json b/docs/schemas/labview-template-comparevi-capabilities-v1.schema.json new file mode 100644 index 0000000..26f21dc --- /dev/null +++ b/docs/schemas/labview-template-comparevi-capabilities-v1.schema.json @@ -0,0 +1,220 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/LabVIEW-Community-CI-CD/LabviewGitHubCiTemplate/blob/develop/docs/schemas/labview-template-comparevi-capabilities-v1.schema.json", + "title": "LabVIEW Template CompareVI Capabilities Manifest", + "type": "object", + "additionalProperties": false, + "required": [ + "schema", + "templateDistributorRepository", + "templateDistributorBranch", + "capabilities" + ], + "properties": { + "schema": { + "const": "labview-template/comparevi-capabilities@v1" + }, + "templateDistributorRepository": { + "const": "LabVIEW-Community-CI-CD/LabviewGitHubCiTemplate" + }, + "templateDistributorBranch": { + "const": "develop" + }, + "capabilities": { + "type": "object", + "additionalProperties": false, + "required": [ + "viHistory" + ], + "properties": { + "viHistory": { + "$ref": "#/$defs/viHistoryCapability" + }, + "dockerProfile": { + "$ref": "#/$defs/dockerProfileCapability" + } + } + } + }, + "$defs": { + "releaseAssetName": { + "type": "string", + "pattern": "^CompareVI\\.Tools-.+\\.zip$" + }, + "viHistoryContractPaths": { + "type": "object", + "additionalProperties": false, + "required": [ + "historyFacade", + "localRuntimeProfiles", + "localOperatorSession", + "diagnosticsCommentRenderer", + "hostedNiLinuxRunner" + ], + "properties": { + "historyFacade": { + "const": "consumerContract.historyFacade" + }, + "localRuntimeProfiles": { + "const": "consumerContract.localRuntimeProfiles" + }, + "localOperatorSession": { + "const": "consumerContract.localOperatorSession" + }, + "diagnosticsCommentRenderer": { + "const": "consumerContract.diagnosticsCommentRenderer" + }, + "hostedNiLinuxRunner": { + "const": "consumerContract.hostedNiLinuxRunner" + } + } + }, + "viHistoryCapability": { + "type": "object", + "additionalProperties": false, + "required": [ + "enabled", + "distributionRole", + "upstreamProducerRepository", + "upstreamCapabilitySchema", + "distributionModel", + "authoritativeConsumerPin", + "releaseAssetName", + "releaseMetadataPath", + "generatedWorkflowPath", + "integrationDocPath", + "contractPaths" + ], + "properties": { + "enabled": { + "type": "boolean" + }, + "distributionRole": { + "const": "template-distributor" + }, + "upstreamProducerRepository": { + "const": "LabVIEW-Community-CI-CD/compare-vi-cli-action" + }, + "upstreamCapabilitySchema": { + "const": "comparevi-tools/vi-history-capability@v1" + }, + "distributionModel": { + "const": "release-bundle" + }, + "authoritativeConsumerPin": { + "type": "string", + "minLength": 1 + }, + "releaseAssetName": { + "$ref": "#/$defs/releaseAssetName" + }, + "releaseMetadataPath": { + "const": "comparevi-tools-release.json" + }, + "generatedWorkflowPath": { + "const": ".github/workflows/vi-history.yml" + }, + "integrationDocPath": { + "const": "docs/VI_HISTORY_CAPABILITY.md" + }, + "contractPaths": { + "$ref": "#/$defs/viHistoryContractPaths" + } + } + }, + "dockerProfileCapability": { + "type": "object", + "additionalProperties": false, + "required": [ + "enabled", + "distributionRole", + "upstreamProducerRepository", + "upstreamCapabilitySchema", + "distributionModel", + "authoritativeConsumerPin", + "releaseAssetName", + "releaseMetadataPath", + "bundleImportPath", + "authoritativeImageContractSource", + "requestedExecutionProfile", + "hostedSurfaceRetained" + ], + "properties": { + "enabled": { + "const": true + }, + "distributionRole": { + "const": "template-distributor" + }, + "upstreamProducerRepository": { + "const": "LabVIEW-Community-CI-CD/compare-vi-cli-action" + }, + "upstreamCapabilitySchema": { + "const": "comparevi-tools/docker-profile-capability@v1" + }, + "distributionModel": { + "const": "release-bundle" + }, + "authoritativeConsumerPin": { + "type": "string", + "minLength": 1 + }, + "releaseAssetName": { + "$ref": "#/$defs/releaseAssetName" + }, + "releaseMetadataPath": { + "const": "comparevi-tools-release.json" + }, + "bundleImportPath": { + "const": "tools/CompareVI.Tools/CompareVI.Tools.psd1" + }, + "authoritativeImageContractSource": { + "const": "consumerContract.dockerImageContract" + }, + "requestedExecutionProfile": { + "enum": [ + "docker", + "mixed" + ] + }, + "hostedSurfaceRetained": { + "type": "boolean" + } + }, + "allOf": [ + { + "if": { + "properties": { + "requestedExecutionProfile": { + "const": "docker" + } + } + }, + "then": { + "properties": { + "hostedSurfaceRetained": { + "const": false + } + } + } + }, + { + "if": { + "properties": { + "requestedExecutionProfile": { + "const": "mixed" + } + } + }, + "then": { + "properties": { + "hostedSurfaceRetained": { + "const": true + } + } + } + } + ] + } + } +} diff --git a/tests/Test-TemplateSmokeRender.ps1 b/tests/Test-TemplateSmokeRender.ps1 index 254ad47..a38dc27 100644 --- a/tests/Test-TemplateSmokeRender.ps1 +++ b/tests/Test-TemplateSmokeRender.ps1 @@ -12,6 +12,7 @@ Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' $repoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path +$capabilitiesSchemaPath = Join-Path $repoRoot 'docs/schemas/labview-template-comparevi-capabilities-v1.schema.json' $dockerLanePolicySchemaPath = Join-Path $repoRoot 'docs/schemas/labview-template-docker-lane-policy-v1.schema.json' $dockerReceiptSchemaPath = Join-Path $repoRoot 'docs/schemas/labview-template-docker-profile-plan-v1.schema.json' $runnerTemp = if ([string]::IsNullOrWhiteSpace($env:RUNNER_TEMP)) { [System.IO.Path]::GetTempPath() } else { $env:RUNNER_TEMP } @@ -79,13 +80,26 @@ try { $generatedPlatformDoc = Get-Content -LiteralPath $generatedPlatformDocPath -Raw $generatedProvingDocPath = Join-Path $generatedRoot 'docs/CONSUMER_PROVING_RAIL.md' $generatedProvingDoc = Get-Content -LiteralPath $generatedProvingDocPath -Raw + $generatedViHistoryDocPath = Join-Path $generatedRoot 'docs/VI_HISTORY_CAPABILITY.md' + $generatedViHistoryDoc = Get-Content -LiteralPath $generatedViHistoryDocPath -Raw $imageContractSnippet = 'authoritative image-contract source: `consumerContract.dockerImageContract`' + $capabilitySchemaSourcePath = 'LabVIEW-Community-CI-CD/LabviewGitHubCiTemplate/docs/schemas/labview-template-comparevi-capabilities-v1.schema.json' $dockerDocPath = Join-Path $generatedRoot 'docs/DOCKER_PROFILE.md' $dockerPolicyPath = Join-Path $generatedRoot '.github/comparevi/docker-lane-policy.json' $dockerReceiptScriptPath = Join-Path $generatedRoot '.github/comparevi/Emit-DockerProfileReceipt.ps1' $dockerReceiptPath = Join-Path $generatedRoot 'tests/results/docker-profile/docker-profile-plan.json' $dockerWorkflowPath = Join-Path $generatedRoot '.github/workflows/docker-profile.yml' + if (-not $generatedReadme.Contains($capabilitySchemaSourcePath)) { + throw 'Generated README is missing the canonical capability-manifest schema source.' + } + if (-not $generatedPlatformDoc.Contains($capabilitySchemaSourcePath)) { + throw 'Generated platform integration doc is missing the canonical capability-manifest schema source.' + } + if (-not $generatedViHistoryDoc.Contains($capabilitySchemaSourcePath)) { + throw 'Generated vi-history capability doc is missing the canonical capability-manifest schema source.' + } + switch ($ExecutionProfile) { 'hosted' { foreach ($relativePath in @($profileContract.forbiddenOutputs)) { @@ -179,23 +193,30 @@ try { } $capabilityPath = Join-Path $generatedRoot '.github/comparevi/capabilities.json' - $capability = Get-Content -LiteralPath $capabilityPath -Raw | ConvertFrom-Json -AsHashtable + $capabilityJson = Get-Content -LiteralPath $capabilityPath -Raw + if (-not (Test-Json -Json $capabilityJson -SchemaFile $capabilitiesSchemaPath)) { + throw 'Rendered capability manifest should validate against the checked-in capability-manifest schema.' + } + $capability = $capabilityJson | ConvertFrom-Json -AsHashtable if (-not $capability.capabilities.viHistory.enabled) { throw 'Rendered capability manifest should enable vi-history for template smoke.' } if ($capability.capabilities.viHistory.authoritativeConsumerPin -ne $CompareViPin) { throw 'Rendered capability manifest did not preserve the CompareVI.Tools consumer pin.' } - if ($capability.capabilities.viHistory.contractPaths.historyFacade -ne 'consumerContract.historyFacade') { - throw 'Rendered capability manifest is missing the distributed historyFacade contract path.' + if ($capability.capabilities.viHistory.releaseAssetName -ne "CompareVI.Tools-$CompareViPin.zip") { + throw 'Rendered capability manifest did not preserve the CompareVI.Tools release asset name.' + } + if ($capability.capabilities.viHistory.releaseMetadataPath -ne 'comparevi-tools-release.json') { + throw 'Rendered capability manifest did not preserve the CompareVI.Tools release metadata path.' } -$dockerCapability = if ($capability.capabilities.ContainsKey('dockerProfile')) { - $capability.capabilities.dockerProfile -} -else { - $null -} + $dockerCapability = if ($capability.capabilities.ContainsKey('dockerProfile')) { + $capability.capabilities.dockerProfile + } + else { + $null + } switch ($ExecutionProfile) { 'hosted' { if ($null -ne $dockerCapability) { @@ -206,15 +227,6 @@ else { if ($null -eq $dockerCapability) { throw 'Docker render should emit a dockerProfile capability entry.' } - if (-not $dockerCapability.enabled) { - throw 'Docker render should enable the dockerProfile capability entry.' - } - if ($dockerCapability.upstreamCapabilitySchema -ne 'comparevi-tools/docker-profile-capability@v1') { - throw 'Docker render should record the published docker-profile capability schema.' - } - if ($dockerCapability.authoritativeImageContractSource -ne 'consumerContract.dockerImageContract') { - throw 'Docker render should record the authoritative image contract source.' - } if ($dockerCapability.authoritativeConsumerPin -ne $CompareViPin) { throw 'Docker render should keep the CompareVI.Tools pin on the dockerProfile capability.' } @@ -238,6 +250,9 @@ else { } $dockerDoc = Get-Content -LiteralPath $dockerDocPath -Raw + if (-not $dockerDoc.Contains($capabilitySchemaSourcePath)) { + throw 'Docker render should document the canonical capability-manifest schema source.' + } foreach ($snippet in @( 'workflow scaffold: `.github/workflows/docker-profile.yml`', 'receipt helper: `.github/comparevi/Emit-DockerProfileReceipt.ps1`', @@ -338,11 +353,8 @@ else { if ($null -eq $dockerCapability) { throw 'Mixed render should emit a dockerProfile capability entry.' } - if (-not $dockerCapability.enabled) { - throw 'Mixed render should enable the dockerProfile capability entry.' - } - if ($dockerCapability.authoritativeImageContractSource -ne 'consumerContract.dockerImageContract') { - throw 'Mixed render should record the authoritative image contract source.' + if ($dockerCapability.authoritativeConsumerPin -ne $CompareViPin) { + throw 'Mixed render should keep the CompareVI.Tools pin on the dockerProfile capability.' } if ($dockerCapability.releaseAssetName -ne "CompareVI.Tools-$CompareViPin.zip") { throw 'Mixed render should record the released CompareVI.Tools asset name on the dockerProfile capability.' @@ -364,6 +376,9 @@ else { } $dockerDoc = Get-Content -LiteralPath $dockerDocPath -Raw + if (-not $dockerDoc.Contains($capabilitySchemaSourcePath)) { + throw 'Mixed render should document the canonical capability-manifest schema source.' + } foreach ($snippet in @( 'hosted surface retained: `true`', 'receipt helper: `.github/comparevi/Emit-DockerProfileReceipt.ps1`', diff --git a/{{ cookiecutter.repo_slug }}/README.md b/{{ cookiecutter.repo_slug }}/README.md index d7d0207..6f5717f 100644 --- a/{{ cookiecutter.repo_slug }}/README.md +++ b/{{ cookiecutter.repo_slug }}/README.md @@ -58,6 +58,8 @@ distributed by `LabviewGitHubCiTemplate`. - enabled: `{{ cookiecutter.enable_vi_history_capability }}` - CompareVI.Tools pin: `{{ cookiecutter.comparevi_tools_consumer_pin }}` - capability manifest: `.github/comparevi/capabilities.json` +- capability schema: `labview-template/comparevi-capabilities@v1` +- canonical capability-manifest schema source: `LabVIEW-Community-CI-CD/LabviewGitHubCiTemplate/docs/schemas/labview-template-comparevi-capabilities-v1.schema.json` - lineage manifest: `.github/comparevi/lineage.json` - manual workflow scaffold: `.github/workflows/vi-history.yml` {% if cookiecutter.execution_profile != "hosted" %} @@ -87,6 +89,7 @@ capability contract in `.github/comparevi/capabilities.json`. - requested execution profile: `{{ cookiecutter.execution_profile }}` - hosted surface retained: `{{ "true" if cookiecutter.execution_profile == "mixed" else "false" }}` - Producer contract owner: `LabVIEW-Community-CI-CD/compare-vi-cli-action` +- canonical capability-manifest schema source: `LabVIEW-Community-CI-CD/LabviewGitHubCiTemplate/docs/schemas/labview-template-comparevi-capabilities-v1.schema.json` - workflow scaffold: `.github/workflows/docker-profile.yml` - lane policy: `.github/comparevi/docker-lane-policy.json` - lane-policy schema: `labview-template/docker-lane-policy@v1` diff --git a/{{ cookiecutter.repo_slug }}/docs/COMPAREVI_PLATFORM_INTEGRATION.md b/{{ cookiecutter.repo_slug }}/docs/COMPAREVI_PLATFORM_INTEGRATION.md index d076e16..f84e7b3 100644 --- a/{{ cookiecutter.repo_slug }}/docs/COMPAREVI_PLATFORM_INTEGRATION.md +++ b/{{ cookiecutter.repo_slug }}/docs/COMPAREVI_PLATFORM_INTEGRATION.md @@ -23,6 +23,8 @@ This repository inherits a distributed `vi-history` pack from the template. Use these local surfaces as the consumer source of truth: - `.github/comparevi/capabilities.json` +- canonical capability-manifest schema source: + `LabVIEW-Community-CI-CD/LabviewGitHubCiTemplate/docs/schemas/labview-template-comparevi-capabilities-v1.schema.json` - `.github/comparevi/lineage.json` - `.github/workflows/vi-history.yml` - `docs/VI_HISTORY_CAPABILITY.md` @@ -34,6 +36,8 @@ Generated `validate.yml` should stay lightweight while still consuming the released CompareVI.Tools bundle through the distributed capability manifest: 1. read `.github/comparevi/capabilities.json` + - canonical capability-manifest schema source: + `LabVIEW-Community-CI-CD/LabviewGitHubCiTemplate/docs/schemas/labview-template-comparevi-capabilities-v1.schema.json` 2. resolve `authoritativeConsumerPin` 3. download `CompareVI.Tools-$pin.zip` from the released compare bundle 4. fail closed unless the manifest exposes `consumerContract.capabilities.viHistory` diff --git a/{{ cookiecutter.repo_slug }}/docs/DOCKER_PROFILE.md b/{{ cookiecutter.repo_slug }}/docs/DOCKER_PROFILE.md index 4d1811f..27d896b 100644 --- a/{{ cookiecutter.repo_slug }}/docs/DOCKER_PROFILE.md +++ b/{{ cookiecutter.repo_slug }}/docs/DOCKER_PROFILE.md @@ -19,6 +19,9 @@ contract published by `compare-vi-cli-action`. Use `.github/comparevi/capabilities.json -> capabilities.dockerProfile` as the machine-readable Docker manifest contract. +The canonical capability-manifest schema source is +`LabVIEW-Community-CI-CD/LabviewGitHubCiTemplate/docs/schemas/labview-template-comparevi-capabilities-v1.schema.json`. + That entry records: - `authoritativeConsumerPin` @@ -67,6 +70,8 @@ Use `.github/comparevi/capabilities.json` and - pinned CompareVI.Tools release - authoritative image-contract source - whether the hosted surface remains retained +- capability schema: `labview-template/comparevi-capabilities@v1` +- canonical capability-manifest schema source: `LabVIEW-Community-CI-CD/LabviewGitHubCiTemplate/docs/schemas/labview-template-comparevi-capabilities-v1.schema.json` - lane-policy schema: `labview-template/docker-lane-policy@v1` - canonical lane-policy schema source: `LabVIEW-Community-CI-CD/LabviewGitHubCiTemplate/docs/schemas/labview-template-docker-lane-policy-v1.schema.json` diff --git a/{{ cookiecutter.repo_slug }}/docs/VI_HISTORY_CAPABILITY.md b/{{ cookiecutter.repo_slug }}/docs/VI_HISTORY_CAPABILITY.md index 0fcd191..0ec5a5c 100644 --- a/{{ cookiecutter.repo_slug }}/docs/VI_HISTORY_CAPABILITY.md +++ b/{{ cookiecutter.repo_slug }}/docs/VI_HISTORY_CAPABILITY.md @@ -13,6 +13,8 @@ capability pack. ## Local Surfaces - capability manifest: `.github/comparevi/capabilities.json` +- capability schema: `labview-template/comparevi-capabilities@v1` +- canonical capability-manifest schema source: `LabVIEW-Community-CI-CD/LabviewGitHubCiTemplate/docs/schemas/labview-template-comparevi-capabilities-v1.schema.json` - lineage manifest: `.github/comparevi/lineage.json` - manual workflow scaffold: `.github/workflows/vi-history.yml`