@cassiomc1/forgeloop 1.13.0 → 2.0.0-rc.2
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/AGENT_COMPATIBILITY.md +8 -0
- package/CONTRIBUTING.md +11 -7
- package/DOCS_INDEX.md +37 -4
- package/ENG/c-development-eng.md +2 -2
- package/ENG/cpp-development-eng.md +2 -2
- package/ENG/dotnet-aspnetcore-development-eng.md +7 -1
- package/ENG/flutter-development-eng.md +4 -2
- package/ENG/go-development-eng.md +2 -2
- package/ENG/java-development-eng.md +3 -3
- package/ENG/nodejs-backend-development-eng.md +10 -6
- package/ENG/php-development-eng.md +3 -3
- package/ENG/python-development-eng.md +205 -0
- package/ENG/rust-development-eng.md +42 -1
- package/ENG/sec-code-eng.md +7 -7
- package/ENG/sql-development-eng.md +4 -4
- package/ENG/swift-development-eng.md +5 -4
- package/ENG/test-code-eng.md +1 -1
- package/ENG/typescript-development-eng.md +4 -4
- package/EXECUTION_STATE.md +12 -0
- package/GUIDE_ROUTER.md +6 -0
- package/LICENSE +133 -21
- package/LOOP_ENGINEERING.md +28 -2
- package/LOOP_SYSTEM_DESIGN.md +10 -10
- package/ORCHESTRATOR_INTEGRATION.md +9 -5
- package/PROTOCOL_INTEGRATION.md +55 -2
- package/README.md +63 -59
- package/TERMINOLOGY.md +2 -0
- package/THIRD_PARTY_NOTICES.md +3 -4
- package/THREAT_MODEL.md +140 -1
- package/completions/_forgeloop +29 -3
- package/completions/forgeloop.bash +55 -3
- package/completions/forgeloop.fish +183 -1
- package/docs/ADVISORY_CONTEXT.md +25 -0
- package/docs/AGENT_BROWSER_ADAPTER.md +81 -0
- package/docs/AGENT_BROWSER_VERIFICATION.md +6 -0
- package/docs/AGENT_PROTOCOL_SUMMARY.md +74 -39
- package/docs/AGENT_SKILL.md +66 -0
- package/docs/ARTIFACT_REFERENCE.md +171 -35
- package/docs/AUDIT_UX.md +46 -0
- package/docs/BROWSER_VERIFICATION.md +136 -0
- package/docs/CLI_REFERENCE.md +512 -8
- package/docs/CODE_ATTESTATION.md +2 -2
- package/docs/DOCUMENTATION_GUIDE.md +34 -11
- package/docs/GETTING_STARTED.md +3 -3
- package/docs/JEV_BENCHMARKS.md +31 -0
- package/docs/MODEL_ROUTING.md +37 -0
- package/docs/OPENSRC_ADAPTER.md +241 -0
- package/docs/PACKAGE_CONTENTS.md +46 -18
- package/docs/PROVIDERS.md +126 -0
- package/docs/PROVIDER_ARCHITECTURE.md +199 -0
- package/docs/RECIPES.md +9 -0
- package/docs/RELEASE_CHECKLIST.md +116 -12
- package/docs/SECURITY_REVIEW.md +71 -0
- package/docs/SELF_HOSTED_TESTS.md +121 -0
- package/docs/SEMANTIC_DECISION_PLANE.md +71 -0
- package/docs/TEST_INTELLIGENCE.md +29 -0
- package/docs/TEST_PRUNING.md +14 -0
- package/docs/TROUBLESHOOTING.md +198 -1
- package/docs/UNIVERSAL_INTEGRATION.md +31 -0
- package/docs/assets/diagrams/forgeloop-code-attestation-flow.html +2 -2
- package/docs/assets/diagrams/forgeloop-code-attestation-flow.receipt.json +5 -5
- package/docs/assets/diagrams/forgeloop-code-attestation-flow.svg +1 -1
- package/docs/assets/diagrams/forgeloop-engineering-flow.html +39 -26
- package/docs/assets/diagrams/forgeloop-engineering-flow.receipt.json +6 -6
- package/docs/assets/diagrams/forgeloop-engineering-flow.svg +26 -26
- package/docs/assets/diagrams/forgeloop-verification-trust-flow.html +2 -1
- package/docs/assets/diagrams/forgeloop-verification-trust-flow.receipt.json +5 -5
- package/docs/assets/diagrams/forgeloop-verification-trust-flow.svg +1 -1
- package/docs/diagrams/README.md +13 -9
- package/docs/diagrams/forgeloop-code-attestation-flow.workflow.json +1 -1
- package/docs/diagrams/forgeloop-engineering-flow.workflow.json +24 -19
- package/docs/diagrams/forgeloop-verification-trust-flow.workflow.json +1 -0
- package/docs/diagrams/reviews/forgeloop-code-attestation-flow.review.json +4 -4
- package/docs/diagrams/reviews/forgeloop-engineering-flow.review.json +4 -4
- package/docs/diagrams/reviews/forgeloop-verification-trust-flow.review.json +4 -4
- package/docs/documentation-manifest.json +774 -16
- package/docs/protocol-requirements.json +24 -0
- package/package.json +43 -11
- package/schemas/config.schema.json +14 -0
- package/schemas/context-plan.schema.json +18 -0
- package/schemas/semantic-decision.schema.json +46 -0
- package/schemas/task-bundle.schema.json +7 -2
- package/schemas/test-utility.schema.json +44 -0
- package/scripts/CI_VALIDATORS.md +6 -6
- package/scripts/benchmark-jev.mjs +5 -0
- package/scripts/benchmark-test-intelligence.mjs +4 -0
- package/scripts/generate-agent-protocol-summary.mjs +6 -3
- package/scripts/generate-forgeloop-skill.mjs +133 -0
- package/scripts/jev-smoke.mjs +19 -0
- package/scripts/run-mcp-clean-tests.mjs +30 -0
- package/skills/forgeloop/README.md +9 -0
- package/skills/forgeloop/SKILL.md +77 -0
- package/skills/forgeloop/references/lifecycle.md +9 -0
- package/skills/forgeloop/references/recovery.md +7 -0
- package/skills/forgeloop/references/verification.md +7 -0
- package/src/adapters/agent-browser/assertions.js +47 -0
- package/src/adapters/agent-browser/commands.js +54 -0
- package/src/adapters/agent-browser/index.js +3 -0
- package/src/adapters/agent-browser/locator.js +40 -0
- package/src/adapters/agent-browser/process.js +215 -0
- package/src/adapters/agent-browser/provider.js +316 -0
- package/src/adapters/emulated-services/constants.js +24 -0
- package/src/adapters/emulated-services/index.js +7 -0
- package/src/adapters/emulated-services/process.js +162 -0
- package/src/adapters/emulated-services/provider.js +289 -0
- package/src/adapters/opensrc/normalize.js +90 -0
- package/src/adapters/opensrc/process.js +248 -0
- package/src/adapters/opensrc/provider.js +338 -0
- package/src/adapters/opensrc/search.js +264 -0
- package/src/adapters/temporary-paths.js +18 -0
- package/src/adapters/typesafe/client.js +28 -0
- package/src/adapters/typesafe/engine.js +63 -0
- package/src/adapters/typesafe/normalize.js +41 -0
- package/src/cli.js +218 -174
- package/src/commands/action-show.js +5 -2
- package/src/commands/advance.js +5 -1
- package/src/commands/baseline.js +13 -11
- package/src/commands/checkpoint-revalidate.js +192 -0
- package/src/commands/complete.js +19 -3
- package/src/commands/context-plan.js +38 -0
- package/src/commands/contract-create.js +266 -0
- package/src/commands/contract-revise.js +236 -0
- package/src/commands/decision-show.js +14 -0
- package/src/commands/decision-status.js +22 -0
- package/src/commands/discover.js +41 -0
- package/src/commands/doctor.js +51 -13
- package/src/commands/gate-record.js +194 -0
- package/src/commands/gate-revalidate.js +135 -0
- package/src/commands/handoff-list.js +6 -1
- package/src/commands/handoff-show.js +6 -1
- package/src/commands/migrate-protocol.js +2 -2
- package/src/commands/model-route.js +40 -0
- package/src/commands/next.js +7 -1
- package/src/commands/policy-diff.js +25 -4
- package/src/commands/progress.js +17 -11
- package/src/commands/record-diagnosis.js +3 -0
- package/src/commands/route.js +146 -18
- package/src/commands/semantic-plan.js +41 -0
- package/src/commands/status.js +6 -1
- package/src/commands/storage-backup.js +15 -0
- package/src/commands/storage-migrate.js +5 -0
- package/src/commands/storage-migration-resume.js +5 -0
- package/src/commands/storage-migration-status.js +44 -0
- package/src/commands/storage-restore-resume.js +7 -0
- package/src/commands/storage-restore.js +8 -0
- package/src/commands/task-abandon.js +224 -0
- package/src/commands/task-list.js +8 -43
- package/src/commands/task-migrate-contract-bootstrap-repair.js +288 -0
- package/src/commands/task-migrate.js +3 -3
- package/src/commands/task-recover.js +10 -2
- package/src/commands/task-repair-contract-bootstrap.js +263 -0
- package/src/commands/task-scope.js +18 -2
- package/src/commands/task-show.js +16 -2
- package/src/commands/test-inventory.js +5 -0
- package/src/commands/test-prune-plan.js +5 -0
- package/src/commands/test-prune-probe.js +5 -0
- package/src/commands/test-utility.js +5 -0
- package/src/commands/validate-protocol.js +39 -15
- package/src/commands/validate-receipt.js +8 -1
- package/src/config/guides.json +4 -0
- package/src/core/action-ledger-projection.js +10 -3
- package/src/core/actions.js +38 -75
- package/src/core/activation.js +11 -3
- package/src/core/approvals.js +17 -51
- package/src/core/artifact-registry.js +83 -2
- package/src/core/artifacts.js +81 -27
- package/src/core/attachment-paths.js +45 -0
- package/src/core/attestation-coverage.js +6 -1
- package/src/core/attestation-verifier.js +21 -2
- package/src/core/attestation.js +17 -6
- package/src/core/audit-ux.js +514 -0
- package/src/core/audit.js +6 -1
- package/src/core/browser-verification/constants.js +149 -0
- package/src/core/browser-verification/normalize.js +254 -0
- package/src/core/browser-verification/provider.js +519 -0
- package/src/core/browser-verification/service.js +115 -0
- package/src/core/bundles.js +160 -66
- package/src/core/checkpoint-revalidation.js +321 -0
- package/src/core/cli-command-definitions.js +350 -7
- package/src/core/code-manifest.js +9 -3
- package/src/core/command-executors.js +274 -108
- package/src/core/command-input.js +136 -44
- package/src/core/completion-artifacts.js +85 -34
- package/src/core/completion-ownership.js +19 -8
- package/src/core/completion-recovery-rebind.js +11 -1
- package/src/core/completion.js +64 -15
- package/src/core/config.js +3 -0
- package/src/core/conformance.js +2 -1
- package/src/core/context-compiler/budget.js +9 -0
- package/src/core/context-compiler/candidates.js +39 -0
- package/src/core/context-compiler/compiler.js +63 -0
- package/src/core/context-compiler/fingerprint.js +11 -0
- package/src/core/context-compiler/policy.js +13 -0
- package/src/core/context-compiler/result.js +23 -0
- package/src/core/continuity-lint.js +13 -2
- package/src/core/continuity-reconciliation.js +16 -3
- package/src/core/continuity.js +16 -8
- package/src/core/contract-bootstrap-recovery.js +656 -0
- package/src/core/contract-revision.js +209 -0
- package/src/core/decision/artifact.js +70 -0
- package/src/core/decision/benchmarks.js +103 -0
- package/src/core/decision/cache.js +27 -0
- package/src/core/decision/constants.js +58 -0
- package/src/core/decision/cutover.js +34 -0
- package/src/core/decision/engine.js +22 -0
- package/src/core/decision/errors.js +68 -0
- package/src/core/decision/events.js +104 -0
- package/src/core/decision/freshness.js +19 -0
- package/src/core/decision/normalizers/index.js +115 -0
- package/src/core/decision/policy.js +18 -0
- package/src/core/decision/projection.js +16 -0
- package/src/core/decision/question-registry.js +201 -0
- package/src/core/decision/request.js +26 -0
- package/src/core/decision/resolver.js +138 -0
- package/src/core/decision/result.js +58 -0
- package/src/core/decision/service.js +181 -0
- package/src/core/decision/state-builder.js +65 -0
- package/src/core/decision/task-bindings.js +35 -0
- package/src/core/decision/test-provider.js +32 -0
- package/src/core/decision/thresholds.js +15 -0
- package/src/core/diagnosis-model.js +11 -0
- package/src/core/diagnosis.js +10 -1
- package/src/core/diagnostic-projection.js +29 -12
- package/src/core/diagnostic-record.js +28 -3
- package/src/core/efficiency.js +6 -1
- package/src/core/error-codes.js +278 -0
- package/src/core/events.js +535 -147
- package/src/core/evidence-readiness.js +9 -0
- package/src/core/execution-prerequisites.js +36 -13
- package/src/core/execution-profile.js +63 -38
- package/src/core/execution.js +26 -40
- package/src/core/filesystem.js +11 -0
- package/src/core/gate-artifact.js +105 -19
- package/src/core/gate-provenance.js +125 -0
- package/src/core/handoff.js +26 -14
- package/src/core/history.js +39 -35
- package/src/core/hypothesis-projection.js +32 -0
- package/src/core/information-gain-projection.js +104 -100
- package/src/core/inspect.js +12 -3
- package/src/core/integration-invocation-policy.js +37 -4
- package/src/core/integration-resources.js +119 -87
- package/src/core/ledger-event-collection.js +189 -0
- package/src/core/ledger-relations.js +23 -0
- package/src/core/model-router/constants.js +10 -0
- package/src/core/model-router/policy.js +103 -0
- package/src/core/model-router/router.js +37 -0
- package/src/core/native-storage.js +12 -0
- package/src/core/next-action-continuity.js +31 -9
- package/src/core/next-action-model.js +58 -0
- package/src/core/next-action-phases.js +131 -43
- package/src/core/next-action-refresh.js +43 -9
- package/src/core/next-action-review-phase.js +9 -4
- package/src/core/next-action-verification-phase.js +2 -1
- package/src/core/next-action.js +56 -9
- package/src/core/phase.js +266 -103
- package/src/core/policy-engine.js +12 -13
- package/src/core/preflight-consistency.js +89 -62
- package/src/core/preflight-loaders.js +48 -9
- package/src/core/preflight-model.js +6 -1
- package/src/core/preflight.js +29 -12
- package/src/core/progress.js +21 -10
- package/src/core/protocol-info.js +68 -0
- package/src/core/protocol-migration.js +7 -6
- package/src/core/protocol.js +20 -0
- package/src/core/reconcile-closure.js +281 -67
- package/src/core/recovery-history.js +67 -47
- package/src/core/reflection.js +72 -77
- package/src/core/responsibility.js +25 -13
- package/src/core/resumability.js +154 -44
- package/src/core/route-artifact.js +28 -4
- package/src/core/router.js +67 -1
- package/src/core/runtime-context.js +123 -61
- package/src/core/schema-validation.js +11 -3
- package/src/core/security-review/constants.js +64 -0
- package/src/core/security-review/normalize.js +245 -0
- package/src/core/security-review/provider.js +204 -0
- package/src/core/security-review/service.js +134 -0
- package/src/core/semantic-planning/constants.js +19 -0
- package/src/core/semantic-planning/projection.js +94 -0
- package/src/core/semantic-planning/service.js +15 -0
- package/src/core/settlement.js +12 -1
- package/src/core/signing/bundle-file.js +53 -0
- package/src/core/signing/bundle-reference.js +15 -0
- package/src/core/signing/sigstore.js +60 -33
- package/src/core/signing/statement-file.js +27 -0
- package/src/core/sources.js +37 -0
- package/src/core/storage-runtime-registry.js +41 -0
- package/src/core/structural-quality/artifacts.js +20 -14
- package/src/core/structural-quality/service.js +81 -56
- package/src/core/target-layout.js +1 -1
- package/src/core/task-claim-state.js +249 -15
- package/src/core/task-command.js +21 -1
- package/src/core/task-conflict-inspection.js +133 -102
- package/src/core/task-descriptor.js +29 -22
- package/src/core/task-discovery.js +288 -90
- package/src/core/task-lock.js +66 -306
- package/src/core/task-migration-validation.js +5 -5
- package/src/core/task-migration.js +17 -287
- package/src/core/task-paths.js +13 -0
- package/src/core/task-recovery.js +1 -0
- package/src/core/task-snapshot.js +51 -8
- package/src/core/task-storage-migration.js +18 -0
- package/src/core/templates.js +23 -8
- package/src/core/test-intelligence/benchmarks.js +68 -0
- package/src/core/test-intelligence/inventory.js +73 -0
- package/src/core/test-intelligence/prune.js +94 -0
- package/src/core/test-intelligence/semantic-state.js +15 -0
- package/src/core/test-intelligence/service.js +40 -0
- package/src/core/test-intelligence/utility.js +50 -0
- package/src/core/trace.js +164 -105
- package/src/core/trajectory-evaluation.js +15 -3
- package/src/core/trajectory-metrics.js +47 -32
- package/src/core/transaction.js +16 -287
- package/src/core/verification-scope.js +20 -12
- package/src/core/work-state.js +100 -59
- package/src/integration.d.ts +492 -0
- package/src/integration.js +54 -0
- package/src/providers/README.md +47 -0
- package/src/providers/capabilities.js +46 -0
- package/src/providers/errors.js +15 -0
- package/src/providers/index.js +29 -0
- package/src/providers/json-snapshot.js +105 -0
- package/src/providers/registry.js +152 -0
- package/src/repository-index/binary-manager.js +7 -5
- package/src/storage/artifact-bytes.js +22 -0
- package/src/storage/attachment-binding.js +11 -0
- package/src/storage/attachment-files.js +82 -0
- package/src/storage/attachment-references.js +36 -0
- package/src/storage/backup.js +53 -0
- package/src/storage/capabilities.js +31 -0
- package/src/storage/connection-owner.js +119 -0
- package/src/storage/connection.js +376 -0
- package/src/storage/existing-project-scope.js +45 -0
- package/src/storage/export-destination.js +44 -0
- package/src/storage/exporter.js +284 -0
- package/src/storage/file-durability.js +46 -0
- package/src/storage/importer.js +461 -0
- package/src/storage/index.js +74 -0
- package/src/storage/leases.js +63 -0
- package/src/storage/ledger-event-snapshot.js +150 -0
- package/src/storage/ledger-relations.js +220 -0
- package/src/storage/legacy-maintenance-boundary.js +18 -0
- package/src/storage/legacy-signature.js +115 -0
- package/src/storage/maintenance-handoff.js +129 -0
- package/src/storage/maintenance-owner.js +42 -0
- package/src/storage/maintenance.js +310 -0
- package/src/storage/metadata-json.js +35 -0
- package/src/storage/migration-activate.js +59 -0
- package/src/storage/migration-archive.js +57 -0
- package/src/storage/migration-attachment-root.js +61 -0
- package/src/storage/migration-candidate.js +398 -0
- package/src/storage/migration-publication.js +143 -0
- package/src/storage/migration-rollback-admission.js +54 -0
- package/src/storage/migration-rollback-preparation.js +132 -0
- package/src/storage/migration-rollback-publication.js +183 -0
- package/src/storage/migration-rollback-release.js +66 -0
- package/src/storage/migration-rollback-source-stage.js +103 -0
- package/src/storage/migration-rollback.js +43 -0
- package/src/storage/migration-source-partition.js +56 -0
- package/src/storage/migration-source.js +255 -0
- package/src/storage/migration-terminal.js +39 -0
- package/src/storage/migration-validation.js +75 -0
- package/src/storage/migration.js +43 -0
- package/src/storage/operational-attachments.js +49 -0
- package/src/storage/operational-context.js +50 -0
- package/src/storage/project-backup.js +98 -0
- package/src/storage/project-bootstrap.js +24 -0
- package/src/storage/project-boundary.js +79 -0
- package/src/storage/project-read-snapshot.js +18 -0
- package/src/storage/project-replacement.js +137 -0
- package/src/storage/project-restore.js +203 -0
- package/src/storage/record-definitions.js +45 -0
- package/src/storage/repository.js +507 -0
- package/src/storage/restore-journal.js +62 -0
- package/src/storage/restore-layout.js +30 -0
- package/src/storage/restore-preparation.js +84 -0
- package/src/storage/restore-replacement-archive.js +162 -0
- package/src/storage/restore-replacement-owner.js +31 -0
- package/src/storage/restore-replacement-preparation.js +128 -0
- package/src/storage/restore-replacement.js +142 -0
- package/src/storage/restore-terminal.js +32 -0
- package/src/storage/runtime.js +31 -0
- package/src/storage/schema.js +214 -0
- package/src/storage/singleton-import.js +84 -0
- package/src/storage/snapshot-continuity.js +16 -0
- package/src/storage/snapshot.js +36 -0
- package/src/storage/storage-marker.js +79 -0
- package/src/storage/task-guards.js +107 -0
- package/src/storage/topology.js +42 -0
- package/src/storage/transaction.js +179 -0
- package/src/storage/unit-of-work.js +663 -0
- package/src/storage/windows-process-incarnation.js +206 -0
- package/.forgeloop/forgeloop.gitignore +0 -4
- package/LICENSE-DOCS.md +0 -13
- package/src/core/transaction-maintenance.js +0 -70
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { lstat } from "node:fs/promises";
|
|
2
|
+
import { assertSafePath } from "../core/filesystem.js";
|
|
3
|
+
import { readMaintenanceOwner } from "../storage/maintenance-owner.js";
|
|
4
|
+
|
|
5
|
+
async function present(target, relativePath) {
|
|
6
|
+
try { await lstat(await assertSafePath(target, relativePath)); return true; }
|
|
7
|
+
catch (error) { if (error.code === "ENOENT") return false; throw error; }
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async function inspectOwner(target) {
|
|
11
|
+
const marker = await assertSafePath(target, ".forgeloop/.storage-maintenance");
|
|
12
|
+
if (!(await lstat(marker)).isDirectory()) return { status: "MALFORMED", owner: null };
|
|
13
|
+
try {
|
|
14
|
+
const { value } = await readMaintenanceOwner(target);
|
|
15
|
+
return {
|
|
16
|
+
status: "RETAINED",
|
|
17
|
+
owner: { ownerId: value.ownerId, pid: value.pid, acquiredAt: value.acquiredAt, ...(value.hostname ? { hostname: value.hostname } : {}) },
|
|
18
|
+
ownerLiveness: "NOT_VERIFIED",
|
|
19
|
+
handoffPresent: await present(target, ".forgeloop/.storage-maintenance/handoff")
|
|
20
|
+
|| await present(target, `.forgeloop/storage-maintenance-history/handoffs/${value.ownerId}.json`),
|
|
21
|
+
};
|
|
22
|
+
} catch (error) {
|
|
23
|
+
if (error.code === "ENOENT") return { status: "INCOMPLETE", owner: null };
|
|
24
|
+
if (error.code === "E_STORAGE_MAINTENANCE_OWNER_INVALID") return { status: "MALFORMED", owner: null };
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Inspect bootstrap layout even when ordinary database dispatch is excluded. */
|
|
30
|
+
export async function runStorageMigrationStatus({ target }) {
|
|
31
|
+
const databasePresent = await present(target, ".forgeloop/state.sqlite");
|
|
32
|
+
const legacyOperationalPaths = [];
|
|
33
|
+
for (const relativePath of [".forgeloop/task-state", ".forgeloop/work-state.json", ".forgeloop/events.ndjson", ".forgeloop/.txn"]) {
|
|
34
|
+
if (await present(target, relativePath)) legacyOperationalPaths.push(relativePath);
|
|
35
|
+
}
|
|
36
|
+
const maintenance = await present(target, ".forgeloop/.storage-maintenance")
|
|
37
|
+
? await inspectOwner(target) : { status: "NONE", owner: null };
|
|
38
|
+
return {
|
|
39
|
+
schemaVersion: 1,
|
|
40
|
+
layout: databasePresent ? legacyOperationalPaths.length ? "MIXED" : "SQLITE_PRESENT" : "FILESYSTEM",
|
|
41
|
+
databasePresent, legacyOperationalPaths, maintenance,
|
|
42
|
+
databaseValidation: "NOT_RUN", publicationValidation: "NOT_VERIFIED",
|
|
43
|
+
};
|
|
44
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { resumeProjectStorageRestore } from "../storage/project-restore.js";
|
|
2
|
+
import { resumeActiveProjectReplacement } from "../storage/project-replacement.js";
|
|
3
|
+
|
|
4
|
+
export async function runStorageRestoreResume({ target, operationId, expectedOwnerId, writersQuiesced = false, replaceActive = false, packageRoot } = {}) {
|
|
5
|
+
const resume = replaceActive === true ? resumeActiveProjectReplacement : resumeProjectStorageRestore;
|
|
6
|
+
return resume(target, { operationId, expectedOwnerId, writersQuiesced, packageRoot });
|
|
7
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { restoreProjectStorageToFreshProject } from "../storage/project-restore.js";
|
|
3
|
+
import { replaceActiveProjectStorage } from "../storage/project-replacement.js";
|
|
4
|
+
|
|
5
|
+
export async function runStorageRestore({ target, source, writersQuiesced = false, replaceActive = false, packageRoot } = {}) {
|
|
6
|
+
const restore = replaceActive === true ? replaceActiveProjectStorage : restoreProjectStorageToFreshProject;
|
|
7
|
+
return restore(path.resolve(target, source), target, { writersQuiesced, packageRoot });
|
|
8
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
import { resolveTaskContext } from "../core/task-context.js";
|
|
4
|
+
import { inspectTaskConflictState } from "../core/task-conflict-inspection.js";
|
|
5
|
+
import { appendProtocolEvent } from "../core/events.js";
|
|
6
|
+
import { withTaskTransaction } from "../core/transaction.js";
|
|
7
|
+
import { currentRepositoryFingerprint } from "../core/repository.js";
|
|
8
|
+
import { withProjectClaimsLock } from "../core/task-lock.js";
|
|
9
|
+
import { readTaskDescriptor } from "../core/task-descriptor.js";
|
|
10
|
+
import { createTaskRecovery, writeTaskRecovery } from "../core/task-recovery.js";
|
|
11
|
+
import { resolveTaskClaimState } from "../core/task-claim-state.js";
|
|
12
|
+
import {
|
|
13
|
+
E_TASK_ABANDON_AUTHORIZATION_REQUIRED,
|
|
14
|
+
E_TASK_ABANDON_INCONSISTENT,
|
|
15
|
+
E_TASK_ABANDON_INVALID_STATE,
|
|
16
|
+
E_TASK_ABANDON_UNSAFE,
|
|
17
|
+
E_TASK_ALREADY_ABANDONED,
|
|
18
|
+
} from "../core/error-codes.js";
|
|
19
|
+
|
|
20
|
+
const ABANDONMENT_REASON_CODES = Object.freeze(["CALLER_ABANDONED"]);
|
|
21
|
+
|
|
22
|
+
function abandonError(code, message, details = {}) {
|
|
23
|
+
const error = new Error(message);
|
|
24
|
+
error.code = code;
|
|
25
|
+
Object.assign(error, details);
|
|
26
|
+
return error;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function sameList(left, right) {
|
|
30
|
+
return Array.isArray(left)
|
|
31
|
+
&& Array.isArray(right)
|
|
32
|
+
&& left.length === right.length
|
|
33
|
+
&& left.every((value, index) => value === right[index]);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function snapshot(inspection) {
|
|
37
|
+
return {
|
|
38
|
+
phase: inspection.evidence.phase,
|
|
39
|
+
revision: inspection.evidence.workStateRevision,
|
|
40
|
+
ledgerLastSeq: inspection.evidence.ledgerLastSeq,
|
|
41
|
+
claimState: inspection.evidence.claimState,
|
|
42
|
+
effectiveWriteClaims: [...inspection.evidence.effectiveWriteClaims],
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function assertSnapshotUnchanged(before, after, taskId) {
|
|
47
|
+
const unchanged = before.phase === after.phase
|
|
48
|
+
&& before.revision === after.revision
|
|
49
|
+
&& before.ledgerLastSeq === after.ledgerLastSeq
|
|
50
|
+
&& before.claimState === after.claimState
|
|
51
|
+
&& sameList(before.effectiveWriteClaims, after.effectiveWriteClaims);
|
|
52
|
+
if (!unchanged) {
|
|
53
|
+
throw abandonError(
|
|
54
|
+
E_TASK_ABANDON_UNSAFE,
|
|
55
|
+
`Task ${taskId} changed during abandonment precondition validation`,
|
|
56
|
+
{ before, after },
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function assertAbandonableOwnership(target, taskId, packageRoot) {
|
|
62
|
+
const projection = await resolveTaskClaimState(target, { taskId, packageRoot });
|
|
63
|
+
if (projection.phase === "COMPLETE" || projection.claimState === "RELEASED_BY_COMPLETION") {
|
|
64
|
+
throw abandonError(E_TASK_ABANDON_INVALID_STATE, `Task ${taskId} is COMPLETE and cannot be abandoned`);
|
|
65
|
+
}
|
|
66
|
+
if (!projection.valid || !projection.ownershipValid) {
|
|
67
|
+
throw abandonError(
|
|
68
|
+
E_TASK_ABANDON_INCONSISTENT,
|
|
69
|
+
`Task ${taskId} claim ownership is inconsistent; abandonment is blocked`,
|
|
70
|
+
{ reasonCodes: projection.reasonCodes, ownershipErrors: projection.ownershipErrors },
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
if (projection.claimState === "RELEASED_BY_RECOVERY") {
|
|
74
|
+
throw abandonError(
|
|
75
|
+
E_TASK_ALREADY_ABANDONED,
|
|
76
|
+
`Task ${taskId} is already abandoned/recovered`,
|
|
77
|
+
{ recovery: projection.recovery },
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
if (projection.claimState !== "ACTIVE" || projection.mutationAllowed !== true) {
|
|
81
|
+
throw abandonError(E_TASK_ABANDON_INVALID_STATE, `Task ${taskId} is not an active mutable task`);
|
|
82
|
+
}
|
|
83
|
+
return projection;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function assertInspectionAbandonable(taskId, inspection) {
|
|
87
|
+
if (!inspection.evidence.healthy || !inspection.evidence.ledgerValid || !inspection.evidence.ownershipValid) {
|
|
88
|
+
throw abandonError(
|
|
89
|
+
E_TASK_ABANDON_INCONSISTENT,
|
|
90
|
+
`Task ${taskId} cannot be abandoned because canonical ownership or ledger validation failed`,
|
|
91
|
+
{ reasonCodes: inspection.reasonCodes, ledgerErrors: inspection.evidence.ledgerErrors },
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
if (!inspection.evidence.phase || inspection.evidence.phase === "COMPLETE") {
|
|
95
|
+
throw abandonError(E_TASK_ABANDON_INVALID_STATE, `Task ${taskId} is not eligible for abandonment`);
|
|
96
|
+
}
|
|
97
|
+
if (inspection.evidence.claimState !== "ACTIVE" || inspection.evidence.mutationAllowed !== true) {
|
|
98
|
+
throw abandonError(E_TASK_ABANDON_INVALID_STATE, `Task ${taskId} is not an active mutable task`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export async function runTaskAbandon({
|
|
103
|
+
target,
|
|
104
|
+
packageRoot,
|
|
105
|
+
taskId,
|
|
106
|
+
acknowledgeAbandonment = false,
|
|
107
|
+
} = {}) {
|
|
108
|
+
if (typeof taskId !== "string" || taskId.trim() === "") {
|
|
109
|
+
throw abandonError(E_TASK_ABANDON_INVALID_STATE, "task-abandon requires an explicit --task ID");
|
|
110
|
+
}
|
|
111
|
+
if (!acknowledgeAbandonment) {
|
|
112
|
+
throw abandonError(
|
|
113
|
+
E_TASK_ABANDON_AUTHORIZATION_REQUIRED,
|
|
114
|
+
"task-abandon requires explicit caller acknowledgement: re-run with --acknowledge-abandonment",
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
let context;
|
|
119
|
+
try {
|
|
120
|
+
context = await resolveTaskContext(target, { taskId, packageRoot, explicitRequired: true });
|
|
121
|
+
} catch (error) {
|
|
122
|
+
throw abandonError(
|
|
123
|
+
E_TASK_ABANDON_INCONSISTENT,
|
|
124
|
+
`Task ${taskId} cannot be abandoned because its canonical identity is unreadable`,
|
|
125
|
+
{ causeCode: error.code, causeMessage: error.message },
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
const effectiveTaskId = context.taskId;
|
|
129
|
+
await assertAbandonableOwnership(target, effectiveTaskId, packageRoot);
|
|
130
|
+
|
|
131
|
+
return withProjectClaimsLock(target, "task-abandon", async () => {
|
|
132
|
+
await assertAbandonableOwnership(target, effectiveTaskId, packageRoot);
|
|
133
|
+
const inspectionBeforeLock = await inspectTaskConflictState(target, {
|
|
134
|
+
taskId: effectiveTaskId,
|
|
135
|
+
packageRoot,
|
|
136
|
+
});
|
|
137
|
+
assertInspectionAbandonable(effectiveTaskId, inspectionBeforeLock);
|
|
138
|
+
const before = snapshot(inspectionBeforeLock);
|
|
139
|
+
|
|
140
|
+
return withTaskTransaction({
|
|
141
|
+
target,
|
|
142
|
+
taskId: effectiveTaskId,
|
|
143
|
+
operation: "task-abandon",
|
|
144
|
+
packageRoot,
|
|
145
|
+
recordCommitEvent: true,
|
|
146
|
+
}, async (transaction) => {
|
|
147
|
+
const descriptor = await readTaskDescriptor(target, context.taskKey, packageRoot);
|
|
148
|
+
if (descriptor.value.taskId !== effectiveTaskId || descriptor.value.taskKey !== context.taskKey) {
|
|
149
|
+
throw abandonError(E_TASK_ABANDON_UNSAFE, `Task ${effectiveTaskId} identity changed during abandonment`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
await assertAbandonableOwnership(target, effectiveTaskId, packageRoot);
|
|
153
|
+
const inspection = await inspectTaskConflictState(target, {
|
|
154
|
+
taskId: effectiveTaskId,
|
|
155
|
+
packageRoot,
|
|
156
|
+
ignoredLockId: transaction.lock?.lockId ?? null,
|
|
157
|
+
});
|
|
158
|
+
assertInspectionAbandonable(effectiveTaskId, inspection);
|
|
159
|
+
assertSnapshotUnchanged(before, snapshot(inspection), effectiveTaskId);
|
|
160
|
+
|
|
161
|
+
const repository = await currentRepositoryFingerprint(target);
|
|
162
|
+
const recoveryId = `recovery-${randomUUID()}`;
|
|
163
|
+
const abandonedAt = new Date().toISOString();
|
|
164
|
+
const releasedClaims = [...inspection.evidence.effectiveWriteClaims];
|
|
165
|
+
const details = {
|
|
166
|
+
recoveryId,
|
|
167
|
+
classification: "ABANDONED",
|
|
168
|
+
reasonCodes: [...ABANDONMENT_REASON_CODES],
|
|
169
|
+
previousPhase: inspection.evidence.phase,
|
|
170
|
+
previousRevision: inspection.evidence.workStateRevision,
|
|
171
|
+
previousHead: inspection.evidence.repositoryHead,
|
|
172
|
+
previousBranch: inspection.evidence.repositoryBranch,
|
|
173
|
+
currentHead: repository.head,
|
|
174
|
+
currentBranch: repository.branch,
|
|
175
|
+
releasedClaims,
|
|
176
|
+
authorityKind: "CALLER_ACKNOWLEDGED",
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const abandonmentEvent = await appendProtocolEvent(target, {
|
|
180
|
+
taskId: effectiveTaskId,
|
|
181
|
+
event: "TASK_ABANDONED",
|
|
182
|
+
at: abandonedAt,
|
|
183
|
+
details,
|
|
184
|
+
}, packageRoot, { taskId: effectiveTaskId });
|
|
185
|
+
|
|
186
|
+
await writeTaskRecovery(target, createTaskRecovery({
|
|
187
|
+
taskId: effectiveTaskId,
|
|
188
|
+
recoveredAt: abandonedAt,
|
|
189
|
+
recoveryId,
|
|
190
|
+
recoveryEventSeq: abandonmentEvent.seq,
|
|
191
|
+
classificationAtRecovery: "ABANDONED",
|
|
192
|
+
reasonCodes: ABANDONMENT_REASON_CODES,
|
|
193
|
+
releasedClaims,
|
|
194
|
+
previousPhase: inspection.evidence.phase,
|
|
195
|
+
previousRevision: inspection.evidence.workStateRevision,
|
|
196
|
+
repositoryFingerprint: repository,
|
|
197
|
+
authority: { kind: "CALLER_ACKNOWLEDGED" },
|
|
198
|
+
}), packageRoot);
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
taskId: effectiveTaskId,
|
|
202
|
+
taskKey: context.taskKey,
|
|
203
|
+
abandoned: true,
|
|
204
|
+
recovered: true,
|
|
205
|
+
recoveryId,
|
|
206
|
+
classification: "ABANDONED",
|
|
207
|
+
reasonCodes: [...ABANDONMENT_REASON_CODES],
|
|
208
|
+
phase: inspection.evidence.phase,
|
|
209
|
+
claimsReleased: true,
|
|
210
|
+
releasedClaims,
|
|
211
|
+
effectiveWriteClaims: [],
|
|
212
|
+
claimState: "RELEASED_BY_RECOVERY",
|
|
213
|
+
mutationAllowed: false,
|
|
214
|
+
authority: { kind: "CALLER_ACKNOWLEDGED" },
|
|
215
|
+
message: `Task ${effectiveTaskId} explicitly abandoned by caller acknowledgement; write claims released without completion claim`,
|
|
216
|
+
};
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function formatTaskAbandonResult(result) {
|
|
222
|
+
const claims = result.releasedClaims.length === 0 ? "none" : result.releasedClaims.join(", ");
|
|
223
|
+
return `${result.message}\nclassification: ${result.classification}\nphase: ${result.phase} (unchanged)\nreleased claims: ${claims}\n`;
|
|
224
|
+
}
|
|
@@ -1,60 +1,25 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { discoverTaskListPage } from "../core/task-discovery.js";
|
|
2
2
|
import { WORK_PHASES } from "../core/protocol.js";
|
|
3
3
|
|
|
4
4
|
export async function runTaskList({ target, packageRoot, phase = null, active = false, limit = null, offset = 0 } = {}) {
|
|
5
|
-
const tasks = await discoverTasks(target, packageRoot);
|
|
6
5
|
const normalizedPhase = typeof phase === "string" && phase.trim() !== "" ? phase.trim().toUpperCase() : null;
|
|
6
|
+
const start = Number.isInteger(offset) && offset >= 0 ? offset : 0;
|
|
7
|
+
const pageLimit = Number.isInteger(limit) && limit >= 0 ? limit : null;
|
|
8
|
+
const { tasks: projected, total } = await discoverTaskListPage(target, packageRoot, { phase: normalizedPhase, active, offset: start, limit: pageLimit });
|
|
7
9
|
if (normalizedPhase && !WORK_PHASES.includes(normalizedPhase)) {
|
|
8
10
|
const error = new Error(`Unknown task phase filter: ${phase}`);
|
|
9
11
|
error.code = "E_TASK_PHASE_INVALID";
|
|
10
12
|
throw error;
|
|
11
13
|
}
|
|
12
|
-
const
|
|
13
|
-
const filtered = active
|
|
14
|
-
? phaseFiltered.filter((task) => task.claimState === "ACTIVE" && task.mutationAllowed === true)
|
|
15
|
-
: phaseFiltered;
|
|
16
|
-
const start = Number.isInteger(offset) && offset >= 0 ? offset : 0;
|
|
17
|
-
const end = Number.isInteger(limit) && limit >= 0 ? start + limit : undefined;
|
|
18
|
-
const projected = filtered.slice(start, end);
|
|
14
|
+
const end = pageLimit === null ? undefined : start + pageLimit;
|
|
19
15
|
return {
|
|
20
|
-
tasks: projected
|
|
21
|
-
if (task.healthy === false) {
|
|
22
|
-
return {
|
|
23
|
-
taskId: task.taskId ?? null,
|
|
24
|
-
taskKey: task.taskKey,
|
|
25
|
-
directory: task.directory,
|
|
26
|
-
healthy: false,
|
|
27
|
-
error: task.error,
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
return {
|
|
31
|
-
taskId: task.taskId,
|
|
32
|
-
taskKey: task.taskKey,
|
|
33
|
-
directory: task.directory,
|
|
34
|
-
healthy: true,
|
|
35
|
-
phase: task.phase,
|
|
36
|
-
writeClaims: task.writeClaims ?? [],
|
|
37
|
-
historicalWriteClaims: task.historicalWriteClaims ?? [],
|
|
38
|
-
effectiveWriteClaims: task.effectiveWriteClaims ?? [],
|
|
39
|
-
claimState: task.claimState,
|
|
40
|
-
recovery: task.recovery,
|
|
41
|
-
mutationAllowed: task.mutationAllowed,
|
|
42
|
-
ownershipValid: task.ownershipValid,
|
|
43
|
-
ownershipErrors: task.ownershipErrors ?? task.errors ?? [],
|
|
44
|
-
reasonCodes: task.reasonCodes ?? [],
|
|
45
|
-
locked: task.locked,
|
|
46
|
-
hasContinuity: task.hasContinuity,
|
|
47
|
-
hasReceipt: task.hasReceipt,
|
|
48
|
-
createdAt: task.createdAt,
|
|
49
|
-
updatedAt: task.updatedAt,
|
|
50
|
-
};
|
|
51
|
-
}),
|
|
16
|
+
tasks: projected,
|
|
52
17
|
...(normalizedPhase ? { phase: normalizedPhase } : {}),
|
|
53
18
|
...(active ? { active: true } : {}),
|
|
54
19
|
offset: start,
|
|
55
20
|
...(Number.isInteger(limit) && limit >= 0 ? { limit } : {}),
|
|
56
|
-
total
|
|
57
|
-
hasMore: end !== undefined ? end <
|
|
21
|
+
total,
|
|
22
|
+
hasMore: end !== undefined ? end < total : false,
|
|
58
23
|
};
|
|
59
24
|
}
|
|
60
25
|
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import { canonicalFingerprint } from "../core/artifacts.js";
|
|
2
|
+
import { readContract } from "../core/contract.js";
|
|
3
|
+
import { appendProtocolEvent, validateEventLedger, validateStateLedgerCoherence } from "../core/events.js";
|
|
4
|
+
import { readPersistedRoute } from "../core/route-artifact.js";
|
|
5
|
+
import { resolveTaskContext } from "../core/task-context.js";
|
|
6
|
+
import { taskArtifactPath } from "../core/task-paths.js";
|
|
7
|
+
import {
|
|
8
|
+
classifyLockStaleness,
|
|
9
|
+
readLockInfo,
|
|
10
|
+
releaseStaleTaskLockIfUnchanged,
|
|
11
|
+
withProjectClaimsLock,
|
|
12
|
+
} from "../core/task-lock.js";
|
|
13
|
+
import { withTaskTransaction } from "../core/transaction.js";
|
|
14
|
+
import { resolveTaskClaimState } from "../core/task-claim-state.js";
|
|
15
|
+
import { readWorkState } from "../core/work-state.js";
|
|
16
|
+
import {
|
|
17
|
+
CONTRACT_BOOTSTRAP_REPAIR_AUTHORITY,
|
|
18
|
+
CONTRACT_BOOTSTRAP_REPAIR_MIGRATION_DEFECT,
|
|
19
|
+
CONTRACT_BOOTSTRAP_REPAIR_MIGRATION_EVENT,
|
|
20
|
+
CONTRACT_BOOTSTRAP_REPAIR_MIGRATION_VERSION,
|
|
21
|
+
assertContractBootstrapRepairMigrationDetails,
|
|
22
|
+
isLegacyContractBootstrapRepairMigrationCandidate,
|
|
23
|
+
resolveEffectiveContractBootstrapRepairAnchor,
|
|
24
|
+
} from "../core/contract-bootstrap-recovery.js";
|
|
25
|
+
import {
|
|
26
|
+
E_CONTRACT_BOOTSTRAP_REPAIR_MIGRATION_AUTHORIZATION_REQUIRED,
|
|
27
|
+
E_CONTRACT_BOOTSTRAP_REPAIR_MIGRATION_INVALID,
|
|
28
|
+
E_TASK_LOCKED,
|
|
29
|
+
} from "../core/error-codes.js";
|
|
30
|
+
|
|
31
|
+
function migrationError(code, message, details = {}) {
|
|
32
|
+
const error = new Error(message);
|
|
33
|
+
error.code = code;
|
|
34
|
+
Object.assign(error, details);
|
|
35
|
+
return error;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function routePath(taskId) {
|
|
39
|
+
return taskArtifactPath(taskId, "route");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function proveLegacyRepairState(target, packageRoot, taskId, marker) {
|
|
43
|
+
const contract = await readContract(target, packageRoot, { taskId });
|
|
44
|
+
if (contract.fingerprint !== marker.details.contractFingerprint) {
|
|
45
|
+
throw migrationError(
|
|
46
|
+
E_CONTRACT_BOOTSTRAP_REPAIR_MIGRATION_INVALID,
|
|
47
|
+
"Current contract does not match the legacy repair marker fingerprint",
|
|
48
|
+
{ artifacts: [taskArtifactPath(taskId, "contract")] },
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const state = await readWorkState(target, { packageRoot, taskId });
|
|
53
|
+
if (!state) {
|
|
54
|
+
throw migrationError(
|
|
55
|
+
E_CONTRACT_BOOTSTRAP_REPAIR_MIGRATION_INVALID,
|
|
56
|
+
"Current work-state is required to prove the legacy repair boundary",
|
|
57
|
+
{ artifacts: [taskArtifactPath(taskId, "state")] },
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
if (state.taskId !== taskId
|
|
61
|
+
|| state.phase !== marker.details.reconstructedPhase
|
|
62
|
+
|| state.contractFingerprint !== marker.details.contractFingerprint
|
|
63
|
+
|| canonicalFingerprint(state) !== marker.details.reconstructedStateFingerprint) {
|
|
64
|
+
throw migrationError(
|
|
65
|
+
E_CONTRACT_BOOTSTRAP_REPAIR_MIGRATION_INVALID,
|
|
66
|
+
"Current work-state does not exactly match the legacy repair marker reconstruction",
|
|
67
|
+
{ artifacts: [taskArtifactPath(taskId, "state")] },
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
if (!Number.isInteger(state.revision) || state.revision < 0) {
|
|
71
|
+
throw migrationError(
|
|
72
|
+
E_CONTRACT_BOOTSTRAP_REPAIR_MIGRATION_INVALID,
|
|
73
|
+
"Current work-state revision is not a valid non-negative integer",
|
|
74
|
+
{ artifacts: [taskArtifactPath(taskId, "state")] },
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
let route = null;
|
|
79
|
+
if (state.phase === "ROUTED") {
|
|
80
|
+
try {
|
|
81
|
+
route = await readPersistedRoute(target, packageRoot, { taskId });
|
|
82
|
+
} catch (error) {
|
|
83
|
+
throw migrationError(
|
|
84
|
+
E_CONTRACT_BOOTSTRAP_REPAIR_MIGRATION_INVALID,
|
|
85
|
+
`ROUTED legacy repair requires a readable persisted route: ${error.message}`,
|
|
86
|
+
{ artifacts: [routePath(taskId)] },
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
if (marker.details.routeFingerprint === null
|
|
90
|
+
|| state.routeFingerprint !== marker.details.routeFingerprint
|
|
91
|
+
|| route.fingerprint !== marker.details.routeFingerprint
|
|
92
|
+
|| route.value.contractFingerprint !== marker.details.contractFingerprint) {
|
|
93
|
+
throw migrationError(
|
|
94
|
+
E_CONTRACT_BOOTSTRAP_REPAIR_MIGRATION_INVALID,
|
|
95
|
+
"Persisted route does not exactly match the legacy repair marker reconstruction",
|
|
96
|
+
{ artifacts: [routePath(taskId)] },
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
} else if (marker.details.routeFingerprint !== null || state.routeFingerprint !== undefined) {
|
|
100
|
+
throw migrationError(
|
|
101
|
+
E_CONTRACT_BOOTSTRAP_REPAIR_MIGRATION_INVALID,
|
|
102
|
+
"CONTRACT_READY legacy repair must not claim a route",
|
|
103
|
+
{ artifacts: [taskArtifactPath(taskId, "state")] },
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return { contract, state, route };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function migrationResult(taskId, candidate, migrationEvent, alreadyMigrated) {
|
|
111
|
+
return {
|
|
112
|
+
taskId,
|
|
113
|
+
migrated: !alreadyMigrated,
|
|
114
|
+
alreadyMigrated,
|
|
115
|
+
legacyMarkerSeq: candidate.marker.seq,
|
|
116
|
+
legacyRepairCommitSeq: candidate.repairCommit.seq,
|
|
117
|
+
migrationEventSeq: migrationEvent?.seq ?? null,
|
|
118
|
+
repairId: candidate.marker.details.repairId,
|
|
119
|
+
reconstructedPhase: candidate.marker.details.reconstructedPhase,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function inspectAlreadyMigrated(target, packageRoot, taskId, ledger) {
|
|
124
|
+
const anchor = resolveEffectiveContractBootstrapRepairAnchor(ledger.events);
|
|
125
|
+
if (!ledger.valid || !anchor || anchor.kind !== "MIGRATED_LEGACY") {
|
|
126
|
+
throw migrationError(
|
|
127
|
+
E_CONTRACT_BOOTSTRAP_REPAIR_MIGRATION_INVALID,
|
|
128
|
+
"Existing contract bootstrap migration is invalid or the ledger is not strict",
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
const ownership = await resolveTaskClaimState(target, { taskId, packageRoot });
|
|
132
|
+
if (!ownership.ownershipValid) {
|
|
133
|
+
throw migrationError(
|
|
134
|
+
E_CONTRACT_BOOTSTRAP_REPAIR_MIGRATION_INVALID,
|
|
135
|
+
"Current task state no longer satisfies the migrated task ownership invariants",
|
|
136
|
+
{ errors: ownership.errors },
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
return migrationResult(taskId, { marker: anchor.sourceMarker, repairCommit: ledger.events[anchor.sourceMarker.seq] }, anchor.migrationEvent, true);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export async function runTaskMigrateContractBootstrapRepair({ target, packageRoot, taskId, acknowledgeMigration } = {}) {
|
|
143
|
+
if (!acknowledgeMigration) {
|
|
144
|
+
throw migrationError(
|
|
145
|
+
E_CONTRACT_BOOTSTRAP_REPAIR_MIGRATION_AUTHORIZATION_REQUIRED,
|
|
146
|
+
"task-migrate-contract-bootstrap-repair requires fresh explicit --acknowledge-migration",
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const context = await resolveTaskContext(target, { taskId, packageRoot, explicitRequired: true });
|
|
151
|
+
const effectiveTaskId = context.taskId;
|
|
152
|
+
const initial = await validateEventLedger(target, packageRoot, {
|
|
153
|
+
taskId: effectiveTaskId,
|
|
154
|
+
allowUnmigratedLegacyContractBootstrapRepairMarkers: true,
|
|
155
|
+
});
|
|
156
|
+
if (!initial.valid) {
|
|
157
|
+
const strict = await validateEventLedger(target, packageRoot, { taskId: effectiveTaskId });
|
|
158
|
+
if (strict.valid) return inspectAlreadyMigrated(target, packageRoot, effectiveTaskId, strict);
|
|
159
|
+
throw migrationError(
|
|
160
|
+
E_CONTRACT_BOOTSTRAP_REPAIR_MIGRATION_INVALID,
|
|
161
|
+
"Ledger is not structurally trustworthy for legacy contract bootstrap migration",
|
|
162
|
+
{ errors: initial.errors },
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
const initialCandidate = isLegacyContractBootstrapRepairMigrationCandidate(initial.events, effectiveTaskId);
|
|
166
|
+
if (!initialCandidate) {
|
|
167
|
+
const strict = await validateEventLedger(target, packageRoot, { taskId: effectiveTaskId });
|
|
168
|
+
if (strict.valid) return inspectAlreadyMigrated(target, packageRoot, effectiveTaskId, strict);
|
|
169
|
+
throw migrationError(
|
|
170
|
+
E_CONTRACT_BOOTSTRAP_REPAIR_MIGRATION_INVALID,
|
|
171
|
+
"Ledger does not match the exact legacy contract bootstrap repair migration boundary",
|
|
172
|
+
{ errors: initial.errors },
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return withProjectClaimsLock(target, "task-migrate-contract-bootstrap-repair", async () => {
|
|
177
|
+
const lock = await readLockInfo(target, effectiveTaskId);
|
|
178
|
+
const status = classifyLockStaleness(lock);
|
|
179
|
+
if (status.status === "LIVE") throw migrationError(E_TASK_LOCKED, `Task ${effectiveTaskId} has a live mutation lock`);
|
|
180
|
+
if (status.status === "UNKNOWN" || status.status === "CORRUPT") {
|
|
181
|
+
throw migrationError(
|
|
182
|
+
E_CONTRACT_BOOTSTRAP_REPAIR_MIGRATION_INVALID,
|
|
183
|
+
`Task ${effectiveTaskId} lock state is ${status.status}; refusing unsafe migration`,
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
if (status.status === "STALE") {
|
|
187
|
+
const released = await releaseStaleTaskLockIfUnchanged(target, effectiveTaskId, lock);
|
|
188
|
+
if (!released.released && released.reason !== "LOCK_MISSING") {
|
|
189
|
+
throw migrationError(
|
|
190
|
+
E_CONTRACT_BOOTSTRAP_REPAIR_MIGRATION_INVALID,
|
|
191
|
+
`Task ${effectiveTaskId} stale lock could not be safely released: ${released.reason}`,
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const result = await withTaskTransaction({
|
|
197
|
+
target,
|
|
198
|
+
taskId: effectiveTaskId,
|
|
199
|
+
operation: "task-migrate-contract-bootstrap-repair",
|
|
200
|
+
packageRoot,
|
|
201
|
+
recordCommitEvent: true,
|
|
202
|
+
}, async () => {
|
|
203
|
+
const locked = await validateEventLedger(target, packageRoot, {
|
|
204
|
+
taskId: effectiveTaskId,
|
|
205
|
+
allowUnmigratedLegacyContractBootstrapRepairMarkers: true,
|
|
206
|
+
});
|
|
207
|
+
if (!locked.valid) {
|
|
208
|
+
const strict = await validateEventLedger(target, packageRoot, { taskId: effectiveTaskId });
|
|
209
|
+
if (strict.valid) return inspectAlreadyMigrated(target, packageRoot, effectiveTaskId, strict);
|
|
210
|
+
throw migrationError(
|
|
211
|
+
E_CONTRACT_BOOTSTRAP_REPAIR_MIGRATION_INVALID,
|
|
212
|
+
"Pre-commit ledger revalidation failed inside the migration transaction",
|
|
213
|
+
{ errors: locked.errors },
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
const candidate = isLegacyContractBootstrapRepairMigrationCandidate(locked.events, effectiveTaskId);
|
|
217
|
+
if (!candidate
|
|
218
|
+
|| candidate.marker.seq !== initialCandidate.marker.seq
|
|
219
|
+
|| candidate.marker.hash !== initialCandidate.marker.hash
|
|
220
|
+
|| candidate.repairCommit.hash !== initialCandidate.repairCommit.hash) {
|
|
221
|
+
const strict = await validateEventLedger(target, packageRoot, { taskId: effectiveTaskId });
|
|
222
|
+
if (strict.valid) return inspectAlreadyMigrated(target, packageRoot, effectiveTaskId, strict);
|
|
223
|
+
throw migrationError(
|
|
224
|
+
E_CONTRACT_BOOTSTRAP_REPAIR_MIGRATION_INVALID,
|
|
225
|
+
"Ledger changed between inspection and migration lock; aborting",
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const proof = await proveLegacyRepairState(target, packageRoot, effectiveTaskId, candidate.marker);
|
|
230
|
+
const coherenceErrors = validateStateLedgerCoherence(proof.state, locked.events);
|
|
231
|
+
if (coherenceErrors.length > 0) {
|
|
232
|
+
throw migrationError(
|
|
233
|
+
E_CONTRACT_BOOTSTRAP_REPAIR_MIGRATION_INVALID,
|
|
234
|
+
"Work-state and ledger are not coherent at the legacy repair boundary",
|
|
235
|
+
{ errors: coherenceErrors },
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
const migratedAt = new Date().toISOString();
|
|
239
|
+
const details = {
|
|
240
|
+
migrationVersion: CONTRACT_BOOTSTRAP_REPAIR_MIGRATION_VERSION,
|
|
241
|
+
defect: CONTRACT_BOOTSTRAP_REPAIR_MIGRATION_DEFECT,
|
|
242
|
+
legacyMarkerSeq: candidate.marker.seq,
|
|
243
|
+
legacyMarkerHash: candidate.marker.hash,
|
|
244
|
+
legacyRepairId: candidate.marker.details.repairId,
|
|
245
|
+
legacyRepairCommitSeq: candidate.repairCommit.seq,
|
|
246
|
+
legacyRepairCommitHash: candidate.repairCommit.hash,
|
|
247
|
+
legacyRepairTransactionId: candidate.repairCommit.details.transactionId,
|
|
248
|
+
taskId: effectiveTaskId,
|
|
249
|
+
contractFingerprint: candidate.marker.details.contractFingerprint,
|
|
250
|
+
reconstructedPhase: candidate.marker.details.reconstructedPhase,
|
|
251
|
+
routeFingerprint: candidate.marker.details.routeFingerprint,
|
|
252
|
+
reconstructedStateFingerprint: candidate.marker.details.reconstructedStateFingerprint,
|
|
253
|
+
reconstructedStateRevision: proof.state.revision,
|
|
254
|
+
migratedAt,
|
|
255
|
+
authorityKind: CONTRACT_BOOTSTRAP_REPAIR_AUTHORITY,
|
|
256
|
+
};
|
|
257
|
+
assertContractBootstrapRepairMigrationDetails(details);
|
|
258
|
+
const migrationEvent = await appendProtocolEvent(target, {
|
|
259
|
+
taskId: effectiveTaskId,
|
|
260
|
+
event: CONTRACT_BOOTSTRAP_REPAIR_MIGRATION_EVENT,
|
|
261
|
+
at: migratedAt,
|
|
262
|
+
details,
|
|
263
|
+
}, packageRoot, { taskId: effectiveTaskId });
|
|
264
|
+
return migrationResult(effectiveTaskId, candidate, migrationEvent, false);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
const finalLedger = await validateEventLedger(target, packageRoot, { taskId: effectiveTaskId });
|
|
268
|
+
if (!finalLedger.valid) {
|
|
269
|
+
throw migrationError(
|
|
270
|
+
E_CONTRACT_BOOTSTRAP_REPAIR_MIGRATION_INVALID,
|
|
271
|
+
"Post-migration ledger validation failed; the legacy repair remains unverified",
|
|
272
|
+
{ errors: finalLedger.errors },
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
return result;
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export function formatTaskMigrateContractBootstrapRepairResult(result) {
|
|
280
|
+
return [
|
|
281
|
+
`taskId: ${result.taskId}`,
|
|
282
|
+
`migration: ${result.alreadyMigrated ? "already migrated" : "migrated"}`,
|
|
283
|
+
`legacy marker seq: ${result.legacyMarkerSeq}`,
|
|
284
|
+
`migration event seq: ${result.migrationEventSeq}`,
|
|
285
|
+
`repairId: ${result.repairId}`,
|
|
286
|
+
"",
|
|
287
|
+
].join("\n");
|
|
288
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { migrateLegacyTaskStorage } from "../core/task-storage-migration.js";
|
|
2
2
|
|
|
3
|
-
export async function runTaskMigrate({ target, packageRoot, dryRun = false } = {}) {
|
|
4
|
-
const result = await
|
|
3
|
+
export async function runTaskMigrate({ target, packageRoot, dryRun = false, destination, writersQuiesced = false } = {}) {
|
|
4
|
+
const result = await migrateLegacyTaskStorage(target, { packageRoot, dryRun, destination, writersQuiesced });
|
|
5
5
|
return result;
|
|
6
6
|
}
|
|
7
7
|
|