@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,4 +1,5 @@
|
|
|
1
|
-
import { access, readdir } from 'node:fs/promises';
|
|
1
|
+
import { access, open, readdir } from 'node:fs/promises';
|
|
2
|
+
import { constants } from 'node:fs';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import { readBooleanFlag, readStringFlag } from '../cli/args/readers.js';
|
|
4
5
|
import { findProjectRoot } from '../adapters/filesystem/project-discovery.js';
|
|
@@ -11,27 +12,35 @@ import { governanceArtifactPaths, validateGovernancePolicy } from '../repository
|
|
|
11
12
|
import { governanceAssessmentCommand } from '../governance-assessment/command.js';
|
|
12
13
|
import { loadActivationState } from './activation-state.js';
|
|
13
14
|
import { canonicalSha256 } from '../domain/governance/activation/canonical-json.js';
|
|
14
|
-
import { canonicalApprovalEnvelopeHash, evaluateApprovalForTransitionPlan, transitionPlanForPhase } from '../domain/governance/activation/approvals.js';
|
|
15
|
+
import { approvalRequestForSavedPlan, canonicalApprovalEnvelopeHash, evaluateApprovalForTransitionPlan, transitionPlanForPhase } from '../domain/governance/activation/approvals.js';
|
|
15
16
|
import { buildPatEnrollmentGuidance, canonicalCredentialRepository, credentialPolicyPathParts, detectCredentialLeaks, runnerPreflightPermissions, validateCredentialPolicyUsage } from './credentials.js';
|
|
16
|
-
import { selectLatestPhaseEvidence } from '../domain/governance/activation/evidence.js';
|
|
17
|
+
import { assertPhaseOutputsBound, selectLatestPhaseEvidence } from '../domain/governance/activation/evidence.js';
|
|
17
18
|
import { canonicalPhaseGraph, canonicalPhaseGraphHash, currentActivationIdentity } from '../domain/governance/activation/graph.js';
|
|
18
19
|
import { governanceActivationPolicyVersion } from '../domain/governance/policy/identity.js';
|
|
19
20
|
import { calculatePhaseReadiness } from '../domain/governance/activation/readiness.js';
|
|
20
|
-
import { projectOpenSpecTaskCheckboxes } from './task-projection.js';
|
|
21
|
+
import { projectGovernanceChangeTasks, projectOpenSpecTaskCheckboxes } from './task-projection.js';
|
|
21
22
|
import { inspectGovernanceSourceOfTruth } from './source-of-truth.js';
|
|
22
23
|
import { discoverGeneratedSeed, inspectArchivedSeedIntegrity, seedInfrastructureBaselineBlocker } from './seed-lifecycle.js';
|
|
23
24
|
import { executeApplyNext, previewApplyNext } from './transitions.js';
|
|
24
|
-
import { phaseIds } from '../domain/governance/activation/types.js';
|
|
25
|
-
import { activationEvidenceContexts, readActivationInputSnapshot } from './inputs.js';
|
|
25
|
+
import { phaseIds, phaseScope } from '../domain/governance/activation/types.js';
|
|
26
|
+
import { activationEvidenceContexts, activationSensitivePathExclusions, protectedLocalInputBlockers, readActivationInputSnapshot } from './inputs.js';
|
|
26
27
|
import { phaseCapabilities } from '../domain/governance/activation/capabilities.js';
|
|
27
28
|
import { readActivationEvidence, readReviewedTransitionPlans } from './read-only.js';
|
|
28
|
-
import { inspectActivationMigrationHistory } from './migration-history.js';
|
|
29
|
+
import { inspectActivationMigrationHistory, historicalLifecyclePhaseBlockers } from './migration-history.js';
|
|
29
30
|
import { migrationRevalidationPhaseIds } from './history-contracts.js';
|
|
30
31
|
import { validateApprovalEnvelope, validateManagedPhaseGraph, validateCredentialPolicy, validateManifestActivationForExecution } from '../domain/governance/activation/validators.js';
|
|
32
|
+
import { validateActivationConfiguration } from '../domain/governance/activation/validators.js';
|
|
33
|
+
import { remoteRepository } from '../domain/governance/activation/inputs.js';
|
|
34
|
+
import { isRecord } from '../domain/governance/activation/canonical-json.js';
|
|
35
|
+
import { approveGovernancePreview, loadGovernancePreview, saveGovernancePreview } from './public-plans.js';
|
|
36
|
+
import { parseArgs } from '../cli/args/parser.js';
|
|
31
37
|
const governanceSubcommands = new Set([
|
|
32
38
|
'status',
|
|
33
39
|
'plan',
|
|
40
|
+
'approve',
|
|
34
41
|
'apply-next',
|
|
42
|
+
'credential-enroll',
|
|
43
|
+
'recover',
|
|
35
44
|
'resume',
|
|
36
45
|
'verify'
|
|
37
46
|
]);
|
|
@@ -66,7 +75,12 @@ function errorCode(error) {
|
|
|
66
75
|
return typeof code === 'string' ? code : undefined;
|
|
67
76
|
}
|
|
68
77
|
function json(presentation, value) {
|
|
69
|
-
|
|
78
|
+
const text = `${JSON.stringify(value, null, 2)}\n`;
|
|
79
|
+
const scan = detectCredentialLeaks([{ source: 'generated-artifact', label: 'governance command output', text }]);
|
|
80
|
+
if (scan.status === 'compromised') {
|
|
81
|
+
throw new Error('Governance output contained credential-shaped data and was withheld; inspect the protected execution checkpoint.');
|
|
82
|
+
}
|
|
83
|
+
presentation.rawStdout(text);
|
|
70
84
|
}
|
|
71
85
|
function parseGovernanceSubcommand(parsed) {
|
|
72
86
|
if (!parsed.subcommand || !governanceSubcommands.has(parsed.subcommand)) {
|
|
@@ -117,7 +131,9 @@ function notStartedState(manifest) {
|
|
|
117
131
|
applicability: {
|
|
118
132
|
statePath: 'none',
|
|
119
133
|
privateStagingDast: 'unknown',
|
|
120
|
-
credentialRequired: 'unknown'
|
|
134
|
+
credentialRequired: 'unknown',
|
|
135
|
+
cloudStateRequired: 'unknown',
|
|
136
|
+
privateRunnerRequired: 'unknown'
|
|
121
137
|
},
|
|
122
138
|
phases: emptyPhaseState(now),
|
|
123
139
|
createdAt: now,
|
|
@@ -193,14 +209,15 @@ async function loadEvidence(projectRoot) {
|
|
|
193
209
|
return readActivationEvidence(projectRoot);
|
|
194
210
|
}
|
|
195
211
|
function repositoryFromState(state) {
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
212
|
+
const remote = remoteRepository(state);
|
|
213
|
+
const repositoryName = remote.name.includes('/')
|
|
214
|
+
? remote.name.split('/').at(-1)
|
|
215
|
+
: remote.name;
|
|
216
|
+
const owner = remote.name.includes('/')
|
|
217
|
+
? remote.name.split('/')[0]
|
|
201
218
|
: 'local';
|
|
202
219
|
return canonicalCredentialRepository({
|
|
203
|
-
id:
|
|
220
|
+
id: remote.id,
|
|
204
221
|
owner,
|
|
205
222
|
name: repositoryName
|
|
206
223
|
});
|
|
@@ -235,7 +252,7 @@ async function inspectCredentialPolicy(projectRoot, state) {
|
|
|
235
252
|
ready: false,
|
|
236
253
|
guidance,
|
|
237
254
|
policy: null,
|
|
238
|
-
issues: [`${pathLabel} is missing.
|
|
255
|
+
issues: [`${pathLabel} is missing. Preview credential-ready and use governance credential-enroll with the approved plan and a private input channel.`]
|
|
239
256
|
};
|
|
240
257
|
}
|
|
241
258
|
const text = bytes.toString('utf8');
|
|
@@ -285,7 +302,7 @@ async function inspectCredentialPolicy(projectRoot, state) {
|
|
|
285
302
|
ready: false,
|
|
286
303
|
guidance: null,
|
|
287
304
|
policy,
|
|
288
|
-
issues: [...usage.issues
|
|
305
|
+
issues: [...usage.issues]
|
|
289
306
|
};
|
|
290
307
|
}
|
|
291
308
|
catch (error) {
|
|
@@ -322,14 +339,17 @@ function buildEvidenceFreshness(graph, evidence, contexts) {
|
|
|
322
339
|
return [phase.id, freshnessEntry(phase, selection)];
|
|
323
340
|
}));
|
|
324
341
|
}
|
|
325
|
-
async function inspectGovernance(projectRoot, runner, now = new Date()) {
|
|
342
|
+
async function inspectGovernance(projectRoot, runner, now = new Date(), options = {}) {
|
|
326
343
|
const manifest = await loadManifest(projectRoot);
|
|
327
344
|
validateManifestActivationForExecution(manifest);
|
|
328
345
|
const graph = await loadGovernanceGraph(projectRoot);
|
|
329
346
|
await assertPolicyIdentity(projectRoot, manifest);
|
|
330
347
|
const loadedState = await loadActivationState(projectRoot);
|
|
331
348
|
const migration = await inspectActivationMigrationHistory(projectRoot);
|
|
332
|
-
const state = loadedState?.state ?? notStartedState(manifest)
|
|
349
|
+
const state = { ...(loadedState?.state ?? notStartedState(manifest)),
|
|
350
|
+
...(options.activationInputs ? { activationInputs: options.activationInputs } : {}) };
|
|
351
|
+
const isStatusOrVerify = options.command === 'status' || options.command === 'verify';
|
|
352
|
+
const scope = options.scope ?? (isStatusOrVerify ? 'activation' : 'local');
|
|
333
353
|
if (state.identity.phaseGraphHash !== graph.hash) {
|
|
334
354
|
throw new Error(`Activation state graph hash ${state.identity.phaseGraphHash} does not match loaded graph hash ${graph.hash}.`);
|
|
335
355
|
}
|
|
@@ -338,9 +358,19 @@ async function inspectGovernance(projectRoot, runner, now = new Date()) {
|
|
|
338
358
|
}
|
|
339
359
|
const approvals = await loadApprovals(projectRoot, state.identity);
|
|
340
360
|
const evidence = await loadEvidence(projectRoot);
|
|
341
|
-
|
|
342
|
-
const contexts = activationEvidenceContexts(graph.graph, state, snapshot, now);
|
|
361
|
+
assertPhaseOutputsBound(state, evidence);
|
|
343
362
|
const reviewedPlans = await readReviewedTransitionPlans(projectRoot);
|
|
363
|
+
if (!options.activationInputs) {
|
|
364
|
+
const approvedConfiguration = reviewedPlans.filter((plan) => plan.configuration &&
|
|
365
|
+
approvals.some((approval) => approval.id === plan.approval.envelopeId &&
|
|
366
|
+
canonicalApprovalEnvelopeHash(approval) === plan.approval.envelopeHash))
|
|
367
|
+
.sort((left, right) => Date.parse(right.createdAt) - Date.parse(left.createdAt))[0]?.configuration;
|
|
368
|
+
if (approvedConfiguration)
|
|
369
|
+
state.activationInputs = approvedConfiguration;
|
|
370
|
+
}
|
|
371
|
+
const sensitivePathExclusions = activationSensitivePathExclusions(state, migration.status === 'committed' ? migration.lifecycleObligations.map((obligation) => obligation.retention) : []);
|
|
372
|
+
const snapshot = await readActivationInputSnapshot(projectRoot, manifest, runner, { sensitivePathExclusions });
|
|
373
|
+
const contexts = activationEvidenceContexts(graph.graph, state, snapshot, now);
|
|
344
374
|
for (const phase of phaseIds)
|
|
345
375
|
contexts[phase].reviewedPlans = reviewedPlans;
|
|
346
376
|
const sourceOfTruth = await inspectGovernanceSourceOfTruth({
|
|
@@ -364,7 +394,19 @@ async function inspectGovernance(projectRoot, runner, now = new Date()) {
|
|
|
364
394
|
!evidence.some((record) => archiveAndLaterPhases.includes(record.header.phaseId));
|
|
365
395
|
const credential = await inspectCredentialPolicy(projectRoot, state);
|
|
366
396
|
const evidenceFreshness = buildEvidenceFreshness(graph.graph, evidence, contexts);
|
|
397
|
+
if (credential.policy && credential.status === 'valid') {
|
|
398
|
+
const proof = selectLatestPhaseEvidence(evidence.filter((entry) => entry.header.phaseId === 'credential-ready'), contexts['credential-ready']).selected;
|
|
399
|
+
credential.ready = !!proof && isRecord(proof.payload) && proof.payload.policyDigest === canonicalSha256(credential.policy);
|
|
400
|
+
if (!credential.ready)
|
|
401
|
+
credential.issues = ['Independent credential readback and current verification are required; a policy file alone is not proof.'];
|
|
402
|
+
}
|
|
367
403
|
const infrastructureBlocker = seedInfrastructureBaselineBlocker(manifest);
|
|
404
|
+
const localInputBlockers = [
|
|
405
|
+
...(infrastructureBlocker ? [infrastructureBlocker] : []),
|
|
406
|
+
...protectedLocalInputBlockers(sensitivePathExclusions)
|
|
407
|
+
];
|
|
408
|
+
const historicalLifecycleObligations = migration.status === 'committed' ? migration.lifecycleObligations : [];
|
|
409
|
+
const historicalProtection = historicalLifecyclePhaseBlockers(historicalLifecycleObligations);
|
|
368
410
|
const readiness = calculatePhaseReadiness({
|
|
369
411
|
graph: graph.graph,
|
|
370
412
|
state,
|
|
@@ -372,15 +414,43 @@ async function inspectGovernance(projectRoot, runner, now = new Date()) {
|
|
|
372
414
|
evidence,
|
|
373
415
|
transitionContexts: contexts,
|
|
374
416
|
retryArchivedSeedBaseline,
|
|
417
|
+
scope,
|
|
418
|
+
recoverPhase: options.recoverPhase,
|
|
419
|
+
historicalLifecycleBlockers: historicalProtection['bootstrap-state-disposed'],
|
|
375
420
|
phaseBlockers: {
|
|
376
421
|
...Object.fromEntries(Object.entries(phaseCapabilities).filter(([, capability]) => capability.blocker)
|
|
377
422
|
.map(([id, capability]) => [id, [capability.blocker]])),
|
|
378
423
|
...(archivedSeedIntegrity.status === 'invalid' ? { 'seed-archived': archivedSeedIntegrity.issues } : {}),
|
|
379
424
|
...(seedDiscovery.state === 'blocked' ? { 'seed-valid': seedDiscovery.issues } : {}),
|
|
380
|
-
...(
|
|
425
|
+
...(localInputBlockers.length ? { 'seed-verified': localInputBlockers } : {}),
|
|
426
|
+
...historicalProtection
|
|
381
427
|
},
|
|
382
428
|
now
|
|
383
429
|
});
|
|
430
|
+
let resolvedScope = options.scope ?? (isStatusOrVerify ? 'activation' : (readiness.completion.local ? 'activation' : 'local'));
|
|
431
|
+
let finalReadiness = readiness;
|
|
432
|
+
if (resolvedScope !== scope) {
|
|
433
|
+
finalReadiness = calculatePhaseReadiness({
|
|
434
|
+
graph: graph.graph,
|
|
435
|
+
state,
|
|
436
|
+
approvals,
|
|
437
|
+
evidence,
|
|
438
|
+
transitionContexts: contexts,
|
|
439
|
+
retryArchivedSeedBaseline,
|
|
440
|
+
scope: resolvedScope,
|
|
441
|
+
recoverPhase: options.recoverPhase,
|
|
442
|
+
historicalLifecycleBlockers: historicalProtection['bootstrap-state-disposed'],
|
|
443
|
+
phaseBlockers: {
|
|
444
|
+
...Object.fromEntries(Object.entries(phaseCapabilities).filter(([, capability]) => capability.blocker)
|
|
445
|
+
.map(([id, capability]) => [id, [capability.blocker]])),
|
|
446
|
+
...(archivedSeedIntegrity.status === 'invalid' ? { 'seed-archived': archivedSeedIntegrity.issues } : {}),
|
|
447
|
+
...(seedDiscovery.state === 'blocked' ? { 'seed-valid': seedDiscovery.issues } : {}),
|
|
448
|
+
...(localInputBlockers.length ? { 'seed-verified': localInputBlockers } : {}),
|
|
449
|
+
...historicalProtection
|
|
450
|
+
},
|
|
451
|
+
now
|
|
452
|
+
});
|
|
453
|
+
}
|
|
384
454
|
return {
|
|
385
455
|
projectRoot,
|
|
386
456
|
manifest,
|
|
@@ -392,13 +462,18 @@ async function inspectGovernance(projectRoot, runner, now = new Date()) {
|
|
|
392
462
|
evidence,
|
|
393
463
|
contexts,
|
|
394
464
|
evidenceFreshness,
|
|
395
|
-
readiness,
|
|
465
|
+
readiness: finalReadiness,
|
|
396
466
|
sourceOfTruth,
|
|
397
467
|
credential,
|
|
398
468
|
archivedSeedIntegrity,
|
|
399
469
|
retryArchivedSeedBaseline,
|
|
400
470
|
expectedActiveSeed,
|
|
401
|
-
migration: migration.status === 'committed' ? migration.journal : null
|
|
471
|
+
migration: migration.status === 'committed' ? migration.journal : null,
|
|
472
|
+
scope: resolvedScope,
|
|
473
|
+
...(state.activationInputs ? { activationInputs: state.activationInputs } : {}),
|
|
474
|
+
...(options.recoverPhase ? { recoverPhase: options.recoverPhase } : {}),
|
|
475
|
+
sensitivePathExclusions,
|
|
476
|
+
historicalLifecycleObligations
|
|
402
477
|
};
|
|
403
478
|
}
|
|
404
479
|
async function assertPolicyIdentity(projectRoot, manifest) {
|
|
@@ -418,7 +493,10 @@ function approvalStatus(approval, now = new Date()) {
|
|
|
418
493
|
return Date.parse(approval.expiresAt) > now.getTime() ? 'valid' : 'expired';
|
|
419
494
|
}
|
|
420
495
|
function approvalEvaluationForPhase(phase, inspection) {
|
|
421
|
-
const
|
|
496
|
+
const reviewed = inspection.contexts[phase.id].reviewedPlans?.filter((plan) => plan.phaseId === phase.id &&
|
|
497
|
+
plan.inputDigest === inspection.contexts[phase.id].inputDigest && plan.baselineDigest === inspection.contexts[phase.id].baselineSha)
|
|
498
|
+
.sort((left, right) => Date.parse(right.createdAt) - Date.parse(left.createdAt))[0];
|
|
499
|
+
const plan = reviewed ? approvalRequestForSavedPlan(reviewed, phase, inspection.state) : transitionPlanForPhase(phase, inspection.state, inspection.contexts[phase.id].transition, inspection.projectRoot, inspection.contexts[phase.id].publicationDestination);
|
|
422
500
|
return evaluateApprovalForTransitionPlan(plan, inspection.approvals);
|
|
423
501
|
}
|
|
424
502
|
function approvalEvaluationJson(evaluation) {
|
|
@@ -453,7 +531,7 @@ function summarizeMigration(inspection) {
|
|
|
453
531
|
nextRecordedPhase: phases.find((phase) => phase.status !== 'complete')?.phaseId ?? null,
|
|
454
532
|
currentProofRequired: true,
|
|
455
533
|
remedy: needsRevalidation
|
|
456
|
-
? `Keep the committed
|
|
534
|
+
? `Keep the committed v3 successor and preserved v1/v2 history. Repair the named blockers or stale current proof, run ${checkCommand} for a fresh preview, then explicitly approve the exact remaining local plan before retrying. Prior migration approval does not authorize new work.`
|
|
457
535
|
: null
|
|
458
536
|
};
|
|
459
537
|
}
|
|
@@ -477,21 +555,34 @@ function renderMigrationHuman(summary, presentation) {
|
|
|
477
555
|
if (summary.revalidation.nextAction) {
|
|
478
556
|
presentation.status('pending', 'Recorded next action', summary.revalidation.nextAction);
|
|
479
557
|
}
|
|
480
|
-
presentation.status('info', 'Migration scope', 'Journal progress is audit information, not current proof, governance completion, approval, or provider authority. Current readiness is evaluated separately from
|
|
558
|
+
presentation.status('info', 'Migration scope', 'Journal progress is audit information, not current proof, governance completion, approval, or provider authority. Current readiness is evaluated separately from v3 evidence.');
|
|
481
559
|
if (summary.remedy)
|
|
482
560
|
presentation.remedy(summary.remedy);
|
|
483
561
|
}
|
|
484
562
|
function statusJson(inspection, command) {
|
|
485
|
-
const blockers =
|
|
563
|
+
const blockers = verificationPhaseIds(inspection).flatMap((phaseId) => inspection.readiness.phases[phaseId].blockers.map((message) => ({ phaseId, message })));
|
|
486
564
|
return {
|
|
487
|
-
schemaVersion:
|
|
565
|
+
schemaVersion: 2,
|
|
566
|
+
scope: inspection.scope,
|
|
488
567
|
command: `governance ${command}`,
|
|
489
568
|
projectRoot: inspection.projectRoot,
|
|
490
569
|
readOnly: command !== 'apply-next',
|
|
491
570
|
stateSource: inspection.stateSource,
|
|
571
|
+
activationDisabled: inspection.manifest.governance.profile === 'none',
|
|
572
|
+
progress: inspection.readiness.completion,
|
|
573
|
+
localComplete: inspection.readiness.completion.local,
|
|
574
|
+
activationComplete: inspection.readiness.completion.activation,
|
|
575
|
+
lifecycleComplete: inspection.readiness.completion.lifecycle,
|
|
576
|
+
nextActions: governanceNextActions(inspection),
|
|
577
|
+
blockerFingerprint: canonicalSha256({
|
|
578
|
+
scope: inspection.scope, stateHash: inspection.loadedState?.contentHash ?? null,
|
|
579
|
+
inputs: verificationPhaseIds(inspection).map((id) => [id, inspection.contexts[id].inputDigest]), blockers
|
|
580
|
+
}),
|
|
492
581
|
activationIdentity: inspection.state.identity,
|
|
493
582
|
migration: inspection.migration,
|
|
494
583
|
migrationSummary: summarizeMigration(inspection),
|
|
584
|
+
taskProjectionAudit: inspection.state.taskProjection ?? null,
|
|
585
|
+
historicalLifecycleObligations: inspection.historicalLifecycleObligations,
|
|
495
586
|
executionAnchor: inspection.state.repository.id === 'unbound' ? null : inspection.state.repository.id,
|
|
496
587
|
remoteBinding: inspection.state.remoteBinding ?? null,
|
|
497
588
|
graphHash: inspection.graph.hash,
|
|
@@ -508,6 +599,10 @@ function statusJson(inspection, command) {
|
|
|
508
599
|
label: phase.label,
|
|
509
600
|
state: inspection.readiness.phases[phase.id].state,
|
|
510
601
|
storedState: inspection.state.phases[phase.id].state,
|
|
602
|
+
scope: phaseScope(phase.id),
|
|
603
|
+
plannable: inspection.readiness.phases[phase.id].plannable ?? false,
|
|
604
|
+
externalOperation: inspection.state.phases[phase.id].operation ?? null,
|
|
605
|
+
executionPlanDigest: inspection.state.phases[phase.id].executionPlanDigest ?? null,
|
|
511
606
|
storedBlockers: inspection.state.phases[phase.id].blockers,
|
|
512
607
|
retryable: phaseCapabilities[phase.id].retry === 'explicit-local' &&
|
|
513
608
|
['failed', 'blocked'].includes(inspection.state.phases[phase.id].state),
|
|
@@ -523,6 +618,7 @@ function statusJson(inspection, command) {
|
|
|
523
618
|
allowedMutations: phase.allowedMutations
|
|
524
619
|
})),
|
|
525
620
|
nextReadyPhase: inspection.readiness.nextReadyPhase,
|
|
621
|
+
nextPlannablePhase: inspection.readiness.nextPlannablePhase,
|
|
526
622
|
blockers,
|
|
527
623
|
approvals: inspection.approvals.map((approval) => ({
|
|
528
624
|
id: approval.id,
|
|
@@ -535,11 +631,51 @@ function statusJson(inspection, command) {
|
|
|
535
631
|
evidenceFreshness: phaseIds.map((phaseId) => inspection.evidenceFreshness[phaseId])
|
|
536
632
|
};
|
|
537
633
|
}
|
|
634
|
+
function governanceAction(inspection, subcommand, scope, extras = [], approvalRequired = false) {
|
|
635
|
+
const args = ['governance', subcommand, '--project', inspection.projectRoot, '--scope', scope, ...extras, '--json'];
|
|
636
|
+
parseArgs(args);
|
|
637
|
+
return {
|
|
638
|
+
id: `governance-${subcommand}-${scope}`, label: `${subcommand} ${scope} governance`,
|
|
639
|
+
command: { executable: 'liftoff', args }, cwd: inspection.projectRoot, scope, approvalRequired
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
function governanceNextActions(inspection, preview) {
|
|
643
|
+
if (inspection.manifest.governance.profile === 'none' && inspection.scope === 'activation')
|
|
644
|
+
return [];
|
|
645
|
+
if (inspection.readiness.completion[inspection.scope]) {
|
|
646
|
+
if (inspection.scope === 'local')
|
|
647
|
+
return [governanceAction(inspection, 'plan', 'activation')];
|
|
648
|
+
if (inspection.scope === 'activation' && !inspection.readiness.completion.lifecycle) {
|
|
649
|
+
return [governanceAction(inspection, 'status', 'lifecycle')];
|
|
650
|
+
}
|
|
651
|
+
return [];
|
|
652
|
+
}
|
|
653
|
+
if (preview) {
|
|
654
|
+
const planArgs = ['--plan', preview.fingerprint];
|
|
655
|
+
if (preview.plan.approval.evaluation.approvalRequired) {
|
|
656
|
+
return [governanceAction(inspection, 'approve', preview.plan.scope, planArgs, true)];
|
|
657
|
+
}
|
|
658
|
+
const subcommand = preview.plan.recovery ? 'recover' : 'apply-next';
|
|
659
|
+
return [governanceAction(inspection, subcommand, preview.plan.scope, [...(inspection.stateSource === 'not-started' ? [] : planArgs), '--execute'])];
|
|
660
|
+
}
|
|
661
|
+
const interrupted = phaseIds.find((id) => phaseScope(id) === inspection.scope && inspection.readiness.phases[id].recoveryRequired);
|
|
662
|
+
if (interrupted)
|
|
663
|
+
return [governanceAction(inspection, 'plan', inspection.scope, ['--recover-phase', interrupted])];
|
|
664
|
+
if (inspection.scope !== 'local' && !inspection.readiness.completion.local) {
|
|
665
|
+
return [governanceAction(inspection, 'plan', 'local')];
|
|
666
|
+
}
|
|
667
|
+
return [governanceAction(inspection, 'plan', inspection.scope)];
|
|
668
|
+
}
|
|
669
|
+
function verificationPhaseIds(inspection) {
|
|
670
|
+
return phaseIds.filter((id) => phaseScope(id) === inspection.scope ||
|
|
671
|
+
inspection.scope === 'activation' && phaseScope(id) === 'local');
|
|
672
|
+
}
|
|
538
673
|
function renderStatusHuman(inspection, command) {
|
|
539
674
|
const presentation = inspectionPresentation(inspection);
|
|
540
675
|
presentation.commandIdentity(`governance ${command}`, 'Deterministic activation status');
|
|
541
676
|
presentation.definitions('Activation identity', [
|
|
542
677
|
{ label: 'Project', value: inspection.projectRoot },
|
|
678
|
+
{ label: 'Scope', value: inspection.scope },
|
|
543
679
|
{ label: 'State', value: inspection.stateSource },
|
|
544
680
|
{ label: 'Policy', value: inspection.state.identity.policyVersion },
|
|
545
681
|
{ label: 'Contract', value: String(inspection.state.identity.activationContractVersion) },
|
|
@@ -596,17 +732,23 @@ function inspectionPresentation(inspection) {
|
|
|
596
732
|
return inspection.presentation;
|
|
597
733
|
}
|
|
598
734
|
function planJson(inspection) {
|
|
599
|
-
const
|
|
735
|
+
const scopedPhases = inspection.scope === 'local'
|
|
736
|
+
? inspection.graph.graph.phases.filter((phase) => phaseScope(phase.id) === 'local')
|
|
737
|
+
: inspection.scope === 'lifecycle'
|
|
738
|
+
? inspection.graph.graph.phases.filter((phase) => phaseScope(phase.id) === 'lifecycle')
|
|
739
|
+
: inspection.graph.graph.phases.filter((phase) => phaseScope(phase.id) !== 'lifecycle');
|
|
740
|
+
const ready = scopedPhases
|
|
600
741
|
.filter((phase) => inspection.readiness.phases[phase.id].state === 'ready')
|
|
601
742
|
.map((phase) => planPhase(phase, inspection));
|
|
602
|
-
const blocked =
|
|
743
|
+
const blocked = scopedPhases
|
|
603
744
|
.filter((phase) => inspection.readiness.phases[phase.id].state === 'blocked')
|
|
604
745
|
.map((phase) => ({
|
|
605
746
|
...planPhase(phase, inspection),
|
|
606
747
|
blockers: inspection.readiness.phases[phase.id].blockers
|
|
607
748
|
}));
|
|
608
749
|
return {
|
|
609
|
-
schemaVersion:
|
|
750
|
+
schemaVersion: 2,
|
|
751
|
+
scope: inspection.scope,
|
|
610
752
|
command: 'governance plan',
|
|
611
753
|
projectRoot: inspection.projectRoot,
|
|
612
754
|
readOnly: true,
|
|
@@ -616,6 +758,10 @@ function planJson(inspection) {
|
|
|
616
758
|
activeChange: inspection.state.activeChange,
|
|
617
759
|
activeSourceOfTruth: inspection.sourceOfTruth,
|
|
618
760
|
credential: inspection.credential,
|
|
761
|
+
progress: inspection.readiness.completion,
|
|
762
|
+
nextReadyPhase: inspection.readiness.nextReadyPhase,
|
|
763
|
+
nextPlannablePhase: inspection.readiness.nextPlannablePhase,
|
|
764
|
+
nextActions: governanceNextActions(inspection),
|
|
619
765
|
readyPhases: ready,
|
|
620
766
|
blockedPhases: blocked
|
|
621
767
|
};
|
|
@@ -651,10 +797,15 @@ function costEnvelope(phase) {
|
|
|
651
797
|
};
|
|
652
798
|
}
|
|
653
799
|
function renderPlanHuman(inspection, presentation) {
|
|
654
|
-
presentation.commandIdentity('governance plan',
|
|
655
|
-
const
|
|
656
|
-
|
|
657
|
-
|
|
800
|
+
presentation.commandIdentity('governance plan', `Project-read-only ${inspection.scope} transition plan`);
|
|
801
|
+
const scopedPhases = inspection.scope === 'local'
|
|
802
|
+
? inspection.graph.graph.phases.filter((phase) => phaseScope(phase.id) === 'local')
|
|
803
|
+
: inspection.scope === 'lifecycle'
|
|
804
|
+
? inspection.graph.graph.phases.filter((phase) => phaseScope(phase.id) === 'lifecycle')
|
|
805
|
+
: inspection.graph.graph.phases.filter((phase) => phaseScope(phase.id) !== 'lifecycle');
|
|
806
|
+
const ready = scopedPhases.filter((phase) => inspection.readiness.phases[phase.id].state === 'ready');
|
|
807
|
+
const blocked = scopedPhases.filter((phase) => inspection.readiness.phases[phase.id].state === 'blocked');
|
|
808
|
+
presentation.status('info', 'Project-read-only', 'No project or provider data is changed; any external preview receipt is disclosed separately.');
|
|
658
809
|
presentation.table('Ready phases', ['Phase', 'Evidence', 'Question', 'Approval', 'Mutations'], ready.map((phase) => {
|
|
659
810
|
const approval = approvalEvaluationForPhase(phase, inspection);
|
|
660
811
|
return [
|
|
@@ -703,7 +854,7 @@ function validateStateEvidence(inspection) {
|
|
|
703
854
|
if (inspection.stateSource === 'not-started') {
|
|
704
855
|
return { id: 'state-evidence', status: 'passed', issues };
|
|
705
856
|
}
|
|
706
|
-
for (const phaseId of
|
|
857
|
+
for (const phaseId of verificationPhaseIds(inspection)) {
|
|
707
858
|
const stored = inspection.state.phases[phaseId];
|
|
708
859
|
if (!terminalEvidenceStates.has(stored.state)) {
|
|
709
860
|
continue;
|
|
@@ -718,7 +869,7 @@ function validateStateEvidence(inspection) {
|
|
|
718
869
|
}
|
|
719
870
|
function validatePhaseTerminalStates(inspection) {
|
|
720
871
|
const issues = [];
|
|
721
|
-
for (const phase of inspection.graph.graph.phases) {
|
|
872
|
+
for (const phase of inspection.graph.graph.phases.filter((phase) => verificationPhaseIds(inspection).includes(phase.id))) {
|
|
722
873
|
const allowed = phase.terminalStates;
|
|
723
874
|
const stored = inspection.state.phases[phase.id].state;
|
|
724
875
|
const selected = selectLatestPhaseEvidence(inspection.evidence.filter((record) => record.header.phaseId === phase.id), inspection.contexts[phase.id]).selected;
|
|
@@ -743,7 +894,7 @@ function validatePhaseTerminalStates(inspection) {
|
|
|
743
894
|
};
|
|
744
895
|
}
|
|
745
896
|
function validateEvidenceFreshnessCheck(inspection) {
|
|
746
|
-
const issues =
|
|
897
|
+
const issues = verificationPhaseIds(inspection).flatMap((phaseId) => {
|
|
747
898
|
const freshness = inspection.evidenceFreshness[phaseId];
|
|
748
899
|
return freshness.status === 'fresh'
|
|
749
900
|
? []
|
|
@@ -775,44 +926,14 @@ function validateReadinessCheck(inspection) {
|
|
|
775
926
|
issues: [inspection.readiness.identityBlocker ?? 'Activation identity is incompatible.']
|
|
776
927
|
};
|
|
777
928
|
}
|
|
778
|
-
async function activeTaskProjectionCheck(inspection) {
|
|
779
|
-
if (inspection.manifest.project.specWorkflow === 'spec-kit') {
|
|
780
|
-
const bytes = await readProjectFile(inspection.projectRoot, ['specs', '000-liftoff-bootstrap', 'tasks.md']);
|
|
781
|
-
if (!bytes)
|
|
782
|
-
return { id: 'task-projection', status: 'failed', issues: ['Spec Kit seed-adoption-required: real bootstrap tasks are missing.'] };
|
|
783
|
-
const expected = inspection.readiness.phases['seed-verified'].state === 'verified';
|
|
784
|
-
const tasks = [...bytes.toString('utf8').matchAll(/^\s*- \[([ xX])\] (B00[1-6]) /gm)];
|
|
785
|
-
const issues = tasks.filter((match) => (match[1].toLowerCase() === 'x') !== expected)
|
|
786
|
-
.map((match) => `Spec Kit task ${match[2]} differs from the authoritative local baseline projection. Verification did not edit it.`);
|
|
787
|
-
return { id: 'task-projection', status: issues.length ? 'failed' : 'passed', issues };
|
|
788
|
-
}
|
|
789
|
-
const activeChange = inspection.state.activeChange;
|
|
790
|
-
if (!activeChange || activeChange.kind !== 'openspec') {
|
|
791
|
-
return { id: 'task-projection', status: 'skipped', issues: [] };
|
|
792
|
-
}
|
|
793
|
-
const pathParts = validateArtifactPathParts(['openspec', 'changes', activeChange.id, 'tasks.md'], 'Active OpenSpec task path');
|
|
794
|
-
const bytes = await readProjectFile(inspection.projectRoot, pathParts);
|
|
795
|
-
if (bytes === undefined) {
|
|
796
|
-
return { id: 'task-projection', status: 'skipped', issues: [] };
|
|
797
|
-
}
|
|
798
|
-
const markdown = bytes.toString('utf8');
|
|
799
|
-
const mappings = extractPhaseTaskMappings(markdown);
|
|
800
|
-
if (mappings.length === 0) {
|
|
801
|
-
return { id: 'task-projection', status: 'skipped', issues: [] };
|
|
802
|
-
}
|
|
803
|
-
const projection = projectOpenSpecTaskCheckboxes(markdown, mappings, inspection.readiness.phases);
|
|
804
|
-
const issues = projection.changes.map((change) => `Task ${change.taskId} for ${change.phaseId} is ${change.fromChecked ? 'checked' : 'unchecked'} but authoritative phase state is ${change.state}.`);
|
|
805
|
-
return { id: 'task-projection', status: issues.length === 0 ? 'passed' : 'failed', issues };
|
|
806
|
-
}
|
|
807
929
|
function extractPhaseTaskMappings(markdown) {
|
|
808
930
|
const phaseIdSet = new Set(phaseIds);
|
|
809
931
|
const mappings = [];
|
|
810
932
|
const pattern = /^\s*[-*]\s+\[[ xX]\]\s+(\S+).*<!--\s*liftoff-phase:\s*([a-z0-9-]+)\s*-->/u;
|
|
811
933
|
for (const line of markdown.split(/\r?\n/u)) {
|
|
812
934
|
const match = line.match(pattern);
|
|
813
|
-
if (!match)
|
|
935
|
+
if (!match)
|
|
814
936
|
continue;
|
|
815
|
-
}
|
|
816
937
|
const phaseId = match[2];
|
|
817
938
|
if (!phaseIdSet.has(phaseId)) {
|
|
818
939
|
throw new Error(`Task projection references unknown phase ${phaseId}.`);
|
|
@@ -821,6 +942,52 @@ function extractPhaseTaskMappings(markdown) {
|
|
|
821
942
|
}
|
|
822
943
|
return mappings;
|
|
823
944
|
}
|
|
945
|
+
async function activeTaskProjectionCheck(inspection) {
|
|
946
|
+
const issues = [];
|
|
947
|
+
let inspected = false;
|
|
948
|
+
if (inspection.scope !== 'lifecycle' && inspection.manifest.project.specWorkflow === 'spec-kit') {
|
|
949
|
+
inspected = true;
|
|
950
|
+
const bytes = await readProjectFile(inspection.projectRoot, ['specs', '000-liftoff-bootstrap', 'tasks.md']);
|
|
951
|
+
if (!bytes)
|
|
952
|
+
return { id: 'task-projection', status: 'failed', issues: ['Spec Kit seed-adoption-required: real bootstrap tasks are missing.'] };
|
|
953
|
+
const expected = inspection.readiness.phases['seed-verified'].state === 'verified';
|
|
954
|
+
const tasks = [...bytes.toString('utf8').matchAll(/^\s*- \[([ xX])\] (B00[1-6]) /gm)];
|
|
955
|
+
issues.push(...tasks.filter((match) => (match[1].toLowerCase() === 'x') !== expected)
|
|
956
|
+
.map((match) => `Spec Kit task ${match[2]} differs from the authoritative local baseline projection. Verification did not edit it.`));
|
|
957
|
+
}
|
|
958
|
+
const activeChange = inspection.state.activeChange;
|
|
959
|
+
if (activeChange && activeChange.kind === 'openspec') {
|
|
960
|
+
const pathParts = validateArtifactPathParts(['openspec', 'changes', activeChange.id, 'tasks.md'], 'Active OpenSpec task path');
|
|
961
|
+
const bytes = await readProjectFile(inspection.projectRoot, pathParts);
|
|
962
|
+
if (bytes !== undefined) {
|
|
963
|
+
inspected = true;
|
|
964
|
+
const markdown = bytes.toString('utf8');
|
|
965
|
+
const mappings = extractPhaseTaskMappings(markdown);
|
|
966
|
+
if (mappings.length > 0) {
|
|
967
|
+
const projection = projectOpenSpecTaskCheckboxes(markdown, mappings, inspection.readiness.phases);
|
|
968
|
+
issues.push(...projection.changes.filter((change) => verificationPhaseIds(inspection).includes(change.phaseId)).map((change) => `Task ${change.taskId} for ${change.phaseId} is ${change.fromChecked ? 'checked' : 'unchecked'} but authoritative phase state is ${change.state}.`));
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
const source = inspection.sourceOfTruth;
|
|
973
|
+
if (inspection.scope !== 'local' && source.status === 'selected') {
|
|
974
|
+
inspected = true;
|
|
975
|
+
if (!source.selected.metadata) {
|
|
976
|
+
issues.push('The selected current governance source has no validated metadata.');
|
|
977
|
+
}
|
|
978
|
+
else {
|
|
979
|
+
const pathParts = validateArtifactPathParts([...source.selected.pathParts, 'tasks.md'], 'Current governance task path');
|
|
980
|
+
const bytes = await readProjectFile(inspection.projectRoot, pathParts);
|
|
981
|
+
if (!bytes)
|
|
982
|
+
issues.push(`Current governance tasks ${pathParts.join('/')} are missing.`);
|
|
983
|
+
else {
|
|
984
|
+
const projection = projectGovernanceChangeTasks(bytes.toString('utf8'), source.selected.metadata, inspection.readiness.phases);
|
|
985
|
+
issues.push(...projection.changes.filter((change) => verificationPhaseIds(inspection).includes(change.phaseId)).map((change) => `Task ${change.taskId} for ${change.phaseId} is ${change.fromChecked ? 'checked' : 'unchecked'} but current authoritative phase state is ${change.state}.`));
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
return { id: 'task-projection', status: issues.length ? 'failed' : inspected ? 'passed' : 'skipped', issues };
|
|
990
|
+
}
|
|
824
991
|
function activeChangeIdentityCheck(inspection) {
|
|
825
992
|
const activeChange = inspection.state.activeChange;
|
|
826
993
|
if (!activeChange) {
|
|
@@ -838,7 +1005,7 @@ function activeChangeIdentityCheck(inspection) {
|
|
|
838
1005
|
return { id: 'active-change-identity', status: issues.length === 0 ? 'passed' : 'failed', issues };
|
|
839
1006
|
}
|
|
840
1007
|
function liveReadbackCheck(inspection) {
|
|
841
|
-
const issues = inspection.graph.graph.phases.flatMap((phase) => {
|
|
1008
|
+
const issues = inspection.graph.graph.phases.filter((phase) => verificationPhaseIds(inspection).includes(phase.id)).flatMap((phase) => {
|
|
842
1009
|
if (phase.evidence.liveReadbackProviders.length === 0) {
|
|
843
1010
|
return [];
|
|
844
1011
|
}
|
|
@@ -850,6 +1017,8 @@ function liveReadbackCheck(inspection) {
|
|
|
850
1017
|
return { id: 'live-readback', status: issues.length === 0 ? 'passed' : 'failed', issues };
|
|
851
1018
|
}
|
|
852
1019
|
function credentialPolicyCheck(inspection) {
|
|
1020
|
+
if (inspection.scope !== 'activation')
|
|
1021
|
+
return { id: 'credential-policy', status: 'skipped', issues: [] };
|
|
853
1022
|
if (!inspection.credential.applicable) {
|
|
854
1023
|
return { id: 'credential-policy', status: 'skipped', issues: [] };
|
|
855
1024
|
}
|
|
@@ -865,6 +1034,12 @@ function credentialPolicyCheck(inspection) {
|
|
|
865
1034
|
}
|
|
866
1035
|
function activeSourceOfTruthCheck(inspection) {
|
|
867
1036
|
const source = inspection.sourceOfTruth;
|
|
1037
|
+
if (inspection.scope === 'local') {
|
|
1038
|
+
if (source.status === 'seed-blocked' && !inspection.expectedActiveSeed) {
|
|
1039
|
+
return { id: 'active-source-of-truth', status: 'failed', issues: source.blockers };
|
|
1040
|
+
}
|
|
1041
|
+
return { id: 'active-source-of-truth', status: 'skipped', issues: [] };
|
|
1042
|
+
}
|
|
868
1043
|
if (inspection.expectedActiveSeed) {
|
|
869
1044
|
return {
|
|
870
1045
|
id: 'active-source-of-truth',
|
|
@@ -948,19 +1123,15 @@ function setupCompletion(inspection) {
|
|
|
948
1123
|
summary: `Verification is consistent, but setup has not started. Next ready phase: ${inspection.readiness.nextReadyPhase ?? 'none'}.`
|
|
949
1124
|
};
|
|
950
1125
|
}
|
|
951
|
-
|
|
952
|
-
const everyPhaseComplete = inspection.graph.graph.phases.every((phase) => {
|
|
953
|
-
const phaseId = phase.id;
|
|
954
|
-
const state = inspection.readiness.phases[phaseId].state;
|
|
955
|
-
return state !== 'identity-incompatible' &&
|
|
956
|
-
successfulSetupStates.has(state) &&
|
|
957
|
-
phase.terminalStates.includes(state);
|
|
958
|
-
});
|
|
959
|
-
if (everyPhaseComplete && (terminal === 'disposed' || terminal === 'inapplicable')) {
|
|
1126
|
+
if (inspection.readiness.completion[inspection.scope]) {
|
|
960
1127
|
return {
|
|
961
1128
|
status: 'complete',
|
|
962
1129
|
complete: true,
|
|
963
|
-
summary:
|
|
1130
|
+
summary: inspection.scope === 'local'
|
|
1131
|
+
? 'Local setup is complete. Repository publication, cloud deployment, and governance activation are separate approved work.'
|
|
1132
|
+
: inspection.scope === 'activation'
|
|
1133
|
+
? 'Governance activation is complete. Delayed retained-state disposal is tracked separately.'
|
|
1134
|
+
: 'Lifecycle work is complete.'
|
|
964
1135
|
};
|
|
965
1136
|
}
|
|
966
1137
|
return {
|
|
@@ -982,7 +1153,8 @@ async function verifyJson(inspection) {
|
|
|
982
1153
|
? completion.status
|
|
983
1154
|
: 'in-progress';
|
|
984
1155
|
return {
|
|
985
|
-
schemaVersion:
|
|
1156
|
+
schemaVersion: 2,
|
|
1157
|
+
scope: inspection.scope,
|
|
986
1158
|
command: 'governance verify',
|
|
987
1159
|
projectRoot: inspection.projectRoot,
|
|
988
1160
|
readOnly: true,
|
|
@@ -1000,7 +1172,11 @@ async function verifyJson(inspection) {
|
|
|
1000
1172
|
activeChange: inspection.state.activeChange,
|
|
1001
1173
|
activeSourceOfTruth: inspection.sourceOfTruth,
|
|
1002
1174
|
nextReadyPhase: inspection.readiness.nextReadyPhase,
|
|
1003
|
-
|
|
1175
|
+
progress: inspection.readiness.completion,
|
|
1176
|
+
nextActions: governanceNextActions(inspection),
|
|
1177
|
+
checks,
|
|
1178
|
+
historicalLifecycleObligations: inspection.historicalLifecycleObligations,
|
|
1179
|
+
taskProjectionAudit: inspection.state.taskProjection ?? null
|
|
1004
1180
|
};
|
|
1005
1181
|
}
|
|
1006
1182
|
async function renderVerifyHuman(inspection, presentation) {
|
|
@@ -1014,13 +1190,15 @@ async function renderVerifyHuman(inspection, presentation) {
|
|
|
1014
1190
|
}
|
|
1015
1191
|
return result.ok === true ? 0 : 1;
|
|
1016
1192
|
}
|
|
1017
|
-
function renderInspectionFailure(subcommand, projectRoot, error, presentation, jsonMode) {
|
|
1193
|
+
function renderInspectionFailure(subcommand, projectRoot, error, presentation, jsonMode, scope = 'activation') {
|
|
1018
1194
|
const result = {
|
|
1019
|
-
schemaVersion:
|
|
1195
|
+
schemaVersion: 2,
|
|
1196
|
+
scope,
|
|
1020
1197
|
command: `governance ${subcommand}`,
|
|
1021
1198
|
projectRoot,
|
|
1022
1199
|
readOnly: true,
|
|
1023
1200
|
ok: false,
|
|
1201
|
+
nextActions: [],
|
|
1024
1202
|
...(subcommand === 'verify'
|
|
1025
1203
|
? {
|
|
1026
1204
|
consistent: false,
|
|
@@ -1055,6 +1233,11 @@ function transitionInspection(inspection) {
|
|
|
1055
1233
|
manifest: inspection.manifest,
|
|
1056
1234
|
graph: inspection.graph.graph,
|
|
1057
1235
|
graphHash: inspection.graph.hash,
|
|
1236
|
+
scope: inspection.scope,
|
|
1237
|
+
...(inspection.activationInputs ? { activationInputs: inspection.activationInputs } : {}),
|
|
1238
|
+
...(inspection.recoverPhase ? { recoverPhase: inspection.recoverPhase } : {}),
|
|
1239
|
+
sensitivePathExclusions: inspection.sensitivePathExclusions,
|
|
1240
|
+
historicalLifecycleObligations: inspection.historicalLifecycleObligations,
|
|
1058
1241
|
...(inspection.loadedState ? { loadedState: inspection.loadedState } : {}),
|
|
1059
1242
|
state: inspection.state,
|
|
1060
1243
|
approvals: inspection.approvals,
|
|
@@ -1065,7 +1248,7 @@ function transitionInspection(inspection) {
|
|
|
1065
1248
|
};
|
|
1066
1249
|
}
|
|
1067
1250
|
export async function inspectGovernanceTransition(projectRoot, options = {}) {
|
|
1068
|
-
return transitionInspection(await inspectGovernance(projectRoot, options.runner, options.now));
|
|
1251
|
+
return transitionInspection(await inspectGovernance(projectRoot, options.runner, options.now, options));
|
|
1069
1252
|
}
|
|
1070
1253
|
function renderApplyNextHuman(result, presentation) {
|
|
1071
1254
|
presentation.commandIdentity('governance apply-next', 'Controlled activation transition');
|
|
@@ -1103,6 +1286,33 @@ function renderApplyNextHuman(result, presentation) {
|
|
|
1103
1286
|
presentation.status('info', 'Preview only', 'No writes occurred; rerun with --execute to execute at most one phase.');
|
|
1104
1287
|
}
|
|
1105
1288
|
}
|
|
1289
|
+
function governanceScopeFlag(parsed) {
|
|
1290
|
+
const value = readStringFlag(parsed.flags, 'scope') ?? 'activation';
|
|
1291
|
+
if (value !== 'local' && value !== 'activation' && value !== 'lifecycle')
|
|
1292
|
+
throw new Error('Governance scope must be local, activation, or lifecycle.');
|
|
1293
|
+
return value;
|
|
1294
|
+
}
|
|
1295
|
+
async function publicActivationInputs(filePath) {
|
|
1296
|
+
const handle = await open(filePath, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
|
|
1297
|
+
try {
|
|
1298
|
+
const stat = await handle.stat();
|
|
1299
|
+
if (!stat.isFile() || stat.nlink !== 1 || stat.size > 64 * 1024) {
|
|
1300
|
+
throw new Error('Activation inputs must be a singly linked regular public JSON file no larger than 64 KiB.');
|
|
1301
|
+
}
|
|
1302
|
+
const text = await handle.readFile('utf8');
|
|
1303
|
+
let value;
|
|
1304
|
+
try {
|
|
1305
|
+
value = JSON.parse(text);
|
|
1306
|
+
}
|
|
1307
|
+
catch {
|
|
1308
|
+
throw new Error('Activation inputs are not valid JSON; credential or state content must not be supplied here.');
|
|
1309
|
+
}
|
|
1310
|
+
return validateActivationConfiguration(value);
|
|
1311
|
+
}
|
|
1312
|
+
finally {
|
|
1313
|
+
await handle.close();
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1106
1316
|
export async function governanceCommand(parsed, context) {
|
|
1107
1317
|
if (parsed.subcommand === 'assess') {
|
|
1108
1318
|
return governanceAssessmentCommand(parsed, context);
|
|
@@ -1111,16 +1321,20 @@ export async function governanceCommand(parsed, context) {
|
|
|
1111
1321
|
const jsonMode = readBooleanFlag(parsed.flags, 'json') ?? false;
|
|
1112
1322
|
const subcommand = parseGovernanceSubcommand(parsed);
|
|
1113
1323
|
if (!subcommand) {
|
|
1114
|
-
presentation.error('Missing governance subcommand.', 'Run `liftoff governance --help`
|
|
1324
|
+
presentation.error('Missing governance subcommand.', 'Run `liftoff governance --help` to choose a supported planning, approval, execution, or inspection command.');
|
|
1115
1325
|
return 1;
|
|
1116
1326
|
}
|
|
1327
|
+
const rawScope = readStringFlag(parsed.flags, 'scope');
|
|
1328
|
+
if (rawScope && rawScope !== 'local' && rawScope !== 'activation' && rawScope !== 'lifecycle') {
|
|
1329
|
+
throw new Error('Governance scope must be local, activation, or lifecycle.');
|
|
1330
|
+
}
|
|
1331
|
+
const scope = rawScope;
|
|
1117
1332
|
let projectRoot;
|
|
1118
1333
|
try {
|
|
1119
1334
|
projectRoot = await resolveGovernanceProjectRoot(parsed, context);
|
|
1120
1335
|
}
|
|
1121
1336
|
catch (error) {
|
|
1122
|
-
|
|
1123
|
-
return 1;
|
|
1337
|
+
throw new Error(`${errorMessage(error)} Run liftoff governance --help to review accepted project arguments.`);
|
|
1124
1338
|
}
|
|
1125
1339
|
if (!projectRoot) {
|
|
1126
1340
|
const start = parsed.positional[0] ?? readStringFlag(parsed.flags, 'project') ?? context.cwd;
|
|
@@ -1128,13 +1342,34 @@ export async function governanceCommand(parsed, context) {
|
|
|
1128
1342
|
presentation.error(failure.message, failure.remedy);
|
|
1129
1343
|
return 1;
|
|
1130
1344
|
}
|
|
1345
|
+
const inputsFile = readStringFlag(parsed.flags, 'inputs');
|
|
1346
|
+
const activationInputs = inputsFile ? await publicActivationInputs(path.resolve(context.cwd, inputsFile)) : undefined;
|
|
1347
|
+
const fingerprint = readStringFlag(parsed.flags, 'plan');
|
|
1348
|
+
const reviewed = fingerprint ? await loadGovernancePreview(projectRoot, fingerprint) : undefined;
|
|
1349
|
+
if (reviewed && scope && reviewed.plan.scope !== scope)
|
|
1350
|
+
throw new Error('The selected scope does not match the reviewed plan.');
|
|
1351
|
+
if (subcommand === 'recover' && !reviewed?.plan.recovery)
|
|
1352
|
+
throw new Error('Recovery requires a preview explicitly created with governance plan --recover-phase.');
|
|
1353
|
+
if (subcommand === 'apply-next' && reviewed?.plan.recovery)
|
|
1354
|
+
throw new Error('A recovery preview can be executed only with governance recover.');
|
|
1355
|
+
if (subcommand === 'credential-enroll' && reviewed?.plan.phaseId !== 'credential-ready') {
|
|
1356
|
+
throw new Error('Credential enrollment requires a credential-ready preview, not another activation or repair plan.');
|
|
1357
|
+
}
|
|
1358
|
+
const requestedRecovery = readStringFlag(parsed.flags, 'recover-phase');
|
|
1359
|
+
const recoverPhase = reviewed?.plan.recovery ? reviewed.plan.phaseId : phaseIds.find((id) => id === requestedRecovery);
|
|
1360
|
+
const options = {
|
|
1361
|
+
...(scope ? { scope } : {}),
|
|
1362
|
+
command: subcommand,
|
|
1363
|
+
...(activationInputs ?? reviewed?.plan.configuration ? { activationInputs: activationInputs ?? reviewed?.plan.configuration } : {}),
|
|
1364
|
+
...(recoverPhase ? { recoverPhase } : {})
|
|
1365
|
+
};
|
|
1131
1366
|
let inspection;
|
|
1132
1367
|
try {
|
|
1133
|
-
inspection = attachPresentation(await inspectGovernance(projectRoot, context.runner), presentation);
|
|
1368
|
+
inspection = attachPresentation(await inspectGovernance(projectRoot, context.runner, new Date(), options), presentation);
|
|
1134
1369
|
}
|
|
1135
1370
|
catch (error) {
|
|
1136
1371
|
if (subcommand === 'verify') {
|
|
1137
|
-
return renderInspectionFailure(subcommand, projectRoot, error, presentation, jsonMode);
|
|
1372
|
+
return renderInspectionFailure(subcommand, projectRoot, error, presentation, jsonMode, scope);
|
|
1138
1373
|
}
|
|
1139
1374
|
throw error;
|
|
1140
1375
|
}
|
|
@@ -1148,14 +1383,62 @@ export async function governanceCommand(parsed, context) {
|
|
|
1148
1383
|
return 0;
|
|
1149
1384
|
}
|
|
1150
1385
|
if (subcommand === 'plan') {
|
|
1386
|
+
let saved;
|
|
1387
|
+
try {
|
|
1388
|
+
saved = await saveGovernancePreview(transitionInspection(inspection), { runner: context.runner });
|
|
1389
|
+
}
|
|
1390
|
+
catch (error) {
|
|
1391
|
+
if (jsonMode)
|
|
1392
|
+
json(presentation, {
|
|
1393
|
+
...planJson(inspection), ready: false, reason: 'planning-blocked',
|
|
1394
|
+
blockers: [errorMessage(error)], preview: null
|
|
1395
|
+
});
|
|
1396
|
+
else
|
|
1397
|
+
presentation.error(errorMessage(error), 'Supply the named supported inputs or resolve the prerequisite, then request a fresh plan.');
|
|
1398
|
+
return 1;
|
|
1399
|
+
}
|
|
1151
1400
|
if (jsonMode) {
|
|
1152
|
-
json(presentation,
|
|
1401
|
+
json(presentation, {
|
|
1402
|
+
...planJson(inspection),
|
|
1403
|
+
preview: saved ? { fingerprint: saved.preview.fingerprint, path: saved.path, expiresAt: saved.preview.plan.expiresAt } : null,
|
|
1404
|
+
plan: saved?.preview.plan ?? null,
|
|
1405
|
+
projectWrites: false,
|
|
1406
|
+
providerWrites: false,
|
|
1407
|
+
noWrites: true,
|
|
1408
|
+
externalPreviewWritten: saved !== null,
|
|
1409
|
+
nextActions: governanceNextActions(inspection, saved?.preview)
|
|
1410
|
+
});
|
|
1153
1411
|
}
|
|
1154
1412
|
else {
|
|
1155
1413
|
renderPlanHuman(inspection, presentation);
|
|
1414
|
+
if (saved) {
|
|
1415
|
+
presentation.status('info', 'External preview', `${saved.path}; fingerprint ${saved.preview.fingerprint}. This is not approval.`);
|
|
1416
|
+
const next = governanceNextActions(inspection, saved.preview)[0];
|
|
1417
|
+
if (next)
|
|
1418
|
+
presentation.remedy([next.command.executable, ...next.command.args.map((arg) => /\s/u.test(arg) ? JSON.stringify(arg) : arg)].join(' '));
|
|
1419
|
+
}
|
|
1156
1420
|
}
|
|
1157
1421
|
return 0;
|
|
1158
1422
|
}
|
|
1423
|
+
if (subcommand === 'approve') {
|
|
1424
|
+
const approved = await approveGovernancePreview({
|
|
1425
|
+
projectRoot, fingerprint: fingerprint,
|
|
1426
|
+
inspect: async () => transitionInspection(await inspectGovernance(projectRoot, context.runner, new Date(), options)),
|
|
1427
|
+
runner: context.runner
|
|
1428
|
+
});
|
|
1429
|
+
const refreshed = attachPresentation(await inspectGovernance(projectRoot, context.runner, new Date(), options), presentation);
|
|
1430
|
+
const result = {
|
|
1431
|
+
schemaVersion: 2, command: 'governance approve', projectRoot, scope,
|
|
1432
|
+
approved: true, executed: false, envelopeId: approved.envelope.id,
|
|
1433
|
+
envelopeHash: canonicalApprovalEnvelopeHash(approved.envelope), expiresAt: approved.envelope.expiresAt,
|
|
1434
|
+
nextActions: governanceNextActions(refreshed, { fingerprint: fingerprint, plan: approved.plan })
|
|
1435
|
+
};
|
|
1436
|
+
if (jsonMode)
|
|
1437
|
+
json(presentation, result);
|
|
1438
|
+
else
|
|
1439
|
+
presentation.status('success', 'Plan approved', `Approval ${approved.envelope.id} was saved without executing its operations.`);
|
|
1440
|
+
return 0;
|
|
1441
|
+
}
|
|
1159
1442
|
if (subcommand === 'resume') {
|
|
1160
1443
|
const result = {
|
|
1161
1444
|
...statusJson(inspection, 'resume'),
|
|
@@ -1182,16 +1465,21 @@ export async function governanceCommand(parsed, context) {
|
|
|
1182
1465
|
return await renderVerifyHuman(inspection, presentation);
|
|
1183
1466
|
}
|
|
1184
1467
|
catch (error) {
|
|
1185
|
-
return renderInspectionFailure(subcommand, projectRoot, error, presentation, jsonMode);
|
|
1468
|
+
return renderInspectionFailure(subcommand, projectRoot, error, presentation, jsonMode, scope);
|
|
1186
1469
|
}
|
|
1187
1470
|
}
|
|
1188
|
-
const execute = readBooleanFlag(parsed.flags, 'execute') ?? false;
|
|
1471
|
+
const execute = subcommand === 'credential-enroll' || (readBooleanFlag(parsed.flags, 'execute') ?? false);
|
|
1189
1472
|
const transitionInput = transitionInspection(inspection);
|
|
1190
1473
|
const result = execute
|
|
1191
1474
|
? await executeApplyNext({
|
|
1192
1475
|
inspection: transitionInput,
|
|
1193
1476
|
runner: context.runner,
|
|
1194
|
-
|
|
1477
|
+
reviewedPlan: reviewed?.plan,
|
|
1478
|
+
recovery: subcommand === 'recover',
|
|
1479
|
+
...(subcommand === 'credential-enroll' ? {
|
|
1480
|
+
credentialEnrollment: { protectedStdin: readBooleanFlag(parsed.flags, 'protected-stdin') ?? false }
|
|
1481
|
+
} : {}),
|
|
1482
|
+
reinspect: async () => transitionInspection(attachPresentation(await inspectGovernance(projectRoot, context.runner, new Date(), options), presentation))
|
|
1195
1483
|
})
|
|
1196
1484
|
: await previewApplyNext({
|
|
1197
1485
|
inspection: transitionInput,
|
|
@@ -1199,11 +1487,18 @@ export async function governanceCommand(parsed, context) {
|
|
|
1199
1487
|
execute: false
|
|
1200
1488
|
});
|
|
1201
1489
|
if (jsonMode) {
|
|
1202
|
-
|
|
1490
|
+
const refreshed = execute
|
|
1491
|
+
? await inspectGovernance(projectRoot, context.runner, new Date(), { ...options, recoverPhase: undefined })
|
|
1492
|
+
: inspection;
|
|
1493
|
+
json(presentation, {
|
|
1494
|
+
...result, command: `governance ${subcommand}`,
|
|
1495
|
+
progress: refreshed.readiness.completion,
|
|
1496
|
+
nextActions: governanceNextActions(refreshed)
|
|
1497
|
+
});
|
|
1203
1498
|
}
|
|
1204
1499
|
else {
|
|
1205
1500
|
renderApplyNextHuman(result, presentation);
|
|
1206
1501
|
}
|
|
1207
|
-
return result.applied ||
|
|
1502
|
+
return result.applied || ['execute-required', 'external-operation-pending'].includes(result.reason) ? 0 : 1;
|
|
1208
1503
|
}
|
|
1209
1504
|
//# sourceMappingURL=commands.js.map
|