@cassiomc1/forgeloop 1.14.0 → 2.0.0-rc.4
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 +30 -40
- 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 +110 -10
- package/docs/SELF_HOSTED_TESTS.md +121 -0
- package/docs/TROUBLESHOOTING.md +33 -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 +85 -36
- 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 +15 -7
- 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-execution-provenance.js +26 -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 +667 -0
- package/src/storage/windows-process-incarnation.js +206 -0
- package/.forgeloop/forgeloop.gitignore +0 -4
- package/LICENSE-DOCS.md +0 -13
- package/src/core/transaction-maintenance.js +0 -70
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { needsExistingProjectScope, withExistingProjectScope } from "../../storage/existing-project-scope.js";
|
|
1
2
|
import { readdir } from "node:fs/promises";
|
|
3
|
+
import { listOperationalArtifactNames, operationalArtifactExists } from "../../storage/operational-context.js";
|
|
2
4
|
|
|
3
5
|
import {
|
|
4
6
|
canonicalFingerprint,
|
|
@@ -205,21 +207,25 @@ export async function writeStructuralQualityBaseline(
|
|
|
205
207
|
}
|
|
206
208
|
|
|
207
209
|
export async function listStructuralQualityEvaluations(target, taskId, packageRoot = getPackageRoot()) {
|
|
210
|
+
if (await needsExistingProjectScope(target)) {
|
|
211
|
+
return withExistingProjectScope(target, () => listStructuralQualityEvaluations(target, taskId, packageRoot), { readOnly: true });
|
|
212
|
+
}
|
|
208
213
|
const relativeDirectory = taskStructuralQualityEvaluationsDirectory(taskId);
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
214
|
+
let names = listOperationalArtifactNames(target, taskId, "structural-quality/evaluations");
|
|
215
|
+
if (names === null) {
|
|
216
|
+
await assertSafePath(target, relativeDirectory);
|
|
217
|
+
const absoluteDirectory = ensureWithin(target, relativeDirectory);
|
|
218
|
+
if (!(await fileExists(absoluteDirectory))) return [];
|
|
219
|
+
try {
|
|
220
|
+
names = (await readdir(absoluteDirectory, { withFileTypes: true })).filter(entry => entry.isFile()).map(entry => entry.name);
|
|
221
|
+
} catch (error) {
|
|
222
|
+
throw qualityArtifactError(E_STRUCTURAL_QUALITY_EVIDENCE_STALE, `Unable to list structural-quality evaluations: ${error.message}`, [relativeDirectory]);
|
|
223
|
+
}
|
|
217
224
|
}
|
|
218
|
-
const candidates =
|
|
219
|
-
.
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
return match ? { name: entry.name, verificationCycle: Number(match[1]), attempt: Number(match[2]) } : null;
|
|
225
|
+
const candidates = names
|
|
226
|
+
.map((name) => {
|
|
227
|
+
const match = /^cycle-(\d+)-attempt-(\d+)\.json$/u.exec(name);
|
|
228
|
+
return match ? { name, verificationCycle: Number(match[1]), attempt: Number(match[2]) } : null;
|
|
223
229
|
})
|
|
224
230
|
.filter(Boolean)
|
|
225
231
|
.filter((item) => item.verificationCycle >= 1 && item.attempt >= 1)
|
|
@@ -267,7 +273,7 @@ export async function writeStructuralQualityEvaluation(target, taskId, verificat
|
|
|
267
273
|
throw qualityArtifactError(E_STRUCTURAL_QUALITY_EVIDENCE_STALE, `Evaluation identity does not match ${relativePath}`, [relativePath]);
|
|
268
274
|
}
|
|
269
275
|
validateStructuralQualityArtifact(value, relativePath);
|
|
270
|
-
const existing = await fileExists(ensureWithin(target, relativePath));
|
|
276
|
+
const existing = operationalArtifactExists(target, relativePath) ?? await fileExists(ensureWithin(target, relativePath));
|
|
271
277
|
if (existing) {
|
|
272
278
|
const current = await readStructuralQualityEvaluation(target, taskId, cycle, normalizedAttempt, packageRoot);
|
|
273
279
|
if (current.fingerprint === canonicalFingerprint(value)) return { ...current, existing: true, identical: true };
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { needsExistingProjectScope } from "../../storage/existing-project-scope.js";
|
|
2
|
+
import { withProjectReadSnapshot } from "../../storage/project-read-snapshot.js";
|
|
1
3
|
import { canonicalFingerprint, readJsonArtifact } from "../artifacts.js";
|
|
2
4
|
import { readConfig } from "../config.js";
|
|
3
5
|
import { readContract } from "../contract.js";
|
|
@@ -36,6 +38,22 @@ import {
|
|
|
36
38
|
import { appendProtocolEvent } from "../events.js";
|
|
37
39
|
import { getPackageRoot } from "../templates.js";
|
|
38
40
|
import { withTaskMutation } from "../task-command.js";
|
|
41
|
+
import { withTaskTransaction } from "../transaction.js";
|
|
42
|
+
import { getOperationalStore } from "../../storage/operational-context.js";
|
|
43
|
+
|
|
44
|
+
async function withQualityMutation(target, options, operation, callback) {
|
|
45
|
+
const native = Boolean(getOperationalStore(target)) || await needsExistingProjectScope(target);
|
|
46
|
+
for (let retry = 0; ; retry += 1) {
|
|
47
|
+
try {
|
|
48
|
+
return await withTaskMutation(target, options, operation, callback);
|
|
49
|
+
} catch (error) {
|
|
50
|
+
// These callbacks persist an already captured observation. A native
|
|
51
|
+
// conflict discards their staged writes; rerun admission and bindings
|
|
52
|
+
// against current records without invoking the provider again.
|
|
53
|
+
if (!native || error.code !== "E_STATE_REVISION_CONFLICT" || retry >= 2) throw error;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
39
57
|
import { findTaskById } from "../task-discovery.js";
|
|
40
58
|
import { currentChangedPaths } from "../repository.js";
|
|
41
59
|
import { assertClaimsCoverChangedPaths } from "../task-scope.js";
|
|
@@ -142,7 +160,11 @@ function normalizeScanResult(result, provider, detection, projectPath) {
|
|
|
142
160
|
};
|
|
143
161
|
}
|
|
144
162
|
|
|
145
|
-
export async function resolveStructuralQualityContext({
|
|
163
|
+
export async function resolveStructuralQualityContext(options = {}) {
|
|
164
|
+
return withProjectReadSnapshot(options.target, () => resolveStructuralQualityContextObservation(options));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function resolveStructuralQualityContextObservation({
|
|
146
168
|
target,
|
|
147
169
|
packageRoot = getPackageRoot(),
|
|
148
170
|
taskId,
|
|
@@ -378,31 +400,43 @@ export async function captureStructuralQualityBaseline({ target, packageRoot = g
|
|
|
378
400
|
scope: updatedInputs.scope,
|
|
379
401
|
snapshot: scan.snapshot,
|
|
380
402
|
};
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
403
|
+
return withTaskTransaction({ target, taskId, packageRoot, operation: "quality-baseline" }, async () => {
|
|
404
|
+
const currentInputs = await taskInputs(target, packageRoot, taskId, { runtimeContext, timeoutMs, maxOutputBytes });
|
|
405
|
+
if (currentInputs.state.phase !== "PLANNED"
|
|
406
|
+
|| currentInputs.contract.fingerprint !== inputs.contract.fingerprint
|
|
407
|
+
|| currentInputs.route.fingerprint !== inputs.route.fingerprint
|
|
408
|
+
|| structuralQualityPolicyFingerprint(currentInputs.policy) !== policyFingerprint
|
|
409
|
+
|| currentInputs.scopeFingerprint !== inputs.scopeFingerprint) {
|
|
410
|
+
throw qualityError(E_STRUCTURAL_QUALITY_OBSERVATION_EPOCH_STALE, "Task baseline configuration changed during observation");
|
|
411
|
+
}
|
|
412
|
+
await assertReadyPreflight(target, packageRoot, taskId);
|
|
413
|
+
const currentBaseline = await readStructuralQualityBaseline(target, taskId, packageRoot);
|
|
414
|
+
const written = await writeStructuralQualityBaseline(target, taskId, value, packageRoot, { phase: currentInputs.state.phase, replace, taskId });
|
|
415
|
+
if (!written.identical) {
|
|
416
|
+
await appendProtocolEvent(target, {
|
|
417
|
+
taskId,
|
|
418
|
+
event: "STRUCTURAL_QUALITY_BASELINE_RECORDED",
|
|
419
|
+
details: {
|
|
420
|
+
artifactRef: written.path,
|
|
421
|
+
artifactFingerprint: written.fingerprint,
|
|
422
|
+
providerId: scan.provider.id,
|
|
423
|
+
providerVersion: scan.provider.version,
|
|
424
|
+
replaced: Boolean(currentBaseline),
|
|
425
|
+
supersededFingerprint: currentBaseline?.fingerprint ?? null,
|
|
426
|
+
},
|
|
427
|
+
}, packageRoot, { taskId });
|
|
428
|
+
}
|
|
429
|
+
return {
|
|
430
|
+
status: written.existing ? "REPLACED" : "CAPTURED",
|
|
431
|
+
mode: policy.mode,
|
|
432
|
+
provider: scan.provider,
|
|
433
|
+
artifactRef: written.path,
|
|
434
|
+
artifactFingerprint: written.fingerprint,
|
|
435
|
+
baseline: written.value,
|
|
436
|
+
existing: written.existing,
|
|
437
|
+
identical: written.identical,
|
|
438
|
+
};
|
|
439
|
+
});
|
|
406
440
|
}
|
|
407
441
|
|
|
408
442
|
async function persistEvaluationAndCheck({ target, packageRoot, taskId, inputs, persisted, authorityContext, runtimeContext }) {
|
|
@@ -443,7 +477,7 @@ async function persistEvaluationAndCheck({ target, packageRoot, taskId, inputs,
|
|
|
443
477
|
runtimeContext,
|
|
444
478
|
taskId: context?.taskId ?? taskId,
|
|
445
479
|
});
|
|
446
|
-
const recorded = await
|
|
480
|
+
const recorded = await withQualityMutation(target, { taskId, packageRoot }, "quality-verify-check", async (context) => {
|
|
447
481
|
const effectiveTaskId = context?.taskId ?? taskId;
|
|
448
482
|
const evaluations = await listStructuralQualityEvaluations(target, effectiveTaskId, packageRoot);
|
|
449
483
|
const latest = evaluations
|
|
@@ -599,34 +633,33 @@ export async function evaluateStructuralQuality({ target, packageRoot = getPacka
|
|
|
599
633
|
|
|
600
634
|
// Execute provider observation OUTSIDE task mutation lock
|
|
601
635
|
const sourceFingerprintBefore = await computeMaterialSourceFingerprint(target);
|
|
636
|
+
// Every non-pass branch binds the same reserved task/cycle and source epoch.
|
|
637
|
+
const evaluationInputs = {
|
|
638
|
+
taskId,
|
|
639
|
+
inputs: initialInputs,
|
|
640
|
+
cycle,
|
|
641
|
+
attempt,
|
|
642
|
+
policy,
|
|
643
|
+
baseline,
|
|
644
|
+
sourceMaterialFingerprint: sourceFingerprintBefore,
|
|
645
|
+
};
|
|
602
646
|
let evaluation;
|
|
603
647
|
|
|
604
648
|
if (!baseline) {
|
|
605
649
|
evaluation = nonPassEvaluation({
|
|
606
|
-
|
|
607
|
-
inputs: initialInputs,
|
|
608
|
-
cycle,
|
|
609
|
-
attempt,
|
|
610
|
-
policy,
|
|
650
|
+
...evaluationInputs,
|
|
611
651
|
reasonCode: E_STRUCTURAL_QUALITY_BASELINE_MISSING,
|
|
612
652
|
sourceObservation: { beforeFingerprint: sourceFingerprintBefore, afterFingerprint: sourceFingerprintBefore, stable: true },
|
|
613
|
-
sourceMaterialFingerprint: sourceFingerprintBefore,
|
|
614
653
|
});
|
|
615
654
|
} else {
|
|
616
655
|
try {
|
|
617
656
|
const scopeChanged = baseline.value.bindings?.scopeFingerprint !== initialInputs.scopeFingerprint;
|
|
618
657
|
if (scopeChanged) {
|
|
619
658
|
evaluation = nonPassEvaluation({
|
|
620
|
-
|
|
621
|
-
inputs: initialInputs,
|
|
622
|
-
cycle,
|
|
623
|
-
attempt,
|
|
624
|
-
policy,
|
|
659
|
+
...evaluationInputs,
|
|
625
660
|
reasonCode: E_STRUCTURAL_QUALITY_EVALUATION_INCOMPARABLE,
|
|
626
661
|
reasonCodes: ["PROVIDER_CONFIG_CHANGED"],
|
|
627
|
-
baseline,
|
|
628
662
|
sourceObservation: { beforeFingerprint: sourceFingerprintBefore, afterFingerprint: sourceFingerprintBefore, stable: true },
|
|
629
|
-
sourceMaterialFingerprint: sourceFingerprintBefore,
|
|
630
663
|
});
|
|
631
664
|
evaluation.comparison.reasonCodes = [E_STRUCTURAL_QUALITY_EVALUATION_INCOMPARABLE, "PROVIDER_CONFIG_CHANGED"];
|
|
632
665
|
} else {
|
|
@@ -643,18 +676,12 @@ export async function evaluateStructuralQuality({ target, packageRoot = getPacka
|
|
|
643
676
|
|
|
644
677
|
if (!isStable) {
|
|
645
678
|
evaluation = nonPassEvaluation({
|
|
646
|
-
|
|
647
|
-
inputs: initialInputs,
|
|
648
|
-
cycle,
|
|
649
|
-
attempt,
|
|
650
|
-
policy,
|
|
679
|
+
...evaluationInputs,
|
|
651
680
|
reasonCode: E_STRUCTURAL_QUALITY_SOURCE_DRIFT,
|
|
652
681
|
providerId: scan.provider.id,
|
|
653
682
|
providerVersion: scan.provider.version,
|
|
654
683
|
detection: scan.detection,
|
|
655
|
-
baseline,
|
|
656
684
|
sourceObservation: { beforeFingerprint: sourceFingerprintBefore, afterFingerprint: sourceFingerprintAfter, stable: false },
|
|
657
|
-
sourceMaterialFingerprint: sourceFingerprintBefore,
|
|
658
685
|
});
|
|
659
686
|
} else {
|
|
660
687
|
const updatedInputs = initialInputs;
|
|
@@ -699,26 +726,20 @@ export async function evaluateStructuralQuality({ target, packageRoot = getPacka
|
|
|
699
726
|
const sourceFingerprintAfter = await computeMaterialSourceFingerprint(target).catch(() => sourceFingerprintBefore);
|
|
700
727
|
const code = error.code ?? E_STRUCTURAL_QUALITY_PROVIDER_UNAVAILABLE;
|
|
701
728
|
evaluation = nonPassEvaluation({
|
|
702
|
-
|
|
703
|
-
inputs: initialInputs,
|
|
704
|
-
cycle,
|
|
705
|
-
attempt,
|
|
706
|
-
policy,
|
|
729
|
+
...evaluationInputs,
|
|
707
730
|
reasonCode: code,
|
|
708
731
|
providerId: policy.provider,
|
|
709
|
-
baseline,
|
|
710
732
|
sourceObservation: {
|
|
711
733
|
beforeFingerprint: sourceFingerprintBefore,
|
|
712
734
|
afterFingerprint: sourceFingerprintAfter,
|
|
713
735
|
stable: sourceFingerprintBefore === sourceFingerprintAfter,
|
|
714
736
|
},
|
|
715
|
-
sourceMaterialFingerprint: sourceFingerprintBefore,
|
|
716
737
|
});
|
|
717
738
|
}
|
|
718
739
|
}
|
|
719
740
|
|
|
720
741
|
// Commit under task mutation lock
|
|
721
|
-
const captured = await
|
|
742
|
+
const captured = await withQualityMutation(target, { taskId, packageRoot }, "quality-verify-evaluation", async (context) => {
|
|
722
743
|
const lockedTaskId = context?.taskId ?? taskId;
|
|
723
744
|
const inputs = await taskInputs(target, packageRoot, lockedTaskId, { runtimeContext, timeoutMs, maxOutputBytes });
|
|
724
745
|
if (inputs.state.phase !== "VERIFYING") {
|
|
@@ -908,7 +929,11 @@ function projectionFromArtifacts(policy, baseline, current, state, evaluations =
|
|
|
908
929
|
return projection;
|
|
909
930
|
}
|
|
910
931
|
|
|
911
|
-
export async function projectStructuralQualityStatus(
|
|
932
|
+
export async function projectStructuralQualityStatus(options = {}) {
|
|
933
|
+
return withProjectReadSnapshot(options.target, () => projectStructuralQualityStatusObservation(options));
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
async function projectStructuralQualityStatusObservation({ target, packageRoot = getPackageRoot(), taskId, runtimeContext } = {}) {
|
|
912
937
|
const config = await loadOptionalConfig(target, packageRoot);
|
|
913
938
|
const policy = configuredPolicy(config);
|
|
914
939
|
if (!policy || policy.mode === "off") return emptyProjection(null);
|
|
@@ -26,7 +26,7 @@ export function targetPathForSource(sourcePath) {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
export function legacyPathForSource(sourcePath) {
|
|
29
|
-
return sourcePath
|
|
29
|
+
return sourcePath;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
export function isKitPath(relativePath) {
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { needsExistingProjectScope } from "../storage/existing-project-scope.js";
|
|
2
|
+
import { getOperationalStore } from "../storage/operational-context.js";
|
|
3
|
+
import { ledgerTypeSummary } from "./ledger-event-collection.js";
|
|
1
4
|
import {
|
|
2
5
|
E_COMPLETION_OWNERSHIP_UNPROVEN,
|
|
3
6
|
E_TASK_CLAIM_OWNERSHIP_INCONSISTENT,
|
|
@@ -6,8 +9,8 @@ import {
|
|
|
6
9
|
E_TASK_RECOVERY_INCONSISTENT,
|
|
7
10
|
} from "./error-codes.js";
|
|
8
11
|
import { validateCompletionOwnershipProof } from "./completion-ownership.js";
|
|
9
|
-
import { validateEventLedger, validateStateLedgerCoherence } from "./events.js";
|
|
10
|
-
import { classifyRecoveryHistory } from "./recovery-history.js";
|
|
12
|
+
import { validateEventLedger, validatePortableEventLedger, validateStateLedgerCoherence, withEventLedgerAudit } from "./events.js";
|
|
13
|
+
import { classifyRecoveryHistory, summarizeRecoveryHistory } from "./recovery-history.js";
|
|
11
14
|
import { readTaskDescriptor } from "./task-descriptor.js";
|
|
12
15
|
import { normalizeWriteClaims } from "./task-scope.js";
|
|
13
16
|
import {
|
|
@@ -126,7 +129,7 @@ function evolvedRepairErrors(anchor, events, state, artifacts) {
|
|
|
126
129
|
errors.push(...lateRouteCheckpointErrors(events, state, anchor.sourceMarker));
|
|
127
130
|
errors.push(...validateStateLedgerCoherence(state, events).map((error) => repairInvalid(error.message)));
|
|
128
131
|
const requiredEvent = REPAIR_PHASE_EVENTS[state.phase];
|
|
129
|
-
if (requiredEvent && !events
|
|
132
|
+
if (requiredEvent && !ledgerTypeSummary(events, requiredEvent).count) {
|
|
130
133
|
errors.push(repairInvalid(`Current work-state phase ${state.phase} is not supported by the repair ledger history`));
|
|
131
134
|
}
|
|
132
135
|
return errors;
|
|
@@ -135,7 +138,7 @@ function evolvedRepairErrors(anchor, events, state, artifacts) {
|
|
|
135
138
|
function validateContractBootstrapRepairConsistency(taskId, events, state, artifacts) {
|
|
136
139
|
const anchor = resolveEffectiveContractBootstrapRepairAnchor(events);
|
|
137
140
|
if (!anchor) {
|
|
138
|
-
if (!events
|
|
141
|
+
if (!ledgerTypeSummary(events, CONTRACT_BOOTSTRAP_REPAIR_EVENT).count) return [];
|
|
139
142
|
return [repairInvalid("Contract bootstrap repair marker is invalid")];
|
|
140
143
|
}
|
|
141
144
|
if (!state) return [repairInvalid("Work-state is missing after a recorded contract bootstrap repair")];
|
|
@@ -184,6 +187,7 @@ async function collectContractBootstrapRepairConsistency(target, packageRoot, ta
|
|
|
184
187
|
artifacts.contractError = "work-state contract fingerprint does not match marker contract fingerprint";
|
|
185
188
|
}
|
|
186
189
|
} catch (error) {
|
|
190
|
+
if (error.code === "E_STATE_REVISION_CONFLICT") throw error;
|
|
187
191
|
artifacts.contractError = error.code === "ARTIFACT_MISSING"
|
|
188
192
|
? "contract artifact is missing after a recorded repair"
|
|
189
193
|
: `contract artifact is invalid after a recorded repair: ${error.message}`;
|
|
@@ -202,6 +206,7 @@ async function collectContractBootstrapRepairConsistency(target, packageRoot, ta
|
|
|
202
206
|
artifacts.routeError = "route artifact contract binding does not match repair anchor contract fingerprint";
|
|
203
207
|
}
|
|
204
208
|
} catch (error) {
|
|
209
|
+
if (error.code === "E_STATE_REVISION_CONFLICT") throw error;
|
|
205
210
|
artifacts.routeError = error.code === "ARTIFACT_MISSING"
|
|
206
211
|
? "route artifact is missing after a routed repair"
|
|
207
212
|
: `route artifact is invalid after a routed repair: ${error.message}`;
|
|
@@ -214,7 +219,10 @@ async function collectContractBootstrapRepairConsistency(target, packageRoot, ta
|
|
|
214
219
|
function appendClaims(target, claims, errors, source) {
|
|
215
220
|
if (claims === undefined || claims === null) return;
|
|
216
221
|
try {
|
|
217
|
-
|
|
222
|
+
const normalized = normalizeWriteClaims(claims);
|
|
223
|
+
if (target instanceof Set || typeof target.add === "function") {
|
|
224
|
+
for (const claim of normalized) target.add(claim);
|
|
225
|
+
} else target.push(...normalized);
|
|
218
226
|
} catch (error) {
|
|
219
227
|
errors.push(ownershipError(`Invalid ${source} write claims: ${error.message}`, error));
|
|
220
228
|
}
|
|
@@ -226,12 +234,12 @@ function appendClaims(target, claims, errors, source) {
|
|
|
226
234
|
* classified recovery history. Classification is a pure function of this
|
|
227
235
|
* evidence, so conflict inspection can reuse it without rereading artifacts.
|
|
228
236
|
*/
|
|
229
|
-
|
|
237
|
+
async function collectClaimEvidence(target, {
|
|
230
238
|
taskId,
|
|
231
239
|
packageRoot,
|
|
232
240
|
descriptor: suppliedDescriptor = null,
|
|
233
241
|
state: suppliedState = null,
|
|
234
|
-
} = {}) {
|
|
242
|
+
} = {}, suppliedLedger = null) {
|
|
235
243
|
const errors = [];
|
|
236
244
|
let descriptor = suppliedDescriptor;
|
|
237
245
|
let state = suppliedState;
|
|
@@ -242,6 +250,7 @@ export async function collectTaskClaimEvidence(target, {
|
|
|
242
250
|
try {
|
|
243
251
|
descriptor = (await readTaskDescriptor(target, taskId, packageRoot)).value;
|
|
244
252
|
} catch (error) {
|
|
253
|
+
if (error.code === "E_STATE_REVISION_CONFLICT") throw error;
|
|
245
254
|
errors.push(ownershipError(`Task descriptor cannot establish claim ownership: ${error.message}`, error));
|
|
246
255
|
}
|
|
247
256
|
}
|
|
@@ -249,6 +258,7 @@ export async function collectTaskClaimEvidence(target, {
|
|
|
249
258
|
try {
|
|
250
259
|
state = await readWorkState(target, { taskId, packageRoot });
|
|
251
260
|
} catch (error) {
|
|
261
|
+
if (error.code === "E_STATE_REVISION_CONFLICT") throw error;
|
|
252
262
|
errors.push(ownershipError(`Task work state cannot establish claim ownership: ${error.message}`, error));
|
|
253
263
|
}
|
|
254
264
|
}
|
|
@@ -256,14 +266,27 @@ export async function collectTaskClaimEvidence(target, {
|
|
|
256
266
|
try {
|
|
257
267
|
recovery = (await readTaskRecovery(target, { taskId, packageRoot }))?.value ?? null;
|
|
258
268
|
} catch (error) {
|
|
269
|
+
if (error.code === "E_STATE_REVISION_CONFLICT") throw error;
|
|
259
270
|
errors.push(ownershipError(`Task recovery artifact cannot establish claim ownership: ${error.message}`, error));
|
|
260
271
|
}
|
|
261
272
|
|
|
262
273
|
try {
|
|
263
|
-
ledger = await validateEventLedger(target, packageRoot, { taskId });
|
|
274
|
+
ledger = suppliedLedger ?? await (getOperationalStore(target) || await needsExistingProjectScope(target) ? validateEventLedger : validatePortableEventLedger)(target, packageRoot, { taskId });
|
|
264
275
|
} catch (error) {
|
|
276
|
+
if (error.code === "E_STATE_REVISION_CONFLICT") throw error;
|
|
265
277
|
errors.push(ownershipError(`Task event ledger cannot establish claim ownership: ${error.message}`, error));
|
|
266
278
|
}
|
|
279
|
+
const repairArtifacts = await collectContractBootstrapRepairConsistency(target, packageRoot, taskId, ledger.events, state);
|
|
280
|
+
return buildTaskClaimEvidence({ taskId, descriptor, state, recovery, ledger, repairArtifacts, initialErrors: errors });
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export async function collectTaskClaimEvidence(target, options = {}) {
|
|
284
|
+
return collectClaimEvidence(target, options);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/** Shared pure ownership evidence projection for filesystem and store snapshots. */
|
|
288
|
+
export function buildTaskClaimEvidence({ taskId, descriptor, state, recovery, ledger, repairArtifacts = null, initialErrors = [] }) {
|
|
289
|
+
const errors = [...initialErrors];
|
|
267
290
|
if (!ledger.valid) {
|
|
268
291
|
for (const error of ledger.errors) {
|
|
269
292
|
errors.push(ownershipError(`Task event ledger is invalid: ${error.message}`, error));
|
|
@@ -282,19 +305,19 @@ export async function collectTaskClaimEvidence(target, {
|
|
|
282
305
|
}
|
|
283
306
|
}
|
|
284
307
|
|
|
285
|
-
const repairArtifacts = await collectContractBootstrapRepairConsistency(target, packageRoot, taskId, ledger.events, state);
|
|
286
308
|
const repairConsistencyErrors = validateContractBootstrapRepairConsistency(taskId, ledger.events, state, repairArtifacts);
|
|
287
309
|
errors.push(...repairConsistencyErrors);
|
|
288
310
|
|
|
289
|
-
const history = classifyRecoveryHistory(ledger.events);
|
|
290
311
|
const descriptorClaims = [];
|
|
291
312
|
appendClaims(descriptorClaims, descriptor?.writeClaims, errors, "descriptor");
|
|
292
313
|
const normalizedDescriptorClaims = normalizeWriteClaims(descriptorClaims);
|
|
293
314
|
|
|
294
|
-
const historicalClaims =
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
315
|
+
const historicalClaims = new Set(normalizedDescriptorClaims);
|
|
316
|
+
const observeRecovery = cycle => appendClaims(historicalClaims, cycle.event?.details?.releasedClaims, errors, "recovery ledger");
|
|
317
|
+
const history = Array.isArray(ledger.events)
|
|
318
|
+
? classifyRecoveryHistory(ledger.events)
|
|
319
|
+
: summarizeRecoveryHistory(ledger.events, observeRecovery);
|
|
320
|
+
if (Array.isArray(ledger.events)) for (const cycle of history.recoveries) observeRecovery(cycle);
|
|
298
321
|
appendClaims(historicalClaims, recovery?.releasedClaims, errors, "recovery artifact");
|
|
299
322
|
|
|
300
323
|
return {
|
|
@@ -306,7 +329,7 @@ export async function collectTaskClaimEvidence(target, {
|
|
|
306
329
|
ledger,
|
|
307
330
|
history,
|
|
308
331
|
normalizedDescriptorClaims,
|
|
309
|
-
historicalWriteClaims: normalizeWriteClaims(historicalClaims),
|
|
332
|
+
historicalWriteClaims: normalizeWriteClaims([...historicalClaims]),
|
|
310
333
|
errors,
|
|
311
334
|
};
|
|
312
335
|
}
|
|
@@ -429,7 +452,7 @@ export function classifyTaskClaimState(evidence) {
|
|
|
429
452
|
claimState: "ACTIVE",
|
|
430
453
|
mutationAllowed: true,
|
|
431
454
|
recovery: null,
|
|
432
|
-
recoveryStatus: history.completedRecoveries.length > 0 ? "COMPLETED" : "ABSENT",
|
|
455
|
+
recoveryStatus: (history.completedRecoveryCount ?? history.completedRecoveries.length) > 0 ? "COMPLETED" : "ABSENT",
|
|
433
456
|
valid: true,
|
|
434
457
|
ownershipValid: true,
|
|
435
458
|
reasonCodes: [],
|
|
@@ -443,9 +466,20 @@ export function classifyTaskClaimState(evidence) {
|
|
|
443
466
|
* tombstone, and the complete validated task ledger. Any disagreement retains
|
|
444
467
|
* every claim that can be recovered from validated inputs and fails closed.
|
|
445
468
|
*/
|
|
469
|
+
export async function withTaskClaimEvidence(target, options, callback) {
|
|
470
|
+
let store;
|
|
471
|
+
try { store = getOperationalStore(target); } catch { /* Preserve collected fail-closed evidence for invalid contexts. */ }
|
|
472
|
+
if (store && !store.hasPreparedMutations()
|
|
473
|
+
&& typeof options.taskId === "string" && options.taskId) {
|
|
474
|
+
return withEventLedgerAudit(target, options.packageRoot, { taskId: options.taskId }, async ledger => (
|
|
475
|
+
callback(await collectClaimEvidence(target, options, ledger))
|
|
476
|
+
));
|
|
477
|
+
}
|
|
478
|
+
return callback(await collectTaskClaimEvidence(target, options));
|
|
479
|
+
}
|
|
480
|
+
|
|
446
481
|
export async function resolveTaskClaimState(target, options = {}) {
|
|
447
|
-
|
|
448
|
-
return classifyTaskClaimState(evidence);
|
|
482
|
+
return withTaskClaimEvidence(target, options, classifyTaskClaimState);
|
|
449
483
|
}
|
|
450
484
|
|
|
451
485
|
export async function assertTaskMutationAllowed(target, options = {}) {
|
package/src/core/task-command.js
CHANGED
|
@@ -2,6 +2,8 @@ import { resolveTaskContext, TASK_SELECTION_MODES } from "./task-context.js";
|
|
|
2
2
|
import { withTaskTransaction } from "./transaction.js";
|
|
3
3
|
import { assertTaskMutationAllowed } from "./task-claim-state.js";
|
|
4
4
|
import { assertWorkspaceBinding } from "./workspace-binding.js";
|
|
5
|
+
import { getOperationalStore } from "../storage/operational-context.js";
|
|
6
|
+
import { withTaskLock } from "./task-lock.js";
|
|
5
7
|
|
|
6
8
|
export async function withResolvedTask(
|
|
7
9
|
target,
|
|
@@ -27,15 +29,33 @@ export async function withTaskMutation(
|
|
|
27
29
|
callback,
|
|
28
30
|
{ explicitRequired = false, skipWorkspaceBinding = false } = {},
|
|
29
31
|
) {
|
|
32
|
+
if (!getOperationalStore(target)) {
|
|
33
|
+
const { withProjectStorage } = await import("../storage/project-boundary.js");
|
|
34
|
+
return withProjectStorage(target, () => withTaskMutation(target, options, operation, callback, { explicitRequired, skipWorkspaceBinding }));
|
|
35
|
+
}
|
|
30
36
|
const taskOption = options.taskId ?? options.task ?? null;
|
|
31
37
|
const taskContext = await resolveTaskContext(target, {
|
|
32
38
|
taskId: taskOption,
|
|
33
|
-
explicitRequired,
|
|
39
|
+
explicitRequired: explicitRequired || Boolean(getOperationalStore(target)),
|
|
34
40
|
packageRoot: options.packageRoot,
|
|
35
41
|
selectionMode: TASK_SELECTION_MODES.MUTATION,
|
|
36
42
|
});
|
|
37
43
|
|
|
38
44
|
if (taskContext) {
|
|
45
|
+
if (getOperationalStore(target) && ["run-action", "run-check"].includes(operation)) {
|
|
46
|
+
return withTaskLock(target, taskContext.taskId, operation, async () => {
|
|
47
|
+
await withTaskTransaction({ target, taskId: taskContext.taskId, operation: `${operation}:prepare`, packageRoot: options.packageRoot }, async () => {
|
|
48
|
+
await assertTaskMutationAllowed(target, { taskId: taskContext.taskId, packageRoot: options.packageRoot });
|
|
49
|
+
if (!skipWorkspaceBinding) await assertWorkspaceBinding(target, { taskId: taskContext.taskId, packageRoot: options.packageRoot, operation });
|
|
50
|
+
});
|
|
51
|
+
// External work runs with a durable semantic reservation and no open
|
|
52
|
+
// database transaction. Its domain services commit intent/outcome in
|
|
53
|
+
// separate transactions, so a crash cannot erase a durable start.
|
|
54
|
+
const result = await callback(taskContext);
|
|
55
|
+
await withTaskTransaction({ target, taskId: taskContext.taskId, operation, packageRoot: options.packageRoot, recordCommitEvent: true }, () => undefined);
|
|
56
|
+
return result;
|
|
57
|
+
});
|
|
58
|
+
}
|
|
39
59
|
return withTaskTransaction({ target, taskId: taskContext.taskId, operation, packageRoot: options.packageRoot, recordCommitEvent: true }, async (transaction) => {
|
|
40
60
|
await assertTaskMutationAllowed(target, { taskId: taskContext.taskId, packageRoot: options.packageRoot });
|
|
41
61
|
if (!skipWorkspaceBinding) {
|