@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
|
@@ -1,16 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { ensureWithin, fileExists, writeFileAtomic } from "./filesystem.js";
|
|
1
|
+
import { ensureWithin, fileExists } from "./filesystem.js";
|
|
4
2
|
import { getPackageRoot } from "./templates.js";
|
|
5
3
|
import {
|
|
6
4
|
LEGACY_TASK_ARTIFACT_PATHS,
|
|
7
|
-
TASK_STATE_ROOT,
|
|
8
|
-
taskArtifactPath,
|
|
9
5
|
taskDirectory,
|
|
10
6
|
} from "./task-paths.js";
|
|
11
7
|
import { assertTaskId, taskStorageKey } from "./task-identity.js";
|
|
12
|
-
import {
|
|
13
|
-
import { readJsonArtifact } from "./artifacts.js";
|
|
8
|
+
import { readPortableJsonArtifact } from "./artifacts.js";
|
|
14
9
|
import { validateMigrationSnapshot } from "./task-migration-validation.js";
|
|
15
10
|
import {
|
|
16
11
|
E_TASK_MIGRATION_IDENTITY_MISMATCH,
|
|
@@ -31,51 +26,11 @@ export async function detectLegacySingletonLayout(target) {
|
|
|
31
26
|
};
|
|
32
27
|
}
|
|
33
28
|
|
|
34
|
-
async function removeLegacyArtifact(target, relativePath) {
|
|
35
|
-
const fullPath = ensureWithin(target, relativePath);
|
|
36
|
-
await rm(fullPath, { recursive: true, force: true });
|
|
37
|
-
if (await fileExists(fullPath)) {
|
|
38
|
-
const error = new Error(
|
|
39
|
-
`Legacy artifact still exists after migration cleanup: ${relativePath}`,
|
|
40
|
-
);
|
|
41
|
-
error.code = E_TASK_MIGRATION_INVALID;
|
|
42
|
-
throw error;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function assertMigrationSnapshotsEqual(source, destination, label) {
|
|
47
|
-
for (const [key, hash] of Object.entries(source.artifactFingerprints)) {
|
|
48
|
-
if (destination.artifactFingerprints[key] !== hash) {
|
|
49
|
-
const error = new Error(`Migration fingerprint mismatch in ${label}: ${key}`);
|
|
50
|
-
error.code = E_TASK_MIGRATION_INVALID;
|
|
51
|
-
throw error;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
for (const [key, hash] of Object.entries(source.directoryFingerprints)) {
|
|
56
|
-
if (destination.directoryFingerprints[key] !== hash) {
|
|
57
|
-
const error = new Error(`Migration directory fingerprint mismatch in ${label}: ${key}`);
|
|
58
|
-
error.code = E_TASK_MIGRATION_INVALID;
|
|
59
|
-
throw error;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (destination.eventCount !== source.eventCount) {
|
|
64
|
-
const error = new Error(`Migration event count mismatch in ${label}`);
|
|
65
|
-
error.code = E_TASK_MIGRATION_INVALID;
|
|
66
|
-
throw error;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
29
|
export async function migrateLegacyLayout(
|
|
71
30
|
target,
|
|
72
31
|
{
|
|
73
32
|
dryRun = false,
|
|
74
33
|
packageRoot = getPackageRoot(),
|
|
75
|
-
afterCopyForTest = null,
|
|
76
|
-
afterPublishForTest = null,
|
|
77
|
-
beforeLegacyCleanupForTest = null,
|
|
78
|
-
removeLegacyArtifactForTest = null,
|
|
79
34
|
} = {},
|
|
80
35
|
) {
|
|
81
36
|
const detection = await detectLegacySingletonLayout(target);
|
|
@@ -90,60 +45,21 @@ export async function migrateLegacyLayout(
|
|
|
90
45
|
let canonicalTaskId = null;
|
|
91
46
|
const artifactIdentities = [];
|
|
92
47
|
|
|
93
|
-
|
|
48
|
+
const identitySchemas = { contract: "current-contract", state: "work-state", continuity: "continuity", receipt: "execution-receipt" };
|
|
94
49
|
for (const item of detection.legacyFiles) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
102
|
-
} catch (err) {
|
|
103
|
-
const error = new Error(`Legacy contract artifact is invalid: ${err.message}`);
|
|
104
|
-
error.code = E_TASK_MIGRATION_INVALID;
|
|
105
|
-
error.cause = err;
|
|
106
|
-
throw error;
|
|
107
|
-
}
|
|
108
|
-
} else if (item.key === "state") {
|
|
109
|
-
try {
|
|
110
|
-
const state = await readJsonArtifact(target, item.path, "work-state", packageRoot);
|
|
111
|
-
if (state.value?.taskId) {
|
|
112
|
-
if (!canonicalTaskId) canonicalTaskId = state.value.taskId;
|
|
113
|
-
artifactIdentities.push({ artifact: "state", taskId: state.value.taskId });
|
|
114
|
-
}
|
|
115
|
-
} catch (err) {
|
|
116
|
-
const error = new Error(`Legacy state artifact is invalid: ${err.message}`);
|
|
117
|
-
error.code = E_TASK_MIGRATION_INVALID;
|
|
118
|
-
error.cause = err;
|
|
119
|
-
throw error;
|
|
120
|
-
}
|
|
121
|
-
} else if (item.key === "continuity") {
|
|
122
|
-
try {
|
|
123
|
-
const continuity = await readJsonArtifact(target, item.path, "continuity", packageRoot);
|
|
124
|
-
if (continuity.value?.taskId) {
|
|
125
|
-
if (!canonicalTaskId) canonicalTaskId = continuity.value.taskId;
|
|
126
|
-
artifactIdentities.push({ artifact: "continuity", taskId: continuity.value.taskId });
|
|
127
|
-
}
|
|
128
|
-
} catch (err) {
|
|
129
|
-
const error = new Error(`Legacy continuity artifact is invalid: ${err.message}`);
|
|
130
|
-
error.code = E_TASK_MIGRATION_INVALID;
|
|
131
|
-
error.cause = err;
|
|
132
|
-
throw error;
|
|
133
|
-
}
|
|
134
|
-
} else if (item.key === "receipt") {
|
|
135
|
-
try {
|
|
136
|
-
const receipt = await readJsonArtifact(target, item.path, "execution-receipt", packageRoot);
|
|
137
|
-
if (receipt.value?.taskId) {
|
|
138
|
-
if (!canonicalTaskId) canonicalTaskId = receipt.value.taskId;
|
|
139
|
-
artifactIdentities.push({ artifact: "receipt", taskId: receipt.value.taskId });
|
|
140
|
-
}
|
|
141
|
-
} catch (err) {
|
|
142
|
-
const error = new Error(`Legacy receipt artifact is invalid: ${err.message}`);
|
|
143
|
-
error.code = E_TASK_MIGRATION_INVALID;
|
|
144
|
-
error.cause = err;
|
|
145
|
-
throw error;
|
|
50
|
+
const schema = identitySchemas[item.key];
|
|
51
|
+
if (!schema) continue;
|
|
52
|
+
try {
|
|
53
|
+
const artifact = await readPortableJsonArtifact(target, item.path, schema, packageRoot);
|
|
54
|
+
if (artifact.value?.taskId) {
|
|
55
|
+
canonicalTaskId ??= artifact.value.taskId;
|
|
56
|
+
artifactIdentities.push({ artifact: item.key, taskId: artifact.value.taskId });
|
|
146
57
|
}
|
|
58
|
+
} catch (err) {
|
|
59
|
+
const error = new Error(`Legacy ${item.key} artifact is invalid: ${err.message}`);
|
|
60
|
+
error.code = E_TASK_MIGRATION_INVALID;
|
|
61
|
+
error.cause = err;
|
|
62
|
+
throw error;
|
|
147
63
|
}
|
|
148
64
|
}
|
|
149
65
|
|
|
@@ -167,7 +83,7 @@ export async function migrateLegacyLayout(
|
|
|
167
83
|
}
|
|
168
84
|
|
|
169
85
|
// 1. Validate complete legacy source snapshot fail-closed before creating any directories
|
|
170
|
-
|
|
86
|
+
await validateMigrationSnapshot(target, {
|
|
171
87
|
taskId: canonicalTaskId,
|
|
172
88
|
packageRoot,
|
|
173
89
|
paths: LEGACY_TASK_ARTIFACT_PATHS,
|
|
@@ -176,8 +92,6 @@ export async function migrateLegacyLayout(
|
|
|
176
92
|
const taskKey = taskStorageKey(canonicalTaskId);
|
|
177
93
|
const finalDirRel = taskDirectory(canonicalTaskId);
|
|
178
94
|
const finalDirAbs = ensureWithin(target, finalDirRel);
|
|
179
|
-
const tempDirRel = `${TASK_STATE_ROOT}/.tmp-${taskKey}`;
|
|
180
|
-
const tempDirAbs = ensureWithin(target, tempDirRel);
|
|
181
95
|
|
|
182
96
|
if (await fileExists(finalDirAbs)) {
|
|
183
97
|
const error = new Error(`Target task directory already exists: ${finalDirRel}`);
|
|
@@ -185,8 +99,6 @@ export async function migrateLegacyLayout(
|
|
|
185
99
|
throw error;
|
|
186
100
|
}
|
|
187
101
|
|
|
188
|
-
const migratedArtifacts = [];
|
|
189
|
-
|
|
190
102
|
if (dryRun) {
|
|
191
103
|
return {
|
|
192
104
|
migrated: false,
|
|
@@ -198,187 +110,5 @@ export async function migrateLegacyLayout(
|
|
|
198
110
|
};
|
|
199
111
|
}
|
|
200
112
|
|
|
201
|
-
|
|
202
|
-
let cleanupStarted = false;
|
|
203
|
-
|
|
204
|
-
// 2. Create temporary migration directory
|
|
205
|
-
await mkdir(tempDirAbs, { recursive: true });
|
|
206
|
-
|
|
207
|
-
try {
|
|
208
|
-
// 3. Create and write task.json descriptor
|
|
209
|
-
const descriptor = createTaskDescriptor({
|
|
210
|
-
taskId: canonicalTaskId,
|
|
211
|
-
writeClaims: [],
|
|
212
|
-
});
|
|
213
|
-
await writeTaskDescriptor(target, descriptor, packageRoot, {
|
|
214
|
-
relativePathOverride: `${tempDirRel}/task.json`,
|
|
215
|
-
});
|
|
216
|
-
migratedArtifacts.push("task.json");
|
|
217
|
-
|
|
218
|
-
// 4. Copy and map files into temp namespace
|
|
219
|
-
const fileMapping = [
|
|
220
|
-
[LEGACY_TASK_ARTIFACT_PATHS.contract, "contract.json"],
|
|
221
|
-
[LEGACY_TASK_ARTIFACT_PATHS.route, "routing-result.json"],
|
|
222
|
-
[LEGACY_TASK_ARTIFACT_PATHS.preflight, "preflight.json"],
|
|
223
|
-
[LEGACY_TASK_ARTIFACT_PATHS.state, "work-state.json"],
|
|
224
|
-
[LEGACY_TASK_ARTIFACT_PATHS.continuity, "continuity.json"],
|
|
225
|
-
[LEGACY_TASK_ARTIFACT_PATHS.receipt, "execution-receipt.json"],
|
|
226
|
-
[LEGACY_TASK_ARTIFACT_PATHS.events, "events.ndjson"],
|
|
227
|
-
];
|
|
228
|
-
|
|
229
|
-
for (const [legacyRel, destName] of fileMapping) {
|
|
230
|
-
const srcAbs = ensureWithin(target, legacyRel);
|
|
231
|
-
if (await fileExists(srcAbs)) {
|
|
232
|
-
const destAbs = path.join(tempDirAbs, destName);
|
|
233
|
-
await cp(srcAbs, destAbs);
|
|
234
|
-
migratedArtifacts.push(destName);
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// 5. Copy directories: gates/ and executions/
|
|
239
|
-
const dirMapping = [
|
|
240
|
-
[LEGACY_TASK_ARTIFACT_PATHS.gates, "gates"],
|
|
241
|
-
[LEGACY_TASK_ARTIFACT_PATHS.executions, "executions"],
|
|
242
|
-
];
|
|
243
|
-
|
|
244
|
-
for (const [legacyRel, destDirName] of dirMapping) {
|
|
245
|
-
const srcAbs = ensureWithin(target, legacyRel);
|
|
246
|
-
if (await fileExists(srcAbs)) {
|
|
247
|
-
const destAbs = path.join(tempDirAbs, destDirName);
|
|
248
|
-
await cp(srcAbs, destAbs, { recursive: true });
|
|
249
|
-
migratedArtifacts.push(`${destDirName}/`);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// Test hook for corruption / failure testing
|
|
254
|
-
if (typeof afterCopyForTest === "function") {
|
|
255
|
-
await afterCopyForTest({ tempDirAbs, tempDirRel, target });
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
// 6. Validate temp snapshot
|
|
259
|
-
const tempPaths = {
|
|
260
|
-
contract: `${tempDirRel}/contract.json`,
|
|
261
|
-
route: `${tempDirRel}/routing-result.json`,
|
|
262
|
-
preflight: `${tempDirRel}/preflight.json`,
|
|
263
|
-
state: `${tempDirRel}/work-state.json`,
|
|
264
|
-
continuity: `${tempDirRel}/continuity.json`,
|
|
265
|
-
receipt: `${tempDirRel}/execution-receipt.json`,
|
|
266
|
-
events: `${tempDirRel}/events.ndjson`,
|
|
267
|
-
gates: `${tempDirRel}/gates`,
|
|
268
|
-
executions: `${tempDirRel}/executions`,
|
|
269
|
-
};
|
|
270
|
-
|
|
271
|
-
const tempSnapshot = await validateMigrationSnapshot(target, {
|
|
272
|
-
taskId: canonicalTaskId,
|
|
273
|
-
packageRoot,
|
|
274
|
-
paths: tempPaths,
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
// 7. Compare copied fingerprints
|
|
278
|
-
assertMigrationSnapshotsEqual(sourceSnapshot, tempSnapshot, "temporary namespace");
|
|
279
|
-
|
|
280
|
-
// 8. Atomically publish
|
|
281
|
-
await mkdir(ensureWithin(target, TASK_STATE_ROOT), { recursive: true });
|
|
282
|
-
await rename(tempDirAbs, finalDirAbs);
|
|
283
|
-
published = true;
|
|
284
|
-
|
|
285
|
-
// Test hook for corruption / failure testing after publication
|
|
286
|
-
if (typeof afterPublishForTest === "function") {
|
|
287
|
-
await afterPublishForTest({ finalDirAbs, finalDirRel, target });
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// 9. Post-publish validation: verify final namespace snapshot and task descriptor
|
|
291
|
-
const finalPaths = {
|
|
292
|
-
contract: taskArtifactPath(canonicalTaskId, "contract"),
|
|
293
|
-
route: taskArtifactPath(canonicalTaskId, "route"),
|
|
294
|
-
preflight: taskArtifactPath(canonicalTaskId, "preflight"),
|
|
295
|
-
state: taskArtifactPath(canonicalTaskId, "state"),
|
|
296
|
-
continuity: taskArtifactPath(canonicalTaskId, "continuity"),
|
|
297
|
-
receipt: taskArtifactPath(canonicalTaskId, "receipt"),
|
|
298
|
-
events: taskArtifactPath(canonicalTaskId, "events"),
|
|
299
|
-
gates: taskArtifactPath(canonicalTaskId, "gates"),
|
|
300
|
-
executions: taskArtifactPath(canonicalTaskId, "executions"),
|
|
301
|
-
};
|
|
302
|
-
|
|
303
|
-
const finalSnapshot = await validateMigrationSnapshot(target, {
|
|
304
|
-
taskId: canonicalTaskId,
|
|
305
|
-
packageRoot,
|
|
306
|
-
paths: finalPaths,
|
|
307
|
-
});
|
|
308
|
-
assertMigrationSnapshotsEqual(sourceSnapshot, finalSnapshot, "published namespace");
|
|
309
|
-
await readTaskDescriptor(target, canonicalTaskId, packageRoot);
|
|
310
|
-
|
|
311
|
-
const migrationReceipt = {
|
|
312
|
-
schemaVersion: 1,
|
|
313
|
-
protocolVersion: 1,
|
|
314
|
-
kind: "LEGACY_TASK_MIGRATION",
|
|
315
|
-
taskId: canonicalTaskId,
|
|
316
|
-
taskKey,
|
|
317
|
-
migratedAt: new Date().toISOString(),
|
|
318
|
-
source: sourceSnapshot,
|
|
319
|
-
destination: finalSnapshot,
|
|
320
|
-
cleanupStatus: "PENDING",
|
|
321
|
-
};
|
|
322
|
-
await writeFileAtomic(
|
|
323
|
-
ensureWithin(target, `${finalDirRel}/migration-receipt.json`),
|
|
324
|
-
`${JSON.stringify(migrationReceipt, null, 2)}\n`,
|
|
325
|
-
);
|
|
326
|
-
migratedArtifacts.push("migration-receipt.json");
|
|
327
|
-
|
|
328
|
-
// Test hook for cleanup failure testing
|
|
329
|
-
if (typeof beforeLegacyCleanupForTest === "function") {
|
|
330
|
-
await beforeLegacyCleanupForTest({ finalDirAbs, finalDirRel, target });
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
// 10. Cleanup legacy task files (strictly after successful publish and validation)
|
|
334
|
-
cleanupStarted = true;
|
|
335
|
-
try {
|
|
336
|
-
for (const item of detection.legacyFiles) {
|
|
337
|
-
if (typeof removeLegacyArtifactForTest === "function") {
|
|
338
|
-
await removeLegacyArtifactForTest(target, item.path);
|
|
339
|
-
} else {
|
|
340
|
-
await removeLegacyArtifact(target, item.path);
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
migrationReceipt.cleanupStatus = "COMPLETED";
|
|
344
|
-
migrationReceipt.cleanedAt = new Date().toISOString();
|
|
345
|
-
await writeFileAtomic(
|
|
346
|
-
ensureWithin(target, `${finalDirRel}/migration-receipt.json`),
|
|
347
|
-
`${JSON.stringify(migrationReceipt, null, 2)}\n`,
|
|
348
|
-
);
|
|
349
|
-
} catch (cleanupErr) {
|
|
350
|
-
const error = new Error(
|
|
351
|
-
`Migration published successfully to ${finalDirRel}, but legacy cleanup failed: ${cleanupErr.message}. Manual cleanup required.`,
|
|
352
|
-
);
|
|
353
|
-
error.code = E_TASK_MIGRATION_INVALID;
|
|
354
|
-
error.cause = cleanupErr;
|
|
355
|
-
throw error;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
return {
|
|
359
|
-
migrated: true,
|
|
360
|
-
taskId: canonicalTaskId,
|
|
361
|
-
taskKey,
|
|
362
|
-
targetDirectory: finalDirRel,
|
|
363
|
-
migratedArtifacts,
|
|
364
|
-
};
|
|
365
|
-
} catch (error) {
|
|
366
|
-
if (published && !cleanupStarted && (await fileExists(finalDirAbs))) {
|
|
367
|
-
try {
|
|
368
|
-
await rm(finalDirAbs, { recursive: true, force: true });
|
|
369
|
-
} catch (rollbackErr) {
|
|
370
|
-
error.rollbackError = rollbackErr;
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
// Cleanup temp dir on failure if it still exists
|
|
375
|
-
if (await fileExists(tempDirAbs)) {
|
|
376
|
-
try {
|
|
377
|
-
await rm(tempDirAbs, { recursive: true, force: true });
|
|
378
|
-
} catch (tempErr) {
|
|
379
|
-
error.tempCleanupError = tempErr;
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
throw error;
|
|
383
|
-
}
|
|
113
|
+
throw Object.assign(new Error("Filesystem task migration apply is retired; use explicit SQLite task migration with destination and excluded writers"), { code: "E_STORAGE_OPERATION_UNSUPPORTED" });
|
|
384
114
|
}
|
package/src/core/task-paths.js
CHANGED
|
@@ -27,6 +27,8 @@ export const TASK_ARTIFACT_FILES = Object.freeze({
|
|
|
27
27
|
verificationScope: "verification-scope.json",
|
|
28
28
|
attestations: "attestations",
|
|
29
29
|
structuralQuality: "structural-quality",
|
|
30
|
+
decisions: "decisions",
|
|
31
|
+
testUtility: "test-utility.json",
|
|
30
32
|
});
|
|
31
33
|
|
|
32
34
|
export const POLICY_ROOT = ".forgeloop/policy";
|
|
@@ -106,6 +108,15 @@ export function taskEvaluationPath(taskId, evaluationId) {
|
|
|
106
108
|
return `${taskDirectory(taskId)}/${TASK_ARTIFACT_FILES.evaluations}/${evaluationId}.json`;
|
|
107
109
|
}
|
|
108
110
|
|
|
111
|
+
export function taskDecisionDirectory(taskId) {
|
|
112
|
+
return taskArtifactPath(taskId, "decisions");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function taskDecisionPath(taskId, decisionId) {
|
|
116
|
+
assertArtifactId(decisionId, /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/, "decision ID");
|
|
117
|
+
return `${taskDecisionDirectory(taskId)}/${decisionId}.json`;
|
|
118
|
+
}
|
|
119
|
+
|
|
109
120
|
function assertArtifactId(value, pattern, label) {
|
|
110
121
|
if (typeof value !== "string" || !pattern.test(value)) {
|
|
111
122
|
throw new Error(`Invalid ${label}: ${value}`);
|
|
@@ -217,6 +228,8 @@ export function buildTaskArtifactPaths(taskId) {
|
|
|
217
228
|
actions: `${dir}/${TASK_ARTIFACT_FILES.actions}`,
|
|
218
229
|
approvals: `${dir}/${TASK_ARTIFACT_FILES.approvals}`,
|
|
219
230
|
evaluations: `${dir}/${TASK_ARTIFACT_FILES.evaluations}`,
|
|
231
|
+
decisions: `${dir}/${TASK_ARTIFACT_FILES.decisions}`,
|
|
232
|
+
testUtility: `${dir}/${TASK_ARTIFACT_FILES.testUtility}`,
|
|
220
233
|
usage: `${dir}/${TASK_ARTIFACT_FILES.usage}`,
|
|
221
234
|
lock: taskLockPath(taskId),
|
|
222
235
|
policySnapshot: `${dir}/${TASK_ARTIFACT_FILES.policySnapshot}`,
|
|
@@ -1,16 +1,21 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { taskArtifactPath } from "./task-paths.js";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { getOperationalStore } from "../storage/operational-context.js";
|
|
4
|
+
import { needsExistingProjectScope, withExistingProjectScope } from "../storage/existing-project-scope.js";
|
|
5
|
+
import { iterateEvents, validateEventLedger, withEventLedgerAudit } from "./events.js";
|
|
2
6
|
import { readWorkState } from "./work-state.js";
|
|
3
7
|
import { canonicalFingerprint } from "./artifacts.js";
|
|
4
8
|
|
|
5
|
-
function ledgerTail(
|
|
6
|
-
|
|
9
|
+
async function ledgerTail(target, packageRoot, options) {
|
|
10
|
+
let last = null;
|
|
11
|
+
for await (const event of iterateEvents(target, packageRoot, options)) last = event;
|
|
7
12
|
return {
|
|
8
13
|
sequence: last?.seq ?? 0,
|
|
9
14
|
hash: last?.hash ?? null,
|
|
10
15
|
};
|
|
11
16
|
}
|
|
12
17
|
|
|
13
|
-
|
|
18
|
+
async function buildPortableTaskSnapshot({
|
|
14
19
|
target,
|
|
15
20
|
packageRoot,
|
|
16
21
|
taskId = null,
|
|
@@ -19,19 +24,17 @@ export async function buildTaskSnapshot({
|
|
|
19
24
|
} = {}) {
|
|
20
25
|
const options = { packageRoot, taskId, ...(eventsPath ? { eventsPath } : {}) };
|
|
21
26
|
const state = await readWorkState(target, { ...options, ...(stateFile ? { statePath: stateFile } : {}) });
|
|
22
|
-
const events = await readEvents(target, packageRoot, options);
|
|
23
27
|
const before = {
|
|
24
28
|
stateRevision: state?.revision ?? null,
|
|
25
|
-
...ledgerTail(
|
|
29
|
+
...await ledgerTail(target, packageRoot, options),
|
|
26
30
|
};
|
|
27
31
|
|
|
28
32
|
const validation = await validateEventLedger(target, packageRoot, options);
|
|
29
33
|
|
|
30
34
|
const rereadState = await readWorkState(target, { ...options, ...(stateFile ? { statePath: stateFile } : {}) });
|
|
31
|
-
const rereadEvents = await readEvents(target, packageRoot, options);
|
|
32
35
|
const after = {
|
|
33
36
|
stateRevision: rereadState?.revision ?? null,
|
|
34
|
-
...ledgerTail(
|
|
37
|
+
...await ledgerTail(target, packageRoot, options),
|
|
35
38
|
};
|
|
36
39
|
|
|
37
40
|
const consistent = JSON.stringify(before) === JSON.stringify(after);
|
|
@@ -51,3 +54,43 @@ export async function buildTaskSnapshot({
|
|
|
51
54
|
events: validation.events,
|
|
52
55
|
};
|
|
53
56
|
}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
function eventSummaryFingerprint(events) {
|
|
60
|
+
const digest = createHash("sha256");
|
|
61
|
+
digest.update("[");
|
|
62
|
+
let first = true;
|
|
63
|
+
for (const { seq, event, at } of events) {
|
|
64
|
+
if (!first) digest.update(",");
|
|
65
|
+
first = false;
|
|
66
|
+
// Same sorted keys/undefined omission as canonicalFingerprint({seq,event,at}).
|
|
67
|
+
digest.update(JSON.stringify({ at, event, seq }));
|
|
68
|
+
}
|
|
69
|
+
return digest.update("]").digest("hex");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Internal callback keeps event source owned until the projection finishes. */
|
|
73
|
+
export async function withTaskSnapshot(options, callback) {
|
|
74
|
+
const { target, packageRoot, taskId = null, eventsPath = null, stateFile = null } = options;
|
|
75
|
+
if (await needsExistingProjectScope(target)) {
|
|
76
|
+
return withExistingProjectScope(target, () => withTaskSnapshot(options, callback), { readOnly: true });
|
|
77
|
+
}
|
|
78
|
+
if (!getOperationalStore(target) || (stateFile && (!taskId || stateFile !== taskArtifactPath(taskId, "state")))) {
|
|
79
|
+
return callback(await buildPortableTaskSnapshot(options));
|
|
80
|
+
}
|
|
81
|
+
return withEventLedgerAudit(target, packageRoot, { taskId, ...(eventsPath ? { eventsPath } : {}) }, async validation => {
|
|
82
|
+
if (Array.isArray(validation.events)) return callback(await buildPortableTaskSnapshot(options));
|
|
83
|
+
const state = await readWorkState(target, { packageRoot, taskId, ...(stateFile ? { statePath: stateFile } : {}) });
|
|
84
|
+
const tail = validation.events.at(-1);
|
|
85
|
+
return callback({ consistent: true, taskId: taskId ?? state?.taskId ?? null,
|
|
86
|
+
anchors: { stateRevision: state?.revision ?? null, sequence: tail?.seq ?? 0, hash: tail?.hash ?? null },
|
|
87
|
+
capturedAt: new Date().toISOString(),
|
|
88
|
+
integrity: { valid: validation.valid, errors: validation.errors, eventCount: validation.events.length,
|
|
89
|
+
fingerprint: eventSummaryFingerprint(validation.events) },
|
|
90
|
+
state, events: validation.events });
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export async function buildTaskSnapshot(options = {}) {
|
|
95
|
+
return withTaskSnapshot(options, snapshot => ({ ...snapshot, events: Array.isArray(snapshot.events) ? snapshot.events : [...snapshot.events] }));
|
|
96
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { detectLegacySingletonLayout, migrateLegacyLayout } from "./task-migration.js";
|
|
2
|
+
import { migrateProjectStorage } from "../storage/migration.js";
|
|
3
|
+
|
|
4
|
+
/** Explicit legacy conversion now publishes canonical SQLite storage. */
|
|
5
|
+
export async function migrateLegacyTaskStorage(target, { packageRoot, dryRun = false, destination, writersQuiesced = false } = {}) {
|
|
6
|
+
const detected = await detectLegacySingletonLayout(target);
|
|
7
|
+
if (!detected.hasLegacy) return migrateLegacyLayout(target, { packageRoot, dryRun });
|
|
8
|
+
if (dryRun) return { ...await migrateLegacyLayout(target, { packageRoot, dryRun }), targetDirectory: ".forgeloop/state.sqlite" };
|
|
9
|
+
if (typeof destination !== "string" || !destination || writersQuiesced !== true) throw Object.assign(new Error("Legacy task migration requires --destination and explicit --writers-quiesced for SQLite cutover"), { code: "E_CLI_INVOCATION_INVALID" });
|
|
10
|
+
const planned = await migrateLegacyLayout(target, { packageRoot, dryRun: true });
|
|
11
|
+
const storage = await migrateProjectStorage(target, { destination, writersQuiesced, packageRoot });
|
|
12
|
+
return {
|
|
13
|
+
migrated: true, taskId: planned.taskId, taskKey: planned.taskKey,
|
|
14
|
+
targetDirectory: ".forgeloop/state.sqlite", storage,
|
|
15
|
+
migrationReceipt: `${destination}/publication/publication-journal.json`,
|
|
16
|
+
migratedArtifacts: detected.legacyFiles.map(item => item.path),
|
|
17
|
+
};
|
|
18
|
+
}
|
package/src/core/templates.js
CHANGED
|
@@ -12,8 +12,17 @@ import { GUIDE_TEMPLATE_PATHS } from "./guide-registry.js";
|
|
|
12
12
|
|
|
13
13
|
const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
|
14
14
|
|
|
15
|
-
const
|
|
16
|
-
".forgeloop/.gitignore":
|
|
15
|
+
const INLINE_TEMPLATE_CONTENT = Object.freeze({
|
|
16
|
+
".forgeloop/.gitignore": [
|
|
17
|
+
"# Local resumable task state is untrusted, target-specific data.",
|
|
18
|
+
"work-state.json",
|
|
19
|
+
"state.sqlite",
|
|
20
|
+
"state.sqlite-*",
|
|
21
|
+
"storage-version.json",
|
|
22
|
+
"executions/",
|
|
23
|
+
"repository-index/",
|
|
24
|
+
"",
|
|
25
|
+
].join("\n"),
|
|
17
26
|
});
|
|
18
27
|
|
|
19
28
|
export const TEMPLATE_PATHS = [
|
|
@@ -36,8 +45,6 @@ export const TEMPLATE_PATHS = [
|
|
|
36
45
|
"PROTOCOL_INTEGRATION.md",
|
|
37
46
|
"AGENT_COMPATIBILITY.md",
|
|
38
47
|
"THIRD_PARTY_NOTICES.md",
|
|
39
|
-
"LICENSE",
|
|
40
|
-
"LICENSE-DOCS.md",
|
|
41
48
|
...GUIDE_TEMPLATE_PATHS,
|
|
42
49
|
"schemas/routing-input.schema.json",
|
|
43
50
|
"schemas/routing-result.schema.json",
|
|
@@ -88,6 +95,9 @@ export const TEMPLATE_PATHS = [
|
|
|
88
95
|
"schemas/execution-profile-benchmark-run.schema.json",
|
|
89
96
|
"schemas/execution-profile-benchmark-aggregate.schema.json",
|
|
90
97
|
"schemas/structural-quality.schema.json",
|
|
98
|
+
"schemas/semantic-decision.schema.json",
|
|
99
|
+
"schemas/context-plan.schema.json",
|
|
100
|
+
"schemas/test-utility.schema.json",
|
|
91
101
|
];
|
|
92
102
|
|
|
93
103
|
export function getPackageRoot() {
|
|
@@ -97,14 +107,19 @@ export function getPackageRoot() {
|
|
|
97
107
|
export async function readTemplateEntries(packageRoot = PACKAGE_ROOT) {
|
|
98
108
|
return Promise.all(
|
|
99
109
|
TEMPLATE_PATHS.map(async (relativePath) => {
|
|
100
|
-
const sourcePath =
|
|
110
|
+
const sourcePath = relativePath;
|
|
111
|
+
const inlineContent = INLINE_TEMPLATE_CONTENT[relativePath];
|
|
101
112
|
return {
|
|
102
113
|
relativePath: targetPathForSource(relativePath),
|
|
103
114
|
sourcePath,
|
|
104
115
|
legacyRelativePath: legacyPathForSource(sourcePath),
|
|
105
|
-
bytes: Buffer.from(
|
|
106
|
-
|
|
107
|
-
|
|
116
|
+
bytes: Buffer.from(
|
|
117
|
+
inlineContent !== undefined
|
|
118
|
+
? inlineContent
|
|
119
|
+
: isNativeAdapterPath(relativePath)
|
|
120
|
+
? nativeShim(relativePath)
|
|
121
|
+
: await readFile(path.join(packageRoot, sourcePath)),
|
|
122
|
+
),
|
|
108
123
|
};
|
|
109
124
|
}),
|
|
110
125
|
);
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { canonicalFingerprint } from "../artifacts.js";
|
|
2
|
+
import { buildTestUtilityArtifact } from "./utility.js";
|
|
3
|
+
|
|
4
|
+
const FIXTURE_TESTS = Object.freeze([
|
|
5
|
+
{ testId: "protected-security", file: "tests/security.test.js", framework: "node:test", suite: "", name: "security", line: 1, signals: { security: true } },
|
|
6
|
+
{ testId: "protected-public-api", file: "tests/api.test.js", framework: "node:test", suite: "", name: "api", line: 1, signals: { publicApi: true } },
|
|
7
|
+
{ testId: "semantic-duplicate", file: "tests/duplicate.test.js", framework: "node:test", suite: "", name: "duplicate", line: 1, signals: {} },
|
|
8
|
+
{ testId: "unique-branch", file: "tests/required.test.js", framework: "node:test", suite: "", name: "required", line: 1, signals: { requiredBehavior: true } },
|
|
9
|
+
{ testId: "obsolete-test", file: "tests/obsolete.test.js", framework: "node:test", suite: "", name: "obsolete", line: 1, signals: {} },
|
|
10
|
+
{ testId: "slow-required", file: "tests/critical.test.js", framework: "node:test", suite: "", name: "critical", line: 1, signals: { criticalPath: true } },
|
|
11
|
+
{ testId: "flaky-low-signal", file: "tests/flaky.test.js", framework: "node:test", suite: "", name: "flaky", line: 1, signals: {} },
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
const CASES = Object.freeze([
|
|
15
|
+
{ id: "protected-security", expected: "KEEP_RISK_GUARD", protected: true },
|
|
16
|
+
{ id: "protected-public-api", expected: "KEEP_UNIQUE", protected: true },
|
|
17
|
+
{ id: "semantic-duplicate", expected: "REDUNDANT_CANDIDATE", protected: false },
|
|
18
|
+
{ id: "unique-branch", expected: "KEEP_REQUIRED", protected: true },
|
|
19
|
+
{ id: "obsolete-test", expected: "OBSOLETE_CANDIDATE", protected: false },
|
|
20
|
+
{ id: "slow-required", expected: "KEEP_RECOVERY_INVARIANT", protected: true },
|
|
21
|
+
{ id: "flaky-low-signal", expected: "FLAKY_LOW_SIGNAL", protected: false },
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
function precision(cases, label) {
|
|
25
|
+
const predicted = cases.filter((item) => item.observed === label);
|
|
26
|
+
if (predicted.length === 0) return null;
|
|
27
|
+
return predicted.filter((item) => item.expected === label).length / predicted.length;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function recall(cases, label) {
|
|
31
|
+
const expected = cases.filter((item) => item.expected === label);
|
|
32
|
+
if (expected.length === 0) return null;
|
|
33
|
+
return expected.filter((item) => item.observed === label).length / expected.length;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function runTestIntelligenceBenchmark() {
|
|
37
|
+
const candidateIds = ["semantic-duplicate", "obsolete-test", "flaky-low-signal"];
|
|
38
|
+
const decisions = { candidateIds, tests: {
|
|
39
|
+
"semantic-duplicate": { semantic_duplicate: true },
|
|
40
|
+
"obsolete-test": { likely_obsolete: true },
|
|
41
|
+
"flaky-low-signal": { likely_flaky_low_signal: true },
|
|
42
|
+
} };
|
|
43
|
+
const confidence = Object.fromEntries(
|
|
44
|
+
candidateIds.flatMap((id, index) => (
|
|
45
|
+
Object.keys(decisions.tests[id]).map((dimension) => [`test_${index}_${dimension}`, 0.95])
|
|
46
|
+
)),
|
|
47
|
+
);
|
|
48
|
+
const observedTests = buildTestUtilityArtifact({ taskId: "benchmark", inventory: { schemaVersion: 1, source: "FIXTURE", tests: FIXTURE_TESTS }, semanticDecision: { decisionId: "benchmark-decision", decision: decisions, confidence } }).tests;
|
|
49
|
+
const cases = CASES.map((item) => ({ ...item, observed: observedTests.find((test) => test.testId === item.id)?.classification ?? "UNKNOWN" }));
|
|
50
|
+
const protectedFalsePositives = cases.filter((item) => item.protected && ["REDUNDANT_CANDIDATE", "OBSOLETE_CANDIDATE", "SAFE_TO_REMOVE"].includes(item.observed)).length;
|
|
51
|
+
const summary = {
|
|
52
|
+
schemaVersion: 1,
|
|
53
|
+
status: "OFFLINE_DETERMINISTIC_FIXTURE",
|
|
54
|
+
caseCount: CASES.length,
|
|
55
|
+
metrics: {
|
|
56
|
+
redundantCandidatePrecision: precision(cases, "REDUNDANT_CANDIDATE"),
|
|
57
|
+
redundantCandidateRecall: recall(cases, "REDUNDANT_CANDIDATE"),
|
|
58
|
+
obsoleteCandidatePrecision: precision(cases, "OBSOLETE_CANDIDATE"),
|
|
59
|
+
obsoleteCandidateRecall: recall(cases, "OBSOLETE_CANDIDATE"),
|
|
60
|
+
protectedTestFalsePositiveRate: protectedFalsePositives / CASES.filter((item) => item.protected).length,
|
|
61
|
+
safeToRemoveFalsePositiveRate: precision(cases, "SAFE_TO_REMOVE") === null ? null : 1 - precision(cases, "SAFE_TO_REMOVE"),
|
|
62
|
+
rewriteRecommendationPrecision: null,
|
|
63
|
+
},
|
|
64
|
+
deletionAuthority: false,
|
|
65
|
+
cases,
|
|
66
|
+
};
|
|
67
|
+
return { ...summary, fingerprint: canonicalFingerprint(summary) };
|
|
68
|
+
}
|