@cassiomc1/forgeloop 1.14.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/CONTRIBUTING.md +11 -7
- package/DOCS_INDEX.md +2 -1
- 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 +8 -4
- 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/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/GUIDE_ROUTER.md +6 -0
- package/LICENSE +133 -21
- package/LOOP_SYSTEM_DESIGN.md +10 -10
- package/README.md +28 -39
- package/THIRD_PARTY_NOTICES.md +3 -4
- package/completions/_forgeloop +11 -3
- package/completions/forgeloop.bash +19 -3
- package/completions/forgeloop.fish +61 -1
- package/docs/AGENT_PROTOCOL_SUMMARY.md +50 -40
- package/docs/ARTIFACT_REFERENCE.md +50 -37
- package/docs/CLI_REFERENCE.md +147 -3
- package/docs/DOCUMENTATION_GUIDE.md +6 -4
- package/docs/GETTING_STARTED.md +3 -3
- package/docs/PACKAGE_CONTENTS.md +14 -13
- package/docs/RELEASE_CHECKLIST.md +81 -10
- package/docs/SELF_HOSTED_TESTS.md +121 -0
- package/docs/documentation-manifest.json +25 -12
- package/package.json +15 -10
- package/schemas/task-bundle.schema.json +7 -2
- package/scripts/generate-agent-protocol-summary.mjs +2 -2
- package/scripts/run-mcp-clean-tests.mjs +30 -0
- package/src/adapters/agent-browser/provider.js +14 -11
- package/src/adapters/emulated-services/provider.js +11 -4
- package/src/adapters/temporary-paths.js +18 -0
- package/src/cli.js +146 -210
- 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 +24 -8
- package/src/commands/complete.js +19 -3
- package/src/commands/contract-create.js +3 -1
- package/src/commands/doctor.js +36 -13
- package/src/commands/gate-record.js +20 -31
- package/src/commands/gate-revalidate.js +11 -13
- 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 +9 -1
- 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/semantic-plan.js +26 -2
- 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 +1 -1
- package/src/commands/task-list.js +8 -43
- package/src/commands/task-migrate.js +3 -3
- package/src/commands/task-recover.js +10 -2
- package/src/commands/task-scope.js +18 -2
- package/src/commands/task-show.js +16 -2
- package/src/commands/validate-protocol.js +29 -14
- 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 +59 -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.js +6 -1
- package/src/core/bundles.js +160 -66
- package/src/core/checkpoint-revalidation.js +12 -10
- package/src/core/cli-command-definitions.js +109 -7
- package/src/core/code-manifest.js +9 -3
- package/src/core/command-executors.js +182 -126
- package/src/core/command-input.js +21 -1
- package/src/core/completion-artifacts.js +71 -29
- package/src/core/completion-ownership.js +19 -8
- package/src/core/completion-recovery-rebind.js +11 -1
- package/src/core/completion.js +60 -9
- package/src/core/conformance.js +2 -1
- 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 +42 -41
- package/src/core/contract-revision.js +8 -9
- package/src/core/decision/artifact.js +2 -1
- package/src/core/decision/events.js +9 -6
- package/src/core/decision/resolver.js +19 -11
- package/src/core/decision/service.js +63 -38
- package/src/core/decision/task-bindings.js +6 -1
- 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/events.js +316 -97
- package/src/core/execution-prerequisites.js +22 -13
- 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 +6 -5
- 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 +10 -0
- package/src/core/integration-resources.js +41 -34
- package/src/core/ledger-event-collection.js +189 -0
- package/src/core/ledger-relations.js +23 -0
- package/src/core/native-storage.js +12 -0
- package/src/core/next-action-continuity.js +31 -9
- package/src/core/next-action-phases.js +1 -1
- package/src/core/next-action-review-phase.js +2 -2
- package/src/core/next-action-verification-phase.js +2 -1
- package/src/core/next-action.js +21 -2
- package/src/core/phase.js +138 -93
- package/src/core/policy-engine.js +12 -13
- package/src/core/preflight-consistency.js +72 -59
- package/src/core/preflight-loaders.js +19 -12
- 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 +3 -0
- package/src/core/protocol-migration.js +7 -6
- package/src/core/reconcile-closure.js +162 -24
- package/src/core/recovery-history.js +66 -47
- package/src/core/reflection.js +72 -77
- package/src/core/responsibility.js +25 -13
- package/src/core/route-artifact.js +13 -3
- package/src/core/runtime-context.js +5 -0
- package/src/core/schema-validation.js +8 -3
- 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/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 +52 -18
- package/src/core/task-command.js +21 -1
- package/src/core/task-conflict-inspection.js +112 -107
- 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-snapshot.js +51 -8
- package/src/core/task-storage-migration.js +18 -0
- package/src/core/templates.js +20 -8
- package/src/core/test-intelligence/prune.js +5 -1
- package/src/core/trace.js +154 -99
- package/src/core/trajectory-evaluation.js +15 -3
- package/src/core/trajectory-metrics.js +47 -32
- package/src/core/transaction.js +16 -288
- package/src/core/verification-scope.js +20 -12
- package/src/core/work-state.js +100 -59
- 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
|
@@ -324,6 +324,25 @@ export function defaultCommandInputValues() {
|
|
|
324
324
|
};
|
|
325
325
|
}
|
|
326
326
|
|
|
327
|
+
function validateStorageRestoreInput(command, options, help) {
|
|
328
|
+
if (command === "storage-restore-resume" && !help && (options.writersQuiesced !== true || typeof options.operationId !== "string" || !/^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/.test(options.operationId) || typeof options.expectedOwnerId !== "string" || !/^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/.test(options.expectedOwnerId))) throw inputError("storage-restore-resume requires exact --operation and --expected-owner UUIDs and explicit --writers-quiesced");
|
|
329
|
+
if (["storage-restore", "storage-restore-resume"].includes(command) && options.replaceActive !== undefined && typeof options.replaceActive !== "boolean") throw inputError("Active replacement selection must be a boolean");
|
|
330
|
+
if (command === "storage-restore" && !help && (typeof options.source !== "string" || !options.source || options.writersQuiesced !== true)) throw inputError("storage-restore requires --source and explicit --writers-quiesced");
|
|
331
|
+
if (command === "storage-backup" && !help && (typeof options.destination !== "string" || !options.destination)) throw inputError("storage-backup requires --destination");
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function validateStorageMigrationInput(command, options, help) {
|
|
335
|
+
if (command === "storage-rollback-resume" && !help && !/^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/.test(options.expectedOwnerId ?? "")) throw inputError("storage-rollback-resume requires exact --expected-owner UUID");
|
|
336
|
+
if (["storage-rollback", "storage-rollback-resume"].includes(command) && !help && (typeof options.destination !== "string" || !options.destination || typeof options.legacyRoot !== "string" || !options.legacyRoot || options.writersQuiesced !== true || options.nativeWritesExcluded !== true)) throw inputError("storage-rollback requires --destination, --legacy-root, explicit --writers-quiesced and --native-writes-excluded");
|
|
337
|
+
if (command === "storage-migrate" && !help && (typeof options.destination !== "string" || !options.destination || options.writersQuiesced !== true)) throw inputError("storage-migrate requires --destination and explicit --writers-quiesced");
|
|
338
|
+
if (command === "storage-migration-resume" && !help && (typeof options.destination !== "string" || !options.destination || options.writersQuiesced !== true || typeof options.expectedOwnerId !== "string" || !/^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/.test(options.expectedOwnerId))) throw inputError("storage-migration-resume requires --destination, exact --expected-owner UUID and explicit --writers-quiesced");
|
|
339
|
+
validateUnboundHandoffAcknowledgement(command, options);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function validateUnboundHandoffAcknowledgement(command, options) {
|
|
343
|
+
if (command === "storage-migration-resume" && options.acknowledgeUnboundHandoff !== undefined && typeof options.acknowledgeUnboundHandoff !== "boolean") throw inputError("--reconcile-unbound-handoff must be a boolean acknowledgement");
|
|
344
|
+
}
|
|
345
|
+
|
|
327
346
|
/**
|
|
328
347
|
* Canonical semantic validation shared by the CLI argv parser and
|
|
329
348
|
* programmatic integrations. JSON-Schema style checks cannot express these
|
|
@@ -332,7 +351,8 @@ export function defaultCommandInputValues() {
|
|
|
332
351
|
export function validateForgeLoopCommandInput({ command, input, help = false } = {}) {
|
|
333
352
|
const options = input ?? {};
|
|
334
353
|
if (!command) return;
|
|
335
|
-
|
|
354
|
+
validateStorageRestoreInput(command, options, help);
|
|
355
|
+
validateStorageMigrationInput(command, options, help);
|
|
336
356
|
validatePolicyInput(command, options);
|
|
337
357
|
validateTaskCreationInput(command, options, help);
|
|
338
358
|
validateGateRecordInput(command, options, help);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
+
import { getTaskTransaction, withTaskTransaction } from "./transaction.js";
|
|
2
3
|
import {
|
|
3
4
|
ARTIFACT_PATHS,
|
|
4
5
|
canonicalFingerprint,
|
|
@@ -80,10 +81,18 @@ function requiredString(value, label) {
|
|
|
80
81
|
return value;
|
|
81
82
|
}
|
|
82
83
|
|
|
84
|
+
async function readBoundReceiptArtifact(target, receiptRel, packageRoot) {
|
|
85
|
+
try { return await readJsonArtifact(target, receiptRel, "execution-receipt", packageRoot); }
|
|
86
|
+
catch (error) {
|
|
87
|
+
if (error.code === "E_STORAGE_PAYLOAD_MISMATCH" && error.taskIdentityMismatch) throw artifactError("E_RECEIPT_TASK_MISMATCH", "Execution receipt does not belong to the current contract task", [receiptRel]);
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
83
92
|
async function readCurrentReceipt(target, packageRoot, options = {}) {
|
|
84
93
|
const receiptRel = options.receiptPath ?? (options.taskId ? taskArtifactPath(options.taskId, "receipt") : ARTIFACT_PATHS.receipt);
|
|
85
94
|
try {
|
|
86
|
-
return await
|
|
95
|
+
return await readBoundReceiptArtifact(target, receiptRel, packageRoot);
|
|
87
96
|
} catch (error) {
|
|
88
97
|
if (error.code === "ARTIFACT_MISSING") {
|
|
89
98
|
throw artifactError(
|
|
@@ -245,7 +254,21 @@ export async function requiredEvidenceForTarget({
|
|
|
245
254
|
])].sort();
|
|
246
255
|
}
|
|
247
256
|
|
|
248
|
-
|
|
257
|
+
async function resolveCompletionTaskOptions(options) {
|
|
258
|
+
if (options.taskId) return options;
|
|
259
|
+
const state = await readWorkState(options.target, { packageRoot: options.packageRoot, statePath: options.statePath });
|
|
260
|
+
return { ...options, taskId: state?.taskId };
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export async function prepareCompletion(options = {}) {
|
|
264
|
+
options = await resolveCompletionTaskOptions(options);
|
|
265
|
+
if (!options.taskId) return prepareSelectedCompletion(options);
|
|
266
|
+
return withTaskTransaction({ target: options.target, packageRoot: options.packageRoot,
|
|
267
|
+
taskId: options.taskId, operation: "prepare-completion", recordCommitEvent: true },
|
|
268
|
+
() => prepareSelectedCompletion(options));
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
async function prepareSelectedCompletion({
|
|
249
272
|
target,
|
|
250
273
|
packageRoot,
|
|
251
274
|
authorityContext,
|
|
@@ -255,6 +278,7 @@ export async function prepareCompletion({
|
|
|
255
278
|
routePath = null,
|
|
256
279
|
statePath = null,
|
|
257
280
|
receiptPath = null,
|
|
281
|
+
eventsPath,
|
|
258
282
|
}) {
|
|
259
283
|
const contract = await readContract(target, packageRoot, { taskId, contractPath });
|
|
260
284
|
const route = await readPersistedRoute(target, packageRoot, { taskId, routePath });
|
|
@@ -266,12 +290,12 @@ export async function prepareCompletion({
|
|
|
266
290
|
throw artifactError("E_STATE_MISSING", "Work state is required before preparing completion", [stateRel]);
|
|
267
291
|
}
|
|
268
292
|
if (hasExecutionStarted(state.phase)) {
|
|
269
|
-
await assertExecutionPrerequisites({ target, state, packageRoot, taskId, statePath, contractPath, routePath });
|
|
293
|
+
await assertExecutionPrerequisites({ target, state, packageRoot, taskId, statePath, contractPath, routePath, eventsPath });
|
|
270
294
|
}
|
|
271
295
|
|
|
272
296
|
let existing = null;
|
|
273
297
|
try {
|
|
274
|
-
existing = await
|
|
298
|
+
existing = await readBoundReceiptArtifact(target, receiptRel, packageRoot);
|
|
275
299
|
await validateReceipt(existing.value, packageRoot, {
|
|
276
300
|
target,
|
|
277
301
|
taskId: contract.value.taskId,
|
|
@@ -463,7 +487,7 @@ export async function assertRecordCheckPrerequisites({
|
|
|
463
487
|
[stateRel],
|
|
464
488
|
);
|
|
465
489
|
}
|
|
466
|
-
await assertExecutionPrerequisites({ target, state, packageRoot, taskId, statePath, contractPath, routePath });
|
|
490
|
+
await assertExecutionPrerequisites({ target, state, packageRoot, taskId, statePath, contractPath, routePath, eventsPath });
|
|
467
491
|
|
|
468
492
|
const contract = await readContract(target, packageRoot, { taskId, contractPath });
|
|
469
493
|
const route = await readPersistedRoute(target, packageRoot, { taskId, routePath });
|
|
@@ -521,29 +545,15 @@ export async function assertRecordCheckPrerequisites({
|
|
|
521
545
|
};
|
|
522
546
|
}
|
|
523
547
|
|
|
524
|
-
export async function recordCheck({
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
result,
|
|
534
|
-
exitCode,
|
|
535
|
-
details,
|
|
536
|
-
executionRef,
|
|
537
|
-
provenance,
|
|
538
|
-
authorityContext,
|
|
539
|
-
runtimeContext,
|
|
540
|
-
taskId = null,
|
|
541
|
-
contractPath = null,
|
|
542
|
-
routePath = null,
|
|
543
|
-
statePath = null,
|
|
544
|
-
receiptPath = null,
|
|
545
|
-
eventsPath = null,
|
|
546
|
-
}) {
|
|
548
|
+
export async function recordCheck(options) {
|
|
549
|
+
validateRecordCheckInput(options);
|
|
550
|
+
options = await resolveCompletionTaskOptions(options);
|
|
551
|
+
if (!options.taskId) return recordValidatedCheck(options);
|
|
552
|
+
return withTaskTransaction({ target: options.target, packageRoot: options.packageRoot,
|
|
553
|
+
taskId: options.taskId, operation: "record-check", recordCommitEvent: true }, () => recordValidatedCheck(options));
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
function validateRecordCheckInput({ id, kind = "command", requirement, status, evidenceKind, command, result, details, executionRef, provenance }) {
|
|
547
557
|
requiredString(id, "check id");
|
|
548
558
|
requiredString(kind, "check kind");
|
|
549
559
|
requiredString(requirement, "check requirement");
|
|
@@ -568,7 +578,31 @@ export async function recordCheck({
|
|
|
568
578
|
&& (typeof result !== "string" || result.trim() === "")) {
|
|
569
579
|
throw artifactError("E_CHECK_INVALID", "record-check requires --command or --result");
|
|
570
580
|
}
|
|
581
|
+
}
|
|
571
582
|
|
|
583
|
+
async function recordValidatedCheck({
|
|
584
|
+
target,
|
|
585
|
+
packageRoot,
|
|
586
|
+
id,
|
|
587
|
+
kind = "command",
|
|
588
|
+
requirement,
|
|
589
|
+
status,
|
|
590
|
+
evidenceKind,
|
|
591
|
+
command,
|
|
592
|
+
result,
|
|
593
|
+
exitCode,
|
|
594
|
+
details,
|
|
595
|
+
executionRef,
|
|
596
|
+
provenance,
|
|
597
|
+
authorityContext,
|
|
598
|
+
runtimeContext,
|
|
599
|
+
taskId = null,
|
|
600
|
+
contractPath = null,
|
|
601
|
+
routePath = null,
|
|
602
|
+
statePath = null,
|
|
603
|
+
receiptPath = null,
|
|
604
|
+
eventsPath = null,
|
|
605
|
+
}) {
|
|
572
606
|
const context = await assertRecordCheckPrerequisites({
|
|
573
607
|
target,
|
|
574
608
|
packageRoot,
|
|
@@ -780,7 +814,15 @@ export async function recordCheck({
|
|
|
780
814
|
};
|
|
781
815
|
}
|
|
782
816
|
|
|
783
|
-
export async function recordTerminalResult({
|
|
817
|
+
export async function recordTerminalResult(options = {}) {
|
|
818
|
+
if (!options.target) return recordTerminalResultInTransaction(options);
|
|
819
|
+
if (await getTaskTransaction(options.target)) return recordTerminalResultInTransaction(options);
|
|
820
|
+
const state = await readWorkState(options.target, { packageRoot: options.packageRoot, taskId: options.taskId, statePath: options.statePath });
|
|
821
|
+
if (!state) return recordTerminalResultInTransaction(options);
|
|
822
|
+
return withTaskTransaction({ target: options.target, taskId: state.taskId, packageRoot: options.packageRoot, operation: "record-terminal-result" }, () => recordTerminalResultInTransaction({ ...options, taskId: options.taskId ?? state.taskId }));
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
async function recordTerminalResultInTransaction({
|
|
784
826
|
target,
|
|
785
827
|
packageRoot,
|
|
786
828
|
requirement,
|
|
@@ -1,7 +1,19 @@
|
|
|
1
|
+
import { isLedgerEventCollection } from "./ledger-event-collection.js";
|
|
1
2
|
import { LIFECYCLE_MILESTONES, validateStateLedgerCoherence } from "./events.js";
|
|
2
3
|
|
|
3
4
|
export const CANONICAL_COMPLETION_EVENT = "COMPLETION_VALIDATED";
|
|
4
5
|
|
|
6
|
+
function completionCandidates(events, taskId) {
|
|
7
|
+
let count = 0;
|
|
8
|
+
let candidate = null;
|
|
9
|
+
for (const event of events) {
|
|
10
|
+
if (event.event !== CANONICAL_COMPLETION_EVENT || event.taskId !== taskId) continue;
|
|
11
|
+
count += 1;
|
|
12
|
+
candidate = event;
|
|
13
|
+
}
|
|
14
|
+
return { count, candidate };
|
|
15
|
+
}
|
|
16
|
+
|
|
5
17
|
/**
|
|
6
18
|
* Canonical completion ownership proof: the minimal, validator-backed evidence
|
|
7
19
|
* that the lifecycle itself officially reached COMPLETE. This is intentionally
|
|
@@ -28,31 +40,30 @@ export function validateCompletionOwnershipProof({ taskId, state, ledger }) {
|
|
|
28
40
|
}
|
|
29
41
|
|
|
30
42
|
let completionEvent = null;
|
|
31
|
-
if (ledger &&
|
|
43
|
+
if (ledger && isLedgerEventCollection(ledger.events)) {
|
|
32
44
|
if (ledger.events.some((event) => event.taskId !== taskId)) {
|
|
33
45
|
errors.push({
|
|
34
46
|
code: "E_COMPLETION_OWNERSHIP_UNPROVEN",
|
|
35
47
|
message: "Ledger contains an event belonging to a different task",
|
|
36
48
|
});
|
|
37
49
|
}
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
if (candidates.length === 0) {
|
|
50
|
+
const { count, candidate } = completionCandidates(ledger.events, taskId);
|
|
51
|
+
if (count === 0) {
|
|
41
52
|
errors.push({
|
|
42
53
|
code: "E_COMPLETION_OWNERSHIP_UNPROVEN",
|
|
43
54
|
message: `No canonical ${CANONICAL_COMPLETION_EVENT} event exists for this task`,
|
|
44
55
|
});
|
|
45
|
-
} else if (
|
|
56
|
+
} else if (count > 1) {
|
|
46
57
|
errors.push({
|
|
47
58
|
code: "E_COMPLETION_OWNERSHIP_UNPROVEN",
|
|
48
59
|
message: `Multiple ${CANONICAL_COMPLETION_EVENT} events exist; completion is ambiguous`,
|
|
49
60
|
});
|
|
50
61
|
} else {
|
|
51
|
-
completionEvent =
|
|
62
|
+
completionEvent = candidate;
|
|
52
63
|
}
|
|
53
64
|
}
|
|
54
65
|
|
|
55
|
-
if (state && ledger &&
|
|
66
|
+
if (state && ledger && isLedgerEventCollection(ledger.events)) {
|
|
56
67
|
const coherenceErrors = validateStateLedgerCoherence(state, ledger.events);
|
|
57
68
|
if (coherenceErrors.length > 0) {
|
|
58
69
|
for (const error of coherenceErrors) {
|
|
@@ -67,7 +78,7 @@ export function validateCompletionOwnershipProof({ taskId, state, ledger }) {
|
|
|
67
78
|
// No contradictory lifecycle activity may follow the canonical completion:
|
|
68
79
|
// any milestone at or after VERIFICATION_RECORDED occurring after the
|
|
69
80
|
// completion event means the lifecycle moved past terminal state.
|
|
70
|
-
if (completionEvent && ledger &&
|
|
81
|
+
if (completionEvent && ledger && isLedgerEventCollection(ledger.events)) {
|
|
71
82
|
const completionIndex = ledger.events.indexOf(completionEvent);
|
|
72
83
|
const contradiction = ledger.events.slice(completionIndex + 1).find((event) => {
|
|
73
84
|
const index = LIFECYCLE_MILESTONES.indexOf(event.event);
|
|
@@ -3,6 +3,7 @@ import { appendProtocolEvent, validateCompletionRecoveryAuthorization, validateE
|
|
|
3
3
|
import { createReceipt } from "./receipt.js";
|
|
4
4
|
import { taskArtifactPath } from "./task-paths.js";
|
|
5
5
|
import { mutateWorkState, readWorkState } from "./work-state.js";
|
|
6
|
+
import { getTaskTransaction, withTaskTransaction } from "./transaction.js";
|
|
6
7
|
|
|
7
8
|
function resolveArtifactPath(key, taskId, override) {
|
|
8
9
|
if (override) return override;
|
|
@@ -65,7 +66,16 @@ function findMatchingRejectionEvent(events, attempt, cycle) {
|
|
|
65
66
|
* rejection carrying the current fingerprints is appended,
|
|
66
67
|
* - the execution receipt is re-bound to the current checkpoint when present.
|
|
67
68
|
*/
|
|
68
|
-
export async function rebindCompletionRejectionSnapshot({
|
|
69
|
+
export async function rebindCompletionRejectionSnapshot(options = {}) {
|
|
70
|
+
if (await getTaskTransaction(options.target)) return rebindRejectionInTransaction(options);
|
|
71
|
+
const state = await readWorkState(options.target, { packageRoot: options.packageRoot,
|
|
72
|
+
taskId: options.taskId, statePath: resolveArtifactPath("state", options.taskId, options.statePath) });
|
|
73
|
+
if (!state) return { rebound: false };
|
|
74
|
+
return withTaskTransaction({ target: options.target, taskId: options.taskId ?? state.taskId,
|
|
75
|
+
packageRoot: options.packageRoot, operation: "rebind-completion-rejection" }, () => rebindRejectionInTransaction(options));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function rebindRejectionInTransaction({
|
|
69
79
|
target,
|
|
70
80
|
packageRoot,
|
|
71
81
|
taskId = null,
|
package/src/core/completion.js
CHANGED
|
@@ -18,7 +18,9 @@ import { readConfig } from "./config.js";
|
|
|
18
18
|
import { resolveResponsibilityStatus } from "./responsibility.js";
|
|
19
19
|
import { createCodeManifest, readCodeManifest, validateCodeManifestBindings, writeCodeManifest } from "./code-manifest.js";
|
|
20
20
|
import { getTaskTransaction, withTaskTransaction } from "./transaction.js";
|
|
21
|
+
import { operationalArtifactExists } from "../storage/operational-context.js";
|
|
21
22
|
import { validateStructuralQualityCheckProvenance } from "./structural-quality/service.js";
|
|
23
|
+
import { withNativeReadScope } from "./native-storage.js";
|
|
22
24
|
|
|
23
25
|
async function attestationConfiguration(target, packageRoot, errors) {
|
|
24
26
|
try {
|
|
@@ -79,6 +81,12 @@ function issue(code, message, artifacts = [], details = {}) {
|
|
|
79
81
|
return { code, message, artifacts, ...details };
|
|
80
82
|
}
|
|
81
83
|
|
|
84
|
+
export function receiptReadFailure(error, relativePath) {
|
|
85
|
+
return error.taskIdentityMismatch
|
|
86
|
+
? issue("E_RECEIPT_TASK_MISMATCH", "Execution receipt does not belong to the current contract task", [relativePath])
|
|
87
|
+
: issue("E_RECEIPT_INVALID", `Repair or remove the invalid execution receipt before continuing: ${error.message}`, [relativePath]);
|
|
88
|
+
}
|
|
89
|
+
|
|
82
90
|
export function completionIdentityErrors({ contract, state, receipt } = {}) {
|
|
83
91
|
return completionRelationshipErrors({ contract, state, receipt })
|
|
84
92
|
.filter((error) => ["E_STATE_TASK_MISMATCH", "E_RECEIPT_TASK_MISMATCH"].includes(error.code));
|
|
@@ -226,6 +234,10 @@ async function loadRequired(readArtifact, errorCode, errorMessage, artifacts, er
|
|
|
226
234
|
try {
|
|
227
235
|
return await readArtifact();
|
|
228
236
|
} catch (error) {
|
|
237
|
+
if (errorCode === "E_RECEIPT_MISSING" && error.taskIdentityMismatch) {
|
|
238
|
+
errors.push(receiptReadFailure(error, artifacts[0]));
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
229
241
|
if (error.code === "ARTIFACT_MISSING") {
|
|
230
242
|
errors.push(issue(errorCode, errorMessage, artifacts));
|
|
231
243
|
return null;
|
|
@@ -235,6 +247,19 @@ async function loadRequired(readArtifact, errorCode, errorMessage, artifacts, er
|
|
|
235
247
|
}
|
|
236
248
|
}
|
|
237
249
|
|
|
250
|
+
async function loadCompletionActions(target, packageRoot, taskId, errors) {
|
|
251
|
+
const { evaluateRequiredActionReadiness } = await import("./action-readiness.js");
|
|
252
|
+
const evidence = taskId ? await loadRequired(async () => ({
|
|
253
|
+
actions: await listActions(target, { packageRoot, taskId }),
|
|
254
|
+
readiness: await evaluateRequiredActionReadiness({ target, packageRoot, taskId }),
|
|
255
|
+
}), "E_ACTION_INVALID", "Durable action evidence is not available", [taskArtifactPath(taskId, "actions")], errors) : null;
|
|
256
|
+
return {
|
|
257
|
+
actions: evidence?.actions ?? [],
|
|
258
|
+
readiness: evidence?.readiness
|
|
259
|
+
?? { total: 0, satisfied: 0, unresolved: 0, ambiguous: 0, failed: 0, untrusted: 0, actions: [] },
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
238
263
|
function publicationStatus(receipt) {
|
|
239
264
|
if (receipt.publicationStatus) return receipt.publicationStatus;
|
|
240
265
|
if (receipt.publication?.deployed) return "deployed";
|
|
@@ -246,9 +271,10 @@ function publicationStatus(receipt) {
|
|
|
246
271
|
async function validateLedger(target, scopedTaskId, contractTaskId, state, errors, packageRoot, options = {}) {
|
|
247
272
|
const eventsRel = options.eventsPath ?? (scopedTaskId ? taskArtifactPath(scopedTaskId, "events") : ARTIFACT_PATHS.events);
|
|
248
273
|
const stateRel = options.statePath ?? (scopedTaskId ? taskArtifactPath(scopedTaskId, "state") : ARTIFACT_PATHS.state);
|
|
249
|
-
|
|
274
|
+
const operationalExists = operationalArtifactExists(target, eventsRel);
|
|
275
|
+
if (operationalExists === null) await assertSafePath(target, eventsRel);
|
|
250
276
|
const eventsFilePath = ensureWithin(target, eventsRel);
|
|
251
|
-
if (!(await fileExists(eventsFilePath))) {
|
|
277
|
+
if (operationalExists === false || (operationalExists === null && !(await fileExists(eventsFilePath)))) {
|
|
252
278
|
errors.push(issue("E_PHASE_CHRONOLOGY_INVALID", "Protocol event ledger is required before completion", [eventsRel]));
|
|
253
279
|
return { valid: false, events: [], errors: [] };
|
|
254
280
|
}
|
|
@@ -286,6 +312,35 @@ export async function evaluateCompletion({
|
|
|
286
312
|
receiptPath = null,
|
|
287
313
|
eventsPath = null,
|
|
288
314
|
preflightPath = null,
|
|
315
|
+
} = {}) {
|
|
316
|
+
const options = { target, packageRoot, strict, authorityContext, runtimeContext, taskId, contractPath, routePath, statePath, receiptPath, eventsPath, preflightPath };
|
|
317
|
+
return withNativeReadScope(target, async () => evaluateSelectedCompletion(await resolveCompletionTaskOptions(options)));
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
async function resolveCompletionTaskOptions(options) {
|
|
321
|
+
if (options.taskId) return options;
|
|
322
|
+
try {
|
|
323
|
+
const state = await readWorkState(options.target, { packageRoot: options.packageRoot, statePath: options.statePath });
|
|
324
|
+
return state ? { ...options, taskId: state.taskId } : options;
|
|
325
|
+
} catch {
|
|
326
|
+
// Preserve the evaluator's structured missing/invalid-state diagnostics.
|
|
327
|
+
return options;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
async function evaluateSelectedCompletion({
|
|
332
|
+
target,
|
|
333
|
+
packageRoot,
|
|
334
|
+
strict = false,
|
|
335
|
+
authorityContext,
|
|
336
|
+
runtimeContext,
|
|
337
|
+
taskId = null,
|
|
338
|
+
contractPath = null,
|
|
339
|
+
routePath = null,
|
|
340
|
+
statePath = null,
|
|
341
|
+
receiptPath = null,
|
|
342
|
+
eventsPath = null,
|
|
343
|
+
preflightPath = null,
|
|
289
344
|
} = {}) {
|
|
290
345
|
const errors = [];
|
|
291
346
|
const preflight = await evaluatePreflight({ target, packageRoot, strict, taskId, contractPath, routePath, statePath });
|
|
@@ -557,19 +612,14 @@ export async function evaluateCompletion({
|
|
|
557
612
|
}
|
|
558
613
|
|
|
559
614
|
const actionTaskId = contract?.value?.taskId ?? taskId;
|
|
560
|
-
|
|
561
|
-
? await listActions(target, { packageRoot, taskId: actionTaskId })
|
|
562
|
-
: [];
|
|
615
|
+
|
|
563
616
|
const contractRequirements = new Set([
|
|
564
617
|
...(contract?.value?.verification ?? []), ...(contract?.value?.successCriteria ?? []),
|
|
565
618
|
]);
|
|
566
619
|
// Completion truth consumes the canonical action-readiness projection, not
|
|
567
620
|
// raw state labels: a forged VERIFIED label can never satisfy a required
|
|
568
621
|
// action (INV-VERIFY-02).
|
|
569
|
-
const {
|
|
570
|
-
const requiredActionReadiness = actionTaskId
|
|
571
|
-
? await evaluateRequiredActionReadiness({ target, packageRoot, taskId: actionTaskId })
|
|
572
|
-
: { total: 0, satisfied: 0, unresolved: 0, ambiguous: 0, failed: 0, untrusted: 0, actions: [] };
|
|
622
|
+
const { actions: durableActions, readiness: requiredActionReadiness } = await loadCompletionActions(target, packageRoot, actionTaskId, errors);
|
|
573
623
|
for (const readiness of requiredActionReadiness.actions) {
|
|
574
624
|
if (readiness.status === "SATISFIED") continue;
|
|
575
625
|
const action = durableActions.find((candidate) => candidate.actionId === readiness.actionId);
|
|
@@ -853,6 +903,7 @@ async function runCompleteInternal({
|
|
|
853
903
|
}
|
|
854
904
|
|
|
855
905
|
export async function runComplete(options = {}) {
|
|
906
|
+
options = await resolveCompletionTaskOptions(options);
|
|
856
907
|
const taskId = options.taskId ?? null;
|
|
857
908
|
if (options.persist !== false && taskId && !(await getTaskTransaction(options.target))) {
|
|
858
909
|
return withTaskTransaction({
|
package/src/core/conformance.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isLedgerEventCollection } from "./ledger-event-collection.js";
|
|
1
2
|
import { PROTOCOL_VERSION } from "./protocol.js";
|
|
2
3
|
import { createEvidence } from "./evidence.js";
|
|
3
4
|
import { canonicalFingerprint } from "./artifacts.js";
|
|
@@ -36,7 +37,7 @@ export function delegationIsInScope({
|
|
|
36
37
|
if (state?.delegatedTasks && state.delegatedTasks.length > 0) return true;
|
|
37
38
|
if (state?.delegatedTaskIds && state.delegatedTaskIds.length > 0) return true;
|
|
38
39
|
if (receipt?.delegatedTasks && receipt.delegatedTasks.length > 0) return true;
|
|
39
|
-
if (
|
|
40
|
+
if (isLedgerEventCollection(events) && events.some((event) => typeof event?.type === "string" && event.type.toLowerCase().includes("delegat"))) {
|
|
40
41
|
return true;
|
|
41
42
|
}
|
|
42
43
|
return false;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { assertSafePath, ensureWithin, fileExists } from "./filesystem.js";
|
|
2
|
+
import { operationalArtifactExists } from "../storage/operational-context.js";
|
|
3
|
+
import { withProjectReadSnapshot } from "../storage/project-read-snapshot.js";
|
|
2
4
|
|
|
3
5
|
function finding(code, severity, field, itemId = null) {
|
|
4
6
|
return { code, severity, field, itemId };
|
|
@@ -15,7 +17,16 @@ function hasOperationalHints(continuity) {
|
|
|
15
17
|
);
|
|
16
18
|
}
|
|
17
19
|
|
|
18
|
-
|
|
20
|
+
async function inspectPathExists(target, relativePath) {
|
|
21
|
+
return operationalArtifactExists(target, relativePath) ?? await fileExists(ensureWithin(target, relativePath));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function lintContinuity(options = {}) {
|
|
25
|
+
if (typeof options.target !== "string" || options.target.trim() === "") return lintSelectedContinuity(options);
|
|
26
|
+
return withProjectReadSnapshot(options.target, () => lintSelectedContinuity(options));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function lintSelectedContinuity({ target, continuity, state } = {}) {
|
|
19
30
|
const findings = [];
|
|
20
31
|
if (!continuity || typeof continuity !== "object" || Array.isArray(continuity)) {
|
|
21
32
|
return { status: "PASS", findings };
|
|
@@ -64,7 +75,7 @@ export async function lintContinuity({ target, continuity, state } = {}) {
|
|
|
64
75
|
for (const [index, inspectPath] of (continuity.inspectFirst ?? []).entries()) {
|
|
65
76
|
try {
|
|
66
77
|
await assertSafePath(target, inspectPath);
|
|
67
|
-
if (!(await
|
|
78
|
+
if (!(await inspectPathExists(target, inspectPath))) {
|
|
68
79
|
findings.push(finding(
|
|
69
80
|
"CONTINUITY_INSPECT_PATH_MISSING",
|
|
70
81
|
"WARN",
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { getOperationalStore } from "../storage/operational-context.js";
|
|
2
|
+
import { withEventLedgerAudit } from "./events.js";
|
|
1
3
|
import { canonicalFingerprint } from "./artifacts.js";
|
|
2
4
|
import { assertContinuitySemantics, readContinuity } from "./continuity.js";
|
|
3
5
|
import { WORK_TRANSITIONS } from "./protocol.js";
|
|
4
6
|
import { lintContinuity } from "./continuity-lint.js";
|
|
7
|
+
import { withNativeReadScope } from "./native-storage.js";
|
|
5
8
|
|
|
6
9
|
const RECONCILIATION_CODE = "E_CONTINUITY_RECONCILIATION_REQUIRED";
|
|
7
10
|
|
|
@@ -171,6 +174,14 @@ export function classifyContinuity({
|
|
|
171
174
|
}
|
|
172
175
|
|
|
173
176
|
export async function reconcileContinuity({ target, packageRoot, taskId = null } = {}) {
|
|
177
|
+
const read = () => reconcileLoadedContinuity({ target, packageRoot, taskId });
|
|
178
|
+
return withNativeReadScope(target, () => {
|
|
179
|
+
if (getOperationalStore(target) && taskId) return withEventLedgerAudit(target, packageRoot, { taskId }, read);
|
|
180
|
+
return read();
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async function reconcileLoadedContinuity({ target, packageRoot, taskId }) {
|
|
174
185
|
const [{ readWorkState }, { readContract }, repository] = await Promise.all([
|
|
175
186
|
import("./work-state.js"),
|
|
176
187
|
import("./contract.js"),
|
|
@@ -267,12 +278,14 @@ async function resolveLatestHandoffSafe(target, { packageRoot, taskId } = {}) {
|
|
|
267
278
|
|
|
268
279
|
async function deriveDiagnosticContextSafe({ target, packageRoot, state }) {
|
|
269
280
|
try {
|
|
270
|
-
const [{
|
|
281
|
+
const [{ withEventLedgerAudit }, { deriveDiagnosticContext }] = await Promise.all([
|
|
271
282
|
import("./events.js"),
|
|
272
283
|
import("./reflection.js"),
|
|
273
284
|
]);
|
|
274
|
-
|
|
275
|
-
|
|
285
|
+
return await withEventLedgerAudit(target, packageRoot, { taskId: state?.taskId ?? null }, audit => {
|
|
286
|
+
if (!audit.valid) return { present: false };
|
|
287
|
+
return { present: true, ...deriveDiagnosticContext(audit.events, state) };
|
|
288
|
+
});
|
|
276
289
|
} catch {
|
|
277
290
|
return { present: false };
|
|
278
291
|
}
|
package/src/core/continuity.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { withExistingProjectScope } from "../storage/existing-project-scope.js";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
|
|
4
4
|
import {
|
|
@@ -7,11 +7,12 @@ import {
|
|
|
7
7
|
readJsonArtifact,
|
|
8
8
|
writeJsonArtifact,
|
|
9
9
|
} from "./artifacts.js";
|
|
10
|
-
import { assertSafePath, ensureWithin, fileExists } from "./filesystem.js";
|
|
11
10
|
import { PROTOCOL_VERSION, WORK_PHASES } from "./protocol.js";
|
|
12
11
|
import { normalizePortableText, assertPortableContextSafe } from "./portable-context.js";
|
|
13
12
|
import { getPackageRoot } from "./templates.js";
|
|
14
13
|
import { taskArtifactPath } from "./task-paths.js";
|
|
14
|
+
import { getOperationalStore, readOperationalText } from "../storage/operational-context.js";
|
|
15
|
+
import { withTaskTransaction } from "./transaction.js";
|
|
15
16
|
|
|
16
17
|
export const CONTINUITY_PATH = ARTIFACT_PATHS.continuity;
|
|
17
18
|
export const CONTINUITY_SCHEMA_VERSION = 1;
|
|
@@ -242,10 +243,17 @@ export async function writeContinuity(target, operationalInput = {}, options = {
|
|
|
242
243
|
}
|
|
243
244
|
|
|
244
245
|
export async function clearContinuity(target, options = {}) {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
const
|
|
248
|
-
if (
|
|
249
|
-
|
|
250
|
-
|
|
246
|
+
if (!options.taskId) throw Object.assign(new Error("Canonical artifact clearing requires a task identity"), { code: "E_STORAGE_OPERATION_UNSUPPORTED" });
|
|
247
|
+
const relPath = taskArtifactPath(options.taskId, "continuity");
|
|
248
|
+
const requested = options.continuityPath ?? options.relativePath ?? relPath;
|
|
249
|
+
if (requested !== relPath) throw Object.assign(new Error("Artifact clearing requires the canonical task path; portable exports are independent files"), { code: "E_STORAGE_OPERATION_UNSUPPORTED", artifacts: [requested] });
|
|
250
|
+
if (!getOperationalStore(target)) {
|
|
251
|
+
return withExistingProjectScope(target, () => clearContinuity(target, options));
|
|
252
|
+
}
|
|
253
|
+
const operational = readOperationalText(target, relPath);
|
|
254
|
+
if (operational.text === null) return { removed: false, path: relPath };
|
|
255
|
+
return withTaskTransaction({ target, taskId: options.taskId, operation: "clear-continuity", packageRoot: options.packageRoot }, async transaction => {
|
|
256
|
+
transaction.stageDelete(relPath);
|
|
257
|
+
return { removed: true, path: relPath };
|
|
258
|
+
});
|
|
251
259
|
}
|