@msn-control/liftoff 0.11.2 → 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 +26 -24
- 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 +45 -2
- 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/process/shell-command.d.ts +1 -0
- package/dist/adapters/process/shell-command.js +5 -2
- package/dist/adapters/process/shell-command.js.map +1 -1
- 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/command-guidance.d.ts +19 -2
- package/dist/application/update/command-guidance.js +20 -6
- package/dist/application/update/command-guidance.js.map +1 -1
- package/dist/application/update/guidance-context.d.ts +2 -0
- package/dist/application/update/guidance-context.js +35 -0
- package/dist/application/update/guidance-context.js.map +1 -0
- package/dist/application/update/inspection.d.ts +21 -4
- package/dist/application/update/inspection.js +88 -29
- package/dist/application/update/inspection.js.map +1 -1
- package/dist/application/update/migration-runtime.js +25 -13
- package/dist/application/update/migration-runtime.js.map +1 -1
- package/dist/application/update/output.d.ts +4 -1
- package/dist/application/update/output.js +14 -4
- package/dist/application/update/output.js.map +1 -1
- package/dist/application/update/preview.d.ts +10 -1
- package/dist/application/update/preview.js +26 -4
- package/dist/application/update/preview.js.map +1 -1
- package/dist/application/update/revalidation-plan.d.ts +1 -0
- package/dist/application/update/revalidation-plan.js +27 -15
- 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 +64 -23
- package/dist/application/update/revalidation.js.map +1 -1
- package/dist/application/update/use-case.js +50 -29
- 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 +3 -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 +94 -23
- package/docs/configuration-and-manifests.md +11 -10
- package/docs/prerequisites.md +93 -18
- package/docs/repository-governance.md +68 -40
- package/docs/spec-workflows-and-agents.md +132 -13
- package/docs/troubleshooting.md +28 -0
- package/package.json +1 -1
|
@@ -1,109 +1,29 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { constants } from 'node:fs';
|
|
2
|
+
import { lstat, open, readdir, realpath } from 'node:fs/promises';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import { canonicalJson, canonicalSha256, isRecord } from '../domain/governance/activation/canonical-json.js';
|
|
4
5
|
import { currentActivationIdentity } from '../domain/governance/activation/graph.js';
|
|
6
|
+
import { historicalActivationIdentities, isHistoricalV2ActivationIdentity } from '../domain/governance/policy/identity.js';
|
|
5
7
|
import { errorCode } from '../adapters/filesystem/errors.js';
|
|
6
8
|
import { parseManifest } from '../application/project/manifest.js';
|
|
7
|
-
import { ActivationHistoryError, historicalIdentity, historyArray, historyBoolean, historyCaseKey, historyDigest, historyEnum, historyExact, historyFail, historyLiteral, historyPathKey, historyPathParts, historyRecord, historyRecordId, historyString, historyStrings, historyTimestamp, historicalActivationIdentity, historicalActivationStatePathParts, historicalManifestPathParts, historicalMetadataPathParts, parseHistoryJson, rawHistoryDigest } from './history-contracts.js';
|
|
9
|
+
import { ActivationHistoryError, historicalIdentity, historicalV1Identity, historyArray, historyBoolean, historyCaseKey, historyDigest, historyEnum, historyExact, historyFail, historyLiteral, historyPathKey, historyPathParts, historyRecord, historyRecordId, historyString, historyStrings, historyTimestamp, historicalActivationIdentity, historicalActivationStatePathParts, historicalManifestPathParts, historicalMetadataPathParts, migrationStateFilePathParts, historicalSourceChangePathParts, parseHistoryJson, rawHistoryDigest, validateHistoricalV2SourceMigrationJournal } from './history-contracts.js';
|
|
8
10
|
import { FileSystemError } from '../domain/project/errors.js';
|
|
9
11
|
import { validateGovernanceCompatibilityMetadata } from './compatibility.js';
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
'dev-proof', 'staging-qualified', 'production-rehearsed', 'green-red-proof',
|
|
18
|
-
'enforcement-approved', 'rulesets-applied', 'live-readback', 'bootstrap-state-disposed'
|
|
19
|
-
];
|
|
20
|
-
const historicalPhaseStates = [
|
|
21
|
-
'pending', 'blocked', 'ready', 'approved', 'running', 'verified', 'failed', 'inapplicable', 'retained', 'disposed'
|
|
22
|
-
];
|
|
23
|
-
const historicalResults = ['verified', 'failed', 'inapplicable', 'retained', 'disposed'];
|
|
24
|
-
const historicalMutationClasses = [
|
|
25
|
-
'none', 'read-worktree', 'write-activation-state', 'write-evidence', 'write-openspec-seed',
|
|
26
|
-
'write-openspec-governance', 'write-local-state', 'delete-local-state', 'write-workflows',
|
|
27
|
-
'write-ruleset-source', 'git-commit', 'git-push', 'github-read', 'github-write',
|
|
28
|
-
'github-secret-write', 'azure-read', 'azure-provider-register', 'azure-network-provision',
|
|
29
|
-
'azure-state-import', 'azure-resource-provision', 'github-ruleset-write'
|
|
30
|
-
];
|
|
31
|
-
const historicalGateKinds = [
|
|
32
|
-
'none', 'repository-publish', 'activation-plan', 'credential-enrollment', 'infrastructure-cost',
|
|
33
|
-
'enforcement', 'destructive-disposal', 'external-blocker'
|
|
34
|
-
];
|
|
35
|
-
const historicalQuestionKinds = [
|
|
36
|
-
'repository-creation-initial-commit-push', 'credential-enrollment',
|
|
37
|
-
'billed-infrastructure-policy-exception-cost-ceiling', 'final-enforcement',
|
|
38
|
-
'destructive-operation', 'external-blocker'
|
|
39
|
-
];
|
|
40
|
-
const destinationTypes = ['local', 'repository', 'subscription', 'environment', 'tenant', 'external'];
|
|
41
|
-
const adapterIds = ['local-evidence', 'selected-spec-workflow', 'git', 'github', 'azure-opentofu', 'local-state'];
|
|
42
|
-
const historicalPhaseGates = {
|
|
43
|
-
'seed-valid': 'none', 'seed-verified': 'none', 'seed-archived': 'none',
|
|
44
|
-
committed: 'repository-publish', pushed: 'repository-publish', 'phase-0-complete': 'none',
|
|
45
|
-
'activation-approved': 'activation-plan', 'credential-ready': 'credential-enrollment',
|
|
46
|
-
'provider-ready': 'infrastructure-cost', 'state-path-selected': 'infrastructure-cost',
|
|
47
|
-
'existing-private-path': 'none', 'bootstrap-local': 'infrastructure-cost',
|
|
48
|
-
'runner-ready': 'infrastructure-cost', 'private-backend-proof': 'none',
|
|
49
|
-
'remote-import-verified': 'infrastructure-cost', 'remote-ready': 'none',
|
|
50
|
-
'application-foundation': 'infrastructure-cost', 'workflow-source-ready': 'none',
|
|
51
|
-
'dev-proof': 'none', 'staging-qualified': 'none', 'production-rehearsed': 'none',
|
|
52
|
-
'green-red-proof': 'none', 'enforcement-approved': 'enforcement', 'rulesets-applied': 'enforcement',
|
|
53
|
-
'live-readback': 'none', 'bootstrap-state-disposed': 'destructive-disposal'
|
|
54
|
-
};
|
|
55
|
-
function phaseId(value, label) {
|
|
56
|
-
return historyEnum(value, historicalPhaseIds, label);
|
|
57
|
-
}
|
|
58
|
-
export function assertHistoricalPhasesComplete(phases) {
|
|
59
|
-
for (const id of historicalPhaseIds) {
|
|
60
|
-
if (phases[id] === undefined)
|
|
61
|
-
historyFail(`phases.${id}`, 'is required.');
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
function reference(value, label) {
|
|
65
|
-
const item = historyExact(value, ['phaseId', 'evidenceId', 'headerDigest', 'result'], label);
|
|
66
|
-
return {
|
|
67
|
-
phaseId: phaseId(item.phaseId, `${label}.phaseId`),
|
|
68
|
-
evidenceId: historyRecordId(item.evidenceId, `${label}.evidenceId`),
|
|
69
|
-
headerDigest: historyDigest(item.headerDigest, `${label}.headerDigest`),
|
|
70
|
-
result: historyEnum(item.result, historicalResults, `${label}.result`)
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
function retention(value, label) {
|
|
74
|
-
const item = historyExact(value, [
|
|
75
|
-
'status', 'remoteImportEvidenceId', 'remoteImportEvidenceDigest', 'retainedAt', 'disposeAfter',
|
|
76
|
-
'encryptedStatePathParts', 'encryptionKeyPathParts'
|
|
77
|
-
], label, ['disposedAt', 'deletionEvidenceId', 'incompleteCleanup']);
|
|
78
|
-
const status = historyEnum(item.status, ['retained', 'disposed'], `${label}.status`);
|
|
79
|
-
const retainedAt = historyTimestamp(item.retainedAt, `${label}.retainedAt`);
|
|
80
|
-
const disposeAfter = historyTimestamp(item.disposeAfter, `${label}.disposeAfter`);
|
|
81
|
-
if (Date.parse(disposeAfter) - Date.parse(retainedAt) !== 30 * 24 * 60 * 60 * 1000) {
|
|
82
|
-
historyFail(label, 'disposeAfter must be exactly 30 days after retainedAt.');
|
|
83
|
-
}
|
|
84
|
-
if (status === 'disposed' && !Object.hasOwn(item, 'disposedAt'))
|
|
85
|
-
historyFail(label, 'disposedAt is required for disposed state.');
|
|
86
|
-
return {
|
|
87
|
-
status,
|
|
88
|
-
remoteImportEvidenceId: historyRecordId(item.remoteImportEvidenceId, `${label}.remoteImportEvidenceId`),
|
|
89
|
-
remoteImportEvidenceDigest: historyDigest(item.remoteImportEvidenceDigest, `${label}.remoteImportEvidenceDigest`),
|
|
90
|
-
retainedAt, disposeAfter,
|
|
91
|
-
encryptedStatePathParts: historyArray(item.encryptedStatePathParts, `${label}.encryptedStatePathParts`)
|
|
92
|
-
.map((entry) => historyPathParts(entry, `${label}.encryptedStatePathParts`)),
|
|
93
|
-
encryptionKeyPathParts: historyArray(item.encryptionKeyPathParts, `${label}.encryptionKeyPathParts`)
|
|
94
|
-
.map((entry) => historyPathParts(entry, `${label}.encryptionKeyPathParts`)),
|
|
95
|
-
...(Object.hasOwn(item, 'disposedAt') ? { disposedAt: historyTimestamp(item.disposedAt, `${label}.disposedAt`) } : {}),
|
|
96
|
-
...(Object.hasOwn(item, 'deletionEvidenceId') ? { deletionEvidenceId: historyRecordId(item.deletionEvidenceId, `${label}.deletionEvidenceId`) } : {}),
|
|
97
|
-
...(Object.hasOwn(item, 'incompleteCleanup') ? { incompleteCleanup: historyStrings(item.incompleteCleanup, `${label}.incompleteCleanup`) } : {})
|
|
98
|
-
};
|
|
99
|
-
}
|
|
12
|
+
import { assertSafeHistoricalBytes, assertSafeHistoricalRecord } from './historical-safety.js';
|
|
13
|
+
import { historicalV1PhaseContractDigests, historicalV1ResultAllowed } from './historical-v1-phase-contracts.js';
|
|
14
|
+
import { validateHistoricalV1AuxiliaryRecord } from './historical-v1-auxiliary.js';
|
|
15
|
+
import { validateHistoricalGovernanceChangeMetadata } from './historical-source-metadata.js';
|
|
16
|
+
import { validateHistoricalV2ActivationState, validateHistoricalV2EvidenceRecord, validateHistoricalV2ApprovalEnvelope, validateHistoricalV2SavedTransitionPlan, validateHistoricalV2Compatibility, historicalV2ApprovalEnvelopeHash, validateHistoricalV2AuxiliaryRecord } from './historical-v2.js';
|
|
17
|
+
export * from './historical-common.js';
|
|
18
|
+
import { historicalPhaseIds, historicalPhaseStates, historicalResults, phaseId, assertHistoricalPhasesComplete, readHistoricalEvidenceReference, validateHistoricalBootstrapState, readHistoricalEvidenceTransition, validateHistoricalEmbeddedPaths, readHistoricalApprovalFields, readHistoricalSavedTransitionPlan } from './historical-common.js';
|
|
100
19
|
export function validateHistoricalActivationState(value) {
|
|
101
20
|
const label = 'historicalActivationState';
|
|
21
|
+
assertSafeHistoricalRecord(value, label);
|
|
102
22
|
const state = historyExact(value, [
|
|
103
23
|
'schemaVersion', 'identity', 'repository', 'activeChange', 'applicability', 'phases', 'createdAt', 'updatedAt'
|
|
104
24
|
], label, ['bootstrapState']);
|
|
105
25
|
historyLiteral(state.schemaVersion, 1, `${label}.schemaVersion`);
|
|
106
|
-
const identity =
|
|
26
|
+
const identity = historicalV1Identity(state.identity, `${label}.identity`);
|
|
107
27
|
const repository = historyExact(state.repository, ['id', 'name', 'defaultBranch'], `${label}.repository`);
|
|
108
28
|
const applicability = historyExact(state.applicability, ['statePath', 'privateStagingDast', 'credentialRequired'], `${label}.applicability`);
|
|
109
29
|
let activeChange = null;
|
|
@@ -119,7 +39,7 @@ export function validateHistoricalActivationState(value) {
|
|
|
119
39
|
for (const id of historicalPhaseIds) {
|
|
120
40
|
const at = `${label}.phases.${id}`;
|
|
121
41
|
const phase = historyExact(rawPhases[id], ['state', 'updatedAt', 'evidence', 'approvals', 'blockers'], at);
|
|
122
|
-
const evidence = historyArray(phase.evidence, `${at}.evidence`).map((entry, index) =>
|
|
42
|
+
const evidence = historyArray(phase.evidence, `${at}.evidence`).map((entry, index) => readHistoricalEvidenceReference(entry, `${at}.evidence[${index}]`));
|
|
123
43
|
if (evidence.some((entry) => entry.phaseId !== id))
|
|
124
44
|
historyFail(at, 'evidence reference names another phase.');
|
|
125
45
|
const approvals = historyArray(phase.approvals, `${at}.approvals`).map((entry) => historyRecordId(entry, `${at}.approvals`));
|
|
@@ -146,29 +66,20 @@ export function validateHistoricalActivationState(value) {
|
|
|
146
66
|
privateStagingDast: historyBoolean(applicability.privateStagingDast, `${label}.applicability.privateStagingDast`),
|
|
147
67
|
credentialRequired: historyBoolean(applicability.credentialRequired, `${label}.applicability.credentialRequired`)
|
|
148
68
|
},
|
|
149
|
-
...(Object.hasOwn(state, 'bootstrapState') ? { bootstrapState:
|
|
69
|
+
...(Object.hasOwn(state, 'bootstrapState') ? { bootstrapState: validateHistoricalBootstrapState(state.bootstrapState, `${label}.bootstrapState`) } : {}),
|
|
150
70
|
phases, createdAt: historyString(state.createdAt, `${label}.createdAt`),
|
|
151
71
|
updatedAt: historyString(state.updatedAt, `${label}.updatedAt`)
|
|
152
72
|
};
|
|
153
73
|
}
|
|
154
|
-
function transition(value, label) {
|
|
155
|
-
const item = historyExact(value, ['phaseId', 'baselineSha', 'inputDigest', 'transitionDigest'], label);
|
|
156
|
-
return {
|
|
157
|
-
phaseId: phaseId(item.phaseId, `${label}.phaseId`),
|
|
158
|
-
baselineSha: historyDigest(item.baselineSha, `${label}.baselineSha`),
|
|
159
|
-
inputDigest: historyDigest(item.inputDigest, `${label}.inputDigest`),
|
|
160
|
-
transitionDigest: historyDigest(item.transitionDigest, `${label}.transitionDigest`)
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
74
|
function historicalProofIdentity(item, label) {
|
|
164
|
-
const identity =
|
|
75
|
+
const identity = historicalV1Identity(item.identity, `${label}.identity`);
|
|
165
76
|
const graphHash = historyDigest(item.phaseGraphHash, `${label}.phaseGraphHash`);
|
|
166
77
|
if (graphHash !== identity.phaseGraphHash)
|
|
167
78
|
historyFail(label, 'graph hash contradicts the historical identity.');
|
|
168
79
|
const id = phaseId(item.phaseId, `${label}.phaseId`);
|
|
169
80
|
const baselineSha = historyDigest(item.baselineSha, `${label}.baselineSha`);
|
|
170
81
|
const inputDigest = historyDigest(item.inputDigest, `${label}.inputDigest`);
|
|
171
|
-
const proofTransition =
|
|
82
|
+
const proofTransition = readHistoricalEvidenceTransition(item.transition, `${label}.transition`);
|
|
172
83
|
if (proofTransition.phaseId !== id || proofTransition.baselineSha !== baselineSha || proofTransition.inputDigest !== inputDigest) {
|
|
173
84
|
historyFail(label, 'transition contradicts the proof phase, baseline or inputs.');
|
|
174
85
|
}
|
|
@@ -180,20 +91,29 @@ function historicalProofIdentity(item, label) {
|
|
|
180
91
|
}
|
|
181
92
|
export function validateHistoricalEvidenceHeader(value) {
|
|
182
93
|
const label = 'historicalEvidenceHeader';
|
|
94
|
+
assertSafeHistoricalRecord(value, label);
|
|
183
95
|
const item = historyExact(value, [
|
|
184
96
|
'schemaVersion', 'repositoryId', 'identity', 'phaseGraphHash', 'phaseId', 'phaseContractDigest',
|
|
185
97
|
'inputDigest', 'baselineSha', 'transition', 'producedAt', 'producer', 'result'
|
|
186
98
|
], label);
|
|
99
|
+
const proof = historicalProofIdentity(item, label);
|
|
100
|
+
const phaseContractDigest = historyDigest(item.phaseContractDigest, `${label}.phaseContractDigest`);
|
|
101
|
+
if (phaseContractDigest !== historicalV1PhaseContractDigests[proof.phaseId]) {
|
|
102
|
+
historyFail(label, 'phase contract digest is not the published v1 behavior.');
|
|
103
|
+
}
|
|
104
|
+
const result = historyEnum(item.result, historicalResults, `${label}.result`);
|
|
105
|
+
if (!historicalV1ResultAllowed(proof.phaseId, result))
|
|
106
|
+
historyFail(label, 'terminal result is not permitted by the published v1 phase.');
|
|
187
107
|
return {
|
|
188
|
-
...
|
|
189
|
-
phaseContractDigest: historyDigest(item.phaseContractDigest, `${label}.phaseContractDigest`),
|
|
108
|
+
...proof, phaseContractDigest,
|
|
190
109
|
producedAt: historyTimestamp(item.producedAt, `${label}.producedAt`),
|
|
191
110
|
producer: historyString(item.producer, `${label}.producer`),
|
|
192
|
-
result
|
|
111
|
+
result
|
|
193
112
|
};
|
|
194
113
|
}
|
|
195
114
|
export function validateHistoricalLiveReadback(value) {
|
|
196
115
|
const label = 'historicalLiveReadback';
|
|
116
|
+
assertSafeHistoricalRecord(value, label);
|
|
197
117
|
const item = historyExact(value, [
|
|
198
118
|
'schemaVersion', 'repositoryId', 'identity', 'phaseGraphHash', 'phaseId', 'baselineSha', 'inputDigest',
|
|
199
119
|
'transition', 'observedAt', 'provider', 'resourceType', 'resourceId', 'sourceDigest', 'readbackDigest', 'matches'
|
|
@@ -209,28 +129,9 @@ export function validateHistoricalLiveReadback(value) {
|
|
|
209
129
|
matches: historyBoolean(item.matches, `${label}.matches`)
|
|
210
130
|
};
|
|
211
131
|
}
|
|
212
|
-
function validateEmbeddedPaths(value, label) {
|
|
213
|
-
if (Array.isArray(value)) {
|
|
214
|
-
value.forEach((entry, index) => validateEmbeddedPaths(entry, `${label}[${index}]`));
|
|
215
|
-
}
|
|
216
|
-
else if (isRecord(value)) {
|
|
217
|
-
for (const [key, entry] of Object.entries(value)) {
|
|
218
|
-
if (key === 'pathParts' || key.endsWith('PathParts')) {
|
|
219
|
-
const parts = historyArray(entry, `${label}.${key}`);
|
|
220
|
-
if (parts.length === 0 && ['cwdPathParts', 'encryptedStatePathParts', 'encryptionKeyPathParts', 'deletedPathParts'].includes(key))
|
|
221
|
-
continue;
|
|
222
|
-
if (parts.length > 0 && Array.isArray(parts[0]))
|
|
223
|
-
parts.forEach((item) => historyPathParts(item, `${label}.${key}`));
|
|
224
|
-
else
|
|
225
|
-
historyPathParts(parts, `${label}.${key}`);
|
|
226
|
-
}
|
|
227
|
-
else
|
|
228
|
-
validateEmbeddedPaths(entry, `${label}.${key}`);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
132
|
export function validateHistoricalEvidenceRecord(value, headerOnlyEvidenceId) {
|
|
233
133
|
const label = 'historicalEvidenceRecord';
|
|
134
|
+
assertSafeHistoricalRecord(value, label);
|
|
234
135
|
const item = historyRecord(value, label);
|
|
235
136
|
// Header-only JSON was an explicit v1 format in commands.ts, not an unversioned import.
|
|
236
137
|
if (!Object.hasOwn(item, 'header')) {
|
|
@@ -241,7 +142,7 @@ export function validateHistoricalEvidenceRecord(value, headerOnlyEvidenceId) {
|
|
|
241
142
|
}
|
|
242
143
|
historyExact(item, ['evidenceId', 'header'], label, ['payload', 'liveReadback']);
|
|
243
144
|
if (Object.hasOwn(item, 'payload'))
|
|
244
|
-
|
|
145
|
+
validateHistoricalEmbeddedPaths(item.payload, `${label}.payload`);
|
|
245
146
|
const header = validateHistoricalEvidenceHeader(item.header);
|
|
246
147
|
const liveReadback = Object.hasOwn(item, 'liveReadback')
|
|
247
148
|
? historyArray(item.liveReadback, `${label}.liveReadback`).map(validateHistoricalLiveReadback) : undefined;
|
|
@@ -255,169 +156,23 @@ export function validateHistoricalEvidenceRecord(value, headerOnlyEvidenceId) {
|
|
|
255
156
|
...(liveReadback === undefined ? {} : { liveReadback })
|
|
256
157
|
};
|
|
257
158
|
}
|
|
258
|
-
function uniqueSorted(values, key, label) {
|
|
259
|
-
if (new Set(values.map(key)).size !== values.length)
|
|
260
|
-
historyFail(label, 'contains duplicate scope entries.');
|
|
261
|
-
return values.sort((a, b) => key(a).localeCompare(key(b), 'en'));
|
|
262
|
-
}
|
|
263
|
-
function nullableString(value, label) {
|
|
264
|
-
return value === null ? null : historyString(value, label);
|
|
265
|
-
}
|
|
266
159
|
export function validateHistoricalApprovalEnvelope(value) {
|
|
267
|
-
const
|
|
268
|
-
const
|
|
269
|
-
|
|
270
|
-
'
|
|
271
|
-
], label);
|
|
272
|
-
const id = phaseId(item.phaseId, `${label}.phaseId`);
|
|
273
|
-
const gateKind = historyEnum(item.gateKind, historicalGateKinds, `${label}.gateKind`);
|
|
274
|
-
if (gateKind !== historicalPhaseGates[id])
|
|
275
|
-
historyFail(label, 'gate does not match its historical phase.');
|
|
276
|
-
const resources = historyArray(item.resources, `${label}.resources`).map((entry, index) => {
|
|
277
|
-
const at = `${label}.resources[${index}]`;
|
|
278
|
-
const resource = historyExact(entry, ['type', 'identity'], at);
|
|
279
|
-
return { type: historyString(resource.type, `${at}.type`).trim().toLowerCase(), identity: historyString(resource.identity, `${at}.identity`).trim() };
|
|
280
|
-
});
|
|
281
|
-
const destinations = historyArray(item.destinations, `${label}.destinations`).map((entry, index) => {
|
|
282
|
-
const at = `${label}.destinations[${index}]`;
|
|
283
|
-
const destination = historyExact(entry, ['type', 'identity', 'repository', 'subscriptionId'], at);
|
|
284
|
-
return {
|
|
285
|
-
type: historyEnum(destination.type, destinationTypes, `${at}.type`),
|
|
286
|
-
identity: historyString(destination.identity, `${at}.identity`).trim(),
|
|
287
|
-
repository: nullableString(destination.repository, `${at}.repository`)?.trim() ?? null,
|
|
288
|
-
subscriptionId: nullableString(destination.subscriptionId, `${at}.subscriptionId`)?.trim() ?? null
|
|
289
|
-
};
|
|
290
|
-
});
|
|
291
|
-
const cost = historyExact(item.costCeiling, ['currency', 'fixedMonthlyCents', 'usageMonthlyCents'], `${label}.costCeiling`);
|
|
292
|
-
const currency = historyString(cost.currency, `${label}.costCeiling.currency`).trim();
|
|
293
|
-
if (!/^[A-Z]{3}$/u.test(currency))
|
|
294
|
-
historyFail(label, 'cost currency must be a three-letter uppercase ISO currency.');
|
|
295
|
-
const cents = (value, at) => {
|
|
296
|
-
if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < 0)
|
|
297
|
-
historyFail(at, 'must be non-negative safe integer cents.');
|
|
298
|
-
return value;
|
|
299
|
-
};
|
|
300
|
-
const scopeStrings = (value, at, lower = false) => uniqueSorted(historyStrings(value, at).map((entry) => lower ? entry.trim().toLowerCase() : entry.trim()), (entry) => entry, at);
|
|
160
|
+
const item = historyRecord(value, 'historicalApprovalEnvelope');
|
|
161
|
+
const fields = readHistoricalApprovalFields(value);
|
|
162
|
+
if (Date.parse(fields.approvedAt) >= Date.parse(fields.expiresAt))
|
|
163
|
+
historyFail('historicalApprovalEnvelope', 'requires approvedAt before expiresAt.');
|
|
301
164
|
return {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
identity:
|
|
305
|
-
baselineSha: historyDigest(item.baselineSha, `${label}.baselineSha`),
|
|
306
|
-
planDigest: historyDigest(item.planDigest, `${label}.planDigest`),
|
|
307
|
-
resources: uniqueSorted(resources, canonicalJson, `${label}.resources`),
|
|
308
|
-
destinations: uniqueSorted(destinations, canonicalJson, `${label}.destinations`),
|
|
309
|
-
permissions: scopeStrings(item.permissions, `${label}.permissions`, true),
|
|
310
|
-
costCeiling: {
|
|
311
|
-
currency, fixedMonthlyCents: cents(cost.fixedMonthlyCents, `${label}.costCeiling.fixedMonthlyCents`),
|
|
312
|
-
usageMonthlyCents: cents(cost.usageMonthlyCents, `${label}.costCeiling.usageMonthlyCents`)
|
|
313
|
-
},
|
|
314
|
-
policyExceptions: scopeStrings(item.policyExceptions, `${label}.policyExceptions`),
|
|
315
|
-
destructiveScope: scopeStrings(item.destructiveScope, `${label}.destructiveScope`),
|
|
316
|
-
expiresAt: historyTimestamp(item.expiresAt, `${label}.expiresAt`),
|
|
317
|
-
approvedAt: historyTimestamp(item.approvedAt, `${label}.approvedAt`),
|
|
318
|
-
approver: historyString(item.approver, `${label}.approver`)
|
|
165
|
+
...fields,
|
|
166
|
+
schemaVersion: historyLiteral(item.schemaVersion, 1, 'historicalApprovalEnvelope.schemaVersion'),
|
|
167
|
+
identity: historicalV1Identity(item.identity, 'historicalApprovalEnvelope.identity')
|
|
319
168
|
};
|
|
320
169
|
}
|
|
321
170
|
export function historicalApprovalEnvelopeHash(envelope) {
|
|
322
171
|
const { id: _id, approvedAt: _approvedAt, approver: _approver, ...scope } = validateHistoricalApprovalEnvelope(envelope);
|
|
323
172
|
return canonicalSha256(scope);
|
|
324
173
|
}
|
|
325
|
-
function operationDestination(value, label) {
|
|
326
|
-
const item = historyExact(value, ['type', 'identity'], label, ['pathParts', 'repository', 'subscriptionId', 'ref']);
|
|
327
|
-
return {
|
|
328
|
-
type: historyEnum(item.type, destinationTypes, `${label}.type`),
|
|
329
|
-
identity: historyString(item.identity, `${label}.identity`),
|
|
330
|
-
...(Object.hasOwn(item, 'pathParts') ? { pathParts: historyPathParts(item.pathParts, `${label}.pathParts`) } : {}),
|
|
331
|
-
...(Object.hasOwn(item, 'repository') ? { repository: historyString(item.repository, `${label}.repository`) } : {}),
|
|
332
|
-
...(Object.hasOwn(item, 'subscriptionId') ? { subscriptionId: historyString(item.subscriptionId, `${label}.subscriptionId`) } : {}),
|
|
333
|
-
...(Object.hasOwn(item, 'ref') ? { ref: historyString(item.ref, `${label}.ref`) } : {})
|
|
334
|
-
};
|
|
335
|
-
}
|
|
336
|
-
function operation(value, label) {
|
|
337
|
-
const item = historyExact(value, ['adapter', 'actionId', 'mutationClass', 'phaseId', 'inputs', 'destination', 'remote', 'destructive'], label);
|
|
338
|
-
const inputs = historyRecord(item.inputs, `${label}.inputs`);
|
|
339
|
-
validateEmbeddedPaths(inputs, `${label}.inputs`);
|
|
340
|
-
return {
|
|
341
|
-
adapter: historyEnum(item.adapter, adapterIds, `${label}.adapter`),
|
|
342
|
-
actionId: historyString(item.actionId, `${label}.actionId`),
|
|
343
|
-
mutationClass: historyEnum(item.mutationClass, historicalMutationClasses, `${label}.mutationClass`),
|
|
344
|
-
phaseId: phaseId(item.phaseId, `${label}.phaseId`), inputs,
|
|
345
|
-
destination: operationDestination(item.destination, `${label}.destination`),
|
|
346
|
-
remote: historyBoolean(item.remote, `${label}.remote`), destructive: historyBoolean(item.destructive, `${label}.destructive`)
|
|
347
|
-
};
|
|
348
|
-
}
|
|
349
|
-
function evaluation(value, label) {
|
|
350
|
-
const item = historyExact(value, [
|
|
351
|
-
'phaseId', 'gateKind', 'questionKind', 'approvalRequired', 'status', 'envelopeId', 'envelopeHash', 'reasons', 'expansionReasons'
|
|
352
|
-
], label);
|
|
353
|
-
return {
|
|
354
|
-
phaseId: phaseId(item.phaseId, `${label}.phaseId`),
|
|
355
|
-
gateKind: historyEnum(item.gateKind, historicalGateKinds, `${label}.gateKind`),
|
|
356
|
-
questionKind: item.questionKind === null ? null : historyEnum(item.questionKind, historicalQuestionKinds, `${label}.questionKind`),
|
|
357
|
-
approvalRequired: historyBoolean(item.approvalRequired, `${label}.approvalRequired`),
|
|
358
|
-
status: historyEnum(item.status, ['not-required', 'approval-required', 'reused', 'expired', 'invalidated'], `${label}.status`),
|
|
359
|
-
envelopeId: item.envelopeId === null ? null : historyRecordId(item.envelopeId, `${label}.envelopeId`),
|
|
360
|
-
envelopeHash: item.envelopeHash === null ? null : historyDigest(item.envelopeHash, `${label}.envelopeHash`),
|
|
361
|
-
reasons: historyStrings(item.reasons, `${label}.reasons`), expansionReasons: historyStrings(item.expansionReasons, `${label}.expansionReasons`)
|
|
362
|
-
};
|
|
363
|
-
}
|
|
364
|
-
function rollbackPlan(value, label) {
|
|
365
|
-
const item = historyExact(value, ['phaseId', 'strategy', 'target', 'operations', 'retained', 'cleanupWarnings'], label);
|
|
366
|
-
return {
|
|
367
|
-
phaseId: phaseId(item.phaseId, `${label}.phaseId`),
|
|
368
|
-
strategy: historyEnum(item.strategy, ['none', 'retain', 'reverse-to', 'dispose'], `${label}.strategy`),
|
|
369
|
-
target: item.target === null ? null : phaseId(item.target, `${label}.target`),
|
|
370
|
-
operations: historyArray(item.operations, `${label}.operations`).map((entry, index) => operation(entry, `${label}.operations[${index}]`)),
|
|
371
|
-
retained: historyStrings(item.retained, `${label}.retained`), cleanupWarnings: historyStrings(item.cleanupWarnings, `${label}.cleanupWarnings`)
|
|
372
|
-
};
|
|
373
|
-
}
|
|
374
174
|
export function validateHistoricalSavedTransitionPlan(value) {
|
|
375
|
-
|
|
376
|
-
const item = historyExact(value, [
|
|
377
|
-
'schemaVersion', 'phaseId', 'createdAt', 'expiresAt', 'identity', 'graphHash', 'stateHash',
|
|
378
|
-
'baselineDigest', 'inputDigest', 'transitionDigest', 'planDigest', 'mutationClasses', 'operations',
|
|
379
|
-
'approval', 'rollbackPlan', 'noSecrets'
|
|
380
|
-
], label);
|
|
381
|
-
const identity = historicalIdentity(item.identity, `${label}.identity`);
|
|
382
|
-
const graphHash = historyDigest(item.graphHash, `${label}.graphHash`);
|
|
383
|
-
if (graphHash !== identity.phaseGraphHash)
|
|
384
|
-
historyFail(label, 'graphHash contradicts the historical identity.');
|
|
385
|
-
const id = phaseId(item.phaseId, `${label}.phaseId`);
|
|
386
|
-
const operations = historyArray(item.operations, `${label}.operations`).map((entry, index) => operation(entry, `${label}.operations[${index}]`));
|
|
387
|
-
if (operations.some((entry) => entry.phaseId !== id))
|
|
388
|
-
historyFail(label, 'operation phase differs from its plan.');
|
|
389
|
-
const mutations = historyExact(item.mutationClasses, ['local', 'remote'], `${label}.mutationClasses`);
|
|
390
|
-
const approval = historyExact(item.approval, ['gateKind', 'required', 'evaluation', 'envelopeId', 'envelopeHash'], `${label}.approval`);
|
|
391
|
-
const approvalEvaluation = evaluation(approval.evaluation, `${label}.approval.evaluation`);
|
|
392
|
-
const gateKind = historyEnum(approval.gateKind, historicalGateKinds, `${label}.approval.gateKind`);
|
|
393
|
-
const required = historyBoolean(approval.required, `${label}.approval.required`);
|
|
394
|
-
const envelopeId = approval.envelopeId === null ? null : historyRecordId(approval.envelopeId, `${label}.approval.envelopeId`);
|
|
395
|
-
const envelopeHash = approval.envelopeHash === null ? null : historyDigest(approval.envelopeHash, `${label}.approval.envelopeHash`);
|
|
396
|
-
if (gateKind !== historicalPhaseGates[id] || required !== (gateKind !== 'none') ||
|
|
397
|
-
approvalEvaluation.phaseId !== id || approvalEvaluation.gateKind !== gateKind ||
|
|
398
|
-
approvalEvaluation.envelopeId !== envelopeId || approvalEvaluation.envelopeHash !== envelopeHash ||
|
|
399
|
-
(envelopeId === null) !== (envelopeHash === null)) {
|
|
400
|
-
historyFail(label, 'approval references or gate contradict the historical phase.');
|
|
401
|
-
}
|
|
402
|
-
const rollback = rollbackPlan(item.rollbackPlan, `${label}.rollbackPlan`);
|
|
403
|
-
if (rollback.phaseId !== id)
|
|
404
|
-
historyFail(label, 'rollback names another phase.');
|
|
405
|
-
return {
|
|
406
|
-
schemaVersion: historyLiteral(item.schemaVersion, 1, `${label}.schemaVersion`), phaseId: id,
|
|
407
|
-
createdAt: historyTimestamp(item.createdAt, `${label}.createdAt`),
|
|
408
|
-
expiresAt: historyTimestamp(item.expiresAt, `${label}.expiresAt`), identity, graphHash,
|
|
409
|
-
stateHash: item.stateHash === null ? null : historyDigest(item.stateHash, `${label}.stateHash`),
|
|
410
|
-
baselineDigest: historyDigest(item.baselineDigest, `${label}.baselineDigest`),
|
|
411
|
-
inputDigest: historyDigest(item.inputDigest, `${label}.inputDigest`),
|
|
412
|
-
transitionDigest: historyDigest(item.transitionDigest, `${label}.transitionDigest`),
|
|
413
|
-
planDigest: historyDigest(item.planDigest, `${label}.planDigest`),
|
|
414
|
-
mutationClasses: {
|
|
415
|
-
local: historyArray(mutations.local, `${label}.mutationClasses.local`).map((entry) => historyEnum(entry, historicalMutationClasses, `${label}.mutationClasses.local`)),
|
|
416
|
-
remote: historyArray(mutations.remote, `${label}.mutationClasses.remote`).map((entry) => historyEnum(entry, historicalMutationClasses, `${label}.mutationClasses.remote`))
|
|
417
|
-
},
|
|
418
|
-
operations, approval: { gateKind, required, evaluation: approvalEvaluation, envelopeId, envelopeHash },
|
|
419
|
-
rollbackPlan: rollback, noSecrets: historyLiteral(item.noSecrets, true, `${label}.noSecrets`)
|
|
420
|
-
};
|
|
175
|
+
return readHistoricalSavedTransitionPlan(value, historicalV1Identity);
|
|
421
176
|
}
|
|
422
177
|
export function historicalTransitionPlanPathParts(plan) {
|
|
423
178
|
return historyPathParts([
|
|
@@ -481,16 +236,32 @@ export async function captureHistoryFile(projectRoot, parts) {
|
|
|
481
236
|
if (!details.isFile() || details.isSymbolicLink() || details.nlink !== 1) {
|
|
482
237
|
historyFail(historyPathKey(pathParts), 'must be a regular unlinked file.', 'unsafe-history-path');
|
|
483
238
|
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
239
|
+
if (details.size > 8 * 1024 * 1024) {
|
|
240
|
+
historyFail(historyPathKey(pathParts), 'exceeds the bounded control-record size; no contents were read.', 'unsafe-historical-payload');
|
|
241
|
+
}
|
|
242
|
+
const handle = await open(target, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0) | (constants.O_NONBLOCK ?? 0));
|
|
243
|
+
try {
|
|
244
|
+
const opened = await handle.stat();
|
|
245
|
+
if (!opened.isFile() || opened.ino !== details.ino || opened.dev !== details.dev || opened.nlink !== 1 ||
|
|
246
|
+
opened.mode !== details.mode || opened.size !== details.size || opened.mtimeMs !== details.mtimeMs) {
|
|
247
|
+
historyFail(historyPathKey(pathParts), 'changed before the bounded read.', 'historical-source-changed');
|
|
248
|
+
}
|
|
249
|
+
await resolveHistoryProjectPath(projectRoot, pathParts);
|
|
250
|
+
const content = await handle.readFile();
|
|
251
|
+
const after = await lstat(await resolveHistoryProjectPath(projectRoot, pathParts));
|
|
252
|
+
if (after.ino !== details.ino || after.dev !== details.dev || after.mode !== details.mode ||
|
|
253
|
+
after.size !== details.size || after.mtimeMs !== details.mtimeMs || after.nlink !== 1) {
|
|
254
|
+
historyFail(historyPathKey(pathParts), 'changed while being read; obtain a fresh preview.', 'historical-source-changed');
|
|
255
|
+
}
|
|
256
|
+
return { pathParts, content, mode: details.mode & 0o7777 };
|
|
257
|
+
}
|
|
258
|
+
finally {
|
|
259
|
+
await handle.close();
|
|
489
260
|
}
|
|
490
|
-
return { pathParts, content, mode: details.mode & 0o7777 };
|
|
491
261
|
}
|
|
492
262
|
export function validateHistoricalSourceManifest(value) {
|
|
493
263
|
const label = 'historicalSourceManifest';
|
|
264
|
+
assertSafeHistoricalRecord(value, label);
|
|
494
265
|
const raw = historyRecord(value, label);
|
|
495
266
|
historyLiteral(raw.artifactVersion, 7, `${label}.artifactVersion`);
|
|
496
267
|
const governance = historyRecord(raw.governance, `${label}.governance`);
|
|
@@ -525,7 +296,7 @@ function validateHistoricalCompatibility(value, label) {
|
|
|
525
296
|
const tuples = historyArray(activation.currentCompatibleTuples, `${label}.activation.currentCompatibleTuples`);
|
|
526
297
|
if (tuples.length !== 1)
|
|
527
298
|
historyFail(label, 'requires the single registered historical activation tuple.');
|
|
528
|
-
|
|
299
|
+
historicalV1Identity(tuples[0], `${label}.activation.currentCompatibleTuples[0]`);
|
|
529
300
|
if (canonicalJson(activation.recognizedGraphHashes) !== canonicalJson([historicalActivationIdentity.phaseGraphHash]) ||
|
|
530
301
|
canonicalJson(activation.graphMappings) !== '[]\n' || canonicalJson(activation.historicalStateMigrations) !== '[]\n') {
|
|
531
302
|
historyFail(label, 'contains an unknown historical graph or migration declaration.');
|
|
@@ -557,6 +328,7 @@ function validateHistoricalCompatibility(value, label) {
|
|
|
557
328
|
}
|
|
558
329
|
function validateHistoricalMetadata(file) {
|
|
559
330
|
const label = historyPathKey(file.pathParts);
|
|
331
|
+
assertSafeHistoricalBytes(file.content, label);
|
|
560
332
|
if (!label.endsWith('.json'))
|
|
561
333
|
return;
|
|
562
334
|
const value = parseHistoryJson(file.content, label);
|
|
@@ -564,7 +336,8 @@ function validateHistoricalMetadata(file) {
|
|
|
564
336
|
// It is preserved as source bytes, never used to authorize a migration lane.
|
|
565
337
|
if (label === '.liftoff/governance/phase-graph.json') {
|
|
566
338
|
const digest = canonicalSha256(value);
|
|
567
|
-
if (
|
|
339
|
+
if (!historicalActivationIdentities.some((identity) => identity.phaseGraphHash === digest) &&
|
|
340
|
+
digest !== currentActivationIdentity.phaseGraphHash) {
|
|
568
341
|
historyFail(label, 'is neither the registered historical nor the installed current managed graph.', 'unsupported-historical-graph');
|
|
569
342
|
}
|
|
570
343
|
}
|
|
@@ -572,6 +345,9 @@ function validateHistoricalMetadata(file) {
|
|
|
572
345
|
if (isRecord(value) && value.schemaVersion === 1) {
|
|
573
346
|
validateHistoricalCompatibility(value, label);
|
|
574
347
|
}
|
|
348
|
+
else if (isRecord(value) && (value.schemaVersion === 2 || value.schemaVersion === 3)) {
|
|
349
|
+
validateHistoricalV2Compatibility(value, label);
|
|
350
|
+
}
|
|
575
351
|
else {
|
|
576
352
|
try {
|
|
577
353
|
const metadata = validateGovernanceCompatibilityMetadata(value);
|
|
@@ -601,7 +377,7 @@ function validateHistoricalMetadata(file) {
|
|
|
601
377
|
if (historyArray(context.commands, `${label}.commands`).length === 0)
|
|
602
378
|
historyFail(label, 'context must declare its generated commands.');
|
|
603
379
|
historyRecord(context.generatedBoundaries, `${label}.generatedBoundaries`);
|
|
604
|
-
|
|
380
|
+
validateHistoricalEmbeddedPaths(context, label);
|
|
605
381
|
}
|
|
606
382
|
else if (label === '.liftoff/governance/credential-policy.schema.json') {
|
|
607
383
|
const schema = historyRecord(value, label);
|
|
@@ -614,7 +390,8 @@ function validateHistoricalMetadata(file) {
|
|
|
614
390
|
return [key, declaration.const];
|
|
615
391
|
}));
|
|
616
392
|
const identityDigest = canonicalSha256(declaredIdentity);
|
|
617
|
-
if (
|
|
393
|
+
if (!historicalActivationIdentities.some((identity) => identityDigest === canonicalSha256(identity)) &&
|
|
394
|
+
identityDigest !== canonicalSha256(currentActivationIdentity)) {
|
|
618
395
|
historyFail(label, 'credential schema must declare the exact historical or installed current activation identity.', 'invalid-managed-source-metadata');
|
|
619
396
|
}
|
|
620
397
|
}
|
|
@@ -626,6 +403,7 @@ function sourceFile(snapshot, kind) {
|
|
|
626
403
|
if (snapshot.content === undefined || snapshot.mode === undefined) {
|
|
627
404
|
historyFail(historyPathKey(snapshot.pathParts), 'required historical source is missing.', 'missing-historical-record');
|
|
628
405
|
}
|
|
406
|
+
assertSafeHistoricalBytes(snapshot.content, historyPathKey(snapshot.pathParts));
|
|
629
407
|
return {
|
|
630
408
|
kind, pathParts: [...snapshot.pathParts], content: snapshot.content,
|
|
631
409
|
digest: rawHistoryDigest(snapshot.content), mode: snapshot.mode
|
|
@@ -657,21 +435,86 @@ function validateAt(file, validator) {
|
|
|
657
435
|
return historyFail(historyPathKey(file.pathParts), error.message, error.code);
|
|
658
436
|
}
|
|
659
437
|
}
|
|
438
|
+
export function validateReadableHistoricalActivationState(value) {
|
|
439
|
+
const item = historyRecord(value, 'historicalActivationState');
|
|
440
|
+
return isHistoricalV2ActivationIdentity(item.identity)
|
|
441
|
+
? validateHistoricalV2ActivationState(value) : validateHistoricalActivationState(value);
|
|
442
|
+
}
|
|
660
443
|
export async function readHistoricalActivationInventory(projectRoot, options = {}) {
|
|
444
|
+
return readHistoricalInventory((parts) => captureHistoryFile(projectRoot, parts), (directory) => historicalActiveRecordPaths(projectRoot, directory), options);
|
|
445
|
+
}
|
|
446
|
+
/** Read only the exact stored copies in an index; never substitute current active files. */
|
|
447
|
+
export async function readHistoricalSnapshotInventory(projectRoot, index) {
|
|
448
|
+
const byPath = new Map(index.files.map((file) => [historyPathKey(file.originalPathParts), file]));
|
|
449
|
+
const inventory = await readHistoricalInventory(async (parts) => {
|
|
450
|
+
const file = byPath.get(historyPathKey(parts));
|
|
451
|
+
if (!file)
|
|
452
|
+
return { pathParts: [...parts] };
|
|
453
|
+
const copy = await captureHistoryFile(projectRoot, file.copyPathParts);
|
|
454
|
+
if (!copy.content || rawHistoryDigest(copy.content) !== file.digest) {
|
|
455
|
+
historyFail(historyPathKey(file.copyPathParts), 'declared copy is missing or differs from its recorded raw digest.', 'history-digest-mismatch');
|
|
456
|
+
}
|
|
457
|
+
return { pathParts: [...parts], content: copy.content, mode: file.mode };
|
|
458
|
+
}, async (directory) => index.files
|
|
459
|
+
.filter((file) => file.originalPathParts.length === 3 && file.originalPathParts[0] === 'governance' &&
|
|
460
|
+
file.originalPathParts[1] === directory).map((file) => [...file.originalPathParts]), {
|
|
461
|
+
reviewedUnreferencedPathParts: index.files
|
|
462
|
+
.filter((file) => ['evidence', 'plan', 'approval'].includes(file.kind)).map((file) => file.originalPathParts)
|
|
463
|
+
}, false);
|
|
464
|
+
if (canonicalSha256(inventory.state.identity) !== canonicalSha256(index.sourceIdentity) ||
|
|
465
|
+
canonicalSha256(inventory.files.map((file) => historyPathKey(file.pathParts)).sort()) !==
|
|
466
|
+
canonicalSha256(index.files.map((file) => historyPathKey(file.originalPathParts)).sort())) {
|
|
467
|
+
historyFail('activationHistoryIndex', 'source contract or complete record inventory contradicts its snapshot.', 'invalid-historical-reference');
|
|
468
|
+
}
|
|
469
|
+
return inventory;
|
|
470
|
+
}
|
|
471
|
+
async function readHistoricalInventory(captureSource, activeRecordPaths, options, requireSourceChange = true) {
|
|
661
472
|
const preconditions = [];
|
|
662
473
|
const capture = async (parts) => {
|
|
663
|
-
const snapshot = await
|
|
474
|
+
const snapshot = await captureSource(parts);
|
|
664
475
|
preconditions.push(snapshot);
|
|
665
476
|
return snapshot;
|
|
666
477
|
};
|
|
667
478
|
const manifestFile = sourceFile(await capture(historicalManifestPathParts), 'manifest');
|
|
668
479
|
const stateFile = sourceFile(await capture(historicalActivationStatePathParts), 'state');
|
|
669
480
|
const manifest = validateAt(manifestFile, validateHistoricalSourceManifest);
|
|
670
|
-
const state = validateAt(stateFile,
|
|
481
|
+
const state = validateAt(stateFile, validateReadableHistoricalActivationState);
|
|
482
|
+
if (!('activationIdentity' in manifest.governance) ||
|
|
483
|
+
canonicalSha256(manifest.governance.activationIdentity) !== canonicalSha256(state.identity)) {
|
|
484
|
+
historyFail(historyPathKey(stateFile.pathParts), 'active state and manifest declare different source contracts.', 'mixed-active-identity');
|
|
485
|
+
}
|
|
671
486
|
if (state.activeChange !== null && state.activeChange.kind !== manifest.project.specWorkflow) {
|
|
672
487
|
historyFail(historyPathKey(stateFile.pathParts), 'active change belongs to a different spec workflow.', 'historical-spec-ownership-conflict');
|
|
673
488
|
}
|
|
674
489
|
const files = [manifestFile, stateFile];
|
|
490
|
+
let sourceMigration;
|
|
491
|
+
const migrationSnapshot = await capture(migrationStateFilePathParts);
|
|
492
|
+
if (migrationSnapshot.content !== undefined) {
|
|
493
|
+
if (state.schemaVersion !== 2)
|
|
494
|
+
historyFail(historyPathKey(migrationStateFilePathParts), 'v1 active state cannot have a committed v2 source migration.', 'mixed-active-identity');
|
|
495
|
+
const file = sourceFile(migrationSnapshot, 'migration');
|
|
496
|
+
sourceMigration = validateAt(file, validateHistoricalV2SourceMigrationJournal);
|
|
497
|
+
if (sourceMigration.successor.repositoryId !== state.repository.id || sourceMigration.successor.createdAt !== state.createdAt) {
|
|
498
|
+
historyFail(historyPathKey(file.pathParts), 'source migration does not identify this active v2 successor.', 'invalid-historical-reference');
|
|
499
|
+
}
|
|
500
|
+
files.push(file);
|
|
501
|
+
}
|
|
502
|
+
let sourceChangeMetadata;
|
|
503
|
+
if (state.activeChange) {
|
|
504
|
+
const base = historicalSourceChangePathParts(state.activeChange);
|
|
505
|
+
const metadataSnapshot = await capture([...base, 'liftoff-governance.json']);
|
|
506
|
+
const tasksSnapshot = await capture([...base, 'tasks.md']);
|
|
507
|
+
if (requireSourceChange || metadataSnapshot.content !== undefined || tasksSnapshot.content !== undefined) {
|
|
508
|
+
const metadataFile = sourceFile(metadataSnapshot, 'source-metadata');
|
|
509
|
+
const tasksFile = sourceFile(tasksSnapshot, 'source-tasks');
|
|
510
|
+
sourceChangeMetadata = validateAt(metadataFile, validateHistoricalGovernanceChangeMetadata);
|
|
511
|
+
if (sourceChangeMetadata.changeId !== state.activeChange.id || sourceChangeMetadata.workflowKind !== state.activeChange.kind ||
|
|
512
|
+
![state.identity, ...(sourceMigration ? [sourceMigration.sourceIdentity] : [])].some((identity) => canonicalSha256(identity) === canonicalSha256(sourceChangeMetadata.activationIdentity))) {
|
|
513
|
+
historyFail(historyPathKey(metadataFile.pathParts), 'metadata does not match the recorded historical source change.', 'invalid-historical-reference');
|
|
514
|
+
}
|
|
515
|
+
files.push(metadataFile, tasksFile);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
675
518
|
for (const parts of historicalMetadataPathParts) {
|
|
676
519
|
const snapshot = await capture(parts);
|
|
677
520
|
if (snapshot.content === undefined)
|
|
@@ -684,12 +527,13 @@ export async function readHistoricalActivationInventory(projectRoot, options = {
|
|
|
684
527
|
const plans = [];
|
|
685
528
|
const approvals = new Map();
|
|
686
529
|
for (const [directory, kind] of [['evidence', 'evidence'], ['plans', 'plan'], ['approvals', 'approval']]) {
|
|
687
|
-
for (const parts of await
|
|
530
|
+
for (const parts of await activeRecordPaths(directory)) {
|
|
688
531
|
const file = sourceFile(await capture(parts), kind);
|
|
689
532
|
if (!parts[2].endsWith('.json'))
|
|
690
533
|
historyFail(historyPathKey(parts), 'active record extension is not the registered lowercase .json layout.');
|
|
691
534
|
if (kind === 'evidence') {
|
|
692
|
-
const record = validateAt(file, (value) =>
|
|
535
|
+
const record = validateAt(file, (value) => state.schemaVersion === 2
|
|
536
|
+
? validateHistoricalV2EvidenceRecord(value) : validateHistoricalEvidenceRecord(value, parts[2].slice(0, -5)));
|
|
693
537
|
if (record.header.repositoryId !== state.repository.id)
|
|
694
538
|
historyFail(historyPathKey(parts), 'historical evidence belongs to another repository.', 'invalid-historical-reference');
|
|
695
539
|
if (evidence.has(record.evidenceId))
|
|
@@ -697,24 +541,44 @@ export async function readHistoricalActivationInventory(projectRoot, options = {
|
|
|
697
541
|
evidence.set(record.evidenceId, { file, record });
|
|
698
542
|
}
|
|
699
543
|
else if (kind === 'plan') {
|
|
700
|
-
plans.push({ file, record: validateAt(file,
|
|
544
|
+
plans.push({ file, record: validateAt(file, (value) => state.schemaVersion === 2
|
|
545
|
+
? validateHistoricalV2SavedTransitionPlan(value) : validateHistoricalSavedTransitionPlan(value)) });
|
|
701
546
|
}
|
|
702
547
|
else {
|
|
703
|
-
const record = validateAt(file,
|
|
548
|
+
const record = validateAt(file, (value) => state.schemaVersion === 2
|
|
549
|
+
? validateHistoricalV2ApprovalEnvelope(value) : validateHistoricalApprovalEnvelope(value));
|
|
704
550
|
if (approvals.has(record.id))
|
|
705
551
|
historyFail(historyPathKey(parts), 'duplicates an approval identity.');
|
|
706
552
|
approvals.set(record.id, { file, record });
|
|
707
553
|
}
|
|
708
554
|
}
|
|
709
555
|
}
|
|
710
|
-
for (const directory of ['supersessions', 'reconciliation']) {
|
|
711
|
-
const records = await
|
|
712
|
-
|
|
713
|
-
|
|
556
|
+
for (const [directory, kind] of [['supersessions', 'supersession'], ['reconciliation', 'reconciliation']]) {
|
|
557
|
+
const records = await activeRecordPaths(directory);
|
|
558
|
+
for (const parts of records) {
|
|
559
|
+
const file = sourceFile(await capture(parts), kind);
|
|
560
|
+
try {
|
|
561
|
+
validateAt(file, (value) => state.schemaVersion === 1
|
|
562
|
+
? validateHistoricalV1AuxiliaryRecord(value, kind) : validateHistoricalV2AuxiliaryRecord(value, kind));
|
|
563
|
+
}
|
|
564
|
+
catch (error) {
|
|
565
|
+
if (!(error instanceof ActivationHistoryError) || error.code !== 'invalid-history-record')
|
|
566
|
+
throw error;
|
|
567
|
+
historyFail(historyPathKey(parts), error.message, 'unsupported-active-record');
|
|
568
|
+
}
|
|
569
|
+
files.push(file);
|
|
570
|
+
}
|
|
714
571
|
}
|
|
715
572
|
for (const parts of [['governance', 'credentials', 'preflight-policy.json'], ['governance', 'activation-baseline.json']]) {
|
|
716
|
-
|
|
717
|
-
|
|
573
|
+
const snapshot = await capture(parts);
|
|
574
|
+
if (snapshot.content !== undefined) {
|
|
575
|
+
if (parts[1] === 'activation-baseline.json') {
|
|
576
|
+
historyFail(historyPathKey(parts), 'active auxiliary proof has no registered source retirement contract; it cannot be silently carried forward.', 'unsupported-active-record');
|
|
577
|
+
}
|
|
578
|
+
const file = sourceFile(snapshot, 'credential-policy');
|
|
579
|
+
validateAt(file, (value) => state.schemaVersion === 1
|
|
580
|
+
? validateHistoricalV1AuxiliaryRecord(value, 'credential-policy') : validateHistoricalV2AuxiliaryRecord(value, 'credential-policy'));
|
|
581
|
+
files.push(file);
|
|
718
582
|
}
|
|
719
583
|
}
|
|
720
584
|
const selected = new Set();
|
|
@@ -723,7 +587,9 @@ export async function readHistoricalActivationInventory(projectRoot, options = {
|
|
|
723
587
|
const found = approvals.get(id);
|
|
724
588
|
if (!found)
|
|
725
589
|
historyFail(`governance/approvals/${id}`, 'referenced historical approval is missing.', 'missing-historical-record');
|
|
726
|
-
|
|
590
|
+
const scopeHash = found.record.schemaVersion === 1
|
|
591
|
+
? historicalApprovalEnvelopeHash(found.record) : historicalV2ApprovalEnvelopeHash(found.record);
|
|
592
|
+
if (found.record.phaseId !== expectedPhase || expectedHash !== undefined && scopeHash !== expectedHash) {
|
|
727
593
|
historyFail(historyPathKey(found.file.pathParts), 'approval phase or scope hash contradicts its reference.', 'invalid-historical-reference');
|
|
728
594
|
}
|
|
729
595
|
select(found.file);
|
|
@@ -758,6 +624,10 @@ export async function readHistoricalActivationInventory(projectRoot, options = {
|
|
|
758
624
|
phase.state === 'approved' && phase.approvals.length === 0) {
|
|
759
625
|
historyFail(`governance/activation-state.json#phases.${id}`, 'terminal historical state has no required record references.', 'missing-historical-record');
|
|
760
626
|
}
|
|
627
|
+
if (['verified', 'inapplicable', 'retained', 'disposed'].includes(phase.state) &&
|
|
628
|
+
!phase.evidence.some((reference) => reference.result === phase.state)) {
|
|
629
|
+
historyFail(`governance/activation-state.json#phases.${id}`, 'terminal historical state has no matching result reference.', 'invalid-historical-reference');
|
|
630
|
+
}
|
|
761
631
|
for (const ref of phase.evidence) {
|
|
762
632
|
const found = evidence.get(ref.evidenceId);
|
|
763
633
|
if (!found)
|
|
@@ -777,6 +647,20 @@ export async function readHistoricalActivationInventory(projectRoot, options = {
|
|
|
777
647
|
canonicalSha256(retained.record.header) !== state.bootstrapState.remoteImportEvidenceDigest) {
|
|
778
648
|
historyFail('governance/activation-state.json#bootstrapState', 'remote import evidence reference is missing or inconsistent.', 'invalid-historical-reference');
|
|
779
649
|
}
|
|
650
|
+
if (sourceMigration) {
|
|
651
|
+
for (const phase of sourceMigration.revalidation.phases) {
|
|
652
|
+
if (phase.status !== 'complete')
|
|
653
|
+
continue;
|
|
654
|
+
for (const id of phase.evidenceIds) {
|
|
655
|
+
const found = evidence.get(id);
|
|
656
|
+
if (!found || found.record.header.phaseId !== phase.phaseId || found.record.header.result !== 'verified' ||
|
|
657
|
+
!state.phases[phase.phaseId].evidence.some((reference) => reference.evidenceId === id &&
|
|
658
|
+
reference.headerDigest === canonicalSha256(found.record.header))) {
|
|
659
|
+
historyFail(historyPathKey(migrationStateFilePathParts), 'completed source revalidation has a missing or contradictory historical proof link.', 'invalid-historical-reference');
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
}
|
|
780
664
|
selectEvidence(retained);
|
|
781
665
|
if (state.bootstrapState.deletionEvidenceId !== undefined) {
|
|
782
666
|
const deletion = evidence.get(state.bootstrapState.deletionEvidenceId);
|
|
@@ -812,6 +696,9 @@ export async function readHistoricalActivationInventory(projectRoot, options = {
|
|
|
812
696
|
unreviewedRecords.push(entry.file);
|
|
813
697
|
}
|
|
814
698
|
files.sort((a, b) => historyPathKey(a.pathParts) < historyPathKey(b.pathParts) ? -1 : 1);
|
|
815
|
-
return {
|
|
699
|
+
return {
|
|
700
|
+
manifest, state, ...(sourceMigration ? { sourceMigration } : {}),
|
|
701
|
+
...(sourceChangeMetadata ? { sourceChangeMetadata } : {}), files, unreviewedRecords, preconditions
|
|
702
|
+
};
|
|
816
703
|
}
|
|
817
704
|
//# sourceMappingURL=historical-state.js.map
|