@msn-control/liftoff 0.11.3 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DEVELOPER.md +19 -18
- package/README.md +18 -18
- package/assets/governance/single-maintainer-gitflow/activation-v2-graph.json +1583 -0
- package/dist/adapters/credentials/github-enrollment.d.ts +51 -0
- package/dist/adapters/credentials/github-enrollment.js +226 -0
- package/dist/adapters/credentials/github-enrollment.js.map +1 -0
- package/dist/adapters/credentials/protected-input.d.ts +7 -0
- package/dist/adapters/credentials/protected-input.js +68 -0
- package/dist/adapters/credentials/protected-input.js.map +1 -0
- package/dist/adapters/filesystem/executables.d.ts +2 -0
- package/dist/adapters/filesystem/executables.js +70 -0
- package/dist/adapters/filesystem/executables.js.map +1 -0
- package/dist/adapters/filesystem/update-previews.d.ts +9 -0
- package/dist/adapters/filesystem/update-previews.js +44 -0
- package/dist/adapters/filesystem/update-previews.js.map +1 -1
- package/dist/adapters/filesystem/workstation-attempts.d.ts +13 -0
- package/dist/adapters/filesystem/workstation-attempts.js +78 -0
- package/dist/adapters/filesystem/workstation-attempts.js.map +1 -0
- package/dist/adapters/github/activation-rest.d.ts +40 -0
- package/dist/adapters/github/activation-rest.js +272 -0
- package/dist/adapters/github/activation-rest.js.map +1 -0
- package/dist/adapters/state/azure-blob.d.ts +43 -0
- package/dist/adapters/state/azure-blob.js +335 -0
- package/dist/adapters/state/azure-blob.js.map +1 -0
- package/dist/adapters/state/darwin-capabilities.d.ts +33 -0
- package/dist/adapters/state/darwin-capabilities.js +146 -0
- package/dist/adapters/state/darwin-capabilities.js.map +1 -0
- package/dist/adapters/state/darwin-native-host.d.ts +32 -0
- package/dist/adapters/state/darwin-native-host.js +294 -0
- package/dist/adapters/state/darwin-native-host.js.map +1 -0
- package/dist/adapters/state/darwin-profile.d.ts +6 -0
- package/dist/adapters/state/darwin-profile.js +56 -0
- package/dist/adapters/state/darwin-profile.js.map +1 -0
- package/dist/adapters/state/darwin-system-program.d.ts +1 -0
- package/dist/adapters/state/darwin-system-program.js +94 -0
- package/dist/adapters/state/darwin-system-program.js.map +1 -0
- package/dist/adapters/state/local-qualification.d.ts +16 -0
- package/dist/adapters/state/local-qualification.js +299 -0
- package/dist/adapters/state/local-qualification.js.map +1 -0
- package/dist/adapters/state/local.d.ts +25 -0
- package/dist/adapters/state/local.js +145 -0
- package/dist/adapters/state/local.js.map +1 -0
- package/dist/adapters/state/native-command.d.ts +21 -0
- package/dist/adapters/state/native-command.js +82 -0
- package/dist/adapters/state/native-command.js.map +1 -0
- package/dist/adapters/state/native-files.d.ts +2 -0
- package/dist/adapters/state/native-files.js +47 -0
- package/dist/adapters/state/native-files.js.map +1 -0
- package/dist/adapters/state/native-system.d.ts +38 -0
- package/dist/adapters/state/native-system.js +86 -0
- package/dist/adapters/state/native-system.js.map +1 -0
- package/dist/adapters/state/opentofu.d.ts +35 -0
- package/dist/adapters/state/opentofu.js +350 -0
- package/dist/adapters/state/opentofu.js.map +1 -0
- package/dist/adapters/state/owned-process.d.ts +43 -0
- package/dist/adapters/state/owned-process.js +252 -0
- package/dist/adapters/state/owned-process.js.map +1 -0
- package/dist/adapters/state/posix-lock-program.d.ts +1 -0
- package/dist/adapters/state/posix-lock-program.js +185 -0
- package/dist/adapters/state/posix-lock-program.js.map +1 -0
- package/dist/adapters/state/posix-native-lock.d.ts +15 -0
- package/dist/adapters/state/posix-native-lock.js +145 -0
- package/dist/adapters/state/posix-native-lock.js.map +1 -0
- package/dist/adapters/state/protected-workspace.d.ts +57 -0
- package/dist/adapters/state/protected-workspace.js +412 -0
- package/dist/adapters/state/protected-workspace.js.map +1 -0
- package/dist/application/context.d.ts +3 -0
- package/dist/application/diagnose/doctor.d.ts +5 -0
- package/dist/application/diagnose/doctor.js +21 -5
- package/dist/application/diagnose/doctor.js.map +1 -1
- package/dist/application/diagnose/generated-project.js +2 -7
- package/dist/application/diagnose/generated-project.js.map +1 -1
- package/dist/application/initialize/fixture.js +8 -1
- package/dist/application/initialize/fixture.js.map +1 -1
- package/dist/application/initialize/use-case.js +70 -36
- package/dist/application/initialize/use-case.js.map +1 -1
- package/dist/application/project/manifest.js +5 -4
- package/dist/application/project/manifest.js.map +1 -1
- package/dist/application/state-migration/execution.d.ts +32 -0
- package/dist/application/state-migration/execution.js +440 -0
- package/dist/application/state-migration/execution.js.map +1 -0
- package/dist/application/state-migration/index.d.ts +22 -0
- package/dist/application/state-migration/index.js +17 -0
- package/dist/application/state-migration/index.js.map +1 -0
- package/dist/application/state-migration/lifecycle.d.ts +3 -0
- package/dist/application/state-migration/lifecycle.js +149 -0
- package/dist/application/state-migration/lifecycle.js.map +1 -0
- package/dist/application/state-migration/planning.d.ts +7 -0
- package/dist/application/state-migration/planning.js +150 -0
- package/dist/application/state-migration/planning.js.map +1 -0
- package/dist/application/state-migration/recovery.d.ts +5 -0
- package/dist/application/state-migration/recovery.js +145 -0
- package/dist/application/state-migration/recovery.js.map +1 -0
- package/dist/application/state-migration/runtime.d.ts +32 -0
- package/dist/application/state-migration/runtime.js +188 -0
- package/dist/application/state-migration/runtime.js.map +1 -0
- package/dist/application/state-migration/service.d.ts +2 -0
- package/dist/application/state-migration/service.js +20 -0
- package/dist/application/state-migration/service.js.map +1 -0
- package/dist/application/update/inspection.d.ts +16 -2
- package/dist/application/update/inspection.js +76 -23
- package/dist/application/update/inspection.js.map +1 -1
- package/dist/application/update/migration-runtime.js +24 -11
- package/dist/application/update/migration-runtime.js.map +1 -1
- package/dist/application/update/output.d.ts +2 -0
- package/dist/application/update/output.js +11 -1
- package/dist/application/update/output.js.map +1 -1
- package/dist/application/update/revalidation-plan.d.ts +1 -0
- package/dist/application/update/revalidation-plan.js +25 -12
- package/dist/application/update/revalidation-plan.js.map +1 -1
- package/dist/application/update/revalidation.d.ts +4 -1
- package/dist/application/update/revalidation.js +62 -21
- package/dist/application/update/revalidation.js.map +1 -1
- package/dist/application/update/use-case.js +25 -17
- package/dist/application/update/use-case.js.map +1 -1
- package/dist/cli/args/definitions.js +11 -3
- package/dist/cli/args/definitions.js.map +1 -1
- package/dist/cli/args/parser.js +29 -3
- package/dist/cli/args/parser.js.map +1 -1
- package/dist/domain/governance/activation/approvals.d.ts +264 -7
- package/dist/domain/governance/activation/approvals.js +166 -23
- package/dist/domain/governance/activation/approvals.js.map +1 -1
- package/dist/domain/governance/activation/capabilities.js +5 -2
- package/dist/domain/governance/activation/capabilities.js.map +1 -1
- package/dist/domain/governance/activation/evidence.d.ts +2 -1
- package/dist/domain/governance/activation/evidence.js +64 -11
- package/dist/domain/governance/activation/evidence.js.map +1 -1
- package/dist/domain/governance/activation/graph.d.ts +11 -384
- package/dist/domain/governance/activation/graph.js +424 -379
- package/dist/domain/governance/activation/graph.js.map +1 -1
- package/dist/domain/governance/activation/inputs.d.ts +3 -1
- package/dist/domain/governance/activation/inputs.js +47 -6
- package/dist/domain/governance/activation/inputs.js.map +1 -1
- package/dist/domain/governance/activation/operations.d.ts +4 -1
- package/dist/domain/governance/activation/operations.js +110 -22
- package/dist/domain/governance/activation/operations.js.map +1 -1
- package/dist/domain/governance/activation/readiness.d.ts +10 -0
- package/dist/domain/governance/activation/readiness.js +77 -38
- package/dist/domain/governance/activation/readiness.js.map +1 -1
- package/dist/domain/governance/activation/types.d.ts +151 -9
- package/dist/domain/governance/activation/types.js +23 -1
- package/dist/domain/governance/activation/types.js.map +1 -1
- package/dist/domain/governance/activation/validators.d.ts +4 -1
- package/dist/domain/governance/activation/validators.js +419 -26
- package/dist/domain/governance/activation/validators.js.map +1 -1
- package/dist/domain/governance/policy/identity.d.ts +55 -13
- package/dist/domain/governance/policy/identity.js +28 -8
- package/dist/domain/governance/policy/identity.js.map +1 -1
- package/dist/domain/project/artifact-lifecycle.d.ts +2 -1
- package/dist/domain/project/artifact-lifecycle.js +7 -1
- package/dist/domain/project/artifact-lifecycle.js.map +1 -1
- package/dist/domain/project/catalog.d.ts +50 -1
- package/dist/domain/project/catalog.js +104 -4
- package/dist/domain/project/catalog.js.map +1 -1
- package/dist/domain/project/contracts.d.ts +9 -1
- package/dist/domain/project/infrastructure-layout.d.ts +1 -1
- package/dist/domain/project/infrastructure-layout.js +2 -1
- package/dist/domain/project/infrastructure-layout.js.map +1 -1
- package/dist/domain/project/manifest/governance.d.ts +1 -1
- package/dist/domain/project/manifest/governance.js +9 -10
- package/dist/domain/project/manifest/governance.js.map +1 -1
- package/dist/domain/project/planning.js +4 -5
- package/dist/domain/project/planning.js.map +1 -1
- package/dist/domain/repair/stateful-bounded.d.ts +1 -0
- package/dist/domain/repair/stateful-bounded.js +37 -0
- package/dist/domain/repair/stateful-bounded.js.map +1 -0
- package/dist/domain/repair/stateful-invariants.d.ts +28 -0
- package/dist/domain/repair/stateful-invariants.js +278 -0
- package/dist/domain/repair/stateful-invariants.js.map +1 -0
- package/dist/domain/repair/stateful.d.ts +690 -0
- package/dist/domain/repair/stateful.js +26 -0
- package/dist/domain/repair/stateful.js.map +1 -0
- package/dist/domain/workstation/constraints.d.ts +1 -0
- package/dist/domain/workstation/constraints.js.map +1 -1
- package/dist/domain/workstation/contracts.d.ts +42 -0
- package/dist/domain/workstation/contracts.js +2 -0
- package/dist/domain/workstation/contracts.js.map +1 -0
- package/dist/domain/workstation/executables.d.ts +19 -0
- package/dist/domain/workstation/executables.js +68 -0
- package/dist/domain/workstation/executables.js.map +1 -0
- package/dist/domain/workstation/versions.d.ts +8 -0
- package/dist/domain/workstation/versions.js +145 -0
- package/dist/domain/workstation/versions.js.map +1 -0
- package/dist/framework-adapters.d.ts +2 -0
- package/dist/framework-adapters.js +148 -22
- package/dist/framework-adapters.js.map +1 -1
- package/dist/framework-validation.d.ts +6 -0
- package/dist/framework-validation.js +89 -24
- package/dist/framework-validation.js.map +1 -1
- package/dist/generators/common/base.js +57 -17
- package/dist/generators/common/base.js.map +1 -1
- package/dist/governance-activation/activation-state.js +39 -7
- package/dist/governance-activation/activation-state.js.map +1 -1
- package/dist/governance-activation/authority-records.d.ts +4 -0
- package/dist/governance-activation/authority-records.js +24 -0
- package/dist/governance-activation/authority-records.js.map +1 -0
- package/dist/governance-activation/commands.d.ts +9 -1
- package/dist/governance-activation/commands.js +392 -97
- package/dist/governance-activation/commands.js.map +1 -1
- package/dist/governance-activation/compatibility.d.ts +11 -8
- package/dist/governance-activation/compatibility.js +37 -23
- package/dist/governance-activation/compatibility.js.map +1 -1
- package/dist/governance-activation/doctor.js +40 -16
- package/dist/governance-activation/doctor.js.map +1 -1
- package/dist/governance-activation/github-config.d.ts +17 -0
- package/dist/governance-activation/github-config.js +88 -0
- package/dist/governance-activation/github-config.js.map +1 -0
- package/dist/governance-activation/github-discovery.d.ts +18 -0
- package/dist/governance-activation/github-discovery.js +229 -0
- package/dist/governance-activation/github-discovery.js.map +1 -0
- package/dist/governance-activation/github-ports.d.ts +12 -0
- package/dist/governance-activation/github-ports.js +4 -0
- package/dist/governance-activation/github-ports.js.map +1 -0
- package/dist/governance-activation/github-publication.d.ts +3 -0
- package/dist/governance-activation/github-publication.js +190 -0
- package/dist/governance-activation/github-publication.js.map +1 -0
- package/dist/governance-activation/historical-common.d.ts +98 -0
- package/dist/governance-activation/historical-common.js +269 -0
- package/dist/governance-activation/historical-common.js.map +1 -0
- package/dist/governance-activation/historical-credential-policy.d.ts +3 -0
- package/dist/governance-activation/historical-credential-policy.js +91 -0
- package/dist/governance-activation/historical-credential-policy.js.map +1 -0
- package/dist/governance-activation/historical-inputs.d.ts +6 -0
- package/dist/governance-activation/historical-inputs.js +57 -0
- package/dist/governance-activation/historical-inputs.js.map +1 -0
- package/dist/governance-activation/historical-safety.d.ts +9 -0
- package/dist/governance-activation/historical-safety.js +102 -0
- package/dist/governance-activation/historical-safety.js.map +1 -0
- package/dist/governance-activation/historical-source-metadata.d.ts +31 -0
- package/dist/governance-activation/historical-source-metadata.js +55 -0
- package/dist/governance-activation/historical-source-metadata.js.map +1 -0
- package/dist/governance-activation/historical-state.d.ts +12 -50
- package/dist/governance-activation/historical-state.js +195 -308
- package/dist/governance-activation/historical-state.js.map +1 -1
- package/dist/governance-activation/historical-v1-auxiliary.d.ts +1 -0
- package/dist/governance-activation/historical-v1-auxiliary.js +43 -0
- package/dist/governance-activation/historical-v1-auxiliary.js.map +1 -0
- package/dist/governance-activation/historical-v1-phase-contracts.d.ts +31 -0
- package/dist/governance-activation/historical-v1-phase-contracts.js +46 -0
- package/dist/governance-activation/historical-v1-phase-contracts.js.map +1 -0
- package/dist/governance-activation/historical-v2.d.ts +53 -0
- package/dist/governance-activation/historical-v2.js +313 -0
- package/dist/governance-activation/historical-v2.js.map +1 -0
- package/dist/governance-activation/history-contracts.d.ts +21 -12
- package/dist/governance-activation/history-contracts.js +99 -27
- package/dist/governance-activation/history-contracts.js.map +1 -1
- package/dist/governance-activation/inputs.d.ts +8 -2
- package/dist/governance-activation/inputs.js +41 -6
- package/dist/governance-activation/inputs.js.map +1 -1
- package/dist/governance-activation/migration-history.d.ts +43 -5
- package/dist/governance-activation/migration-history.js +231 -50
- package/dist/governance-activation/migration-history.js.map +1 -1
- package/dist/governance-activation/migration.d.ts +7 -4
- package/dist/governance-activation/migration.js +53 -252
- package/dist/governance-activation/migration.js.map +1 -1
- package/dist/governance-activation/phase-azure.d.ts +3 -0
- package/dist/governance-activation/phase-azure.js +205 -0
- package/dist/governance-activation/phase-azure.js.map +1 -0
- package/dist/governance-activation/phase-bootstrap-state.js +3 -0
- package/dist/governance-activation/phase-bootstrap-state.js.map +1 -1
- package/dist/governance-activation/phase-github.d.ts +3 -0
- package/dist/governance-activation/phase-github.js +192 -0
- package/dist/governance-activation/phase-github.js.map +1 -0
- package/dist/governance-activation/phase-governance.js +8 -2
- package/dist/governance-activation/phase-governance.js.map +1 -1
- package/dist/governance-activation/public-plans.d.ts +36 -0
- package/dist/governance-activation/public-plans.js +100 -0
- package/dist/governance-activation/public-plans.js.map +1 -0
- package/dist/governance-activation/read-only.d.ts +3 -1
- package/dist/governance-activation/read-only.js +7 -3
- package/dist/governance-activation/read-only.js.map +1 -1
- package/dist/governance-activation/reconciliation.js +10 -0
- package/dist/governance-activation/reconciliation.js.map +1 -1
- package/dist/governance-activation/seed-lifecycle.js +14 -25
- package/dist/governance-activation/seed-lifecycle.js.map +1 -1
- package/dist/governance-activation/source-of-truth.d.ts +21 -5
- package/dist/governance-activation/source-of-truth.js +102 -13
- package/dist/governance-activation/source-of-truth.js.map +1 -1
- package/dist/governance-activation/task-projection.d.ts +4 -1
- package/dist/governance-activation/task-projection.js +32 -3
- package/dist/governance-activation/task-projection.js.map +1 -1
- package/dist/governance-activation/task-writes.d.ts +42 -0
- package/dist/governance-activation/task-writes.js +250 -0
- package/dist/governance-activation/task-writes.js.map +1 -0
- package/dist/governance-activation/transition-files.d.ts +12 -0
- package/dist/governance-activation/transition-files.js +109 -0
- package/dist/governance-activation/transition-files.js.map +1 -0
- package/dist/governance-activation/transition-planning.d.ts +3 -1
- package/dist/governance-activation/transition-planning.js +158 -20
- package/dist/governance-activation/transition-planning.js.map +1 -1
- package/dist/governance-activation/transition-ports.d.ts +31 -3
- package/dist/governance-activation/transition-records.d.ts +11 -1
- package/dist/governance-activation/transition-records.js +52 -6
- package/dist/governance-activation/transition-records.js.map +1 -1
- package/dist/governance-activation/transitions.d.ts +5 -0
- package/dist/governance-activation/transitions.js +271 -37
- package/dist/governance-activation/transitions.js.map +1 -1
- package/dist/governance-assessment/engine.js +22 -15
- package/dist/governance-assessment/engine.js.map +1 -1
- package/dist/governance-assessment/project.d.ts +2 -0
- package/dist/governance-assessment/project.js +2 -1
- package/dist/governance-assessment/project.js.map +1 -1
- package/dist/interactive.d.ts +0 -1
- package/dist/interactive.js +3 -22
- package/dist/interactive.js.map +1 -1
- package/dist/openspec-profile.d.ts +2 -1
- package/dist/openspec-profile.js +11 -41
- package/dist/openspec-profile.js.map +1 -1
- package/dist/repository-governance.d.ts +10 -5
- package/dist/repository-governance.js +178 -92
- package/dist/repository-governance.js.map +1 -1
- package/dist/telemetry/contract.d.ts +1 -1
- package/dist/telemetry/contract.js +3 -0
- package/dist/telemetry/contract.js.map +1 -1
- package/dist/workstation-catalog.d.ts +13 -1
- package/dist/workstation-catalog.js +122 -0
- package/dist/workstation-catalog.js.map +1 -1
- package/dist/workstation.d.ts +69 -14
- package/dist/workstation.js +600 -215
- package/dist/workstation.js.map +1 -1
- package/docs/cli-reference.md +59 -21
- package/docs/configuration-and-manifests.md +9 -8
- package/docs/prerequisites.md +93 -18
- package/docs/repository-governance.md +68 -40
- package/docs/spec-workflows-and-agents.md +132 -13
- package/package.json +1 -1
|
@@ -1,20 +1,26 @@
|
|
|
1
1
|
import { activationCompatibility, canonicalPhaseGraph, canonicalPhaseGraphHash, currentActivationIdentity } from './graph.js';
|
|
2
2
|
import { activationContractVersion, activationStateSchemaVersion, approvalEnvelopeSchemaVersion, credentialPolicySchemaVersion, evidenceHeaderSchemaVersion, governanceActivationPolicyVersion, liftoffActivationPackageVersion, liftoffManifestArtifactVersion, phaseGraphSchemaVersion, resolveActivationCompatibility, supersessionSchemaVersion, isHistoricalActivationIdentity } from '../policy/identity.js';
|
|
3
3
|
import { normalizeApprovalCostCeiling, normalizeApprovalDestinations, normalizeApprovalDestructiveScope, normalizeApprovalPermissions, normalizeApprovalPolicyExceptions, normalizeApprovalResources } from './approvals.js';
|
|
4
|
-
import {
|
|
4
|
+
import { sha256Hex } from './canonical-json.js';
|
|
5
|
+
import { validateArtifactPathParts } from '../../project/paths.js';
|
|
6
|
+
import { approvalGateKinds, governanceScopes, invalidationInputKinds, mutationClasses, phaseIds, phaseStates, phaseScope, rollbackKinds, runnerPreflightDisplayNameTemplate, runnerPreflightOrganizationPermissions, runnerPreflightPatLifetimeDays, runnerPreflightRepositoryPermissions, runnerPreflightRotationLeadDays, runnerPreflightSecretName } from './types.js';
|
|
5
7
|
const phaseIdSet = new Set(phaseIds);
|
|
6
8
|
const terminalPhaseStateSet = new Set(['approved', 'verified', 'failed', 'inapplicable', 'retained', 'disposed']);
|
|
7
9
|
const resultStateSet = new Set(['verified', 'failed', 'inapplicable', 'retained', 'disposed']);
|
|
8
10
|
const hex64Pattern = /^[a-f0-9]{64}$/;
|
|
9
11
|
const isoLikePattern = /^\d{4}-\d{2}-\d{2}T/;
|
|
10
12
|
const liveReadbackProviderSet = new Set(['github', 'azure']);
|
|
11
|
-
const githubRemoteWriteMutations = new Set([
|
|
13
|
+
const githubRemoteWriteMutations = new Set([
|
|
14
|
+
'git-push', 'github-write', 'github-repository-create', 'github-workflow-dispatch',
|
|
15
|
+
'github-secret-write', 'github-ruleset-write'
|
|
16
|
+
]);
|
|
12
17
|
const azureRemoteWriteMutations = new Set([
|
|
13
18
|
'azure-provider-register',
|
|
14
19
|
'azure-network-provision',
|
|
15
20
|
'azure-state-import',
|
|
16
21
|
'azure-resource-provision'
|
|
17
22
|
]);
|
|
23
|
+
const governanceScopeSet = new Set(governanceScopes);
|
|
18
24
|
const transitionAdapterIds = new Set([
|
|
19
25
|
'local-evidence',
|
|
20
26
|
'selected-spec-workflow',
|
|
@@ -105,7 +111,163 @@ function pathPartsArray(value, path) {
|
|
|
105
111
|
if (!Array.isArray(value)) {
|
|
106
112
|
throw new Error(`${path} must be an array.`);
|
|
107
113
|
}
|
|
108
|
-
return value.map((entry, index) =>
|
|
114
|
+
return value.map((entry, index) => safePathParts(entry, `${path}[${index}]`));
|
|
115
|
+
}
|
|
116
|
+
function safePathParts(value, path) {
|
|
117
|
+
return validateArtifactPathParts(value, path);
|
|
118
|
+
}
|
|
119
|
+
function publicJson(value, path, depth = 0) {
|
|
120
|
+
if (depth > 20)
|
|
121
|
+
throw new Error(`${path} exceeds the supported JSON nesting depth.`);
|
|
122
|
+
if (value === null || typeof value === 'boolean' || (typeof value === 'number' && Number.isFinite(value)))
|
|
123
|
+
return value;
|
|
124
|
+
if (typeof value === 'string') {
|
|
125
|
+
if (/https?:\/\/[^/\s]*@|[?&](?:token|key|secret|sig)=|(?:gh[pousr]_|github_pat_)[A-Za-z0-9_]+|-----BEGIN [A-Z ]*PRIVATE KEY-----/iu.test(value)) {
|
|
126
|
+
throw new Error(`${path} contains credential material; use protected credential enrollment instead.`);
|
|
127
|
+
}
|
|
128
|
+
return value;
|
|
129
|
+
}
|
|
130
|
+
if (Array.isArray(value))
|
|
131
|
+
return value.map((entry, index) => publicJson(entry, `${path}[${index}]`, depth + 1));
|
|
132
|
+
const object = record(value, path);
|
|
133
|
+
const result = {};
|
|
134
|
+
for (const [key, entry] of Object.entries(object)) {
|
|
135
|
+
const tokenMetadata = key === 'token' && typeof entry === 'object' && entry !== null && !Array.isArray(entry)
|
|
136
|
+
? record(entry, `${path}.${key}`) : null;
|
|
137
|
+
const publicAppTokenMetadata = tokenMetadata !== null &&
|
|
138
|
+
Object.keys(tokenMetadata).sort().join(',') === 'generatedBy,strategy,ttlSeconds' &&
|
|
139
|
+
tokenMetadata.generatedBy === 'github-app' && tokenMetadata.strategy === 'installation-token' &&
|
|
140
|
+
Number.isSafeInteger(tokenMetadata.ttlSeconds) && Number(tokenMetadata.ttlSeconds) > 0 && Number(tokenMetadata.ttlSeconds) <= 3600;
|
|
141
|
+
if (['__proto__', 'prototype', 'constructor'].includes(key) ||
|
|
142
|
+
/^(?:accessToken|refreshToken|token|password|secret|clientSecret|privateKey|accountKey|connectionString|sasToken)$/iu.test(key) && !publicAppTokenMetadata) {
|
|
143
|
+
throw new Error(`${path}.${key} is not permitted in public activation inputs.`);
|
|
144
|
+
}
|
|
145
|
+
result[key] = publicJson(entry, `${path}.${key}`, depth + 1);
|
|
146
|
+
}
|
|
147
|
+
return result;
|
|
148
|
+
}
|
|
149
|
+
export function validateActivationConfiguration(value) {
|
|
150
|
+
const config = exactWithOptional(value, ['schemaVersion', 'phases'], ['repository', 'azure', 'budget'], 'activationInputs');
|
|
151
|
+
requireVersion(config.schemaVersion, 1, 'activationInputs.schemaVersion');
|
|
152
|
+
const phases = {};
|
|
153
|
+
for (const [id, inputs] of Object.entries(record(config.phases, 'activationInputs.phases'))) {
|
|
154
|
+
const phaseId = enumValue(id, phaseIdSet, 'activationInputs.phases');
|
|
155
|
+
phases[phaseId] = record(publicJson(inputs, `activationInputs.phases.${id}`), `activationInputs.phases.${id}`);
|
|
156
|
+
}
|
|
157
|
+
let repository;
|
|
158
|
+
if (config.repository !== undefined) {
|
|
159
|
+
const target = exactWithOptional(config.repository, ['name'], ['defaultBranch', 'visibility', 'create'], 'activationInputs.repository');
|
|
160
|
+
const name = stringField(target, 'name', 'activationInputs.repository');
|
|
161
|
+
if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/u.test(name)) {
|
|
162
|
+
throw new Error('activationInputs.repository.name must be owner/repository.');
|
|
163
|
+
}
|
|
164
|
+
const defaultBranch = target.defaultBranch === undefined ? undefined : stringField(target, 'defaultBranch', 'activationInputs.repository');
|
|
165
|
+
if (defaultBranch !== undefined && (!/^[A-Za-z0-9][A-Za-z0-9_./-]*$/u.test(defaultBranch) ||
|
|
166
|
+
defaultBranch.includes('..') || defaultBranch.includes('//') || defaultBranch.endsWith('/') || defaultBranch.endsWith('.lock'))) {
|
|
167
|
+
throw new Error('activationInputs.repository.defaultBranch must be a safe Git branch name.');
|
|
168
|
+
}
|
|
169
|
+
repository = {
|
|
170
|
+
name,
|
|
171
|
+
...(defaultBranch === undefined ? {} : { defaultBranch }),
|
|
172
|
+
...(target.visibility === undefined ? {} : { visibility: enumValue(target.visibility, new Set(['private', 'public']), 'activationInputs.repository.visibility') }),
|
|
173
|
+
...(target.create === undefined ? {} : { create: booleanField(target, 'create', 'activationInputs.repository') })
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
let azure;
|
|
177
|
+
if (config.azure !== undefined) {
|
|
178
|
+
const target = exact(config.azure, ['subscriptionId', 'tenantId', 'region'], 'activationInputs.azure');
|
|
179
|
+
const subscriptionId = stringField(target, 'subscriptionId', 'activationInputs.azure');
|
|
180
|
+
const tenantId = stringField(target, 'tenantId', 'activationInputs.azure');
|
|
181
|
+
const region = stringField(target, 'region', 'activationInputs.azure');
|
|
182
|
+
const uuid = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/iu;
|
|
183
|
+
if (!uuid.test(subscriptionId) || !uuid.test(tenantId) || !/^[a-z][a-z0-9]+$/u.test(region)) {
|
|
184
|
+
throw new Error('activationInputs.azure requires concrete subscription/tenant GUIDs and an Azure region name.');
|
|
185
|
+
}
|
|
186
|
+
azure = { subscriptionId, tenantId, region };
|
|
187
|
+
}
|
|
188
|
+
let budget;
|
|
189
|
+
if (config.budget !== undefined) {
|
|
190
|
+
const cost = exact(config.budget, ['currency', 'fixedMonthlyCents', 'usageMonthlyCents'], 'activationInputs.budget');
|
|
191
|
+
budget = normalizeApprovalCostCeiling({
|
|
192
|
+
currency: stringField(cost, 'currency', 'activationInputs.budget'),
|
|
193
|
+
fixedMonthlyCents: integerField(cost, 'fixedMonthlyCents', 'activationInputs.budget'),
|
|
194
|
+
usageMonthlyCents: integerField(cost, 'usageMonthlyCents', 'activationInputs.budget')
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
return { schemaVersion: 1, phases, ...(repository ? { repository } : {}), ...(azure ? { azure } : {}), ...(budget ? { budget } : {}) };
|
|
198
|
+
}
|
|
199
|
+
function validateExternalOperation(value, path) {
|
|
200
|
+
const item = exactWithOptional(value, ['provider', 'actionId', 'operationId', 'resourceId', 'startedAt', 'observedAt', 'status'], ['pollUrl', 'planDigest'], path);
|
|
201
|
+
const pollUrl = item.pollUrl === undefined ? undefined : stringField(item, 'pollUrl', path);
|
|
202
|
+
if (pollUrl !== undefined) {
|
|
203
|
+
publicJson(pollUrl, `${path}.pollUrl`);
|
|
204
|
+
const url = new URL(pollUrl);
|
|
205
|
+
if (url.protocol !== 'https:' || !['api.github.com', 'management.azure.com'].includes(url.hostname) ||
|
|
206
|
+
url.username || url.password || url.port)
|
|
207
|
+
throw new Error(`${path}.pollUrl must be a credential-free supported provider URL.`);
|
|
208
|
+
}
|
|
209
|
+
const startedAt = isoTimestamp(item.startedAt, `${path}.startedAt`);
|
|
210
|
+
const observedAt = isoTimestamp(item.observedAt, `${path}.observedAt`);
|
|
211
|
+
if (Date.parse(observedAt) < Date.parse(startedAt))
|
|
212
|
+
throw new Error(`${path}.observedAt must not precede startedAt.`);
|
|
213
|
+
return {
|
|
214
|
+
provider: enumValue(item.provider, liveReadbackProviderSet, `${path}.provider`),
|
|
215
|
+
actionId: stringField(item, 'actionId', path),
|
|
216
|
+
operationId: stringField(item, 'operationId', path),
|
|
217
|
+
resourceId: stringField(item, 'resourceId', path),
|
|
218
|
+
startedAt, observedAt,
|
|
219
|
+
status: enumValue(item.status, new Set(['running', 'completed', 'failed']), `${path}.status`),
|
|
220
|
+
...(pollUrl ? { pollUrl } : {}),
|
|
221
|
+
...(item.planDigest === undefined ? {} : { planDigest: hexDigest(item.planDigest, `${path}.planDigest`) })
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
function validateFileChanges(value, path) {
|
|
225
|
+
if (!Array.isArray(value))
|
|
226
|
+
throw new Error(`${path} must be an array.`);
|
|
227
|
+
const changes = value.map((entry, index) => {
|
|
228
|
+
const item = exact(entry, ['pathParts', 'beforeHash', 'afterHash'], `${path}[${index}]`);
|
|
229
|
+
return {
|
|
230
|
+
pathParts: safePathParts(item.pathParts, `${path}[${index}].pathParts`),
|
|
231
|
+
beforeHash: item.beforeHash === null ? null : hexDigest(item.beforeHash, `${path}[${index}].beforeHash`),
|
|
232
|
+
afterHash: item.afterHash === null ? null : hexDigest(item.afterHash, `${path}[${index}].afterHash`)
|
|
233
|
+
};
|
|
234
|
+
});
|
|
235
|
+
assertNoDuplicateStrings(changes.map((change) => change.pathParts.join('/')), path);
|
|
236
|
+
return changes;
|
|
237
|
+
}
|
|
238
|
+
function validateGitInput(value, path) {
|
|
239
|
+
const git = exact(value, ['head', 'branch', 'pushUrls'], path);
|
|
240
|
+
const head = git.head === null ? null : stringField(git, 'head', path);
|
|
241
|
+
if (head !== null && !/^[a-f0-9]{40,64}$/u.test(head))
|
|
242
|
+
throw new Error(`${path}.head must be an actual Git object ID.`);
|
|
243
|
+
const pushUrls = stringArray(git.pushUrls, `${path}.pushUrls`);
|
|
244
|
+
if (pushUrls.some((url) => !/^(?:https:\/\/github\.com\/|git@github\.com:)[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+(?:\.git)?\/?$/u.test(url))) {
|
|
245
|
+
throw new Error(`${path}.pushUrls must contain credential-free supported GitHub destinations.`);
|
|
246
|
+
}
|
|
247
|
+
return { head, branch: git.branch === null ? null : stringField(git, 'branch', path), pushUrls };
|
|
248
|
+
}
|
|
249
|
+
function validateOutputBindings(value, path) {
|
|
250
|
+
const outputs = exact(value, ['values', 'resources'], path);
|
|
251
|
+
const values = {};
|
|
252
|
+
for (const [key, item] of Object.entries(record(publicJson(outputs.values, `${path}.values`), `${path}.values`))) {
|
|
253
|
+
if (item !== null && typeof item !== 'string' && typeof item !== 'boolean' && typeof item !== 'number') {
|
|
254
|
+
throw new Error(`${path}.values.${key} must be a public primitive value.`);
|
|
255
|
+
}
|
|
256
|
+
values[key] = item;
|
|
257
|
+
}
|
|
258
|
+
if (!Array.isArray(outputs.resources))
|
|
259
|
+
throw new Error(`${path}.resources must be an array.`);
|
|
260
|
+
return {
|
|
261
|
+
values,
|
|
262
|
+
resources: outputs.resources.map((entry, index) => {
|
|
263
|
+
const resource = exact(entry, ['provider', 'resourceType', 'resourceId'], `${path}.resources[${index}]`);
|
|
264
|
+
return {
|
|
265
|
+
provider: enumValue(resource.provider, liveReadbackProviderSet, `${path}.resources[${index}].provider`),
|
|
266
|
+
resourceType: stringField(resource, 'resourceType', path),
|
|
267
|
+
resourceId: stringField(resource, 'resourceId', path)
|
|
268
|
+
};
|
|
269
|
+
})
|
|
270
|
+
};
|
|
109
271
|
}
|
|
110
272
|
function enumValue(value, allowed, path) {
|
|
111
273
|
if (typeof value !== 'string' || !allowed.has(value)) {
|
|
@@ -276,7 +438,7 @@ function validateApplicability(value, path) {
|
|
|
276
438
|
'exclusiveWith'
|
|
277
439
|
], path);
|
|
278
440
|
enumValue(applicability.kind, new Set(['conditional']), `${path}.kind`);
|
|
279
|
-
const discriminator = enumValue(applicability.discriminator, new Set(['state-path', 'private-staging-dast', 'credential-required']), `${path}.discriminator`);
|
|
441
|
+
const discriminator = enumValue(applicability.discriminator, new Set(['state-path', 'private-staging-dast', 'credential-required', 'cloud-state-required', 'private-runner-required']), `${path}.discriminator`);
|
|
280
442
|
const exclusiveWith = stringArray(applicability.exclusiveWith, `${path}.exclusiveWith`).map((id) => enumValue(id, phaseIdSet, `${path}.exclusiveWith`));
|
|
281
443
|
return {
|
|
282
444
|
kind: 'conditional',
|
|
@@ -408,7 +570,7 @@ function assertReachableToFinal(nodes) {
|
|
|
408
570
|
}
|
|
409
571
|
}
|
|
410
572
|
export function validateManagedPhaseGraph(value) {
|
|
411
|
-
const graph = exact(value, ['schemaVersion', 'versions', 'phases'], 'phaseGraph');
|
|
573
|
+
const graph = exact(value, ['schemaVersion', 'versions', 'phases', 'completionGroups'], 'phaseGraph');
|
|
412
574
|
requireVersion(graph.schemaVersion, phaseGraphSchemaVersion, 'phaseGraph.schemaVersion');
|
|
413
575
|
const versions = exact(graph.versions, [
|
|
414
576
|
'liftoffVersion',
|
|
@@ -514,6 +676,11 @@ export function validateManagedPhaseGraph(value) {
|
|
|
514
676
|
visit(id);
|
|
515
677
|
}
|
|
516
678
|
assertReachableToFinal(nodes);
|
|
679
|
+
const groups = exact(graph.completionGroups, governanceScopes, 'phaseGraph.completionGroups');
|
|
680
|
+
const completionGroups = {};
|
|
681
|
+
for (const scope of governanceScopes) {
|
|
682
|
+
completionGroups[scope] = exactStringSet(stringArray(groups[scope], `phaseGraph.completionGroups.${scope}`), phaseIds.filter((id) => phaseScope(id) === scope), `phaseGraph.completionGroups.${scope}`);
|
|
683
|
+
}
|
|
517
684
|
return {
|
|
518
685
|
schemaVersion: phaseGraphSchemaVersion,
|
|
519
686
|
versions: {
|
|
@@ -522,7 +689,8 @@ export function validateManagedPhaseGraph(value) {
|
|
|
522
689
|
activationContractVersion,
|
|
523
690
|
phaseGraphSchemaVersion
|
|
524
691
|
},
|
|
525
|
-
phases: nodes
|
|
692
|
+
phases: nodes,
|
|
693
|
+
completionGroups
|
|
526
694
|
};
|
|
527
695
|
}
|
|
528
696
|
export function assertCanonicalGraphValid() {
|
|
@@ -543,6 +711,86 @@ function validateEvidenceReference(value, path) {
|
|
|
543
711
|
result
|
|
544
712
|
};
|
|
545
713
|
}
|
|
714
|
+
export function validateGovernanceTaskProjectionContract(value) {
|
|
715
|
+
const label = 'taskProjectionContract';
|
|
716
|
+
const raw = record(value, label);
|
|
717
|
+
const source = enumValue(raw.source, new Set(['existing', 'create']), `${label}.source`);
|
|
718
|
+
const item = exact(value, [
|
|
719
|
+
'schemaVersion', 'derivation', 'source', 'changeId', 'workflowKind', 'taskPathParts',
|
|
720
|
+
'metadataPathParts', 'metadataHash', 'layoutHash', ...(source === 'create' ? ['template', 'metadataText'] : [])
|
|
721
|
+
], label);
|
|
722
|
+
requireVersion(item.schemaVersion, 1, `${label}.schemaVersion`);
|
|
723
|
+
if (item.derivation !== 'validated-current-readiness')
|
|
724
|
+
throw new Error(`${label} requires the bounded current-readiness derivation.`);
|
|
725
|
+
const changeId = stringField(item, 'changeId', label);
|
|
726
|
+
safePathParts([changeId], `${label}.changeId`);
|
|
727
|
+
const workflowKind = enumValue(item.workflowKind, new Set(['openspec', 'spec-kit']), `${label}.workflowKind`);
|
|
728
|
+
if (changeId === 'archive' || changeId.startsWith('bootstrap-') || changeId === '000-liftoff-bootstrap') {
|
|
729
|
+
throw new Error(`${label} cannot target seed tasks or an archive.`);
|
|
730
|
+
}
|
|
731
|
+
const base = workflowKind === 'openspec' ? ['openspec', 'changes', changeId] : ['specs', changeId];
|
|
732
|
+
const taskPathParts = safePathParts(item.taskPathParts, `${label}.taskPathParts`);
|
|
733
|
+
const metadataPathParts = safePathParts(item.metadataPathParts, `${label}.metadataPathParts`);
|
|
734
|
+
if (taskPathParts.join('/') !== [...base, 'tasks.md'].join('/') ||
|
|
735
|
+
metadataPathParts.join('/') !== [...base, 'liftoff-governance.json'].join('/')) {
|
|
736
|
+
throw new Error(`${label} must target the exact current governance task and metadata paths.`);
|
|
737
|
+
}
|
|
738
|
+
const common = {
|
|
739
|
+
schemaVersion: 1, derivation: 'validated-current-readiness',
|
|
740
|
+
changeId, workflowKind, taskPathParts, metadataPathParts,
|
|
741
|
+
metadataHash: hexDigest(item.metadataHash, `${label}.metadataHash`),
|
|
742
|
+
layoutHash: hexDigest(item.layoutHash, `${label}.layoutHash`)
|
|
743
|
+
};
|
|
744
|
+
if (source === 'existing')
|
|
745
|
+
return { ...common, source };
|
|
746
|
+
const template = stringField(item, 'template', label);
|
|
747
|
+
const metadataText = stringField(item, 'metadataText', label);
|
|
748
|
+
if (template.length > 262_144 || metadataText.length > 262_144 || sha256Hex(metadataText) !== common.metadataHash) {
|
|
749
|
+
throw new Error(`${label} has oversized or inconsistently bound creation sources.`);
|
|
750
|
+
}
|
|
751
|
+
publicJson(template, `${label}.template`);
|
|
752
|
+
publicJson(metadataText, `${label}.metadataText`);
|
|
753
|
+
return { ...common, source, template, metadataText };
|
|
754
|
+
}
|
|
755
|
+
export function validateGovernanceTaskProjectionRecord(value) {
|
|
756
|
+
const label = 'taskProjectionRecord';
|
|
757
|
+
const item = exact(value, [
|
|
758
|
+
'schemaVersion', 'purpose', 'phaseId', 'planDigest', 'contractDigest', 'taskPathParts', 'metadataHash',
|
|
759
|
+
'layoutHash', 'status', 'observedAt', 'beforeHash', 'afterHash', 'states', 'blockers'
|
|
760
|
+
], label);
|
|
761
|
+
requireVersion(item.schemaVersion, 1, `${label}.schemaVersion`);
|
|
762
|
+
if (item.purpose !== 'projection-audit-only')
|
|
763
|
+
throw new Error(`${label} is not execution authority.`);
|
|
764
|
+
const status = enumValue(item.status, new Set(['complete', 'blocked']), `${label}.status`);
|
|
765
|
+
const blockers = stringArray(publicJson(item.blockers, `${label}.blockers`), `${label}.blockers`);
|
|
766
|
+
let states = null;
|
|
767
|
+
if (item.states !== null) {
|
|
768
|
+
const raw = exact(item.states, phaseIds, `${label}.states`);
|
|
769
|
+
states = Object.fromEntries(phaseIds.map((id) => [
|
|
770
|
+
id, enumValue(raw[id], new Set([...phaseStates, 'identity-incompatible']), `${label}.states.${id}`)
|
|
771
|
+
]));
|
|
772
|
+
}
|
|
773
|
+
const beforeHash = item.beforeHash === null ? null : hexDigest(item.beforeHash, `${label}.beforeHash`);
|
|
774
|
+
const afterHash = item.afterHash === null ? null : hexDigest(item.afterHash, `${label}.afterHash`);
|
|
775
|
+
const taskPathParts = safePathParts(item.taskPathParts, `${label}.taskPathParts`);
|
|
776
|
+
const registered = taskPathParts.length === 4 && taskPathParts[0] === 'openspec' && taskPathParts[1] === 'changes' &&
|
|
777
|
+
taskPathParts[2] !== 'archive' && !taskPathParts[2].startsWith('bootstrap-') ||
|
|
778
|
+
taskPathParts.length === 3 && taskPathParts[0] === 'specs' && taskPathParts[1] !== '000-liftoff-bootstrap';
|
|
779
|
+
if (!registered || taskPathParts.at(-1) !== 'tasks.md')
|
|
780
|
+
throw new Error(`${label} cannot name an unregistered task destination.`);
|
|
781
|
+
if (status === 'complete' ? states === null || afterHash === null || blockers.length !== 0 :
|
|
782
|
+
states !== null || afterHash !== null || blockers.length === 0) {
|
|
783
|
+
throw new Error(`${label} must distinguish completed projection from blocked, uncommitted task output.`);
|
|
784
|
+
}
|
|
785
|
+
return {
|
|
786
|
+
schemaVersion: 1, purpose: 'projection-audit-only',
|
|
787
|
+
phaseId: enumValue(item.phaseId, phaseIdSet, `${label}.phaseId`),
|
|
788
|
+
planDigest: hexDigest(item.planDigest, `${label}.planDigest`), contractDigest: hexDigest(item.contractDigest, `${label}.contractDigest`),
|
|
789
|
+
taskPathParts,
|
|
790
|
+
metadataHash: hexDigest(item.metadataHash, `${label}.metadataHash`), layoutHash: hexDigest(item.layoutHash, `${label}.layoutHash`),
|
|
791
|
+
status, observedAt: isoTimestamp(item.observedAt, `${label}.observedAt`), beforeHash, afterHash, states, blockers
|
|
792
|
+
};
|
|
793
|
+
}
|
|
546
794
|
export function validateUserActivationState(value) {
|
|
547
795
|
const state = exactWithOptional(value, [
|
|
548
796
|
'schemaVersion',
|
|
@@ -553,7 +801,7 @@ export function validateUserActivationState(value) {
|
|
|
553
801
|
'phases',
|
|
554
802
|
'createdAt',
|
|
555
803
|
'updatedAt'
|
|
556
|
-
], ['bootstrapState', 'remoteBinding'], 'activationState');
|
|
804
|
+
], ['bootstrapState', 'remoteBinding', 'baselineAnchor', 'activationInputs', 'phaseOutputs', 'successorHistory', 'taskProjection'], 'activationState');
|
|
557
805
|
requireVersion(state.schemaVersion, activationStateSchemaVersion, 'activationState.schemaVersion');
|
|
558
806
|
const identity = validateActivationIdentity(state.identity);
|
|
559
807
|
if (identity.phaseGraphHash !== canonicalPhaseGraphHash) {
|
|
@@ -585,18 +833,47 @@ export function validateUserActivationState(value) {
|
|
|
585
833
|
kind: enumValue(change.kind, new Set(['openspec', 'spec-kit']), 'activationState.activeChange.kind')
|
|
586
834
|
};
|
|
587
835
|
}
|
|
588
|
-
|
|
836
|
+
let successorHistory;
|
|
837
|
+
if (state.successorHistory !== undefined) {
|
|
838
|
+
const history = exact(state.successorHistory, [
|
|
839
|
+
'schemaVersion', 'snapshotId', 'journalPathParts', 'historyIndexPathParts', 'historyIndexDigest', 'sourceActiveChange'
|
|
840
|
+
], 'activationState.successorHistory');
|
|
841
|
+
requireVersion(history.schemaVersion, 1, 'activationState.successorHistory.schemaVersion');
|
|
842
|
+
const snapshotId = hexDigest(history.snapshotId, 'activationState.successorHistory.snapshotId');
|
|
843
|
+
const journalPath = safePathParts(history.journalPathParts, 'activationState.successorHistory.journalPathParts');
|
|
844
|
+
const indexPath = safePathParts(history.historyIndexPathParts, 'activationState.successorHistory.historyIndexPathParts');
|
|
845
|
+
if (journalPath.join('/') !== 'governance/migration-state.json' ||
|
|
846
|
+
indexPath.join('/') !== `governance/history/${snapshotId}/index.json`) {
|
|
847
|
+
throw new Error('activationState.successorHistory must name its exact registered migration journal and snapshot index.');
|
|
848
|
+
}
|
|
849
|
+
let sourceActiveChange = null;
|
|
850
|
+
if (history.sourceActiveChange !== null) {
|
|
851
|
+
const source = exact(history.sourceActiveChange, ['id', 'kind'], 'activationState.successorHistory.sourceActiveChange');
|
|
852
|
+
const id = stringField(source, 'id', 'activationState.successorHistory.sourceActiveChange');
|
|
853
|
+
safePathParts([id], 'activationState.successorHistory.sourceActiveChange.id');
|
|
854
|
+
sourceActiveChange = {
|
|
855
|
+
id, kind: enumValue(source.kind, new Set(['openspec', 'spec-kit']), 'activationState.successorHistory.sourceActiveChange.kind')
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
successorHistory = {
|
|
859
|
+
schemaVersion: 1, snapshotId, journalPathParts: ['governance', 'migration-state.json'],
|
|
860
|
+
historyIndexPathParts: indexPath,
|
|
861
|
+
historyIndexDigest: hexDigest(history.historyIndexDigest, 'activationState.successorHistory.historyIndexDigest'),
|
|
862
|
+
sourceActiveChange
|
|
863
|
+
};
|
|
864
|
+
}
|
|
865
|
+
const applicability = exactWithOptional(state.applicability, [
|
|
589
866
|
'statePath',
|
|
590
867
|
'privateStagingDast',
|
|
591
868
|
'credentialRequired'
|
|
592
|
-
], 'activationState.applicability');
|
|
869
|
+
], ['cloudStateRequired', 'privateRunnerRequired'], 'activationState.applicability');
|
|
593
870
|
const phases = record(state.phases, 'activationState.phases');
|
|
594
871
|
const phaseStatesById = {};
|
|
595
872
|
for (const id of phaseIds) {
|
|
596
873
|
if (!Object.hasOwn(phases, id)) {
|
|
597
874
|
throw new Error(`activationState.phases.${id} is required.`);
|
|
598
875
|
}
|
|
599
|
-
const phase =
|
|
876
|
+
const phase = exactWithOptional(phases[id], ['state', 'updatedAt', 'evidence', 'approvals', 'blockers'], ['operation', 'executionPlanDigest'], `activationState.phases.${id}`);
|
|
600
877
|
const phaseState = enumValue(phase.state, new Set(phaseStates), `activationState.phases.${id}.state`);
|
|
601
878
|
if (!Array.isArray(phase.evidence) || !Array.isArray(phase.approvals) || !Array.isArray(phase.blockers)) {
|
|
602
879
|
throw new Error(`activationState.phases.${id} evidence, approvals, and blockers must be arrays.`);
|
|
@@ -606,9 +883,18 @@ export function validateUserActivationState(value) {
|
|
|
606
883
|
updatedAt: stringField(phase, 'updatedAt', `activationState.phases.${id}`),
|
|
607
884
|
evidence: phase.evidence.map((entry, index) => validateEvidenceReference(entry, `activationState.phases.${id}.evidence[${index}]`)),
|
|
608
885
|
approvals: stringArray(phase.approvals, `activationState.phases.${id}.approvals`),
|
|
609
|
-
blockers: stringArray(phase.blockers, `activationState.phases.${id}.blockers`)
|
|
886
|
+
blockers: stringArray(phase.blockers, `activationState.phases.${id}.blockers`),
|
|
887
|
+
...(phase.operation === undefined ? {} : { operation: validateExternalOperation(phase.operation, `activationState.phases.${id}.operation`) }),
|
|
888
|
+
...(phase.executionPlanDigest === undefined ? {} : { executionPlanDigest: hexDigest(phase.executionPlanDigest, `activationState.phases.${id}.executionPlanDigest`) })
|
|
610
889
|
};
|
|
611
890
|
}
|
|
891
|
+
const phaseOutputs = {};
|
|
892
|
+
if (state.phaseOutputs !== undefined) {
|
|
893
|
+
for (const [id, outputs] of Object.entries(record(state.phaseOutputs, 'activationState.phaseOutputs'))) {
|
|
894
|
+
phaseOutputs[enumValue(id, phaseIdSet, 'activationState.phaseOutputs')] =
|
|
895
|
+
validateOutputBindings(outputs, `activationState.phaseOutputs.${id}`);
|
|
896
|
+
}
|
|
897
|
+
}
|
|
612
898
|
for (const key of Object.keys(phases)) {
|
|
613
899
|
if (!phaseIdSet.has(key)) {
|
|
614
900
|
throw new Error(`activationState.phases.${key} is not a canonical phase.`);
|
|
@@ -659,10 +945,21 @@ export function validateUserActivationState(value) {
|
|
|
659
945
|
applicability: {
|
|
660
946
|
statePath: enumValue(applicability.statePath, new Set(['existing-private', 'bootstrap-local', 'none']), 'activationState.applicability.statePath'),
|
|
661
947
|
privateStagingDast: applicability.privateStagingDast === 'unknown' ? 'unknown' : booleanField(applicability, 'privateStagingDast', 'activationState.applicability'),
|
|
662
|
-
credentialRequired: applicability.credentialRequired === 'unknown' ? 'unknown' : booleanField(applicability, 'credentialRequired', 'activationState.applicability')
|
|
948
|
+
credentialRequired: applicability.credentialRequired === 'unknown' ? 'unknown' : booleanField(applicability, 'credentialRequired', 'activationState.applicability'),
|
|
949
|
+
...(applicability.cloudStateRequired === undefined ? {} : {
|
|
950
|
+
cloudStateRequired: applicability.cloudStateRequired === 'unknown' ? 'unknown' : booleanField(applicability, 'cloudStateRequired', 'activationState.applicability')
|
|
951
|
+
}),
|
|
952
|
+
...(applicability.privateRunnerRequired === undefined ? {} : {
|
|
953
|
+
privateRunnerRequired: applicability.privateRunnerRequired === 'unknown' ? 'unknown' : booleanField(applicability, 'privateRunnerRequired', 'activationState.applicability')
|
|
954
|
+
})
|
|
663
955
|
},
|
|
664
956
|
...(remoteBinding ? { remoteBinding } : {}),
|
|
665
957
|
...(bootstrapState ? { bootstrapState } : {}),
|
|
958
|
+
...(state.baselineAnchor === undefined ? {} : { baselineAnchor: hexDigest(state.baselineAnchor, 'activationState.baselineAnchor') }),
|
|
959
|
+
...(successorHistory ? { successorHistory } : {}),
|
|
960
|
+
...(state.taskProjection === undefined ? {} : { taskProjection: validateGovernanceTaskProjectionRecord(state.taskProjection) }),
|
|
961
|
+
...(state.activationInputs === undefined ? {} : { activationInputs: validateActivationConfiguration(state.activationInputs) }),
|
|
962
|
+
...(state.phaseOutputs === undefined ? {} : { phaseOutputs }),
|
|
666
963
|
phases: phaseStatesById,
|
|
667
964
|
createdAt: stringField(state, 'createdAt', 'activationState'),
|
|
668
965
|
updatedAt: stringField(state, 'updatedAt', 'activationState')
|
|
@@ -683,7 +980,7 @@ export function validateEvidenceHeader(value) {
|
|
|
683
980
|
'producer',
|
|
684
981
|
'bodyDigest',
|
|
685
982
|
'result'
|
|
686
|
-
], ['remoteBindingDigest'], 'evidenceHeader');
|
|
983
|
+
], ['remoteBindingDigest', 'scope', 'inputBindings'], 'evidenceHeader');
|
|
687
984
|
requireVersion(header.schemaVersion, evidenceHeaderSchemaVersion, 'evidenceHeader.schemaVersion');
|
|
688
985
|
const identity = validateActivationIdentity(header.identity);
|
|
689
986
|
const phaseGraphHash = hexDigest(header.phaseGraphHash, 'evidenceHeader.phaseGraphHash');
|
|
@@ -701,7 +998,28 @@ export function validateEvidenceHeader(value) {
|
|
|
701
998
|
if (transition.baselineSha !== baselineSha) {
|
|
702
999
|
throw new Error('evidenceHeader.transition.baselineSha must match evidenceHeader.baselineSha.');
|
|
703
1000
|
}
|
|
704
|
-
|
|
1001
|
+
let inputBindings;
|
|
1002
|
+
if (header.inputBindings !== undefined) {
|
|
1003
|
+
const bindings = exactWithOptional(header.inputBindings, ['beforeDigest', 'afterDigest', 'files'], ['git'], 'evidenceHeader.inputBindings');
|
|
1004
|
+
const git = bindings.git === undefined ? undefined : exact(bindings.git, ['before', 'after'], 'evidenceHeader.inputBindings.git');
|
|
1005
|
+
inputBindings = {
|
|
1006
|
+
beforeDigest: hexDigest(bindings.beforeDigest, 'evidenceHeader.inputBindings.beforeDigest'),
|
|
1007
|
+
afterDigest: hexDigest(bindings.afterDigest, 'evidenceHeader.inputBindings.afterDigest'),
|
|
1008
|
+
files: validateFileChanges(bindings.files, 'evidenceHeader.inputBindings.files'),
|
|
1009
|
+
...(git ? { git: {
|
|
1010
|
+
before: validateGitInput(git.before, 'evidenceHeader.inputBindings.git.before'),
|
|
1011
|
+
after: validateGitInput(git.after, 'evidenceHeader.inputBindings.git.after')
|
|
1012
|
+
} } : {})
|
|
1013
|
+
};
|
|
1014
|
+
if (inputBindings.beforeDigest !== transition.inputDigest || inputBindings.afterDigest !== inputDigest) {
|
|
1015
|
+
throw new Error('evidenceHeader.inputBindings must bind the reviewed before-input and observed after-input.');
|
|
1016
|
+
}
|
|
1017
|
+
if (inputBindings.beforeDigest !== inputBindings.afterDigest && !inputBindings.git &&
|
|
1018
|
+
!inputBindings.files.some((file) => file.beforeHash !== file.afterHash)) {
|
|
1019
|
+
throw new Error('Changed phase inputs require a concrete reviewed file or Git transition binding.');
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
if (!inputBindings && transition.inputDigest !== inputDigest) {
|
|
705
1023
|
throw new Error('evidenceHeader.transition.inputDigest must match evidenceHeader.inputDigest.');
|
|
706
1024
|
}
|
|
707
1025
|
return {
|
|
@@ -720,6 +1038,10 @@ export function validateEvidenceHeader(value) {
|
|
|
720
1038
|
...(header.remoteBindingDigest !== undefined ? {
|
|
721
1039
|
remoteBindingDigest: hexDigest(header.remoteBindingDigest, 'evidenceHeader.remoteBindingDigest')
|
|
722
1040
|
} : {}),
|
|
1041
|
+
...(header.scope === undefined ? {} : {
|
|
1042
|
+
scope: enumValue(header.scope, new Set([phaseScope(phaseId)]), 'evidenceHeader.scope')
|
|
1043
|
+
}),
|
|
1044
|
+
...(inputBindings ? { inputBindings } : {}),
|
|
723
1045
|
result: enumValue(header.result, resultStateSet, 'evidenceHeader.result')
|
|
724
1046
|
};
|
|
725
1047
|
}
|
|
@@ -798,7 +1120,7 @@ function identityMatches(left, right) {
|
|
|
798
1120
|
return JSON.stringify(left) === JSON.stringify(right);
|
|
799
1121
|
}
|
|
800
1122
|
export function validateApprovalEnvelope(value, options = {}) {
|
|
801
|
-
const envelope =
|
|
1123
|
+
const envelope = exactWithOptional(value, [
|
|
802
1124
|
'schemaVersion',
|
|
803
1125
|
'id',
|
|
804
1126
|
'phaseId',
|
|
@@ -815,7 +1137,7 @@ export function validateApprovalEnvelope(value, options = {}) {
|
|
|
815
1137
|
'expiresAt',
|
|
816
1138
|
'approvedAt',
|
|
817
1139
|
'approver'
|
|
818
|
-
], 'approvalEnvelope');
|
|
1140
|
+
], ['scope', 'coveredPhases', 'operationDigests', 'phasePlanDigests'], 'approvalEnvelope');
|
|
819
1141
|
requireVersion(envelope.schemaVersion, approvalEnvelopeSchemaVersion, 'approvalEnvelope.schemaVersion');
|
|
820
1142
|
const cost = exact(envelope.costCeiling, [
|
|
821
1143
|
'currency',
|
|
@@ -860,6 +1182,29 @@ export function validateApprovalEnvelope(value, options = {}) {
|
|
|
860
1182
|
if (expectedGateKind !== gateKind) {
|
|
861
1183
|
throw new Error(`approvalEnvelope.gateKind ${gateKind} does not match phase ${phaseId} gate ${expectedGateKind}.`);
|
|
862
1184
|
}
|
|
1185
|
+
const coveredPhases = envelope.coveredPhases === undefined ? undefined : stringArray(envelope.coveredPhases, 'approvalEnvelope.coveredPhases')
|
|
1186
|
+
.map((id) => enumValue(id, phaseIdSet, 'approvalEnvelope.coveredPhases'));
|
|
1187
|
+
const operationDigests = envelope.operationDigests === undefined ? undefined : stringArray(envelope.operationDigests, 'approvalEnvelope.operationDigests')
|
|
1188
|
+
.map((digest) => hexDigest(digest, 'approvalEnvelope.operationDigests'));
|
|
1189
|
+
if (coveredPhases) {
|
|
1190
|
+
assertNoDuplicateStrings(coveredPhases, 'approvalEnvelope.coveredPhases');
|
|
1191
|
+
if (!coveredPhases.includes(phaseId) || coveredPhases.some((id) => phaseScope(id) !== phaseScope(phaseId) || canonicalPhaseGraph.phases.find((phase) => phase.id === id)?.approvalGate.kind !== gateKind)) {
|
|
1192
|
+
throw new Error('An approval bundle must contain its primary phase and only phases in the same scope and authority gate.');
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
if (operationDigests)
|
|
1196
|
+
assertNoDuplicateStrings(operationDigests, 'approvalEnvelope.operationDigests');
|
|
1197
|
+
const phasePlanDigests = {};
|
|
1198
|
+
if (envelope.phasePlanDigests !== undefined) {
|
|
1199
|
+
for (const [id, digest] of Object.entries(record(envelope.phasePlanDigests, 'approvalEnvelope.phasePlanDigests'))) {
|
|
1200
|
+
phasePlanDigests[enumValue(id, phaseIdSet, 'approvalEnvelope.phasePlanDigests')] =
|
|
1201
|
+
hexDigest(digest, `approvalEnvelope.phasePlanDigests.${id}`);
|
|
1202
|
+
}
|
|
1203
|
+
exactStringSet(Object.keys(phasePlanDigests), coveredPhases ?? [phaseId], 'approvalEnvelope.phasePlanDigests');
|
|
1204
|
+
}
|
|
1205
|
+
else if ((coveredPhases?.length ?? 0) > 1) {
|
|
1206
|
+
throw new Error('An approval covering multiple phases requires every exact phase plan digest.');
|
|
1207
|
+
}
|
|
863
1208
|
return {
|
|
864
1209
|
schemaVersion: approvalEnvelopeSchemaVersion,
|
|
865
1210
|
id: stringField(envelope, 'id', 'approvalEnvelope'),
|
|
@@ -876,7 +1221,11 @@ export function validateApprovalEnvelope(value, options = {}) {
|
|
|
876
1221
|
destructiveScope,
|
|
877
1222
|
expiresAt,
|
|
878
1223
|
approvedAt,
|
|
879
|
-
approver: stringField(envelope, 'approver', 'approvalEnvelope')
|
|
1224
|
+
approver: stringField(envelope, 'approver', 'approvalEnvelope'),
|
|
1225
|
+
...(envelope.scope === undefined ? {} : { scope: enumValue(envelope.scope, new Set([phaseScope(phaseId)]), 'approvalEnvelope.scope') }),
|
|
1226
|
+
...(coveredPhases ? { coveredPhases } : {}),
|
|
1227
|
+
...(operationDigests ? { operationDigests } : {}),
|
|
1228
|
+
...(envelope.phasePlanDigests === undefined ? {} : { phasePlanDigests })
|
|
880
1229
|
};
|
|
881
1230
|
}
|
|
882
1231
|
function validateOperationDestination(value, path) {
|
|
@@ -884,7 +1233,7 @@ function validateOperationDestination(value, path) {
|
|
|
884
1233
|
const normalized = {
|
|
885
1234
|
type: enumValue(destination.type, new Set(['local', 'repository', 'subscription', 'environment', 'tenant', 'external']), `${path}.type`),
|
|
886
1235
|
identity: stringField(destination, 'identity', path),
|
|
887
|
-
...(destination.pathParts !== undefined ? { pathParts:
|
|
1236
|
+
...(destination.pathParts !== undefined ? { pathParts: safePathParts(destination.pathParts, `${path}.pathParts`) } : {}),
|
|
888
1237
|
...(destination.repository !== undefined ? { repository: stringField(destination, 'repository', path) } : {}),
|
|
889
1238
|
...(destination.subscriptionId !== undefined ? { subscriptionId: stringField(destination, 'subscriptionId', path) } : {}),
|
|
890
1239
|
...(destination.ref !== undefined ? { ref: stringField(destination, 'ref', path) } : {})
|
|
@@ -892,7 +1241,7 @@ function validateOperationDestination(value, path) {
|
|
|
892
1241
|
return normalized;
|
|
893
1242
|
}
|
|
894
1243
|
function validateTransitionOperation(value, path) {
|
|
895
|
-
const operation =
|
|
1244
|
+
const operation = exactWithOptional(value, [
|
|
896
1245
|
'adapter',
|
|
897
1246
|
'actionId',
|
|
898
1247
|
'mutationClass',
|
|
@@ -901,16 +1250,30 @@ function validateTransitionOperation(value, path) {
|
|
|
901
1250
|
'destination',
|
|
902
1251
|
'remote',
|
|
903
1252
|
'destructive'
|
|
904
|
-
], path);
|
|
1253
|
+
], ['effects'], path);
|
|
1254
|
+
const effects = operation.effects === undefined ? undefined : (() => {
|
|
1255
|
+
if (!Array.isArray(operation.effects))
|
|
1256
|
+
throw new Error(`${path}.effects must be an array.`);
|
|
1257
|
+
return operation.effects.map((entry, index) => {
|
|
1258
|
+
const effect = exact(entry, ['mutationClass', 'destination', 'remote', 'destructive'], `${path}.effects[${index}]`);
|
|
1259
|
+
return {
|
|
1260
|
+
mutationClass: enumValue(effect.mutationClass, new Set(mutationClasses), `${path}.effects[${index}].mutationClass`),
|
|
1261
|
+
destination: validateOperationDestination(effect.destination, `${path}.effects[${index}].destination`),
|
|
1262
|
+
remote: booleanField(effect, 'remote', `${path}.effects[${index}]`),
|
|
1263
|
+
destructive: booleanField(effect, 'destructive', `${path}.effects[${index}]`)
|
|
1264
|
+
};
|
|
1265
|
+
});
|
|
1266
|
+
})();
|
|
905
1267
|
return {
|
|
906
1268
|
adapter: enumValue(operation.adapter, transitionAdapterIds, `${path}.adapter`),
|
|
907
1269
|
actionId: stringField(operation, 'actionId', path),
|
|
908
1270
|
mutationClass: enumValue(operation.mutationClass, new Set(mutationClasses), `${path}.mutationClass`),
|
|
909
1271
|
phaseId: enumValue(operation.phaseId, phaseIdSet, `${path}.phaseId`),
|
|
910
|
-
inputs: record(operation.inputs, `${path}.inputs`),
|
|
1272
|
+
inputs: record(publicJson(operation.inputs, `${path}.inputs`), `${path}.inputs`),
|
|
911
1273
|
destination: validateOperationDestination(operation.destination, `${path}.destination`),
|
|
912
1274
|
remote: booleanField(operation, 'remote', path),
|
|
913
|
-
destructive: booleanField(operation, 'destructive', path)
|
|
1275
|
+
destructive: booleanField(operation, 'destructive', path),
|
|
1276
|
+
...(effects ? { effects } : {})
|
|
914
1277
|
};
|
|
915
1278
|
}
|
|
916
1279
|
function validateApprovalEvaluation(value, path) {
|
|
@@ -983,8 +1346,9 @@ function validateRollbackPlan(value, path) {
|
|
|
983
1346
|
};
|
|
984
1347
|
}
|
|
985
1348
|
export function validateSavedTransitionPlan(value) {
|
|
986
|
-
const plan =
|
|
1349
|
+
const plan = exactWithOptional(value, [
|
|
987
1350
|
'schemaVersion',
|
|
1351
|
+
'scope',
|
|
988
1352
|
'phaseId',
|
|
989
1353
|
'createdAt',
|
|
990
1354
|
'expiresAt',
|
|
@@ -1000,8 +1364,8 @@ export function validateSavedTransitionPlan(value) {
|
|
|
1000
1364
|
'approval',
|
|
1001
1365
|
'rollbackPlan',
|
|
1002
1366
|
'noSecrets'
|
|
1003
|
-
], 'transitionPlan');
|
|
1004
|
-
requireVersion(plan.schemaVersion,
|
|
1367
|
+
], ['configuration', 'fileChanges', 'recovery', 'approvalBundle'], 'transitionPlan');
|
|
1368
|
+
requireVersion(plan.schemaVersion, 2, 'transitionPlan.schemaVersion');
|
|
1005
1369
|
const identity = validateActivationIdentity(plan.identity);
|
|
1006
1370
|
const graphHash = hexDigest(plan.graphHash, 'transitionPlan.graphHash');
|
|
1007
1371
|
if (graphHash !== identity.phaseGraphHash) {
|
|
@@ -1028,8 +1392,33 @@ export function validateSavedTransitionPlan(value) {
|
|
|
1028
1392
|
const envelopeHash = approval.envelopeHash === null
|
|
1029
1393
|
? null
|
|
1030
1394
|
: hexDigest(approval.envelopeHash, 'transitionPlan.approval.envelopeHash');
|
|
1395
|
+
let approvalBundle;
|
|
1396
|
+
if (plan.approvalBundle !== undefined) {
|
|
1397
|
+
if (!Array.isArray(plan.approvalBundle))
|
|
1398
|
+
throw new Error('transitionPlan.approvalBundle must be an array.');
|
|
1399
|
+
approvalBundle = plan.approvalBundle.map((entry, index) => {
|
|
1400
|
+
const item = exact(entry, ['phaseId', 'inputDigest', 'transitionDigest', 'operations', 'fileChanges'], `transitionPlan.approvalBundle[${index}]`);
|
|
1401
|
+
const id = enumValue(item.phaseId, phaseIdSet, 'transitionPlan.approvalBundle.phaseId');
|
|
1402
|
+
const node = canonicalPhaseGraph.phases.find((phase) => phase.id === id);
|
|
1403
|
+
if (id === phaseId || phaseScope(id) !== phaseScope(phaseId) || node.approvalGate.kind !== approval.gateKind) {
|
|
1404
|
+
throw new Error('A bundled phase must be distinct and share the primary phase scope and approval gate.');
|
|
1405
|
+
}
|
|
1406
|
+
if (!Array.isArray(item.operations))
|
|
1407
|
+
throw new Error('Bundled operations must be an array.');
|
|
1408
|
+
const operations = item.operations.map((operation, operationIndex) => validateTransitionOperation(operation, `transitionPlan.approvalBundle[${index}].operations[${operationIndex}]`));
|
|
1409
|
+
if (operations.some((operation) => operation.phaseId !== id))
|
|
1410
|
+
throw new Error('Bundled operations must match their declared phase.');
|
|
1411
|
+
return {
|
|
1412
|
+
phaseId: id, inputDigest: hexDigest(item.inputDigest, 'approvalBundle.inputDigest'),
|
|
1413
|
+
transitionDigest: hexDigest(item.transitionDigest, 'approvalBundle.transitionDigest'),
|
|
1414
|
+
operations, fileChanges: validateFileChanges(item.fileChanges, 'approvalBundle.fileChanges')
|
|
1415
|
+
};
|
|
1416
|
+
});
|
|
1417
|
+
assertNoDuplicateStrings(approvalBundle.map((entry) => entry.phaseId), 'transitionPlan.approvalBundle');
|
|
1418
|
+
}
|
|
1031
1419
|
return {
|
|
1032
|
-
schemaVersion:
|
|
1420
|
+
schemaVersion: 2,
|
|
1421
|
+
scope: enumValue(plan.scope, new Set([phaseScope(phaseId)]), 'transitionPlan.scope'),
|
|
1033
1422
|
phaseId,
|
|
1034
1423
|
createdAt: isoTimestamp(plan.createdAt, 'transitionPlan.createdAt'),
|
|
1035
1424
|
expiresAt: isoTimestamp(plan.expiresAt, 'transitionPlan.expiresAt'),
|
|
@@ -1053,6 +1442,10 @@ export function validateSavedTransitionPlan(value) {
|
|
|
1053
1442
|
envelopeHash
|
|
1054
1443
|
},
|
|
1055
1444
|
rollbackPlan: validateRollbackPlan(plan.rollbackPlan, 'transitionPlan.rollbackPlan'),
|
|
1445
|
+
...(plan.configuration === undefined ? {} : { configuration: validateActivationConfiguration(plan.configuration) }),
|
|
1446
|
+
...(plan.fileChanges === undefined ? {} : { fileChanges: validateFileChanges(plan.fileChanges, 'transitionPlan.fileChanges') }),
|
|
1447
|
+
...(plan.recovery === undefined ? {} : { recovery: booleanField(plan, 'recovery', 'transitionPlan') }),
|
|
1448
|
+
...(approvalBundle ? { approvalBundle } : {}),
|
|
1056
1449
|
noSecrets: plan.noSecrets === true ? true : (() => { throw new Error('transitionPlan.noSecrets must be true.'); })()
|
|
1057
1450
|
};
|
|
1058
1451
|
}
|