@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
|
@@ -9,6 +9,8 @@ import { taskEvaluationPath } from "./task-paths.js";
|
|
|
9
9
|
import { appendProtocolEvent } from "./events.js";
|
|
10
10
|
import { assertSafePath, ensureWithin } from "./filesystem.js";
|
|
11
11
|
import { withTaskTransaction } from "./transaction.js";
|
|
12
|
+
import { withProjectReadSnapshot } from "../storage/project-read-snapshot.js";
|
|
13
|
+
import { needsExistingProjectScope, withExistingProjectScope } from "../storage/existing-project-scope.js";
|
|
12
14
|
|
|
13
15
|
function evaluationError(code, message) { const error = new Error(message); error.code = code; return error; }
|
|
14
16
|
|
|
@@ -25,8 +27,18 @@ export async function evaluateTrajectory({ target, packageRoot, taskId, scenario
|
|
|
25
27
|
}
|
|
26
28
|
assertSchema(scenario, await readSchema("trajectory-scenario", packageRoot), "trajectory scenario");
|
|
27
29
|
if (!/^eval-[A-Za-z0-9_-]+$/.test(evaluationId)) throw evaluationError("E_TRAJECTORY_SCENARIO_INVALID", "evaluationId is invalid");
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
+
const options = { target, packageRoot, taskId, scenarioPath, evaluationId, runtimeContext };
|
|
31
|
+
if (await needsExistingProjectScope(target)) {
|
|
32
|
+
return withExistingProjectScope(target, () => evaluateValidatedTrajectory(options, scenario));
|
|
33
|
+
}
|
|
34
|
+
return evaluateValidatedTrajectory(options, scenario);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function evaluateValidatedTrajectory({ target, packageRoot, taskId, evaluationId, runtimeContext }, scenario) {
|
|
38
|
+
const { trace, metrics } = await withProjectReadSnapshot(target, async () => ({
|
|
39
|
+
trace: await buildTaskTrace({ target, packageRoot, taskId }),
|
|
40
|
+
metrics: await buildTrajectoryMetrics({ target, packageRoot, taskId, runtimeContext }),
|
|
41
|
+
}));
|
|
30
42
|
const milestoneSet = new Set(trace.events.map((event) => event.type));
|
|
31
43
|
const missingMilestones = (scenario.requiredMilestones ?? []).filter((milestone) => !milestoneSet.has(milestone));
|
|
32
44
|
const completionEvent = trace.events.find((event) => event.type === "COMPLETION_VALIDATED");
|
|
@@ -62,7 +74,7 @@ export async function evaluateTrajectory({ target, packageRoot, taskId, scenario
|
|
|
62
74
|
};
|
|
63
75
|
const evaluation = { ...base, evaluationFingerprint: canonicalFingerprint(base) };
|
|
64
76
|
assertSchema(evaluation, await readSchema("trajectory-evaluation", packageRoot), "trajectory evaluation");
|
|
65
|
-
await withTaskTransaction({ target, taskId, operation: "trajectory-evaluated" }, async () => {
|
|
77
|
+
await withTaskTransaction({ target, packageRoot, taskId, operation: "trajectory-evaluated" }, async () => {
|
|
66
78
|
await writeJsonArtifact(target, taskEvaluationPath(taskId, evaluationId), evaluation, "trajectory-evaluation", packageRoot);
|
|
67
79
|
await appendProtocolEvent(target, { taskId, event: "TRAJECTORY_EVALUATED", fingerprint: evaluation.evaluationFingerprint,
|
|
68
80
|
details: { evaluationId, scenarioId: scenario.scenarioId, evaluationFingerprint: evaluation.evaluationFingerprint } }, packageRoot, { taskId });
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { withEventLedgerAudit } from "./events.js";
|
|
2
|
+
import { getOperationalStore } from "../storage/operational-context.js";
|
|
3
|
+
import { withNativeReadScope } from "./native-storage.js";
|
|
1
4
|
import { buildTaskTrace } from "./trace.js";
|
|
2
5
|
import { buildTaskReflection } from "./reflection.js";
|
|
3
6
|
import { readPersistedRoute } from "./route-artifact.js";
|
|
@@ -9,7 +12,44 @@ export const COMPARABLE_WORK_EVENTS = new Set([
|
|
|
9
12
|
"INTERVENTION_RECORDED", "ACTION_STARTED", "ACTION_RECONCILED", "REVIEW_STARTED",
|
|
10
13
|
]);
|
|
11
14
|
|
|
12
|
-
export async function buildTrajectoryMetrics(
|
|
15
|
+
export async function buildTrajectoryMetrics(options) {
|
|
16
|
+
const { target, packageRoot, taskId } = options;
|
|
17
|
+
return withNativeReadScope(target, () => {
|
|
18
|
+
if (getOperationalStore(target)) {
|
|
19
|
+
return withEventLedgerAudit(target, packageRoot, { taskId }, () => projectTrajectoryMetrics(options));
|
|
20
|
+
}
|
|
21
|
+
return projectTrajectoryMetrics(options);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function readActionReadiness(options) {
|
|
26
|
+
try {
|
|
27
|
+
const { evaluateRequiredActionReadiness } = await import("./action-readiness.js");
|
|
28
|
+
const readiness = await evaluateRequiredActionReadiness(options);
|
|
29
|
+
return { satisfied: readiness.satisfied, unresolved: readiness.unresolved };
|
|
30
|
+
} catch { return null; }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function readExecutionProfile(target, packageRoot, taskId) {
|
|
34
|
+
try {
|
|
35
|
+
const route = await readPersistedRoute(target, packageRoot, { taskId });
|
|
36
|
+
return projectExecutionProfile(route.value);
|
|
37
|
+
} catch {
|
|
38
|
+
// Legacy tasks and incomplete task namespaces may not have a route.
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function readMetricsUsage(target, packageRoot, taskId, runtimeContext) {
|
|
44
|
+
const usage = await readTaskUsage(target, packageRoot, taskId);
|
|
45
|
+
const usageProvider = runtimeContext?.usageProvider;
|
|
46
|
+
if (usageProvider && typeof usageProvider.getTaskUsage === "function") {
|
|
47
|
+
return providerUsage(await usageProvider.getTaskUsage({ projectPath: target, taskId }));
|
|
48
|
+
}
|
|
49
|
+
return usage;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function projectTrajectoryMetrics({ target, packageRoot, taskId, runtimeContext = null }) {
|
|
13
53
|
const trace = await buildTaskTrace({ target, packageRoot, taskId });
|
|
14
54
|
const reflection = await buildTaskReflection({ target, packageRoot, taskId });
|
|
15
55
|
const events = trace.events;
|
|
@@ -17,22 +57,13 @@ export async function buildTrajectoryMetrics({ target, packageRoot, taskId, runt
|
|
|
17
57
|
.map((event) => event.data?.verificationCycle).filter(Number.isInteger));
|
|
18
58
|
const diagnosticCycles = new Set(trace.diagnostics.cases.map((item) => item.verificationCycle));
|
|
19
59
|
const firstEventAt = events.find((event) => event.timestampQuality === "authoritative")?.timestamp ?? null;
|
|
20
|
-
const lastEventAt =
|
|
60
|
+
const lastEventAt = events.findLast((event) => event.timestampQuality === "authoritative")?.timestamp ?? null;
|
|
21
61
|
const firstMs = firstEventAt ? Date.parse(firstEventAt) : NaN;
|
|
22
62
|
const lastMs = lastEventAt ? Date.parse(lastEventAt) : NaN;
|
|
23
63
|
const interventions = reflection.interventions ?? { count: 0, informative: 0, nonInformative: 0 };
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
executionProfile = projectExecutionProfile(route.value);
|
|
28
|
-
} catch {
|
|
29
|
-
// Legacy tasks and incomplete task namespaces may not have a route.
|
|
30
|
-
}
|
|
31
|
-
let usage = await readTaskUsage(target, packageRoot, taskId);
|
|
32
|
-
const usageProvider = runtimeContext?.usageProvider;
|
|
33
|
-
if (usageProvider && typeof usageProvider.getTaskUsage === "function") {
|
|
34
|
-
usage = providerUsage(await usageProvider.getTaskUsage({ projectPath: target, taskId }));
|
|
35
|
-
}
|
|
64
|
+
const executionProfile = await readExecutionProfile(target, packageRoot, taskId);
|
|
65
|
+
const usage = await readMetricsUsage(target, packageRoot, taskId, runtimeContext);
|
|
66
|
+
const readiness = await readActionReadiness({ target, packageRoot, taskId });
|
|
36
67
|
return {
|
|
37
68
|
schemaVersion: 1,
|
|
38
69
|
taskId,
|
|
@@ -58,24 +89,8 @@ export async function buildTrajectoryMetrics({ target, packageRoot, taskId, runt
|
|
|
58
89
|
verified: trace.actions?.verified ?? 0,
|
|
59
90
|
// Canonical trust counts come from the action-readiness projection;
|
|
60
91
|
// raw VERIFIED labels are observability only.
|
|
61
|
-
trustedSatisfied:
|
|
62
|
-
|
|
63
|
-
const { evaluateRequiredActionReadiness } = await import("./action-readiness.js");
|
|
64
|
-
const readiness = await evaluateRequiredActionReadiness({ target, packageRoot, taskId });
|
|
65
|
-
return readiness.satisfied;
|
|
66
|
-
} catch {
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
})(),
|
|
70
|
-
unresolvedRequired: await (async () => {
|
|
71
|
-
try {
|
|
72
|
-
const { evaluateRequiredActionReadiness } = await import("./action-readiness.js");
|
|
73
|
-
const readiness = await evaluateRequiredActionReadiness({ target, packageRoot, taskId });
|
|
74
|
-
return readiness.unresolved;
|
|
75
|
-
} catch {
|
|
76
|
-
return null;
|
|
77
|
-
}
|
|
78
|
-
})(),
|
|
92
|
+
trustedSatisfied: readiness ? readiness.satisfied : null,
|
|
93
|
+
unresolvedRequired: readiness ? readiness.unresolved : null,
|
|
79
94
|
failed: trace.actions?.failed ?? 0,
|
|
80
95
|
ambiguous: trace.actions?.ambiguous ?? 0,
|
|
81
96
|
reconciliations: trace.actions?.reconciliationCount ?? 0,
|
package/src/core/transaction.js
CHANGED
|
@@ -1,50 +1,29 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
|
-
import { mkdir, open, readdir, readFile, realpath, rename, stat, truncate, unlink } from "node:fs/promises";
|
|
4
|
-
import { setTimeout as delay } from "node:timers/promises";
|
|
5
|
-
import path from "node:path";
|
|
1
|
+
import { readdir } from "node:fs/promises";
|
|
6
2
|
|
|
7
|
-
import { assertSafePath,
|
|
8
|
-
import {
|
|
9
|
-
import { E_TASK_LOCKED } from "./error-codes.js";
|
|
3
|
+
import { assertSafePath, fileExists } from "./filesystem.js";
|
|
4
|
+
import { getOperationalStore } from "../storage/operational-context.js";
|
|
10
5
|
|
|
11
6
|
const TRANSACTION_ROOT = ".forgeloop/.txn";
|
|
12
|
-
const LOCK_WAIT_TIMEOUT_MS = 5_000;
|
|
13
|
-
const transactionContext = new AsyncLocalStorage();
|
|
14
|
-
|
|
15
|
-
export function getActiveTaskTransaction() {
|
|
16
|
-
return transactionContext.getStore() ?? null;
|
|
17
|
-
}
|
|
18
7
|
|
|
19
8
|
export async function getTaskTransaction(target) {
|
|
20
|
-
|
|
21
|
-
if (transaction && transaction.target !== await realpath(target)) {
|
|
22
|
-
const error = new Error("Cannot reuse a task transaction for a different project");
|
|
23
|
-
error.code = "E_TASK_CONTEXT_MISMATCH";
|
|
24
|
-
throw error;
|
|
25
|
-
}
|
|
26
|
-
return transaction;
|
|
9
|
+
return getOperationalStore(target)?.transaction ?? null;
|
|
27
10
|
}
|
|
28
11
|
|
|
29
12
|
const TERMINAL_STATUSES = new Set(["COMMITTED", "ROLLED_BACK", "ABORTED"]);
|
|
30
13
|
|
|
31
|
-
function transactionPath(transactionId) {
|
|
32
|
-
return `${TRANSACTION_ROOT}/${transactionId}`;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
async function writeManifest(target, relativePath, manifest) {
|
|
36
|
-
await writeFileAtomic(ensureWithin(target, relativePath), `${JSON.stringify(manifest, null, 2)}\n`);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
14
|
export async function findIncompleteTransactions(target) {
|
|
40
|
-
const root =
|
|
15
|
+
const root = await assertSafePath(target, TRANSACTION_ROOT);
|
|
41
16
|
if (!(await fileExists(root))) return [];
|
|
42
17
|
const entries = await readdir(root, { withFileTypes: true });
|
|
43
18
|
const found = [];
|
|
44
19
|
for (const entry of entries) {
|
|
20
|
+
await assertSafePath(target, `${TRANSACTION_ROOT}/${entry.name}`);
|
|
45
21
|
if (!entry.isDirectory()) continue;
|
|
22
|
+
const relative = `${TRANSACTION_ROOT}/${entry.name}/manifest.json`;
|
|
23
|
+
await assertSafePath(target, relative);
|
|
46
24
|
try {
|
|
47
|
-
const
|
|
25
|
+
const { readStorageMetadataJson } = await import("../storage/metadata-json.js");
|
|
26
|
+
const manifest = await readStorageMetadataJson(target, relative);
|
|
48
27
|
if (!TERMINAL_STATUSES.has(manifest.status)) found.push(manifest);
|
|
49
28
|
} catch {
|
|
50
29
|
found.push({ transactionId: entry.name, status: "ABANDONED", malformed: true });
|
|
@@ -53,262 +32,12 @@ export async function findIncompleteTransactions(target) {
|
|
|
53
32
|
return found;
|
|
54
33
|
}
|
|
55
34
|
|
|
56
|
-
function
|
|
57
|
-
return typeof entry === "string" ? entry : entry.path;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
async function rollbackPublishedWrites(target, root, manifest) {
|
|
61
|
-
const writes = [...manifest.writes].reverse();
|
|
62
|
-
for (const entry of writes) {
|
|
63
|
-
if (!entry || typeof entry !== "object") continue;
|
|
64
|
-
const relativePath = writePath(entry);
|
|
65
|
-
await assertSafePath(target, relativePath);
|
|
66
|
-
if (entry.kind === "APPEND") {
|
|
67
|
-
if (!entry.appendStarted) continue;
|
|
68
|
-
const destination = ensureWithin(target, relativePath);
|
|
69
|
-
const originalSize = Number.isInteger(entry.originalSize) && entry.originalSize >= 0 ? entry.originalSize : 0;
|
|
70
|
-
if (await fileExists(destination)) {
|
|
71
|
-
if (originalSize === 0) await unlink(destination);
|
|
72
|
-
else await truncate(destination, originalSize);
|
|
73
|
-
}
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
if (!entry.published && !entry.backupPending && !entry.backupCreated) continue;
|
|
77
|
-
const destination = ensureWithin(target, relativePath);
|
|
78
|
-
const backup = ensureWithin(target, `${root}/backup/${relativePath}`);
|
|
79
|
-
if (entry.published && await fileExists(destination)) await unlink(destination);
|
|
80
|
-
if (entry.hadPrevious && await fileExists(backup)) {
|
|
81
|
-
await mkdir(path.dirname(destination), { recursive: true });
|
|
82
|
-
await rename(backup, destination);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export async function recoverIncompleteTransactions(target) {
|
|
88
|
-
const rootPath = ensureWithin(target, TRANSACTION_ROOT);
|
|
89
|
-
if (!(await fileExists(rootPath))) return [];
|
|
90
|
-
const recovered = [];
|
|
91
|
-
for (const entry of await readdir(rootPath, { withFileTypes: true })) {
|
|
92
|
-
if (!entry.isDirectory()) continue;
|
|
93
|
-
const root = transactionPath(entry.name);
|
|
94
|
-
const manifestPath = `${root}/manifest.json`;
|
|
95
|
-
try {
|
|
96
|
-
const manifest = JSON.parse(await readFile(ensureWithin(target, manifestPath), "utf8"));
|
|
97
|
-
if (manifest.status !== "COMMITTING") continue;
|
|
98
|
-
await withTaskLock(target, manifest.lockTaskId ?? manifest.taskId ?? "transaction-recovery", "recover-transaction", async () => {
|
|
99
|
-
const current = JSON.parse(await readFile(ensureWithin(target, manifestPath), "utf8"));
|
|
100
|
-
if (current.status !== "COMMITTING") return;
|
|
101
|
-
await rollbackPublishedWrites(target, root, current);
|
|
102
|
-
current.status = "ROLLED_BACK";
|
|
103
|
-
current.recoveredAt = new Date().toISOString();
|
|
104
|
-
await writeManifest(target, manifestPath, current);
|
|
105
|
-
recovered.push({ transactionId: current.transactionId, status: current.status });
|
|
106
|
-
});
|
|
107
|
-
} catch (error) {
|
|
108
|
-
recovered.push({ transactionId: entry.name, status: "RECOVERY_FAILED", error: error.message });
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
return recovered;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export async function withTaskTransaction({
|
|
115
|
-
target,
|
|
116
|
-
taskId,
|
|
117
|
-
lockTaskId = taskId,
|
|
118
|
-
operation = "mutation",
|
|
119
|
-
packageRoot,
|
|
120
|
-
recordCommitEvent = false,
|
|
121
|
-
} = {}, callback) {
|
|
35
|
+
export async function withTaskTransaction({ target, taskId, operation = "mutation", packageRoot, recordCommitEvent = false } = {}, callback) {
|
|
122
36
|
if (!target || !taskId) throw new Error("target and taskId are required for a task transaction");
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
if (activeTransaction.taskId !== taskId) {
|
|
127
|
-
throw new Error(`cannot nest task transaction for ${taskId} inside ${activeTransaction.taskId}`);
|
|
128
|
-
}
|
|
129
|
-
return callback(activeTransaction);
|
|
130
|
-
}
|
|
131
|
-
const started = Date.now();
|
|
132
|
-
const runWithLock = async () => withTaskLock(target, lockTaskId, operation, async (lock) => {
|
|
133
|
-
const transactionId = `txn-${randomUUID()}`;
|
|
134
|
-
const root = transactionPath(transactionId);
|
|
135
|
-
const stageRoot = `${root}/stage`;
|
|
136
|
-
const manifestPath = `${root}/manifest.json`;
|
|
137
|
-
await assertSafePath(target, manifestPath);
|
|
138
|
-
await mkdir(ensureWithin(target, stageRoot), { recursive: true });
|
|
139
|
-
const manifest = { schemaVersion: 1, transactionId, taskId, lockTaskId, operation, lockId: lock.lockId, startedAt: new Date().toISOString(), status: "STAGING", writes: [] };
|
|
140
|
-
await writeManifest(target, manifestPath, manifest);
|
|
141
|
-
const tx = {
|
|
142
|
-
target,
|
|
143
|
-
transactionId,
|
|
144
|
-
taskId,
|
|
145
|
-
lock,
|
|
146
|
-
async readText(relativePath) {
|
|
147
|
-
await assertSafePath(target, relativePath);
|
|
148
|
-
if (manifest.writes.some((entry) => writePath(entry) === relativePath && entry.kind === "DELETE")) {
|
|
149
|
-
return null;
|
|
150
|
-
}
|
|
151
|
-
const staged = ensureWithin(target, `${stageRoot}/${relativePath}`);
|
|
152
|
-
if (await fileExists(staged)) return readFile(staged, "utf8");
|
|
153
|
-
const appendStaged = ensureWithin(target, `${stageRoot}/${relativePath}.append`);
|
|
154
|
-
const appendText = await fileExists(appendStaged) ? await readFile(appendStaged, "utf8") : "";
|
|
155
|
-
const destination = ensureWithin(target, relativePath);
|
|
156
|
-
if (!(await fileExists(destination))) return appendText || null;
|
|
157
|
-
return `${await readFile(destination, "utf8")}${appendText}`;
|
|
158
|
-
},
|
|
159
|
-
async stageText(relativePath, text) {
|
|
160
|
-
await assertSafePath(target, relativePath);
|
|
161
|
-
const conflicting = manifest.writes.find((entry) => writePath(entry) === relativePath);
|
|
162
|
-
if (conflicting?.kind === "APPEND" || conflicting?.kind === "DELETE") {
|
|
163
|
-
throw new Error(`cannot replace ${conflicting.kind.toLowerCase()}-staged path: ${relativePath}`);
|
|
164
|
-
}
|
|
165
|
-
const staged = `${stageRoot}/${relativePath}`;
|
|
166
|
-
await assertSafePath(target, staged);
|
|
167
|
-
await writeFileAtomic(ensureWithin(target, staged), text);
|
|
168
|
-
if (!manifest.writes.some((entry) => writePath(entry) === relativePath)) {
|
|
169
|
-
manifest.writes.push({ path: relativePath, hadPrevious: false, published: false });
|
|
170
|
-
}
|
|
171
|
-
await writeManifest(target, manifestPath, manifest);
|
|
172
|
-
},
|
|
173
|
-
async appendText(relativePath, text) {
|
|
174
|
-
await assertSafePath(target, relativePath);
|
|
175
|
-
if (typeof text !== "string" || text.length === 0) throw new Error("transaction append text must be a non-empty string");
|
|
176
|
-
if (manifest.writes.some((entry) => writePath(entry) === relativePath && entry.kind !== "APPEND")) {
|
|
177
|
-
throw new Error(`cannot append to replace-staged path: ${relativePath}`);
|
|
178
|
-
}
|
|
179
|
-
const destination = ensureWithin(target, relativePath);
|
|
180
|
-
const staged = ensureWithin(target, `${stageRoot}/${relativePath}.append`);
|
|
181
|
-
const existing = await fileExists(staged) ? await readFile(staged, "utf8") : "";
|
|
182
|
-
await writeFileAtomic(staged, `${existing}${text}`);
|
|
183
|
-
let entry = manifest.writes.find((candidate) => writePath(candidate) === relativePath);
|
|
184
|
-
if (!entry) {
|
|
185
|
-
entry = {
|
|
186
|
-
path: relativePath,
|
|
187
|
-
kind: "APPEND",
|
|
188
|
-
originalSize: (await fileExists(destination)) ? (await stat(destination)).size : 0,
|
|
189
|
-
appendStarted: false,
|
|
190
|
-
published: false,
|
|
191
|
-
};
|
|
192
|
-
manifest.writes.push(entry);
|
|
193
|
-
}
|
|
194
|
-
await writeManifest(target, manifestPath, manifest);
|
|
195
|
-
},
|
|
196
|
-
async stageDelete(relativePath) {
|
|
197
|
-
await assertSafePath(target, relativePath);
|
|
198
|
-
if (manifest.writes.some((entry) => writePath(entry) === relativePath)) {
|
|
199
|
-
throw new Error(`cannot delete write-staged path: ${relativePath}`);
|
|
200
|
-
}
|
|
201
|
-
const destination = ensureWithin(target, relativePath);
|
|
202
|
-
if (!(await fileExists(destination))) {
|
|
203
|
-
throw new Error(`transaction delete target is missing: ${relativePath}`);
|
|
204
|
-
}
|
|
205
|
-
manifest.writes.push({
|
|
206
|
-
path: relativePath,
|
|
207
|
-
kind: "DELETE",
|
|
208
|
-
hadPrevious: false,
|
|
209
|
-
published: false,
|
|
210
|
-
});
|
|
211
|
-
await writeManifest(target, manifestPath, manifest);
|
|
212
|
-
},
|
|
213
|
-
};
|
|
214
|
-
try {
|
|
215
|
-
const result = await transactionContext.run(tx, async () => {
|
|
216
|
-
const callbackResult = await callback(tx);
|
|
217
|
-
if (recordCommitEvent) {
|
|
218
|
-
// Import lazily to keep the transaction/event dependency directional
|
|
219
|
-
// at module initialization. The event is staged while this task's
|
|
220
|
-
// transaction context is still active and is published last below.
|
|
221
|
-
const { appendProtocolEvent } = await import("./events.js");
|
|
222
|
-
await appendProtocolEvent(target, {
|
|
223
|
-
taskId,
|
|
224
|
-
event: "TRANSACTION_COMMITTED",
|
|
225
|
-
details: { transactionId, operation },
|
|
226
|
-
}, packageRoot, { taskId });
|
|
227
|
-
}
|
|
228
|
-
return callbackResult;
|
|
229
|
-
});
|
|
230
|
-
manifest.status = "COMMITTING";
|
|
231
|
-
await writeManifest(target, manifestPath, manifest);
|
|
232
|
-
// The ledger is the commit witness. Publishing it last means that a
|
|
233
|
-
// visible TRANSACTION_COMMITTED event cannot precede a required staged
|
|
234
|
-
// artifact in the same transaction.
|
|
235
|
-
const writes = [...manifest.writes].sort((left, right) => {
|
|
236
|
-
const leftIsLedger = writePath(left).endsWith("events.ndjson");
|
|
237
|
-
const rightIsLedger = writePath(right).endsWith("events.ndjson");
|
|
238
|
-
return Number(leftIsLedger) - Number(rightIsLedger);
|
|
239
|
-
});
|
|
240
|
-
for (const entry of writes) {
|
|
241
|
-
const relativePath = writePath(entry);
|
|
242
|
-
const staged = ensureWithin(target, `${stageRoot}/${relativePath}`);
|
|
243
|
-
const destination = ensureWithin(target, relativePath);
|
|
244
|
-
if (entry.kind === "APPEND") {
|
|
245
|
-
const appendStaged = ensureWithin(target, `${stageRoot}/${relativePath}.append`);
|
|
246
|
-
const appendText = await readFile(appendStaged, "utf8");
|
|
247
|
-
await mkdir(path.dirname(destination), { recursive: true });
|
|
248
|
-
entry.appendStarted = true;
|
|
249
|
-
await writeManifest(target, manifestPath, manifest);
|
|
250
|
-
const handle = await open(destination, "a", 0o644);
|
|
251
|
-
try {
|
|
252
|
-
await handle.writeFile(appendText);
|
|
253
|
-
await handle.sync();
|
|
254
|
-
} finally {
|
|
255
|
-
await handle.close();
|
|
256
|
-
}
|
|
257
|
-
entry.published = true;
|
|
258
|
-
await writeManifest(target, manifestPath, manifest);
|
|
259
|
-
continue;
|
|
260
|
-
}
|
|
261
|
-
const backup = ensureWithin(target, `${root}/backup/${relativePath}`);
|
|
262
|
-
await mkdir(path.dirname(destination), { recursive: true });
|
|
263
|
-
entry.hadPrevious = await fileExists(destination);
|
|
264
|
-
if (entry.hadPrevious) {
|
|
265
|
-
entry.backupPending = true;
|
|
266
|
-
await writeManifest(target, manifestPath, manifest);
|
|
267
|
-
await mkdir(path.dirname(backup), { recursive: true });
|
|
268
|
-
await rename(destination, backup);
|
|
269
|
-
entry.backupCreated = true;
|
|
270
|
-
}
|
|
271
|
-
await writeManifest(target, manifestPath, manifest);
|
|
272
|
-
if (entry.kind === "DELETE") {
|
|
273
|
-
entry.published = true;
|
|
274
|
-
await writeManifest(target, manifestPath, manifest);
|
|
275
|
-
continue;
|
|
276
|
-
}
|
|
277
|
-
await rename(staged, destination);
|
|
278
|
-
entry.published = true;
|
|
279
|
-
await writeManifest(target, manifestPath, manifest);
|
|
280
|
-
}
|
|
281
|
-
manifest.status = "COMMITTED";
|
|
282
|
-
manifest.committedAt = new Date().toISOString();
|
|
283
|
-
await writeManifest(target, manifestPath, manifest);
|
|
284
|
-
return result;
|
|
285
|
-
} catch (error) {
|
|
286
|
-
if (manifest.status === "COMMITTING") {
|
|
287
|
-
try {
|
|
288
|
-
await rollbackPublishedWrites(target, root, manifest);
|
|
289
|
-
manifest.status = "ROLLED_BACK";
|
|
290
|
-
manifest.recoveredAt = new Date().toISOString();
|
|
291
|
-
} catch (rollbackError) {
|
|
292
|
-
manifest.status = "ABANDONED";
|
|
293
|
-
manifest.rollbackError = { message: rollbackError.message };
|
|
294
|
-
}
|
|
295
|
-
} else {
|
|
296
|
-
// Staging has not published any writes; retain the diagnostic record
|
|
297
|
-
// without requiring recovery of a project that was never modified.
|
|
298
|
-
manifest.status = "ABORTED";
|
|
299
|
-
}
|
|
300
|
-
manifest.failedAt = new Date().toISOString();
|
|
301
|
-
manifest.error = { message: error.message };
|
|
302
|
-
await writeManifest(target, manifestPath, manifest);
|
|
303
|
-
throw error;
|
|
304
|
-
}
|
|
305
|
-
});
|
|
306
|
-
while (true) {
|
|
307
|
-
try {
|
|
308
|
-
return await runWithLock();
|
|
309
|
-
} catch (error) {
|
|
310
|
-
if (error.code !== E_TASK_LOCKED || Date.now() - started >= LOCK_WAIT_TIMEOUT_MS) throw error;
|
|
311
|
-
await delay(20);
|
|
312
|
-
}
|
|
37
|
+
if (!getOperationalStore(target)) {
|
|
38
|
+
const { withProjectStorage } = await import("../storage/project-boundary.js");
|
|
39
|
+
return withProjectStorage(target, () => withTaskTransaction({ target, taskId, operation, packageRoot, recordCommitEvent }, callback));
|
|
313
40
|
}
|
|
41
|
+
const { withOperationalTransaction } = await import("../storage/unit-of-work.js");
|
|
42
|
+
return withOperationalTransaction({ target, taskId, operation, packageRoot, recordCommitEvent }, callback);
|
|
314
43
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { operationalArtifactExists } from "../storage/operational-context.js";
|
|
1
2
|
import { canonicalFingerprint, readJsonArtifact, writeJsonArtifact } from "./artifacts.js";
|
|
2
3
|
import { assertSchema, readSchema } from "./schema-validation.js";
|
|
3
4
|
import { getPackageRoot } from "./templates.js";
|
|
@@ -5,6 +6,7 @@ import { currentRepositoryFingerprint } from "./repository.js";
|
|
|
5
6
|
import { resolveRevisionProvider } from "./revision/provider.js";
|
|
6
7
|
import { REVISION_PROVIDERS } from "./revision/registry.js";
|
|
7
8
|
import { readScopedCheckerCapabilities } from "./verification-scope-capability.js";
|
|
9
|
+
import { withNativeReadScope } from "./native-storage.js";
|
|
8
10
|
import { readContract } from "./contract.js";
|
|
9
11
|
import { readPersistedRoute } from "./route-artifact.js";
|
|
10
12
|
import { readWorkState } from "./work-state.js";
|
|
@@ -123,17 +125,19 @@ export async function validateVerificationScope(scope, packageRoot = getPackageR
|
|
|
123
125
|
}
|
|
124
126
|
|
|
125
127
|
export async function readVerificationScope(target, { taskId, packageRoot = getPackageRoot(), scopePath = null } = {}) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
128
|
+
return withNativeReadScope(target, async () => {
|
|
129
|
+
const relativePath = scopePath ?? taskVerificationScopePath(taskId);
|
|
130
|
+
if (!(operationalArtifactExists(target, relativePath) ?? await fileExists(ensureWithin(target, relativePath)))) {
|
|
131
|
+
throw scopeError("E_VERIFICATION_SCOPE_INVALID", `Verification scope is missing: ${relativePath}`, [relativePath]);
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
const artifact = await readJsonArtifact(target, relativePath, "verification-scope", packageRoot);
|
|
135
|
+
return { ...artifact, value: await validateVerificationScope(artifact.value, packageRoot) };
|
|
136
|
+
} catch (error) {
|
|
137
|
+
if (error.code === "E_VERIFICATION_SCOPE_INVALID") throw error;
|
|
138
|
+
throw scopeError("E_VERIFICATION_SCOPE_INVALID", error.message, [relativePath]);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
137
141
|
}
|
|
138
142
|
|
|
139
143
|
export async function validateVerificationScopeFreshness(target, {
|
|
@@ -249,7 +253,11 @@ export async function resolveVerificationScope(target, {
|
|
|
249
253
|
return { scope, inputs };
|
|
250
254
|
}
|
|
251
255
|
|
|
252
|
-
export async function persistVerificationScope(target, scope,
|
|
256
|
+
export async function persistVerificationScope(target, scope, options = {}) {
|
|
257
|
+
return withTaskTransaction({ target, taskId: options.taskId, packageRoot: options.packageRoot, operation: "verify-scope" }, () => persistVerificationScopeInTransaction(target, scope, options));
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async function persistVerificationScopeInTransaction(target, scope, { taskId, packageRoot = getPackageRoot() } = {}) {
|
|
253
261
|
const relativePath = taskVerificationScopePath(taskId);
|
|
254
262
|
const artifact = await writeJsonArtifact(target, relativePath, scope, "verification-scope", packageRoot, { taskId, operation: "verify-scope" });
|
|
255
263
|
await appendProtocolEvent(target, {
|