@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
package/src/core/task-lock.js
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
1
|
+
import { needsExistingProjectScope, withExistingProjectScope } from "../storage/existing-project-scope.js";
|
|
1
2
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import {
|
|
3
|
-
import path from "node:path";
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
import { assertSafePath, ensureWithin, fileExists } from "./filesystem.js";
|
|
6
6
|
import { taskLockPath } from "./task-paths.js";
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
E_TASK_LOCKED,
|
|
10
|
-
} from "./error-codes.js";
|
|
7
|
+
import { getOperationalStore } from "../storage/operational-context.js";
|
|
8
|
+
import { assertStorageMaintenanceInactive } from "../storage/maintenance.js";
|
|
11
9
|
|
|
12
10
|
export async function readLockInfo(target, taskId) {
|
|
11
|
+
if (await needsExistingProjectScope(target)) {
|
|
12
|
+
return withExistingProjectScope(target, () => readLockInfo(target, taskId), { readOnly: true });
|
|
13
|
+
}
|
|
14
|
+
const store = getOperationalStore(target);
|
|
15
|
+
if (store) {
|
|
16
|
+
const { readOperationalLease } = await import("../storage/leases.js");
|
|
17
|
+
return readOperationalLease(store, taskId);
|
|
18
|
+
}
|
|
13
19
|
const relativePath = taskLockPath(taskId);
|
|
14
20
|
await assertSafePath(target, relativePath);
|
|
15
21
|
const fullPath = ensureWithin(target, relativePath);
|
|
@@ -50,13 +56,6 @@ export function isValidTaskLockIdentity(lock) {
|
|
|
50
56
|
&& hasLeaseIdentity(lock);
|
|
51
57
|
}
|
|
52
58
|
|
|
53
|
-
export function isValidProjectClaimsLockIdentity(lock) {
|
|
54
|
-
return Boolean(lock)
|
|
55
|
-
&& !lock.corrupted
|
|
56
|
-
&& lock.scope === "claims-reservation"
|
|
57
|
-
&& hasLeaseIdentity(lock);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
59
|
function classifyLeaseWindow(lock, now) {
|
|
61
60
|
const heartbeat = Date.parse(lock.heartbeatAt ?? lock.acquiredAt);
|
|
62
61
|
if (!Number.isFinite(heartbeat)) return { status: "UNKNOWN", stale: false };
|
|
@@ -81,191 +80,24 @@ export function currentProcessStartToken(now = Date.now(), uptimeSeconds = proce
|
|
|
81
80
|
return `${process.pid}:${Math.max(0, Math.floor(now - (uptimeSeconds * 1000)))}`;
|
|
82
81
|
}
|
|
83
82
|
|
|
84
|
-
export const CLAIMS_LOCK_REL_PATH = ".forgeloop/.claims.lock";
|
|
85
|
-
|
|
86
|
-
export async function readProjectClaimsLockInfo(target) {
|
|
87
|
-
await assertSafePath(target, CLAIMS_LOCK_REL_PATH);
|
|
88
|
-
const fullPath = ensureWithin(target, CLAIMS_LOCK_REL_PATH);
|
|
89
|
-
|
|
90
|
-
if (!(await fileExists(fullPath))) {
|
|
91
|
-
return null;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
try {
|
|
95
|
-
const raw = await readFile(fullPath, "utf8");
|
|
96
|
-
return JSON.parse(raw);
|
|
97
|
-
} catch {
|
|
98
|
-
return { corrupted: true };
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export function classifyProjectClaimsLock(lock, now = Date.now()) {
|
|
103
|
-
if (!lock) return { status: "NONE", stale: false };
|
|
104
|
-
if (lock.corrupted) return { status: "CORRUPT", stale: false };
|
|
105
|
-
if (!isValidProjectClaimsLockIdentity(lock)) return { status: "UNKNOWN", stale: false };
|
|
106
|
-
return classifyLeaseWindow(lock, now);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function projectClaimsLockError(classification, lockInfo, reason = null) {
|
|
110
|
-
if (classification.status === "LIVE") {
|
|
111
|
-
const error = new Error(
|
|
112
|
-
`Project write claims reservation is locked by operation "${lockInfo?.operation ?? "unknown"}" (pid: ${lockInfo?.pid ?? "unknown"}, acquired: ${lockInfo?.acquiredAt ?? "unknown"}).`,
|
|
113
|
-
);
|
|
114
|
-
error.code = E_TASK_LOCKED;
|
|
115
|
-
error.lockInfo = lockInfo;
|
|
116
|
-
error.classification = classification;
|
|
117
|
-
return error;
|
|
118
|
-
}
|
|
119
|
-
const error = new Error(
|
|
120
|
-
`Project write claims lock ownership is ${classification.status}${reason ? ` (${reason})` : ""}; refusing unsafe claim mutation`,
|
|
121
|
-
);
|
|
122
|
-
error.code = E_PROJECT_CLAIMS_LOCK_INCONSISTENT;
|
|
123
|
-
error.lockInfo = lockInfo;
|
|
124
|
-
error.classification = classification;
|
|
125
|
-
if (reason) error.reason = reason;
|
|
126
|
-
return error;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export async function releaseStaleProjectClaimsLockIfUnchanged(target, expectedLock, { now = Date.now() } = {}) {
|
|
130
|
-
await assertSafePath(target, CLAIMS_LOCK_REL_PATH);
|
|
131
|
-
const fullPath = ensureWithin(target, CLAIMS_LOCK_REL_PATH);
|
|
132
|
-
const expectedClassification = classifyProjectClaimsLock(expectedLock, now);
|
|
133
|
-
if (expectedClassification.status !== "STALE") {
|
|
134
|
-
return { released: false, reason: "EXPECTED_LOCK_NOT_STALE", classification: expectedClassification };
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const quarantinePath = `${fullPath}.releasing-${randomUUID()}`;
|
|
138
|
-
try {
|
|
139
|
-
await rename(fullPath, quarantinePath);
|
|
140
|
-
} catch (error) {
|
|
141
|
-
if (error.code === "ENOENT") return { released: false, reason: "LOCK_MISSING" };
|
|
142
|
-
throw error;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
let observed;
|
|
146
|
-
try {
|
|
147
|
-
observed = JSON.parse(await readFile(quarantinePath, "utf8"));
|
|
148
|
-
} catch {
|
|
149
|
-
await restoreQuarantinedLock(quarantinePath, fullPath);
|
|
150
|
-
return { released: false, reason: "LOCK_CORRUPT", classification: { status: "CORRUPT", stale: false } };
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const observedClassification = classifyProjectClaimsLock(observed, now);
|
|
154
|
-
if (!sameObservedLock(observed, expectedLock, { identityIsValid: isValidProjectClaimsLockIdentity })
|
|
155
|
-
|| observedClassification.status !== "STALE") {
|
|
156
|
-
await restoreQuarantinedLock(quarantinePath, fullPath);
|
|
157
|
-
return {
|
|
158
|
-
released: false,
|
|
159
|
-
reason: sameObservedLock(observed, expectedLock, { identityIsValid: isValidProjectClaimsLockIdentity })
|
|
160
|
-
? "LOCK_NOT_STALE"
|
|
161
|
-
: "LOCK_CHANGED",
|
|
162
|
-
currentLock: observed,
|
|
163
|
-
classification: observedClassification,
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
await unlink(quarantinePath);
|
|
168
|
-
return { released: true, previousLock: observed, classification: observedClassification };
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
export async function acquireProjectClaimsLock(target, operation = "claim-reservation") {
|
|
172
|
-
await assertSafePath(target, CLAIMS_LOCK_REL_PATH);
|
|
173
|
-
const fullPath = ensureWithin(target, CLAIMS_LOCK_REL_PATH);
|
|
174
|
-
|
|
175
|
-
await mkdir(path.dirname(fullPath), { recursive: true });
|
|
176
|
-
|
|
177
|
-
const acquiredAt = new Date().toISOString();
|
|
178
|
-
const lockData = {
|
|
179
|
-
lockId: randomUUID(),
|
|
180
|
-
scope: "claims-reservation",
|
|
181
|
-
operation,
|
|
182
|
-
pid: process.pid,
|
|
183
|
-
hostname: os.hostname(),
|
|
184
|
-
processStartToken: currentProcessStartToken(),
|
|
185
|
-
ownerInstanceId: randomUUID(),
|
|
186
|
-
acquiredAt,
|
|
187
|
-
heartbeatAt: acquiredAt,
|
|
188
|
-
leaseMs: 300000,
|
|
189
|
-
};
|
|
190
|
-
|
|
191
|
-
let fileHandle = null;
|
|
192
|
-
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
193
|
-
try {
|
|
194
|
-
fileHandle = await open(fullPath, "wx");
|
|
195
|
-
break;
|
|
196
|
-
} catch (error) {
|
|
197
|
-
if (error.code !== "EEXIST") throw error;
|
|
198
|
-
const existing = await readProjectClaimsLockInfo(target);
|
|
199
|
-
const classification = classifyProjectClaimsLock(existing);
|
|
200
|
-
if (classification.status === "STALE" && attempt === 0) {
|
|
201
|
-
const released = await releaseStaleProjectClaimsLockIfUnchanged(target, existing);
|
|
202
|
-
if (released.released || released.reason === "LOCK_MISSING") continue;
|
|
203
|
-
throw projectClaimsLockError(
|
|
204
|
-
released.classification ?? { status: "UNKNOWN", stale: false },
|
|
205
|
-
released.currentLock ?? existing,
|
|
206
|
-
released.reason,
|
|
207
|
-
);
|
|
208
|
-
}
|
|
209
|
-
throw projectClaimsLockError(classification, existing);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
if (!fileHandle) {
|
|
213
|
-
throw projectClaimsLockError({ status: "UNKNOWN", stale: false }, null, "ACQUISITION_RETRY_EXHAUSTED");
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
try {
|
|
217
|
-
await fileHandle.writeFile(`${JSON.stringify(lockData, null, 2)}\n`, "utf8");
|
|
218
|
-
await fileHandle.close();
|
|
219
|
-
return {
|
|
220
|
-
lockData,
|
|
221
|
-
release: async () => {
|
|
222
|
-
try {
|
|
223
|
-
const current = await readProjectClaimsLockInfo(target);
|
|
224
|
-
if (current && current.lockId === lockData.lockId) {
|
|
225
|
-
await unlink(fullPath);
|
|
226
|
-
}
|
|
227
|
-
} catch {
|
|
228
|
-
// ignore
|
|
229
|
-
}
|
|
230
|
-
},
|
|
231
|
-
};
|
|
232
|
-
} catch (error) {
|
|
233
|
-
try {
|
|
234
|
-
await fileHandle.close();
|
|
235
|
-
} catch {
|
|
236
|
-
// ignore
|
|
237
|
-
}
|
|
238
|
-
try {
|
|
239
|
-
await unlink(fullPath);
|
|
240
|
-
} catch {
|
|
241
|
-
// ignore
|
|
242
|
-
}
|
|
243
|
-
throw error;
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
83
|
export async function withProjectClaimsLock(target, operationOrCallback, callback) {
|
|
84
|
+
if (!getOperationalStore(target)) {
|
|
85
|
+
const { withProjectStorage } = await import("../storage/project-boundary.js");
|
|
86
|
+
return withProjectStorage(target, () => withProjectClaimsLock(target, operationOrCallback, callback));
|
|
87
|
+
}
|
|
248
88
|
let operation = operationOrCallback;
|
|
249
89
|
let fn = callback;
|
|
250
90
|
if (typeof operationOrCallback === "function" && callback === undefined) {
|
|
251
91
|
fn = operationOrCallback;
|
|
252
92
|
operation = "claim-reservation";
|
|
253
93
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
} finally {
|
|
258
|
-
await lock.release();
|
|
259
|
-
}
|
|
94
|
+
// The preparation scope records the authoritative claim inputs; its short
|
|
95
|
+
// BEGIN IMMEDIATE commit rechecks those inputs and reserves claims atomically.
|
|
96
|
+
return fn({ kind: "sqlite-claim-reservation", operation });
|
|
260
97
|
}
|
|
261
98
|
|
|
262
99
|
export async function acquireTaskLock(target, taskId, operation = "mutation") {
|
|
263
|
-
|
|
264
|
-
await assertSafePath(target, relativePath);
|
|
265
|
-
const fullPath = ensureWithin(target, relativePath);
|
|
266
|
-
|
|
267
|
-
await mkdir(path.dirname(fullPath), { recursive: true });
|
|
268
|
-
|
|
100
|
+
await assertStorageMaintenanceInactive(target);
|
|
269
101
|
const acquiredAt = new Date().toISOString();
|
|
270
102
|
const lockData = {
|
|
271
103
|
lockId: randomUUID(),
|
|
@@ -279,80 +111,46 @@ export async function acquireTaskLock(target, taskId, operation = "mutation") {
|
|
|
279
111
|
heartbeatAt: acquiredAt,
|
|
280
112
|
leaseMs: 300000,
|
|
281
113
|
};
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
await
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
const current = await readLockInfo(target, taskId);
|
|
308
|
-
if (current && current.lockId === lockData.lockId) {
|
|
309
|
-
await unlink(fullPath);
|
|
310
|
-
}
|
|
311
|
-
} catch {
|
|
312
|
-
// ignore already unlinked or overwritten
|
|
313
|
-
}
|
|
314
|
-
},
|
|
315
|
-
};
|
|
316
|
-
} catch (error) {
|
|
317
|
-
try {
|
|
318
|
-
await fileHandle.close();
|
|
319
|
-
} catch {
|
|
320
|
-
// ignore
|
|
321
|
-
}
|
|
322
|
-
try {
|
|
323
|
-
await unlink(fullPath);
|
|
324
|
-
} catch {
|
|
325
|
-
// ignore
|
|
326
|
-
}
|
|
327
|
-
throw error;
|
|
328
|
-
}
|
|
114
|
+
const store = getOperationalStore(target);
|
|
115
|
+
if (store) {
|
|
116
|
+
const { acquireOperationalLease } = await import("../storage/leases.js");
|
|
117
|
+
return acquireOperationalLease(store, taskId, lockData);
|
|
118
|
+
}
|
|
119
|
+
return acquireStandaloneOperationalLease(target, taskId, operation);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Keep only the connection scope alive; acquisition/release hold no SQL transaction. */
|
|
123
|
+
async function acquireStandaloneOperationalLease(target, taskId, operation) {
|
|
124
|
+
let resolveReady;
|
|
125
|
+
let rejectReady;
|
|
126
|
+
let releaseScope;
|
|
127
|
+
const ready = new Promise((resolve, reject) => { resolveReady = resolve; rejectReady = reject; });
|
|
128
|
+
const released = new Promise(resolve => { releaseScope = resolve; });
|
|
129
|
+
const { withProjectStorage } = await import("../storage/project-boundary.js");
|
|
130
|
+
const completion = withProjectStorage(target, async () => {
|
|
131
|
+
const handle = await acquireTaskLock(target, taskId, operation);
|
|
132
|
+
resolveReady(handle.lockData);
|
|
133
|
+
await released;
|
|
134
|
+
return handle.release();
|
|
135
|
+
}, { existingOnly: true });
|
|
136
|
+
completion.catch(rejectReady);
|
|
137
|
+
const lockData = await ready;
|
|
138
|
+
return { lockData, release: () => { releaseScope(); return completion; } };
|
|
329
139
|
}
|
|
330
140
|
|
|
331
141
|
export async function forceUnlockTask(target, taskId, { staleOnly = false } = {}) {
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
if (!(await fileExists(fullPath))) {
|
|
337
|
-
return { unlocked: false, message: "No active lock found" };
|
|
142
|
+
if (!getOperationalStore(target)) {
|
|
143
|
+
const { withProjectStorage } = await import("../storage/project-boundary.js");
|
|
144
|
+
return withProjectStorage(target, () => forceUnlockTask(target, taskId, { staleOnly }), { existingOnly: true });
|
|
338
145
|
}
|
|
339
|
-
|
|
146
|
+
const store = getOperationalStore(target);
|
|
340
147
|
const existing = await readLockInfo(target, taskId);
|
|
148
|
+
if (!existing) return { unlocked: false, message: "No active lock found" };
|
|
341
149
|
const classification = classifyLockStaleness(existing);
|
|
342
|
-
if (staleOnly) {
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
const released = await releaseStaleTaskLockIfUnchanged(target, taskId, existing);
|
|
347
|
-
return {
|
|
348
|
-
unlocked: released.released,
|
|
349
|
-
previousLock: released.previousLock ?? existing,
|
|
350
|
-
classification: released.classification ?? classification,
|
|
351
|
-
...(released.reason ? { reason: released.reason } : {}),
|
|
352
|
-
};
|
|
353
|
-
}
|
|
354
|
-
await unlink(fullPath);
|
|
355
|
-
return { unlocked: true, previousLock: existing, classification };
|
|
150
|
+
if (staleOnly && !classification.stale) return { unlocked: false, previousLock: existing, classification };
|
|
151
|
+
const { releaseOperationalLeaseIfUnchanged } = await import("../storage/leases.js");
|
|
152
|
+
const released = releaseOperationalLeaseIfUnchanged(store, taskId, existing, { force: !staleOnly, classify: classifyLockStaleness, sameIdentity: sameObservedTaskLock });
|
|
153
|
+
return { unlocked: released.released, previousLock: released.previousLock ?? existing, classification: released.classification ?? classification, ...(released.reason ? { reason: released.reason } : {}) };
|
|
356
154
|
}
|
|
357
155
|
|
|
358
156
|
function sameObservedLock(left, right, { identityIsValid = isValidTaskLockIdentity } = {}) {
|
|
@@ -369,61 +167,23 @@ function sameObservedTaskLock(left, right, taskId) {
|
|
|
369
167
|
&& right.taskId === taskId;
|
|
370
168
|
}
|
|
371
169
|
|
|
372
|
-
async function restoreQuarantinedLock(quarantinePath, fullPath) {
|
|
373
|
-
try {
|
|
374
|
-
await link(quarantinePath, fullPath);
|
|
375
|
-
} catch (error) {
|
|
376
|
-
if (error.code !== "EEXIST") throw error;
|
|
377
|
-
} finally {
|
|
378
|
-
try {
|
|
379
|
-
await unlink(quarantinePath);
|
|
380
|
-
} catch {
|
|
381
|
-
// ignore an already-consumed quarantine entry
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
170
|
export async function releaseStaleTaskLockIfUnchanged(target, taskId, expectedLock, { now = Date.now() } = {}) {
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
const expectedClassification = classifyLockStaleness(expectedLock, now);
|
|
391
|
-
if (expectedClassification.status !== "STALE") {
|
|
392
|
-
return { released: false, reason: "EXPECTED_LOCK_NOT_STALE", classification: expectedClassification };
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
const quarantinePath = `${fullPath}.releasing-${randomUUID()}`;
|
|
396
|
-
try {
|
|
397
|
-
await rename(fullPath, quarantinePath);
|
|
398
|
-
} catch (error) {
|
|
399
|
-
if (error.code === "ENOENT") return { released: false, reason: "LOCK_MISSING" };
|
|
400
|
-
throw error;
|
|
171
|
+
if (!getOperationalStore(target)) {
|
|
172
|
+
const { withProjectStorage } = await import("../storage/project-boundary.js");
|
|
173
|
+
return withProjectStorage(target, () => releaseStaleTaskLockIfUnchanged(target, taskId, expectedLock, { now }), { existingOnly: true });
|
|
401
174
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
await restoreQuarantinedLock(quarantinePath, fullPath);
|
|
408
|
-
return { released: false, reason: "LOCK_CORRUPT" };
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
const observedClassification = classifyLockStaleness(observed, now);
|
|
412
|
-
if (!sameObservedTaskLock(observed, expectedLock, taskId) || observedClassification.status !== "STALE") {
|
|
413
|
-
await restoreQuarantinedLock(quarantinePath, fullPath);
|
|
414
|
-
return {
|
|
415
|
-
released: false,
|
|
416
|
-
reason: sameObservedTaskLock(observed, expectedLock, taskId) ? "LOCK_NOT_STALE" : "LOCK_CHANGED",
|
|
417
|
-
currentLock: observed,
|
|
418
|
-
classification: observedClassification,
|
|
419
|
-
};
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
await unlink(quarantinePath);
|
|
423
|
-
return { released: true, previousLock: observed, classification: observedClassification };
|
|
175
|
+
const store = getOperationalStore(target);
|
|
176
|
+
const expectedClassification = classifyLockStaleness(expectedLock, now);
|
|
177
|
+
if (expectedClassification.status !== "STALE") return { released: false, reason: "EXPECTED_LOCK_NOT_STALE", classification: expectedClassification };
|
|
178
|
+
const { releaseOperationalLeaseIfUnchanged } = await import("../storage/leases.js");
|
|
179
|
+
return releaseOperationalLeaseIfUnchanged(store, taskId, expectedLock, { now, classify: classifyLockStaleness, sameIdentity: sameObservedTaskLock });
|
|
424
180
|
}
|
|
425
181
|
|
|
426
182
|
export async function withTaskLock(target, taskId, operationOrCallback, callback) {
|
|
183
|
+
if (!getOperationalStore(target)) {
|
|
184
|
+
const { withProjectStorage } = await import("../storage/project-boundary.js");
|
|
185
|
+
return withProjectStorage(target, () => withTaskLock(target, taskId, operationOrCallback, callback));
|
|
186
|
+
}
|
|
427
187
|
let operation = operationOrCallback;
|
|
428
188
|
let fn = callback;
|
|
429
189
|
if (typeof operationOrCallback === "function" && callback === undefined) {
|
|
@@ -2,8 +2,8 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { readFile, readdir } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { ensureWithin, fileExists } from "./filesystem.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { readPortableJsonArtifact } from "./artifacts.js";
|
|
6
|
+
import { validatePortableEventLedger } from "./events.js";
|
|
7
7
|
import {
|
|
8
8
|
E_TASK_MIGRATION_IDENTITY_MISMATCH,
|
|
9
9
|
E_TASK_MIGRATION_INVALID,
|
|
@@ -42,7 +42,7 @@ export async function validateMigrationSnapshot(target, { taskId, packageRoot, p
|
|
|
42
42
|
const absPath = ensureWithin(target, relPath);
|
|
43
43
|
if (await fileExists(absPath)) {
|
|
44
44
|
try {
|
|
45
|
-
const { value } = await
|
|
45
|
+
const { value } = await readPortableJsonArtifact(target, relPath, schemaName, packageRoot);
|
|
46
46
|
checkIdentity(value, relPath);
|
|
47
47
|
artifactFingerprints[key] = await fingerprintFile(absPath);
|
|
48
48
|
} catch (err) {
|
|
@@ -59,7 +59,7 @@ export async function validateMigrationSnapshot(target, { taskId, packageRoot, p
|
|
|
59
59
|
if (paths.events) {
|
|
60
60
|
const absPath = ensureWithin(target, paths.events);
|
|
61
61
|
if (await fileExists(absPath)) {
|
|
62
|
-
const ledger = await
|
|
62
|
+
const ledger = await validatePortableEventLedger(target, packageRoot, { eventsPath: paths.events });
|
|
63
63
|
if (!ledger.valid) {
|
|
64
64
|
const error = new Error(`Event ledger validation failed for ${paths.events}: ${ledger.errors?.[0]?.message ?? "invalid ledger"}`);
|
|
65
65
|
error.code = E_TASK_MIGRATION_INVALID;
|
|
@@ -103,7 +103,7 @@ export async function validateMigrationSnapshot(target, { taskId, packageRoot, p
|
|
|
103
103
|
const fileAbsPath = path.join(absPath, entry.name);
|
|
104
104
|
|
|
105
105
|
try {
|
|
106
|
-
const { value } = await
|
|
106
|
+
const { value } = await readPortableJsonArtifact(target, fileRelPath, schemaName, packageRoot);
|
|
107
107
|
checkIdentity(value, fileRelPath);
|
|
108
108
|
const hash = await fingerprintFile(fileAbsPath);
|
|
109
109
|
fileHashes.push({ relPath: entry.name, hash });
|