@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,3 +1,5 @@
|
|
|
1
|
+
import { ledgerRelationMap } from "./ledger-relations.js";
|
|
2
|
+
import { isLedgerEventCollection, ledgerEventAt, ledgerEventAtIs, ledgerEventsOfTypes, ledgerTypeSummary } from "./ledger-event-collection.js";
|
|
1
3
|
import { canonicalFingerprint } from "./artifacts.js";
|
|
2
4
|
|
|
3
5
|
export const CONTRACT_BOOTSTRAP_REPAIR_EVENT = "CONTRACT_BOOTSTRAP_REPAIR_RECORDED";
|
|
@@ -212,13 +214,13 @@ function isRepairTransactionCommit(event, taskId) {
|
|
|
212
214
|
* repair transaction merely because they follow the marker.
|
|
213
215
|
*/
|
|
214
216
|
export function resolveContractBootstrapRepairBoundary(events, marker = null) {
|
|
215
|
-
if (!
|
|
216
|
-
const
|
|
217
|
-
if (
|
|
218
|
-
const resolvedMarker = marker ??
|
|
217
|
+
if (!isLedgerEventCollection(events)) return null;
|
|
218
|
+
const { count, first } = ledgerTypeSummary(events, CONTRACT_BOOTSTRAP_REPAIR_EVENT);
|
|
219
|
+
if (count !== 1) return null;
|
|
220
|
+
const resolvedMarker = marker ?? first;
|
|
219
221
|
const markerIndex = events.indexOf(resolvedMarker);
|
|
220
222
|
if (markerIndex < 0 || resolvedMarker.seq !== markerIndex + 1) return null;
|
|
221
|
-
const repairCommit = events
|
|
223
|
+
const repairCommit = ledgerEventAt(events, markerIndex + 1);
|
|
222
224
|
if (!isRepairTransactionCommit(repairCommit, resolvedMarker.taskId)
|
|
223
225
|
|| repairCommit.seq !== resolvedMarker.seq + 1) return null;
|
|
224
226
|
return {
|
|
@@ -261,7 +263,7 @@ export function resolveCanonicalRouteEvolution(events, {
|
|
|
261
263
|
targetRouteFingerprint,
|
|
262
264
|
contractFingerprint,
|
|
263
265
|
} = {}) {
|
|
264
|
-
if (!
|
|
266
|
+
if (!isLedgerEventCollection(events)
|
|
265
267
|
|| typeof taskId !== "string" || !taskId
|
|
266
268
|
|| !Number.isInteger(sourceSeq) || sourceSeq < 0
|
|
267
269
|
|| (sourceRouteFingerprint !== null && !isFingerprint(sourceRouteFingerprint))
|
|
@@ -270,9 +272,9 @@ export function resolveCanonicalRouteEvolution(events, {
|
|
|
270
272
|
let currentFingerprint = sourceRouteFingerprint;
|
|
271
273
|
const reboundEvents = [];
|
|
272
274
|
for (let index = 0; index < events.length - 1; index += 1) {
|
|
273
|
-
const reboundEvent = events
|
|
275
|
+
const reboundEvent = ledgerEventAt(events, index);
|
|
274
276
|
if (reboundEvent?.seq <= sourceSeq || reboundEvent?.event !== "ROUTE_REBOUND") continue;
|
|
275
|
-
const routeCommit = events
|
|
277
|
+
const routeCommit = ledgerEventAt(events, index + 1);
|
|
276
278
|
if (!isCanonicalRouteReboundBoundary(reboundEvent, routeCommit, {
|
|
277
279
|
taskId,
|
|
278
280
|
contractFingerprint,
|
|
@@ -315,8 +317,8 @@ export function resolveCanonicalPostRepairCheckpointBinding(events, marker) {
|
|
|
315
317
|
if (checkpointEvents.length !== 1) return null;
|
|
316
318
|
const checkpointEvent = checkpointEvents[0];
|
|
317
319
|
const checkpointIndex = boundary.postRepairEvents.indexOf(checkpointEvent);
|
|
318
|
-
const phaseEvent = boundary.postRepairEvents
|
|
319
|
-
const transactionCommit = boundary.postRepairEvents
|
|
320
|
+
const phaseEvent = ledgerEventAt(boundary.postRepairEvents, checkpointIndex + 1);
|
|
321
|
+
const transactionCommit = ledgerEventAt(boundary.postRepairEvents, checkpointIndex + 2);
|
|
320
322
|
const details = checkpointEvent.details;
|
|
321
323
|
if (checkpointEvent.taskId !== boundary.marker.taskId
|
|
322
324
|
|| checkpointEvent.hash !== eventHash(checkpointEvent)
|
|
@@ -343,26 +345,25 @@ export function resolveCanonicalPostRepairCheckpointBinding(events, marker) {
|
|
|
343
345
|
}
|
|
344
346
|
|
|
345
347
|
export function isContractBootstrapRepairCandidate(events, ledgerErrors = [], taskId = null) {
|
|
346
|
-
if (!
|
|
347
|
-
const effectiveTaskId = taskId ?? events
|
|
348
|
+
if (!isLedgerEventCollection(events) || events.length < 4) return null;
|
|
349
|
+
const effectiveTaskId = taskId ?? ledgerEventAt(events, 0)?.taskId;
|
|
348
350
|
if (typeof effectiveTaskId !== "string" || !effectiveTaskId) return null;
|
|
349
351
|
if (events.some((event) => event.taskId !== effectiveTaskId)) return null;
|
|
350
|
-
if (events
|
|
351
|
-
if (events
|
|
352
|
-
|| events
|
|
352
|
+
if (ledgerTypeSummary(events, CONTRACT_BOOTSTRAP_REPAIR_EVENT).count) return null;
|
|
353
|
+
if (ledgerEventAt(events, 0)?.event !== "TASK_RECEIVED" || ledgerEventAt(events, 1)?.event !== "TRANSACTION_COMMITTED"
|
|
354
|
+
|| ledgerEventAt(events, 1)?.details?.operation !== "task-create") return null;
|
|
353
355
|
if (!events.some((event) => event.event === "DISCOVERY_STARTED")) return null;
|
|
354
|
-
const
|
|
355
|
-
if (
|
|
356
|
-
const [canonical, duplicate] = contractEvents;
|
|
356
|
+
const { count: contractCount, first: canonical, latest: duplicate } = ledgerTypeSummary(events, "CONTRACT_VALIDATED");
|
|
357
|
+
if (contractCount !== 2) return null;
|
|
357
358
|
if (!isFingerprint(canonical.details?.contractFingerprint)
|
|
358
359
|
|| canonical.details.contractFingerprint !== duplicate.details?.contractFingerprint) return null;
|
|
359
360
|
if (canonical.hash !== eventHash(canonical) || duplicate.hash !== eventHash(duplicate)) return null;
|
|
360
361
|
const canonicalIndex = events.indexOf(canonical);
|
|
361
362
|
const duplicateIndex = events.indexOf(duplicate);
|
|
362
|
-
const canonicalCommit = events
|
|
363
|
-
const duplicateCommit = events
|
|
363
|
+
const canonicalCommit = ledgerEventAt(events, canonicalIndex + 1);
|
|
364
|
+
const duplicateCommit = ledgerEventAt(events, duplicateIndex + 1);
|
|
364
365
|
if (!isCommitFor(canonicalCommit, "contract-create") || !isCommitFor(duplicateCommit, "contract-create")) return null;
|
|
365
|
-
if (duplicateIndex !== events.length - 2 || events.
|
|
366
|
+
if (duplicateIndex !== events.length - 2 || !ledgerEventAtIs(events, events.length - 1, duplicateCommit)) return null;
|
|
366
367
|
if (events.some((event) => HISTORICAL_EVENTS.has(event.event))) return null;
|
|
367
368
|
if (!Array.isArray(ledgerErrors) || ledgerErrors.length < 1) return null;
|
|
368
369
|
if (ledgerErrors.some((error) => !isExactDuplicateContractChronologyError(error))) return null;
|
|
@@ -383,7 +384,7 @@ export function isContractBootstrapRepairMarkerValid(events, marker) {
|
|
|
383
384
|
if (!resolveContractBootstrapRepairBoundary(events, marker)) return false;
|
|
384
385
|
assertContractBootstrapRepairDetails(marker.details);
|
|
385
386
|
if (marker.taskId !== marker.details.taskId || marker.hash !== eventHash(marker)) return false;
|
|
386
|
-
if (marker.seq < 1 || events
|
|
387
|
+
if (marker.seq < 1 || !ledgerEventAtIs(events, marker.seq - 1, marker)) return false;
|
|
387
388
|
const prefix = events.slice(0, marker.seq - 1);
|
|
388
389
|
const prefixErrors = [];
|
|
389
390
|
const candidate = isContractBootstrapRepairCandidate(prefix, prefixErrors, marker.taskId);
|
|
@@ -528,11 +529,11 @@ function migrationMatchesLegacyRepairBoundary({ migration, marker, repairCommit
|
|
|
528
529
|
}
|
|
529
530
|
|
|
530
531
|
function resolveContractBootstrapRepairMigration(events, migration) {
|
|
531
|
-
if (!
|
|
532
|
+
if (!isLedgerEventCollection(events) || !migration || migration.event !== CONTRACT_BOOTSTRAP_REPAIR_MIGRATION_EVENT) return null;
|
|
532
533
|
const marker = events.find((event) => event.seq === migration.details?.legacyMarkerSeq);
|
|
533
534
|
const markerIndex = marker ? events.indexOf(marker) : -1;
|
|
534
535
|
const migrationIndex = events.indexOf(migration);
|
|
535
|
-
const commit = events
|
|
536
|
+
const commit = ledgerEventAt(events, migrationIndex + 1);
|
|
536
537
|
const boundary = marker ? resolveContractBootstrapRepairBoundary(events, marker) : null;
|
|
537
538
|
if (!marker || !isLegacyContractBootstrapRepairMarkerShape(marker)
|
|
538
539
|
|| markerIndex < 0 || !boundary
|
|
@@ -545,9 +546,9 @@ function resolveContractBootstrapRepairMigration(events, migration) {
|
|
|
545
546
|
}
|
|
546
547
|
|
|
547
548
|
export function validateContractBootstrapRepairMigrations(events, errors, { allowUnmigratedLegacyContractBootstrapRepairMarkers = false } = {}) {
|
|
548
|
-
const markers = events
|
|
549
|
-
const migrations = events
|
|
550
|
-
const migrationBySeq =
|
|
549
|
+
const markers = ledgerEventsOfTypes(events, [CONTRACT_BOOTSTRAP_REPAIR_EVENT]);
|
|
550
|
+
const migrations = ledgerEventsOfTypes(events, [CONTRACT_BOOTSTRAP_REPAIR_MIGRATION_EVENT]);
|
|
551
|
+
const migrationBySeq = ledgerRelationMap();
|
|
551
552
|
for (const migration of migrations) {
|
|
552
553
|
try {
|
|
553
554
|
assertContractBootstrapRepairMigrationDetails(migration.details);
|
|
@@ -569,14 +570,15 @@ export function validateContractBootstrapRepairMigrations(events, errors, { allo
|
|
|
569
570
|
});
|
|
570
571
|
continue;
|
|
571
572
|
}
|
|
572
|
-
migrationBySeq.set(migration.details.legacyMarkerSeq, migration);
|
|
573
|
+
migrationBySeq.set(migration.details.legacyMarkerSeq, events.indexOf(migration));
|
|
573
574
|
}
|
|
574
|
-
if (
|
|
575
|
+
if (ledgerTypeSummary(events, CONTRACT_BOOTSTRAP_REPAIR_EVENT).count > 1) {
|
|
575
576
|
errors.push({ code: "E_CONTRACT_BOOTSTRAP_REPAIR_INVALID", message: "contract bootstrap repair marker must occur at most once" });
|
|
576
577
|
}
|
|
577
578
|
for (const marker of markers) {
|
|
578
579
|
if (!isLegacyContractBootstrapRepairMarkerShape(marker)) continue;
|
|
579
|
-
const
|
|
580
|
+
const migrationPosition = migrationBySeq.get(marker.seq);
|
|
581
|
+
const migration = migrationPosition === undefined ? undefined : ledgerEventAt(events, migrationPosition);
|
|
580
582
|
if (!migration) {
|
|
581
583
|
if (!allowUnmigratedLegacyContractBootstrapRepairMarkers) {
|
|
582
584
|
errors.push({
|
|
@@ -595,7 +597,8 @@ export function validateContractBootstrapRepairMigrations(events, errors, { allo
|
|
|
595
597
|
});
|
|
596
598
|
}
|
|
597
599
|
}
|
|
598
|
-
for (const [legacySeq,
|
|
600
|
+
for (const [legacySeq, position] of migrationBySeq) {
|
|
601
|
+
const migration = ledgerEventAt(events, position);
|
|
599
602
|
errors.push({
|
|
600
603
|
code: "E_EVENT_INVALID",
|
|
601
604
|
message: `migration event ${migration.seq} references unknown legacy contract bootstrap repair marker seq ${legacySeq}`,
|
|
@@ -604,24 +607,22 @@ export function validateContractBootstrapRepairMigrations(events, errors, { allo
|
|
|
604
607
|
}
|
|
605
608
|
|
|
606
609
|
export function isLegacyContractBootstrapRepairMigrationCandidate(events, taskId = null) {
|
|
607
|
-
if (!
|
|
608
|
-
const
|
|
609
|
-
if (
|
|
610
|
-
const marker = markers[0];
|
|
610
|
+
if (!isLedgerEventCollection(events)) return null;
|
|
611
|
+
const { count, first: marker } = ledgerTypeSummary(events, CONTRACT_BOOTSTRAP_REPAIR_EVENT);
|
|
612
|
+
if (count !== 1) return null;
|
|
611
613
|
if (!isLegacyContractBootstrapRepairMarkerShape(marker)) return null;
|
|
612
614
|
const effectiveTaskId = taskId ?? marker.taskId;
|
|
613
615
|
if (marker.taskId !== effectiveTaskId || events.some((event) => event.taskId !== effectiveTaskId)) return null;
|
|
614
|
-
if (events
|
|
616
|
+
if (ledgerTypeSummary(events, CONTRACT_BOOTSTRAP_REPAIR_MIGRATION_EVENT).count) return null;
|
|
615
617
|
const boundary = resolveContractBootstrapRepairBoundary(events, marker);
|
|
616
618
|
if (!boundary || boundary.postRepairEvents.length > 0) return null;
|
|
617
619
|
return { taskId: effectiveTaskId, marker, repairCommit: boundary.repairCommit };
|
|
618
620
|
}
|
|
619
621
|
|
|
620
622
|
export function resolveEffectiveContractBootstrapRepairAnchor(events) {
|
|
621
|
-
if (!
|
|
622
|
-
const
|
|
623
|
-
if (
|
|
624
|
-
const sourceMarker = markers[0];
|
|
623
|
+
if (!isLedgerEventCollection(events)) return null;
|
|
624
|
+
const { count, first: sourceMarker } = ledgerTypeSummary(events, CONTRACT_BOOTSTRAP_REPAIR_EVENT);
|
|
625
|
+
if (count !== 1) return null;
|
|
625
626
|
if (isContractBootstrapRepairMarkerValid(events, sourceMarker)) {
|
|
626
627
|
return { kind: "MODERN", sourceMarker, details: sourceMarker.details };
|
|
627
628
|
}
|
|
@@ -643,7 +644,7 @@ export function resolveEffectiveContractBootstrapRepairAnchor(events) {
|
|
|
643
644
|
}
|
|
644
645
|
|
|
645
646
|
export function repairMarkerErrors(events, errors, { allowUnmigratedLegacyContractBootstrapRepairMarkers = false } = {}) {
|
|
646
|
-
const marker = events
|
|
647
|
+
const marker = ledgerTypeSummary(events, CONTRACT_BOOTSTRAP_REPAIR_EVENT).first;
|
|
647
648
|
const boundary = resolveContractBootstrapRepairBoundary(events, marker);
|
|
648
649
|
const migratedLegacy = resolveEffectiveContractBootstrapRepairAnchor(events)?.kind === "MIGRATED_LEGACY";
|
|
649
650
|
const recognized = isContractBootstrapRepairMarkerValid(events, marker)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isLedgerEventCollection, ledgerEventAt, ledgerEventsOfTypes, ledgerTypeSummary } from "./ledger-event-collection.js";
|
|
1
2
|
import { canonicalFingerprint } from "./artifacts.js";
|
|
2
3
|
|
|
3
4
|
export const CONTRACT_REVISED_EVENT = "CONTRACT_REVISED";
|
|
@@ -87,13 +88,13 @@ function validRevisionCommit(revisionEvent, commitEvent) {
|
|
|
87
88
|
}
|
|
88
89
|
|
|
89
90
|
export function resolveContractRevisionBoundary(events, revisionEvent) {
|
|
90
|
-
if (!
|
|
91
|
+
if (!isLedgerEventCollection(events) || !revisionEvent || revisionEvent.event !== CONTRACT_REVISED_EVENT) return null;
|
|
91
92
|
const index = events.findIndex((event) => event === revisionEvent
|
|
92
93
|
|| (event?.seq === revisionEvent.seq
|
|
93
94
|
&& event?.taskId === revisionEvent.taskId
|
|
94
95
|
&& event?.event === CONTRACT_REVISED_EVENT));
|
|
95
96
|
if (index < 0) return null;
|
|
96
|
-
const commitEvent = events
|
|
97
|
+
const commitEvent = ledgerEventAt(events, index + 1);
|
|
97
98
|
return validRevisionCommit(revisionEvent, commitEvent)
|
|
98
99
|
? { revisionEvent, transactionCommitEvent: commitEvent }
|
|
99
100
|
: null;
|
|
@@ -105,7 +106,7 @@ export function resolveCanonicalContractEvolution(events, {
|
|
|
105
106
|
sourceContractFingerprint,
|
|
106
107
|
targetContractFingerprint,
|
|
107
108
|
} = {}) {
|
|
108
|
-
if (!
|
|
109
|
+
if (!isLedgerEventCollection(events)
|
|
109
110
|
|| typeof taskId !== "string" || !taskId
|
|
110
111
|
|| !Number.isInteger(sourceSeq) || sourceSeq < 0
|
|
111
112
|
|| !isFingerprint(sourceContractFingerprint)
|
|
@@ -138,12 +139,12 @@ export function resolveCanonicalContractEvolution(events, {
|
|
|
138
139
|
}
|
|
139
140
|
|
|
140
141
|
export function latestContractRevision(events = []) {
|
|
141
|
-
return events
|
|
142
|
+
return ledgerTypeSummary(events, CONTRACT_REVISED_EVENT).latest ?? null;
|
|
142
143
|
}
|
|
143
144
|
|
|
144
145
|
export function validateContractRevisionEventBindings(events = []) {
|
|
145
146
|
const errors = [];
|
|
146
|
-
const revisions = events
|
|
147
|
+
const revisions = ledgerEventsOfTypes(events, [CONTRACT_REVISED_EVENT]);
|
|
147
148
|
let currentContractFingerprint = null;
|
|
148
149
|
let currentStateRevision = null;
|
|
149
150
|
for (const event of revisions) {
|
|
@@ -184,10 +185,8 @@ export function validateContractRevisionEventBindings(events = []) {
|
|
|
184
185
|
|
|
185
186
|
export function validateContractRevisionCurrentBinding(state, events = []) {
|
|
186
187
|
const errors = [];
|
|
187
|
-
const
|
|
188
|
-
if (
|
|
189
|
-
const first = revisions[0];
|
|
190
|
-
const latest = revisions.at(-1);
|
|
188
|
+
const { count, first, latest } = ledgerTypeSummary(events, CONTRACT_REVISED_EVENT);
|
|
189
|
+
if (!count) return errors;
|
|
191
190
|
const initial = events.find((event) => event.taskId === state?.taskId && event.event === "CONTRACT_VALIDATED");
|
|
192
191
|
const evolution = resolveCanonicalContractEvolution(events, {
|
|
193
192
|
taskId: state?.taskId,
|
|
@@ -5,6 +5,7 @@ import { assertSecretFree } from "../receipt.js";
|
|
|
5
5
|
import { DECISION_AUTHORITY, DECISION_EVIDENCE_AUTHORITY, DECISION_POLICY_VERSION, PINNED_JEV_MODEL } from "./constants.js";
|
|
6
6
|
import { DECISION_ERROR_CODES, decisionError } from "./errors.js";
|
|
7
7
|
import { getTaskTransaction, withTaskTransaction } from "../transaction.js";
|
|
8
|
+
import { operationalArtifactExists } from "../../storage/operational-context.js";
|
|
8
9
|
|
|
9
10
|
export function buildDecisionArtifact(input = {}) {
|
|
10
11
|
const artifact = {
|
|
@@ -58,7 +59,7 @@ export async function writeDecisionArtifact(target, taskId, decisionId, artifact
|
|
|
58
59
|
));
|
|
59
60
|
}
|
|
60
61
|
const relativePath = taskDecisionPath(taskId, decisionId);
|
|
61
|
-
if (await fileExists(ensureWithin(target, relativePath))) {
|
|
62
|
+
if (operationalArtifactExists(target, relativePath) ?? await fileExists(ensureWithin(target, relativePath))) {
|
|
62
63
|
throw decisionError(DECISION_ERROR_CODES.IMMUTABLE, `Semantic decision artifact already exists: ${decisionId}`);
|
|
63
64
|
}
|
|
64
65
|
return writeJsonArtifact(target, relativePath, artifact, "semantic-decision", packageRoot, { ...options, taskId, operation: "semantic-decision" });
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { ledgerRelationSet } from "../ledger-relations.js";
|
|
2
|
+
import { ledgerEventsOfTypes } from "../ledger-event-collection.js";
|
|
1
3
|
import { canonicalFingerprint } from "../artifacts.js";
|
|
2
4
|
import { readDecisionArtifact } from "./artifact.js";
|
|
3
5
|
import { DECISION_KINDS, DECISION_STATUS, SEMANTIC_DECISION_RECORDED_EVENT, SEMANTIC_DECISION_SUPERSEDED_EVENT } from "./constants.js";
|
|
@@ -42,8 +44,8 @@ export function decisionSupersededEventDetails(previousArtifact, supersededBy) {
|
|
|
42
44
|
|
|
43
45
|
export function validateSemanticDecisionEventBindings(events = []) {
|
|
44
46
|
const errors = [];
|
|
45
|
-
const recorded =
|
|
46
|
-
for (const event of events) {
|
|
47
|
+
const recorded = ledgerRelationSet();
|
|
48
|
+
for (const event of ledgerEventsOfTypes(events, [SEMANTIC_DECISION_RECORDED_EVENT, SEMANTIC_DECISION_SUPERSEDED_EVENT])) {
|
|
47
49
|
if (![SEMANTIC_DECISION_RECORDED_EVENT, SEMANTIC_DECISION_SUPERSEDED_EVENT].includes(event.event)) continue;
|
|
48
50
|
try { assertSemanticDecisionDetails(event.details, event.event); } catch (error) {
|
|
49
51
|
errors.push({ code: error.code ?? DECISION_ERROR_CODES.RESULT_INVALID, message: `event ${event.seq} (${event.event}): ${error.message}` });
|
|
@@ -54,7 +56,7 @@ export function validateSemanticDecisionEventBindings(events = []) {
|
|
|
54
56
|
if (recorded.has(key)) {
|
|
55
57
|
errors.push({ code: DECISION_ERROR_CODES.RESULT_INVALID, message: `event ${event.seq} records duplicate decision ID ${event.details.decisionId}` });
|
|
56
58
|
}
|
|
57
|
-
recorded.
|
|
59
|
+
recorded.add(key);
|
|
58
60
|
}
|
|
59
61
|
if (event.event === SEMANTIC_DECISION_SUPERSEDED_EVENT) {
|
|
60
62
|
const key = `${event.taskId}:${event.details.decisionId}`;
|
|
@@ -79,12 +81,12 @@ export function validateSemanticDecisionEventBindings(events = []) {
|
|
|
79
81
|
return errors;
|
|
80
82
|
}
|
|
81
83
|
|
|
82
|
-
export async function validateSemanticDecisionArtifactBindings(target, packageRoot, events = []) {
|
|
84
|
+
export async function validateSemanticDecisionArtifactBindings(target, packageRoot, events = [], { reader = readDecisionArtifact } = {}) {
|
|
83
85
|
const errors = [];
|
|
84
|
-
for (const event of events) {
|
|
86
|
+
for (const event of ledgerEventsOfTypes(events, [SEMANTIC_DECISION_RECORDED_EVENT])) {
|
|
85
87
|
if (event.event !== SEMANTIC_DECISION_RECORDED_EVENT) continue;
|
|
86
88
|
try {
|
|
87
|
-
const artifact = (await
|
|
89
|
+
const artifact = (await reader(target, event.taskId, event.details.decisionId, packageRoot)).value;
|
|
88
90
|
if (artifact.taskId !== event.taskId
|
|
89
91
|
|| artifact.decisionId !== event.details.decisionId
|
|
90
92
|
|| canonicalFingerprint(artifact) !== event.details.artifactFingerprint
|
|
@@ -94,6 +96,7 @@ export async function validateSemanticDecisionArtifactBindings(target, packageRo
|
|
|
94
96
|
errors.push({ code: DECISION_ERROR_CODES.LEDGER_INVALID, message: `event ${event.seq} does not match its immutable semantic decision artifact` });
|
|
95
97
|
}
|
|
96
98
|
} catch (error) {
|
|
99
|
+
if (error.code === "E_STATE_REVISION_CONFLICT") throw error;
|
|
97
100
|
errors.push({ code: DECISION_ERROR_CODES.LEDGER_INVALID, message: `event ${event.seq} semantic decision artifact is unavailable or invalid` });
|
|
98
101
|
}
|
|
99
102
|
}
|
|
@@ -2,12 +2,14 @@ import { readdir } from "node:fs/promises";
|
|
|
2
2
|
|
|
3
3
|
import { canonicalFingerprint, readJsonArtifact } from "../artifacts.js";
|
|
4
4
|
import { fileExists, ensureWithin } from "../filesystem.js";
|
|
5
|
-
import {
|
|
5
|
+
import { validateEventLedger } from "../events.js";
|
|
6
6
|
import { taskDecisionDirectory } from "../task-paths.js";
|
|
7
7
|
import { getQuestionSet } from "./question-registry.js";
|
|
8
8
|
import { DECISION_ERROR_CODES, decisionError } from "./errors.js";
|
|
9
9
|
import { DECISION_ENGINE_ID, DECISION_POLICY_VERSION, PINNED_JEV_MODEL, SEMANTIC_DECISION_RECORDED_EVENT, SEMANTIC_DECISION_SUPERSEDED_EVENT } from "./constants.js";
|
|
10
10
|
import { assertDecisionFresh } from "./freshness.js";
|
|
11
|
+
import { listOperationalArtifactNames } from "../../storage/operational-context.js";
|
|
12
|
+
import { withProjectReadSnapshot } from "../../storage/project-read-snapshot.js";
|
|
11
13
|
|
|
12
14
|
function invalid(message, code = DECISION_ERROR_CODES.BINDING_INVALID) {
|
|
13
15
|
return decisionError(code, message);
|
|
@@ -89,14 +91,16 @@ export function assertRequiredFreshDecision({ artifact, currentBindings = {}, ex
|
|
|
89
91
|
|
|
90
92
|
async function decisionArtifacts(target, taskId, packageRoot) {
|
|
91
93
|
const directory = taskDecisionDirectory(taskId);
|
|
92
|
-
|
|
93
|
-
if (
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
94
|
+
let names = listOperationalArtifactNames(target, taskId, "decisions");
|
|
95
|
+
if (names === null) {
|
|
96
|
+
const absolute = ensureWithin(target, directory);
|
|
97
|
+
if (!(await fileExists(absolute))) return [];
|
|
98
|
+
names = (await readdir(absolute, { withFileTypes: true }))
|
|
99
|
+
.filter(entry => entry.isFile()).map(entry => entry.name);
|
|
100
|
+
}
|
|
101
|
+
const ids = names.filter(name => name.endsWith(".json")).map(name => name.slice(0, -5)).sort();
|
|
98
102
|
const artifacts = [];
|
|
99
|
-
for (const decisionId of
|
|
103
|
+
for (const decisionId of ids) {
|
|
100
104
|
try {
|
|
101
105
|
artifacts.push((await readJsonArtifact(target, `${directory}/${decisionId}.json`, "semantic-decision", packageRoot)).value);
|
|
102
106
|
} catch {
|
|
@@ -106,11 +110,15 @@ async function decisionArtifacts(target, taskId, packageRoot) {
|
|
|
106
110
|
return artifacts;
|
|
107
111
|
}
|
|
108
112
|
|
|
109
|
-
export async function resolveRequiredSemanticDecision(
|
|
110
|
-
if (typeof taskId !== "string" || !taskId) throw decisionError(DECISION_ERROR_CODES.REQUIRED, "A task-bound semantic decision is required.");
|
|
113
|
+
export async function resolveRequiredSemanticDecision(options = {}) {
|
|
114
|
+
if (typeof options.taskId !== "string" || !options.taskId) throw decisionError(DECISION_ERROR_CODES.REQUIRED, "A task-bound semantic decision is required.");
|
|
115
|
+
return withProjectReadSnapshot(options.target, () => resolveSelectedSemanticDecision(options));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function resolveSelectedSemanticDecision({ target, packageRoot, taskId, decisionKind, decisionId = null, currentBindings = {} }) {
|
|
111
119
|
const ledgerResult = await validateEventLedger(target, packageRoot, { taskId });
|
|
112
120
|
if (!ledgerResult.valid) throw decisionError(DECISION_ERROR_CODES.LEDGER_INVALID, "The task ledger is invalid for semantic decision resolution.", ledgerResult.errors);
|
|
113
|
-
const events =
|
|
121
|
+
const events = ledgerResult.events;
|
|
114
122
|
const candidates = decisionId
|
|
115
123
|
? [(await readJsonArtifact(target, `${taskDecisionDirectory(taskId)}/${decisionId}.json`, "semantic-decision", packageRoot)).value]
|
|
116
124
|
: await decisionArtifacts(target, taskId, packageRoot);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { canonicalFingerprint } from "../artifacts.js";
|
|
2
2
|
import { readConfig } from "../config.js";
|
|
3
|
-
import { appendProtocolEvent,
|
|
3
|
+
import { appendProtocolEvent, iterateEvents } from "../events.js";
|
|
4
4
|
import { withTaskMutation } from "../task-command.js";
|
|
5
5
|
import { DECISION_DEFAULT_POLICY, PINNED_JEV_MODEL, SEMANTIC_DECISION_RECORDED_EVENT } from "./constants.js";
|
|
6
6
|
import { buildDecisionArtifact, decisionArtifactFingerprint, readDecisionArtifact, writeDecisionArtifact } from "./artifact.js";
|
|
@@ -15,6 +15,15 @@ import { resolveRequiredSemanticDecision } from "./resolver.js";
|
|
|
15
15
|
import { DECISION_ERROR_CODES, decisionError } from "./errors.js";
|
|
16
16
|
import { getTaskTransaction } from "../transaction.js";
|
|
17
17
|
import { getTestSemanticProvider } from "./test-provider.js";
|
|
18
|
+
import { needsExistingProjectScope, withExistingProjectScope } from "../../storage/existing-project-scope.js";
|
|
19
|
+
import { withProjectReadSnapshot } from "../../storage/project-read-snapshot.js";
|
|
20
|
+
|
|
21
|
+
async function withDecisionScope(options, run) {
|
|
22
|
+
if (options.target && options.taskId && await needsExistingProjectScope(options.target)) {
|
|
23
|
+
return withExistingProjectScope(options.target, () => run(options));
|
|
24
|
+
}
|
|
25
|
+
return run(options);
|
|
26
|
+
}
|
|
18
27
|
|
|
19
28
|
function resolveProvider(provider) {
|
|
20
29
|
return provider ?? getTestSemanticProvider();
|
|
@@ -50,7 +59,7 @@ function buildArtifact({ taskId, decisionId, policy, validated, result, request,
|
|
|
50
59
|
questionSetFingerprint: validated.questionSet.fingerprint,
|
|
51
60
|
questionSet: validated.questionSet,
|
|
52
61
|
stateFingerprint: canonicalFingerprint(validated.state),
|
|
53
|
-
taskStateFingerprint: canonicalFingerprint(validated.state.lifecycle),
|
|
62
|
+
taskStateFingerprint: taskBindings.taskStateFingerprint ?? canonicalFingerprint(validated.state.lifecycle),
|
|
54
63
|
semanticStateFingerprint: canonicalFingerprint(validated.state.semantic),
|
|
55
64
|
policyVersion: policy.policyVersion,
|
|
56
65
|
policyFingerprint: decisionPolicyFingerprint(policy),
|
|
@@ -77,7 +86,7 @@ async function readCachedSemanticDecision({ policy, decisionId, target, taskId,
|
|
|
77
86
|
currentBindings: {
|
|
78
87
|
...taskBindings,
|
|
79
88
|
stateFingerprint: canonicalFingerprint(validated.state),
|
|
80
|
-
taskStateFingerprint: canonicalFingerprint(validated.state.lifecycle),
|
|
89
|
+
taskStateFingerprint: taskBindings.taskStateFingerprint ?? canonicalFingerprint(validated.state.lifecycle),
|
|
81
90
|
semanticStateFingerprint: canonicalFingerprint(validated.state.semantic),
|
|
82
91
|
questionSetFingerprint: validated.questionSet.fingerprint,
|
|
83
92
|
policyFingerprint: decisionPolicyFingerprint(policy),
|
|
@@ -90,13 +99,15 @@ async function readCachedSemanticDecision({ policy, decisionId, target, taskId,
|
|
|
90
99
|
|
|
91
100
|
async function persistDecision(target, packageRoot, resolvedDecisionId, artifact, ctx) {
|
|
92
101
|
await writeDecisionArtifact(target, ctx.taskId, resolvedDecisionId, artifact, packageRoot, { taskId: ctx.taskId, operation: "semantic-decision" });
|
|
93
|
-
const
|
|
102
|
+
const priorRecords = [];
|
|
103
|
+
const supersededIds = new Set();
|
|
104
|
+
for await (const candidate of iterateEvents(target, packageRoot, { taskId: ctx.taskId })) {
|
|
105
|
+
if (candidate.event === SEMANTIC_DECISION_RECORDED_EVENT && candidate.taskId === ctx.taskId
|
|
106
|
+
&& candidate.details?.decisionKind === artifact.decisionKind) priorRecords.push(candidate);
|
|
107
|
+
if (candidate.event === "SEMANTIC_DECISION_SUPERSEDED") supersededIds.add(candidate.details?.decisionId);
|
|
108
|
+
}
|
|
94
109
|
const event = await appendProtocolEvent(target, { taskId: ctx.taskId, event: SEMANTIC_DECISION_RECORDED_EVENT, details: decisionEventDetails(artifact) }, packageRoot, { taskId: ctx.taskId });
|
|
95
|
-
const previous =
|
|
96
|
-
&& candidate.taskId === ctx.taskId
|
|
97
|
-
&& candidate.details?.decisionKind === artifact.decisionKind
|
|
98
|
-
&& !priorEvents.some((superseded) => superseded.event === "SEMANTIC_DECISION_SUPERSEDED"
|
|
99
|
-
&& superseded.details?.decisionId === candidate.details?.decisionId));
|
|
110
|
+
const previous = priorRecords.findLast(candidate => !supersededIds.has(candidate.details?.decisionId));
|
|
100
111
|
if (!previous) return { artifact, artifactFingerprint: decisionArtifactFingerprint(artifact), event, supersededEvent: null };
|
|
101
112
|
const previousArtifact = await readDecisionArtifact(target, ctx.taskId, previous.details.decisionId, packageRoot);
|
|
102
113
|
const supersededEvent = await appendProtocolEvent(target, {
|
|
@@ -107,16 +118,23 @@ async function persistDecision(target, packageRoot, resolvedDecisionId, artifact
|
|
|
107
118
|
return { artifact, artifactFingerprint: decisionArtifactFingerprint(artifact), event, supersededEvent };
|
|
108
119
|
}
|
|
109
120
|
|
|
110
|
-
export async function recordSemanticDecision(
|
|
121
|
+
export async function recordSemanticDecision(options = {}) {
|
|
122
|
+
return withDecisionScope(options, recordSelectedSemanticDecision);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function recordSelectedSemanticDecision({ target, packageRoot, taskId, decisionId, request, provider = null }) {
|
|
111
126
|
const resolvedDecisionId = decisionId ?? `${String(request?.decisionKind ?? "decision").toLowerCase()}-${Date.now()}-${randomUUID().slice(0, 8)}`;
|
|
112
|
-
const policy = await
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
127
|
+
const { policy, taskBindings, validated, cached } = await withProjectReadSnapshot(target, async () => {
|
|
128
|
+
const policy = await loadPolicy(target, packageRoot);
|
|
129
|
+
const taskBindings = target && taskId ? await readCurrentDecisionBindings(target, packageRoot, taskId) : {};
|
|
130
|
+
const validated = validateDecisionRequest({
|
|
131
|
+
...request,
|
|
132
|
+
taskId,
|
|
133
|
+
state: { lifecycle: taskBindings.state ?? {}, semantic: request?.state ?? {} },
|
|
134
|
+
});
|
|
135
|
+
const cached = await readCachedSemanticDecision({ policy, decisionId, target, taskId, packageRoot, validated, taskBindings, request });
|
|
136
|
+
return { policy, taskBindings, validated, cached };
|
|
118
137
|
});
|
|
119
|
-
const cached = await readCachedSemanticDecision({ policy, decisionId, target, taskId, packageRoot, validated, taskBindings, request });
|
|
120
138
|
if (cached) return { artifact: cached, artifactFingerprint: decisionArtifactFingerprint(cached), event: null, supersededEvent: null, cached: true, policy };
|
|
121
139
|
const engine = resolveProvider(provider) ?? createTypesafeEngine({ policy });
|
|
122
140
|
if (provider && (provider.id !== "typesafe-jev" || provider.model !== PINNED_JEV_MODEL || typeof provider.evaluate !== "function")) {
|
|
@@ -128,27 +146,34 @@ export async function recordSemanticDecision({ target, packageRoot, taskId, deci
|
|
|
128
146
|
return { ...persisted, policy };
|
|
129
147
|
}
|
|
130
148
|
|
|
131
|
-
export async function ensureSemanticDecision(
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
149
|
+
export async function ensureSemanticDecision(options = {}) {
|
|
150
|
+
return withDecisionScope(options, ensureSelectedSemanticDecision);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function ensureSelectedSemanticDecision({ target, packageRoot, taskId, decisionId, request, provider = null, allowNetwork = true }) {
|
|
154
|
+
const { policy, validated, cached } = await withProjectReadSnapshot(target, async () => {
|
|
155
|
+
const policy = await loadPolicy(target, packageRoot);
|
|
156
|
+
const taskBindings = await readCurrentDecisionBindings(target, packageRoot, taskId);
|
|
157
|
+
const validated = validateDecisionRequest({
|
|
158
|
+
...request,
|
|
159
|
+
taskId,
|
|
160
|
+
state: { lifecycle: taskBindings.state ?? {}, semantic: request?.state ?? {} },
|
|
161
|
+
});
|
|
162
|
+
const currentBindings = {
|
|
163
|
+
...taskBindings,
|
|
164
|
+
stateFingerprint: canonicalFingerprint(validated.state),
|
|
165
|
+
taskStateFingerprint: taskBindings.taskStateFingerprint ?? canonicalFingerprint(validated.state.lifecycle),
|
|
166
|
+
semanticStateFingerprint: canonicalFingerprint(validated.state.semantic),
|
|
167
|
+
questionSetFingerprint: validated.questionSet.fingerprint,
|
|
168
|
+
policyFingerprint: decisionPolicyFingerprint(policy),
|
|
169
|
+
model: PINNED_JEV_MODEL,
|
|
170
|
+
...(request?.candidateSetFingerprint !== undefined ? { candidateSetFingerprint: request.candidateSetFingerprint } : {}),
|
|
171
|
+
};
|
|
172
|
+
const cached = await getTaskTransaction(target) ? null : await readCachedDecision({
|
|
173
|
+
target, packageRoot, taskId, decisionKind: validated.decisionKind,
|
|
174
|
+
currentBindings,
|
|
175
|
+
});
|
|
176
|
+
return { policy, validated, cached };
|
|
152
177
|
});
|
|
153
178
|
if (cached) return { artifact: cached, artifactFingerprint: decisionArtifactFingerprint(cached), cached: true, policy };
|
|
154
179
|
if (!allowNetwork && !provider) throw decisionError(DECISION_ERROR_CODES.REQUIRED, `A current ${validated.decisionKind} decision is required.`);
|
|
@@ -2,6 +2,7 @@ import { canonicalFingerprint } from "../artifacts.js";
|
|
|
2
2
|
import { readContract } from "../contract.js";
|
|
3
3
|
import { readPersistedRoute } from "../route-artifact.js";
|
|
4
4
|
import { readWorkState } from "../work-state.js";
|
|
5
|
+
import { withProjectReadSnapshot } from "../../storage/project-read-snapshot.js";
|
|
5
6
|
|
|
6
7
|
async function readOptionalArtifact(reader) {
|
|
7
8
|
try {
|
|
@@ -12,7 +13,11 @@ async function readOptionalArtifact(reader) {
|
|
|
12
13
|
}
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
export async function readCurrentDecisionBindings(target, packageRoot, taskId,
|
|
16
|
+
export async function readCurrentDecisionBindings(target, packageRoot, taskId, options = {}) {
|
|
17
|
+
return withProjectReadSnapshot(target, () => readSnapshotBindings(target, packageRoot, taskId, options));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function readSnapshotBindings(target, packageRoot, taskId, { candidateSetFingerprint, policyFingerprint }) {
|
|
16
21
|
const state = await readWorkState(target, { packageRoot, taskId });
|
|
17
22
|
const contract = await readOptionalArtifact(() => readContract(target, packageRoot, { taskId }));
|
|
18
23
|
const route = await readOptionalArtifact(() => readPersistedRoute(target, packageRoot, { taskId }));
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { ledgerEventsOfTypes } from "./ledger-event-collection.js";
|
|
2
|
+
|
|
1
3
|
import { canonicalFingerprint } from "./artifacts.js";
|
|
2
4
|
import { assertFailureClass } from "./protocol.js";
|
|
3
5
|
|
|
@@ -212,3 +214,12 @@ export function currentCycleDiagnosis(events, taskId, verificationCycle) {
|
|
|
212
214
|
}
|
|
213
215
|
return null;
|
|
214
216
|
}
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
export function latestDiagnosisForTask(events, taskId) {
|
|
220
|
+
let latest = null;
|
|
221
|
+
for (const event of ledgerEventsOfTypes(events ?? [], ["DIAGNOSIS_RECORDED"])) {
|
|
222
|
+
if (!taskId || event.taskId === taskId) latest = event;
|
|
223
|
+
}
|
|
224
|
+
return latest;
|
|
225
|
+
}
|
package/src/core/diagnosis.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { appendProtocolEvent, validateEventLedger } from "./events.js";
|
|
2
2
|
import { readWorkState, mutateWorkState } from "./work-state.js";
|
|
3
|
+
import { getTaskTransaction, withTaskTransaction } from "./transaction.js";
|
|
3
4
|
import {
|
|
4
5
|
DIAGNOSIS_INFORMATION_GAIN,
|
|
5
6
|
assertDiagnosisDetails,
|
|
@@ -22,7 +23,15 @@ export {
|
|
|
22
23
|
normalizeDiagnosisText,
|
|
23
24
|
};
|
|
24
25
|
|
|
25
|
-
export async function recordDiagnosis({
|
|
26
|
+
export async function recordDiagnosis(options) {
|
|
27
|
+
if (await getTaskTransaction(options.target)) return recordDiagnosisInTransaction(options);
|
|
28
|
+
const state = await readWorkState(options.target, { packageRoot: options.packageRoot, taskId: options.taskId, statePath: options.statePath });
|
|
29
|
+
if (!state) return recordDiagnosisInTransaction(options);
|
|
30
|
+
return withTaskTransaction({ target: options.target, taskId: options.taskId ?? state.taskId,
|
|
31
|
+
packageRoot: options.packageRoot, operation: "record-diagnosis" }, () => recordDiagnosisInTransaction(options));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function recordDiagnosisInTransaction({
|
|
26
35
|
target,
|
|
27
36
|
packageRoot,
|
|
28
37
|
hypothesis,
|