@cassiomc1/forgeloop 1.13.0 → 2.0.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENT_COMPATIBILITY.md +8 -0
- package/CONTRIBUTING.md +11 -7
- package/DOCS_INDEX.md +37 -4
- package/ENG/c-development-eng.md +2 -2
- package/ENG/cpp-development-eng.md +2 -2
- package/ENG/dotnet-aspnetcore-development-eng.md +7 -1
- package/ENG/flutter-development-eng.md +4 -2
- package/ENG/go-development-eng.md +2 -2
- package/ENG/java-development-eng.md +3 -3
- package/ENG/nodejs-backend-development-eng.md +10 -6
- package/ENG/php-development-eng.md +3 -3
- package/ENG/python-development-eng.md +205 -0
- package/ENG/rust-development-eng.md +42 -1
- package/ENG/sec-code-eng.md +7 -7
- package/ENG/sql-development-eng.md +4 -4
- package/ENG/swift-development-eng.md +5 -4
- package/ENG/test-code-eng.md +1 -1
- package/ENG/typescript-development-eng.md +4 -4
- package/EXECUTION_STATE.md +12 -0
- package/GUIDE_ROUTER.md +6 -0
- package/LICENSE +133 -21
- package/LOOP_ENGINEERING.md +28 -2
- package/LOOP_SYSTEM_DESIGN.md +10 -10
- package/ORCHESTRATOR_INTEGRATION.md +9 -5
- package/PROTOCOL_INTEGRATION.md +55 -2
- package/README.md +63 -59
- package/TERMINOLOGY.md +2 -0
- package/THIRD_PARTY_NOTICES.md +3 -4
- package/THREAT_MODEL.md +140 -1
- package/completions/_forgeloop +29 -3
- package/completions/forgeloop.bash +55 -3
- package/completions/forgeloop.fish +183 -1
- package/docs/ADVISORY_CONTEXT.md +25 -0
- package/docs/AGENT_BROWSER_ADAPTER.md +81 -0
- package/docs/AGENT_BROWSER_VERIFICATION.md +6 -0
- package/docs/AGENT_PROTOCOL_SUMMARY.md +74 -39
- package/docs/AGENT_SKILL.md +66 -0
- package/docs/ARTIFACT_REFERENCE.md +171 -35
- package/docs/AUDIT_UX.md +46 -0
- package/docs/BROWSER_VERIFICATION.md +136 -0
- package/docs/CLI_REFERENCE.md +512 -8
- package/docs/CODE_ATTESTATION.md +2 -2
- package/docs/DOCUMENTATION_GUIDE.md +34 -11
- package/docs/GETTING_STARTED.md +3 -3
- package/docs/JEV_BENCHMARKS.md +31 -0
- package/docs/MODEL_ROUTING.md +37 -0
- package/docs/OPENSRC_ADAPTER.md +241 -0
- package/docs/PACKAGE_CONTENTS.md +46 -18
- package/docs/PROVIDERS.md +126 -0
- package/docs/PROVIDER_ARCHITECTURE.md +199 -0
- package/docs/RECIPES.md +9 -0
- package/docs/RELEASE_CHECKLIST.md +116 -12
- package/docs/SECURITY_REVIEW.md +71 -0
- package/docs/SELF_HOSTED_TESTS.md +121 -0
- package/docs/SEMANTIC_DECISION_PLANE.md +71 -0
- package/docs/TEST_INTELLIGENCE.md +29 -0
- package/docs/TEST_PRUNING.md +14 -0
- package/docs/TROUBLESHOOTING.md +198 -1
- package/docs/UNIVERSAL_INTEGRATION.md +31 -0
- package/docs/assets/diagrams/forgeloop-code-attestation-flow.html +2 -2
- package/docs/assets/diagrams/forgeloop-code-attestation-flow.receipt.json +5 -5
- package/docs/assets/diagrams/forgeloop-code-attestation-flow.svg +1 -1
- package/docs/assets/diagrams/forgeloop-engineering-flow.html +39 -26
- package/docs/assets/diagrams/forgeloop-engineering-flow.receipt.json +6 -6
- package/docs/assets/diagrams/forgeloop-engineering-flow.svg +26 -26
- package/docs/assets/diagrams/forgeloop-verification-trust-flow.html +2 -1
- package/docs/assets/diagrams/forgeloop-verification-trust-flow.receipt.json +5 -5
- package/docs/assets/diagrams/forgeloop-verification-trust-flow.svg +1 -1
- package/docs/diagrams/README.md +13 -9
- package/docs/diagrams/forgeloop-code-attestation-flow.workflow.json +1 -1
- package/docs/diagrams/forgeloop-engineering-flow.workflow.json +24 -19
- package/docs/diagrams/forgeloop-verification-trust-flow.workflow.json +1 -0
- package/docs/diagrams/reviews/forgeloop-code-attestation-flow.review.json +4 -4
- package/docs/diagrams/reviews/forgeloop-engineering-flow.review.json +4 -4
- package/docs/diagrams/reviews/forgeloop-verification-trust-flow.review.json +4 -4
- package/docs/documentation-manifest.json +774 -16
- package/docs/protocol-requirements.json +24 -0
- package/package.json +43 -11
- package/schemas/config.schema.json +14 -0
- package/schemas/context-plan.schema.json +18 -0
- package/schemas/semantic-decision.schema.json +46 -0
- package/schemas/task-bundle.schema.json +7 -2
- package/schemas/test-utility.schema.json +44 -0
- package/scripts/CI_VALIDATORS.md +6 -6
- package/scripts/benchmark-jev.mjs +5 -0
- package/scripts/benchmark-test-intelligence.mjs +4 -0
- package/scripts/generate-agent-protocol-summary.mjs +6 -3
- package/scripts/generate-forgeloop-skill.mjs +133 -0
- package/scripts/jev-smoke.mjs +19 -0
- package/scripts/run-mcp-clean-tests.mjs +30 -0
- package/skills/forgeloop/README.md +9 -0
- package/skills/forgeloop/SKILL.md +77 -0
- package/skills/forgeloop/references/lifecycle.md +9 -0
- package/skills/forgeloop/references/recovery.md +7 -0
- package/skills/forgeloop/references/verification.md +7 -0
- package/src/adapters/agent-browser/assertions.js +47 -0
- package/src/adapters/agent-browser/commands.js +54 -0
- package/src/adapters/agent-browser/index.js +3 -0
- package/src/adapters/agent-browser/locator.js +40 -0
- package/src/adapters/agent-browser/process.js +215 -0
- package/src/adapters/agent-browser/provider.js +316 -0
- package/src/adapters/emulated-services/constants.js +24 -0
- package/src/adapters/emulated-services/index.js +7 -0
- package/src/adapters/emulated-services/process.js +162 -0
- package/src/adapters/emulated-services/provider.js +289 -0
- package/src/adapters/opensrc/normalize.js +90 -0
- package/src/adapters/opensrc/process.js +248 -0
- package/src/adapters/opensrc/provider.js +338 -0
- package/src/adapters/opensrc/search.js +264 -0
- package/src/adapters/temporary-paths.js +18 -0
- package/src/adapters/typesafe/client.js +28 -0
- package/src/adapters/typesafe/engine.js +63 -0
- package/src/adapters/typesafe/normalize.js +41 -0
- package/src/cli.js +218 -174
- package/src/commands/action-show.js +5 -2
- package/src/commands/advance.js +5 -1
- package/src/commands/baseline.js +13 -11
- package/src/commands/checkpoint-revalidate.js +192 -0
- package/src/commands/complete.js +19 -3
- package/src/commands/context-plan.js +38 -0
- package/src/commands/contract-create.js +266 -0
- package/src/commands/contract-revise.js +236 -0
- package/src/commands/decision-show.js +14 -0
- package/src/commands/decision-status.js +22 -0
- package/src/commands/discover.js +41 -0
- package/src/commands/doctor.js +51 -13
- package/src/commands/gate-record.js +194 -0
- package/src/commands/gate-revalidate.js +135 -0
- package/src/commands/handoff-list.js +6 -1
- package/src/commands/handoff-show.js +6 -1
- package/src/commands/migrate-protocol.js +2 -2
- package/src/commands/model-route.js +40 -0
- package/src/commands/next.js +7 -1
- package/src/commands/policy-diff.js +25 -4
- package/src/commands/progress.js +17 -11
- package/src/commands/record-diagnosis.js +3 -0
- package/src/commands/route.js +146 -18
- package/src/commands/semantic-plan.js +41 -0
- package/src/commands/status.js +6 -1
- package/src/commands/storage-backup.js +15 -0
- package/src/commands/storage-migrate.js +5 -0
- package/src/commands/storage-migration-resume.js +5 -0
- package/src/commands/storage-migration-status.js +44 -0
- package/src/commands/storage-restore-resume.js +7 -0
- package/src/commands/storage-restore.js +8 -0
- package/src/commands/task-abandon.js +224 -0
- package/src/commands/task-list.js +8 -43
- package/src/commands/task-migrate-contract-bootstrap-repair.js +288 -0
- package/src/commands/task-migrate.js +3 -3
- package/src/commands/task-recover.js +10 -2
- package/src/commands/task-repair-contract-bootstrap.js +263 -0
- package/src/commands/task-scope.js +18 -2
- package/src/commands/task-show.js +16 -2
- package/src/commands/test-inventory.js +5 -0
- package/src/commands/test-prune-plan.js +5 -0
- package/src/commands/test-prune-probe.js +5 -0
- package/src/commands/test-utility.js +5 -0
- package/src/commands/validate-protocol.js +39 -15
- package/src/commands/validate-receipt.js +8 -1
- package/src/config/guides.json +4 -0
- package/src/core/action-ledger-projection.js +10 -3
- package/src/core/actions.js +38 -75
- package/src/core/activation.js +11 -3
- package/src/core/approvals.js +17 -51
- package/src/core/artifact-registry.js +83 -2
- package/src/core/artifacts.js +81 -27
- package/src/core/attachment-paths.js +45 -0
- package/src/core/attestation-coverage.js +6 -1
- package/src/core/attestation-verifier.js +21 -2
- package/src/core/attestation.js +17 -6
- package/src/core/audit-ux.js +514 -0
- package/src/core/audit.js +6 -1
- package/src/core/browser-verification/constants.js +149 -0
- package/src/core/browser-verification/normalize.js +254 -0
- package/src/core/browser-verification/provider.js +519 -0
- package/src/core/browser-verification/service.js +115 -0
- package/src/core/bundles.js +160 -66
- package/src/core/checkpoint-revalidation.js +321 -0
- package/src/core/cli-command-definitions.js +350 -7
- package/src/core/code-manifest.js +9 -3
- package/src/core/command-executors.js +274 -108
- package/src/core/command-input.js +136 -44
- package/src/core/completion-artifacts.js +85 -34
- package/src/core/completion-ownership.js +19 -8
- package/src/core/completion-recovery-rebind.js +11 -1
- package/src/core/completion.js +64 -15
- package/src/core/config.js +3 -0
- package/src/core/conformance.js +2 -1
- package/src/core/context-compiler/budget.js +9 -0
- package/src/core/context-compiler/candidates.js +39 -0
- package/src/core/context-compiler/compiler.js +63 -0
- package/src/core/context-compiler/fingerprint.js +11 -0
- package/src/core/context-compiler/policy.js +13 -0
- package/src/core/context-compiler/result.js +23 -0
- package/src/core/continuity-lint.js +13 -2
- package/src/core/continuity-reconciliation.js +16 -3
- package/src/core/continuity.js +16 -8
- package/src/core/contract-bootstrap-recovery.js +656 -0
- package/src/core/contract-revision.js +209 -0
- package/src/core/decision/artifact.js +70 -0
- package/src/core/decision/benchmarks.js +103 -0
- package/src/core/decision/cache.js +27 -0
- package/src/core/decision/constants.js +58 -0
- package/src/core/decision/cutover.js +34 -0
- package/src/core/decision/engine.js +22 -0
- package/src/core/decision/errors.js +68 -0
- package/src/core/decision/events.js +104 -0
- package/src/core/decision/freshness.js +19 -0
- package/src/core/decision/normalizers/index.js +115 -0
- package/src/core/decision/policy.js +18 -0
- package/src/core/decision/projection.js +16 -0
- package/src/core/decision/question-registry.js +201 -0
- package/src/core/decision/request.js +26 -0
- package/src/core/decision/resolver.js +138 -0
- package/src/core/decision/result.js +58 -0
- package/src/core/decision/service.js +181 -0
- package/src/core/decision/state-builder.js +65 -0
- package/src/core/decision/task-bindings.js +35 -0
- package/src/core/decision/test-provider.js +32 -0
- package/src/core/decision/thresholds.js +15 -0
- package/src/core/diagnosis-model.js +11 -0
- package/src/core/diagnosis.js +10 -1
- package/src/core/diagnostic-projection.js +29 -12
- package/src/core/diagnostic-record.js +28 -3
- package/src/core/efficiency.js +6 -1
- package/src/core/error-codes.js +278 -0
- package/src/core/events.js +535 -147
- package/src/core/evidence-readiness.js +9 -0
- package/src/core/execution-prerequisites.js +36 -13
- package/src/core/execution-profile.js +63 -38
- package/src/core/execution.js +26 -40
- package/src/core/filesystem.js +11 -0
- package/src/core/gate-artifact.js +105 -19
- package/src/core/gate-provenance.js +125 -0
- package/src/core/handoff.js +26 -14
- package/src/core/history.js +39 -35
- package/src/core/hypothesis-projection.js +32 -0
- package/src/core/information-gain-projection.js +104 -100
- package/src/core/inspect.js +12 -3
- package/src/core/integration-invocation-policy.js +37 -4
- package/src/core/integration-resources.js +119 -87
- package/src/core/ledger-event-collection.js +189 -0
- package/src/core/ledger-relations.js +23 -0
- package/src/core/model-router/constants.js +10 -0
- package/src/core/model-router/policy.js +103 -0
- package/src/core/model-router/router.js +37 -0
- package/src/core/native-storage.js +12 -0
- package/src/core/next-action-continuity.js +31 -9
- package/src/core/next-action-model.js +58 -0
- package/src/core/next-action-phases.js +131 -43
- package/src/core/next-action-refresh.js +43 -9
- package/src/core/next-action-review-phase.js +9 -4
- package/src/core/next-action-verification-phase.js +2 -1
- package/src/core/next-action.js +56 -9
- package/src/core/phase.js +266 -103
- package/src/core/policy-engine.js +12 -13
- package/src/core/preflight-consistency.js +89 -62
- package/src/core/preflight-loaders.js +48 -9
- package/src/core/preflight-model.js +6 -1
- package/src/core/preflight.js +29 -12
- package/src/core/progress.js +21 -10
- package/src/core/protocol-info.js +68 -0
- package/src/core/protocol-migration.js +7 -6
- package/src/core/protocol.js +20 -0
- package/src/core/reconcile-closure.js +281 -67
- package/src/core/recovery-history.js +67 -47
- package/src/core/reflection.js +72 -77
- package/src/core/responsibility.js +25 -13
- package/src/core/resumability.js +154 -44
- package/src/core/route-artifact.js +28 -4
- package/src/core/router.js +67 -1
- package/src/core/runtime-context.js +123 -61
- package/src/core/schema-validation.js +11 -3
- package/src/core/security-review/constants.js +64 -0
- package/src/core/security-review/normalize.js +245 -0
- package/src/core/security-review/provider.js +204 -0
- package/src/core/security-review/service.js +134 -0
- package/src/core/semantic-planning/constants.js +19 -0
- package/src/core/semantic-planning/projection.js +94 -0
- package/src/core/semantic-planning/service.js +15 -0
- package/src/core/settlement.js +12 -1
- package/src/core/signing/bundle-file.js +53 -0
- package/src/core/signing/bundle-reference.js +15 -0
- package/src/core/signing/sigstore.js +60 -33
- package/src/core/signing/statement-file.js +27 -0
- package/src/core/sources.js +37 -0
- package/src/core/storage-runtime-registry.js +41 -0
- package/src/core/structural-quality/artifacts.js +20 -14
- package/src/core/structural-quality/service.js +81 -56
- package/src/core/target-layout.js +1 -1
- package/src/core/task-claim-state.js +249 -15
- package/src/core/task-command.js +21 -1
- package/src/core/task-conflict-inspection.js +133 -102
- package/src/core/task-descriptor.js +29 -22
- package/src/core/task-discovery.js +288 -90
- package/src/core/task-lock.js +66 -306
- package/src/core/task-migration-validation.js +5 -5
- package/src/core/task-migration.js +17 -287
- package/src/core/task-paths.js +13 -0
- package/src/core/task-recovery.js +1 -0
- package/src/core/task-snapshot.js +51 -8
- package/src/core/task-storage-migration.js +18 -0
- package/src/core/templates.js +23 -8
- package/src/core/test-intelligence/benchmarks.js +68 -0
- package/src/core/test-intelligence/inventory.js +73 -0
- package/src/core/test-intelligence/prune.js +94 -0
- package/src/core/test-intelligence/semantic-state.js +15 -0
- package/src/core/test-intelligence/service.js +40 -0
- package/src/core/test-intelligence/utility.js +50 -0
- package/src/core/trace.js +164 -105
- package/src/core/trajectory-evaluation.js +15 -3
- package/src/core/trajectory-metrics.js +47 -32
- package/src/core/transaction.js +16 -287
- package/src/core/verification-scope.js +20 -12
- package/src/core/work-state.js +100 -59
- package/src/integration.d.ts +492 -0
- package/src/integration.js +54 -0
- package/src/providers/README.md +47 -0
- package/src/providers/capabilities.js +46 -0
- package/src/providers/errors.js +15 -0
- package/src/providers/index.js +29 -0
- package/src/providers/json-snapshot.js +105 -0
- package/src/providers/registry.js +152 -0
- package/src/repository-index/binary-manager.js +7 -5
- package/src/storage/artifact-bytes.js +22 -0
- package/src/storage/attachment-binding.js +11 -0
- package/src/storage/attachment-files.js +82 -0
- package/src/storage/attachment-references.js +36 -0
- package/src/storage/backup.js +53 -0
- package/src/storage/capabilities.js +31 -0
- package/src/storage/connection-owner.js +119 -0
- package/src/storage/connection.js +376 -0
- package/src/storage/existing-project-scope.js +45 -0
- package/src/storage/export-destination.js +44 -0
- package/src/storage/exporter.js +284 -0
- package/src/storage/file-durability.js +46 -0
- package/src/storage/importer.js +461 -0
- package/src/storage/index.js +74 -0
- package/src/storage/leases.js +63 -0
- package/src/storage/ledger-event-snapshot.js +150 -0
- package/src/storage/ledger-relations.js +220 -0
- package/src/storage/legacy-maintenance-boundary.js +18 -0
- package/src/storage/legacy-signature.js +115 -0
- package/src/storage/maintenance-handoff.js +129 -0
- package/src/storage/maintenance-owner.js +42 -0
- package/src/storage/maintenance.js +310 -0
- package/src/storage/metadata-json.js +35 -0
- package/src/storage/migration-activate.js +59 -0
- package/src/storage/migration-archive.js +57 -0
- package/src/storage/migration-attachment-root.js +61 -0
- package/src/storage/migration-candidate.js +398 -0
- package/src/storage/migration-publication.js +143 -0
- package/src/storage/migration-rollback-admission.js +54 -0
- package/src/storage/migration-rollback-preparation.js +132 -0
- package/src/storage/migration-rollback-publication.js +183 -0
- package/src/storage/migration-rollback-release.js +66 -0
- package/src/storage/migration-rollback-source-stage.js +103 -0
- package/src/storage/migration-rollback.js +43 -0
- package/src/storage/migration-source-partition.js +56 -0
- package/src/storage/migration-source.js +255 -0
- package/src/storage/migration-terminal.js +39 -0
- package/src/storage/migration-validation.js +75 -0
- package/src/storage/migration.js +43 -0
- package/src/storage/operational-attachments.js +49 -0
- package/src/storage/operational-context.js +50 -0
- package/src/storage/project-backup.js +98 -0
- package/src/storage/project-bootstrap.js +24 -0
- package/src/storage/project-boundary.js +79 -0
- package/src/storage/project-read-snapshot.js +18 -0
- package/src/storage/project-replacement.js +137 -0
- package/src/storage/project-restore.js +203 -0
- package/src/storage/record-definitions.js +45 -0
- package/src/storage/repository.js +507 -0
- package/src/storage/restore-journal.js +62 -0
- package/src/storage/restore-layout.js +30 -0
- package/src/storage/restore-preparation.js +84 -0
- package/src/storage/restore-replacement-archive.js +162 -0
- package/src/storage/restore-replacement-owner.js +31 -0
- package/src/storage/restore-replacement-preparation.js +128 -0
- package/src/storage/restore-replacement.js +142 -0
- package/src/storage/restore-terminal.js +32 -0
- package/src/storage/runtime.js +31 -0
- package/src/storage/schema.js +214 -0
- package/src/storage/singleton-import.js +84 -0
- package/src/storage/snapshot-continuity.js +16 -0
- package/src/storage/snapshot.js +36 -0
- package/src/storage/storage-marker.js +79 -0
- package/src/storage/task-guards.js +107 -0
- package/src/storage/topology.js +42 -0
- package/src/storage/transaction.js +179 -0
- package/src/storage/unit-of-work.js +663 -0
- package/src/storage/windows-process-incarnation.js +206 -0
- package/.forgeloop/forgeloop.gitignore +0 -4
- package/LICENSE-DOCS.md +0 -13
- package/src/core/transaction-maintenance.js +0 -70
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { E_PROTOCOL_MIGRATION_TARGET_UNSUPPORTED } from "./error-codes.js";
|
|
2
2
|
import { PROTOCOL_VERSION } from "./protocol.js";
|
|
3
|
-
import { detectLegacySingletonLayout
|
|
3
|
+
import { detectLegacySingletonLayout } from "./task-migration.js";
|
|
4
|
+
import { migrateLegacyTaskStorage } from "./task-storage-migration.js";
|
|
4
5
|
import { getPackageRoot } from "./templates.js";
|
|
5
6
|
|
|
6
|
-
function
|
|
7
|
+
export function assertSupportedProtocolMigrationTarget(to) {
|
|
7
8
|
if (to === undefined || to === null || to === "") {
|
|
8
9
|
const error = new Error("migrate-protocol requires --to <protocolVersion>");
|
|
9
10
|
error.code = E_PROTOCOL_MIGRATION_TARGET_UNSUPPORTED;
|
|
@@ -26,9 +27,9 @@ function assertSupportedTarget(to) {
|
|
|
26
27
|
*/
|
|
27
28
|
export async function migrateProtocol(
|
|
28
29
|
target,
|
|
29
|
-
{ to, dryRun = false, packageRoot = getPackageRoot() } = {},
|
|
30
|
+
{ to, dryRun = false, packageRoot = getPackageRoot(), destination, writersQuiesced = false } = {},
|
|
30
31
|
) {
|
|
31
|
-
|
|
32
|
+
assertSupportedProtocolMigrationTarget(to);
|
|
32
33
|
const legacy = await detectLegacySingletonLayout(target);
|
|
33
34
|
|
|
34
35
|
if (!legacy.hasLegacy) {
|
|
@@ -43,7 +44,7 @@ export async function migrateProtocol(
|
|
|
43
44
|
};
|
|
44
45
|
}
|
|
45
46
|
|
|
46
|
-
const result = await
|
|
47
|
+
const result = await migrateLegacyTaskStorage(target, { dryRun, packageRoot, destination, writersQuiesced });
|
|
47
48
|
return {
|
|
48
49
|
...result,
|
|
49
50
|
fromProtocol: PROTOCOL_VERSION,
|
|
@@ -52,7 +53,7 @@ export async function migrateProtocol(
|
|
|
52
53
|
actions: [{
|
|
53
54
|
kind: "LEGACY_LAYOUT_MIGRATION",
|
|
54
55
|
command: "task-migrate",
|
|
55
|
-
receipt: "
|
|
56
|
+
receipt: result.migrationReceipt ?? "<destination>/publication/publication-journal.json",
|
|
56
57
|
artifacts: legacy.legacyFiles.map((item) => item.path),
|
|
57
58
|
}],
|
|
58
59
|
};
|
package/src/core/protocol.js
CHANGED
|
@@ -30,6 +30,9 @@ export const FAILURE_CODES = Object.freeze([
|
|
|
30
30
|
"E_GATE_REQUIRED",
|
|
31
31
|
"E_GATE_UNVERIFIED",
|
|
32
32
|
"E_GATE_STALE",
|
|
33
|
+
"E_GATE_NOT_REQUIRED",
|
|
34
|
+
"E_GATE_INVALID",
|
|
35
|
+
"E_PHASE_FREEZE",
|
|
33
36
|
"E_PHASE_TRANSITION_INVALID",
|
|
34
37
|
"E_PHASE_PREREQUISITE_MISSING",
|
|
35
38
|
"E_PHASE_CHRONOLOGY_INVALID",
|
|
@@ -113,6 +116,23 @@ export const FAILURE_CODES = Object.freeze([
|
|
|
113
116
|
"E_INTERVENTION_HYPOTHESIS_MISSING",
|
|
114
117
|
"E_INTERVENTION_REFERENCE_INVALID",
|
|
115
118
|
"E_STRATEGY_OSCILLATION",
|
|
119
|
+
"E_DECISION_ENGINE_UNAVAILABLE",
|
|
120
|
+
"E_DECISION_ENGINE_AUTH_REQUIRED",
|
|
121
|
+
"E_DECISION_ENGINE_AUTH_INVALID",
|
|
122
|
+
"E_DECISION_MODEL_UNSUPPORTED",
|
|
123
|
+
"E_DECISION_REQUEST_INVALID",
|
|
124
|
+
"E_DECISION_RESULT_INVALID",
|
|
125
|
+
"E_DECISION_TIMEOUT",
|
|
126
|
+
"E_DECISION_RATE_LIMITED",
|
|
127
|
+
"E_DECISION_STATE_UNSAFE",
|
|
128
|
+
"E_DECISION_STATE_LIMIT",
|
|
129
|
+
"E_DECISION_LOW_CONFIDENCE",
|
|
130
|
+
"E_DECISION_STALE",
|
|
131
|
+
"E_DECISION_POLICY_INVALID",
|
|
132
|
+
"E_DECISION_CACHE_INVALID",
|
|
133
|
+
"E_DECISION_QUESTION_SET_UNKNOWN",
|
|
134
|
+
"E_DECISION_QUESTION_SET_STALE",
|
|
135
|
+
"E_DECISION_REQUIRED",
|
|
116
136
|
"E_DECISION_CRITERION_INVALID",
|
|
117
137
|
"E_DECISION_NOT_UNRESOLVED",
|
|
118
138
|
]);
|
|
@@ -1,19 +1,81 @@
|
|
|
1
|
+
import { withTaskTransaction } from "./transaction.js";
|
|
1
2
|
import { readContract } from "./contract.js";
|
|
2
3
|
import { canonicalFingerprint, readJsonArtifact, writeJsonArtifact } from "./artifacts.js";
|
|
3
|
-
import { appendProtocolEvent, validateEventLedger } from "./events.js";
|
|
4
|
+
import { appendProtocolEvent, validateCompletionRecoveryAuthorization, validateEventLedger } from "./events.js";
|
|
4
5
|
import { authorizeCompletionRecoveryOrRebind } from "./completion-recovery-rebind.js";
|
|
5
6
|
import { runCommandExecution } from "./execution.js";
|
|
6
7
|
import { createReceipt } from "./receipt.js";
|
|
7
8
|
import { currentRepositoryFingerprint } from "./repository.js";
|
|
8
9
|
import { taskArtifactPath } from "./task-paths.js";
|
|
10
|
+
import { resolveTaskClaimState } from "./task-claim-state.js";
|
|
11
|
+
import { DIAGNOSTIC_SCHEMA_VERSION, diagnosticSemanticFingerprint } from "./diagnostic-model.js";
|
|
9
12
|
import { classifyLoadedWorkState, readWorkState, mutateWorkState } from "./work-state.js";
|
|
10
13
|
import { classifyRequirement } from "./evidence-readiness.js";
|
|
14
|
+
import { assertDiagnosingToCorrectingTransition } from "./phase.js";
|
|
15
|
+
import { resolveCurrentCycleDiagnostic } from "./diagnostic-projection.js";
|
|
11
16
|
|
|
12
17
|
export const RECONCILE_EVENT = "CHECKPOINT_RECONCILED";
|
|
13
18
|
|
|
14
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Build the schema-valid rebound diagnostic case for a drifted DIAGNOSING task
|
|
21
|
+
* that entered the phase before its cycle recorded any diagnosis. The case
|
|
22
|
+
* binds every observation to the just-passed contract-bound evidence execution
|
|
23
|
+
* so the diagnosis carries explicit provenance instead of being manufactured
|
|
24
|
+
* by recovery: the hypothesis is the recovery statement, settled by the
|
|
25
|
+
* successful evidence, with the next safe action being the canonical advance.
|
|
26
|
+
*/
|
|
27
|
+
function reconcileReboundDiagnosticCase({
|
|
28
|
+
taskId,
|
|
29
|
+
verificationCycle,
|
|
30
|
+
checkId,
|
|
31
|
+
requirement,
|
|
32
|
+
command,
|
|
33
|
+
executionId,
|
|
34
|
+
repositoryHead,
|
|
35
|
+
}) {
|
|
36
|
+
const statementLimit = 2000;
|
|
37
|
+
const observations = [{
|
|
38
|
+
id: "obs-reconcile-evidence",
|
|
39
|
+
kind: "CHECK_RESULT",
|
|
40
|
+
evidenceRef: checkId,
|
|
41
|
+
provenance: "FORGELOOP_EXECUTED",
|
|
42
|
+
statement: `Contract-bound evidence ${checkId} passed in the current repository (${repositoryHead}); execution ${executionId}: ${command}`.slice(0, statementLimit),
|
|
43
|
+
}];
|
|
44
|
+
const hypotheses = [{
|
|
45
|
+
id: "h-reconcile-rebind",
|
|
46
|
+
statement: `Repository moved after the task entered DIAGNOSING but before the cycle recorded any diagnosis; cycle ${verificationCycle} diagnosis is rebound from the passed contract-bound evidence requirement "${requirement}".`.slice(0, statementLimit),
|
|
47
|
+
contributorRefs: [],
|
|
48
|
+
evidenceRefs: [checkId],
|
|
49
|
+
settledBy: { type: "CHECK_STATUS", checkId, expectedStatus: "passed" },
|
|
50
|
+
status: "OPEN",
|
|
51
|
+
}];
|
|
52
|
+
const details = {
|
|
53
|
+
schemaVersion: DIAGNOSTIC_SCHEMA_VERSION,
|
|
54
|
+
verificationCycle,
|
|
55
|
+
diagnosticRevision: 1,
|
|
56
|
+
failureClass: "VERIFICATION_FAILURE",
|
|
57
|
+
observations,
|
|
58
|
+
contributors: [],
|
|
59
|
+
hypotheses,
|
|
60
|
+
nextSafeAction: {
|
|
61
|
+
statement: "Advance DIAGNOSING -> CORRECTING -> VERIFYING and continue the canonical verification pipeline in the reconciled repository.".slice(0, statementLimit),
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
details.diagnosticFingerprint = diagnosticSemanticFingerprint({
|
|
65
|
+
verificationCycle: details.verificationCycle,
|
|
66
|
+
failureClass: details.failureClass,
|
|
67
|
+
case_: details,
|
|
68
|
+
});
|
|
69
|
+
details.previousDiagnosticFingerprint = null;
|
|
70
|
+
return details;
|
|
71
|
+
}
|
|
15
72
|
|
|
16
|
-
|
|
73
|
+
// DIAGNOSING is reconcilable under the same diagnosis-boundary condition as
|
|
74
|
+
// CORRECTING: the active cycle must already carry an append-only diagnosis.
|
|
75
|
+
// Without it, drift entering DIAGNOSING before the cycle's first diagnosis is
|
|
76
|
+
// unrecoverable (reconcile-closure, task-recover, handoff, and every mutation
|
|
77
|
+
// all refuse), which stranded real drifted tasks on this exact edge.
|
|
78
|
+
const RECONCILABLE_PHASES = new Set(["EXECUTING", "VERIFYING", "DIAGNOSING", "CORRECTING", "REVIEWING"]);
|
|
17
79
|
|
|
18
80
|
function reconcileError(code, message, artifacts = []) {
|
|
19
81
|
const error = new Error(message);
|
|
@@ -22,14 +84,171 @@ function reconcileError(code, message, artifacts = []) {
|
|
|
22
84
|
return error;
|
|
23
85
|
}
|
|
24
86
|
|
|
87
|
+
function assertRepositoryOnlyFreshness(freshness, stateRel, contractRel) {
|
|
88
|
+
if (freshness.status !== "REVALIDATION_REQUIRED") {
|
|
89
|
+
throw reconcileError(
|
|
90
|
+
"E_RECONCILE_NOT_STALE",
|
|
91
|
+
`work-state checkpoint is ${freshness.status === "FRESH" ? "fresh" : "not revalidation-required"}; no reconciliation required`,
|
|
92
|
+
[stateRel, contractRel],
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
if (freshness.reasons.length === 1 && freshness.reasons[0] === "REPOSITORY_CHANGED") return;
|
|
96
|
+
throw reconcileError(
|
|
97
|
+
"E_RECONCILE_UNSUPPORTED_DRIFT",
|
|
98
|
+
`reconcile-closure only reconciles repository fingerprint drift; unresolved drift: ${freshness.reasons.join(", ")}`,
|
|
99
|
+
[stateRel, contractRel],
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function assertFreshReviewingRecoveryIsAuthorized({
|
|
104
|
+
target,
|
|
105
|
+
packageRoot,
|
|
106
|
+
taskId,
|
|
107
|
+
state,
|
|
108
|
+
eventsRel,
|
|
109
|
+
receiptRel,
|
|
110
|
+
}) {
|
|
111
|
+
if (state.phase !== "REVIEWING" || !Object.prototype.hasOwnProperty.call(state, "lastCompletionAttempt")) return;
|
|
112
|
+
const ledger = await validateEventLedger(target, packageRoot, { taskId });
|
|
113
|
+
if (!ledger.valid) return;
|
|
114
|
+
let receipt = null;
|
|
115
|
+
try {
|
|
116
|
+
receipt = (await readJsonArtifact(target, receiptRel, "execution-receipt", packageRoot)).value;
|
|
117
|
+
} catch (error) {
|
|
118
|
+
if (error.code !== "ARTIFACT_MISSING") throw error;
|
|
119
|
+
}
|
|
120
|
+
const recoveryAuth = validateCompletionRecoveryAuthorization({ state, receipt, events: ledger.events });
|
|
121
|
+
if (!recoveryAuth.authorized) {
|
|
122
|
+
const first = recoveryAuth.errors?.[0] ?? {};
|
|
123
|
+
throw reconcileError(
|
|
124
|
+
first.code ?? "E_COMPLETION_RECOVERY_UNAUTHORIZED",
|
|
125
|
+
`REVIEWING reconciliation requires authorized completion recovery: ${first.message ?? "unauthorized"}`,
|
|
126
|
+
[eventsRel, receiptRel],
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function validateReconciliationCheckpoint({
|
|
132
|
+
target,
|
|
133
|
+
packageRoot,
|
|
134
|
+
taskId,
|
|
135
|
+
state,
|
|
136
|
+
stateRel,
|
|
137
|
+
contractRel,
|
|
138
|
+
eventsRel,
|
|
139
|
+
receiptRel,
|
|
140
|
+
authorityContext,
|
|
141
|
+
runtimeContext,
|
|
142
|
+
}) {
|
|
143
|
+
const freshness = await classifyLoadedWorkState({ target, state, contractFile: contractRel });
|
|
144
|
+
if (freshness.status !== "REVALIDATION_REQUIRED") {
|
|
145
|
+
await assertFreshReviewingRecoveryIsAuthorized({
|
|
146
|
+
target,
|
|
147
|
+
packageRoot,
|
|
148
|
+
taskId,
|
|
149
|
+
state,
|
|
150
|
+
eventsRel,
|
|
151
|
+
receiptRel,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
assertRepositoryOnlyFreshness(freshness, stateRel, contractRel);
|
|
155
|
+
|
|
156
|
+
const ledger = await validateEventLedger(target, packageRoot, { taskId });
|
|
157
|
+
if (!ledger.valid) {
|
|
158
|
+
const first = ledger.errors[0];
|
|
159
|
+
throw reconcileError(
|
|
160
|
+
"E_RECONCILE_LEDGER_INVALID",
|
|
161
|
+
`append-only event ledger must be valid before reconciliation: ${first?.message ?? "invalid ledger"}`,
|
|
162
|
+
[eventsRel],
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
await assertReconciliationClaimOwnership(target, { taskId, packageRoot, state, stateRel, eventsRel });
|
|
167
|
+
|
|
168
|
+
const reboundOrState = await rebindReconciliationState(target, {
|
|
169
|
+
packageRoot,
|
|
170
|
+
taskId,
|
|
171
|
+
state,
|
|
172
|
+
ledger,
|
|
173
|
+
authorityContext,
|
|
174
|
+
runtimeContext,
|
|
175
|
+
stateRel,
|
|
176
|
+
receiptRel,
|
|
177
|
+
});
|
|
178
|
+
return reboundOrState;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** Fail closed unless the task claim is valid, mutable, and ACTIVE in this checkout. */
|
|
182
|
+
async function assertReconciliationClaimOwnership(target, { taskId, packageRoot, state, stateRel, eventsRel }) {
|
|
183
|
+
const ownership = await resolveTaskClaimState(target, { taskId, packageRoot });
|
|
184
|
+
if (!ownership.ownershipValid || !ownership.mutationAllowed || ownership.claimState !== "ACTIVE") {
|
|
185
|
+
const first = ownership.ownershipErrors?.[0] ?? ownership.errors?.[0] ?? {};
|
|
186
|
+
throw reconcileError(
|
|
187
|
+
first.code ?? "E_TASK_CLAIM_OWNERSHIP_INCONSISTENT",
|
|
188
|
+
`reconcile-closure requires active, valid task claim ownership: ${first.message ?? ownership.claimState}`,
|
|
189
|
+
[stateRel, eventsRel],
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** Handle the phase-bound diagnosis rebind gates for CORRECTING/DIAGNOSING and the REVIEWING recovery authorization.
|
|
195
|
+
* Returns the possibly-rebound state (or the original state when no rebind applies). */
|
|
196
|
+
async function rebindReconciliationState(target, {
|
|
197
|
+
packageRoot,
|
|
198
|
+
taskId,
|
|
199
|
+
state,
|
|
200
|
+
ledger,
|
|
201
|
+
authorityContext,
|
|
202
|
+
runtimeContext,
|
|
203
|
+
stateRel,
|
|
204
|
+
receiptRel,
|
|
205
|
+
}) {
|
|
206
|
+
if (state.phase === "CORRECTING" || state.phase === "DIAGNOSING") {
|
|
207
|
+
// The diagnosis boundary for CORRECTING stays absolute: recovery can never
|
|
208
|
+
// manufacture a diagnosis or authorize a retry the ordinary correction
|
|
209
|
+
// transition would refuse. A drifted DIAGNOSING task without a cycle
|
|
210
|
+
// diagnosis is the one legal exception: the drift struck between entering
|
|
211
|
+
// DIAGNOSING and recording the cycle's first diagnosis, and no other
|
|
212
|
+
// command can create either the diagnosis or the fresh checkpoint. Such a
|
|
213
|
+
// task reconciles only after contract-bound evidence passes, and the
|
|
214
|
+
// reconcile itself records the cycle diagnosis below (provenance bound to
|
|
215
|
+
// the passed evidence execution).
|
|
216
|
+
const cycleDiagnosis = resolveCurrentCycleDiagnostic(ledger.events, state.taskId, state.verificationCycle ?? 1);
|
|
217
|
+
if (state.phase === "DIAGNOSING" && !cycleDiagnosis) {
|
|
218
|
+
return { ...state, undiagnosedDiagnosingRebind: true };
|
|
219
|
+
}
|
|
220
|
+
assertDiagnosingToCorrectingTransition({
|
|
221
|
+
state,
|
|
222
|
+
events: ledger.events,
|
|
223
|
+
resolveDiagnosis: resolveCurrentCycleDiagnostic,
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (state.phase !== "REVIEWING" || !Object.prototype.hasOwnProperty.call(state, "lastCompletionAttempt")) {
|
|
228
|
+
return state;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const recovery = await authorizeCompletionRecoveryOrRebind({ target, packageRoot, taskId, authorityContext, runtimeContext });
|
|
232
|
+
if (!recovery.recoveryAuth.authorized) {
|
|
233
|
+
const first = recovery.recoveryAuth.errors?.[0] ?? {};
|
|
234
|
+
throw reconcileError(
|
|
235
|
+
first.code ?? "E_COMPLETION_RECOVERY_UNAUTHORIZED",
|
|
236
|
+
`REVIEWING reconciliation requires authorized completion recovery: ${first.message ?? "unauthorized"}`,
|
|
237
|
+
[stateRel, receiptRel],
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
return recovery.rebound ? recovery.state : state;
|
|
241
|
+
}
|
|
242
|
+
|
|
25
243
|
/**
|
|
26
|
-
* Canonical recovery for an EXECUTING, VERIFYING, or REVIEWING task whose
|
|
244
|
+
* Canonical recovery for an EXECUTING, VERIFYING, CORRECTING, or REVIEWING task whose
|
|
27
245
|
* objective is already satisfied in the current repository but whose
|
|
28
246
|
* work-state checkpoint is stale because the repository fingerprint moved.
|
|
29
247
|
*
|
|
30
248
|
* The command refreshes the checkpoint repository fingerprint only after:
|
|
31
|
-
* - the task is EXECUTING, VERIFYING, or (with
|
|
32
|
-
*
|
|
249
|
+
* - the task is EXECUTING, VERIFYING, CORRECTING, or REVIEWING (with either a
|
|
250
|
+
* persisted completion rejection or the narrow repository-only bootstrap
|
|
251
|
+
* path),
|
|
33
252
|
* - classification requires revalidation and the only drift is
|
|
34
253
|
* REPOSITORY_CHANGED,
|
|
35
254
|
* - the append-only event ledger is valid,
|
|
@@ -41,7 +260,14 @@ function reconcileError(code, message, artifacts = []) {
|
|
|
41
260
|
* VERIFYING, prepare-completion, record-check, complete); claims are
|
|
42
261
|
* released only by canonical COMPLETE.
|
|
43
262
|
*/
|
|
44
|
-
export async function runReconcileClosure({
|
|
263
|
+
export async function runReconcileClosure(options = {}) {
|
|
264
|
+
if (typeof options.taskId !== "string" || !options.taskId.trim()) return reconcileSelectedClosure(options);
|
|
265
|
+
return withTaskTransaction({ target: options.target, packageRoot: options.packageRoot,
|
|
266
|
+
taskId: options.taskId, operation: "reconcile-closure", recordCommitEvent: true },
|
|
267
|
+
() => reconcileSelectedClosure(options));
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
async function reconcileSelectedClosure({
|
|
45
271
|
target,
|
|
46
272
|
packageRoot,
|
|
47
273
|
taskId,
|
|
@@ -52,18 +278,7 @@ export async function runReconcileClosure({
|
|
|
52
278
|
authorityContext,
|
|
53
279
|
runtimeContext,
|
|
54
280
|
} = {}) {
|
|
55
|
-
|
|
56
|
-
throw reconcileError("E_TASK_REQUIRED", "reconcile-closure requires --task", []);
|
|
57
|
-
}
|
|
58
|
-
if (typeof checkId !== "string" || !checkId.trim()) {
|
|
59
|
-
throw reconcileError("E_RECONCILE_REQUIREMENT_UNKNOWN", "reconcile-closure requires --id", []);
|
|
60
|
-
}
|
|
61
|
-
if (typeof requirement !== "string" || !requirement.trim()) {
|
|
62
|
-
throw reconcileError("E_RECONCILE_REQUIREMENT_UNKNOWN", "reconcile-closure requires --requirement", []);
|
|
63
|
-
}
|
|
64
|
-
if (!Array.isArray(argv) || argv.length === 0) {
|
|
65
|
-
throw reconcileError("E_RECONCILE_EVIDENCE_FAILED", "reconcile-closure requires -- followed by the evidence command argv", []);
|
|
66
|
-
}
|
|
281
|
+
requireReconcileInputs({ taskId, checkId, requirement, argv });
|
|
67
282
|
|
|
68
283
|
const stateRel = taskArtifactPath(taskId, "state");
|
|
69
284
|
const contractRel = taskArtifactPath(taskId, "contract");
|
|
@@ -77,57 +292,22 @@ export async function runReconcileClosure({
|
|
|
77
292
|
if (!RECONCILABLE_PHASES.has(state.phase)) {
|
|
78
293
|
throw reconcileError(
|
|
79
294
|
"E_RECONCILE_PHASE_INVALID",
|
|
80
|
-
`reconcile-closure supports EXECUTING, VERIFYING, or REVIEWING tasks whose objective is already satisfied; found ${state.phase}`,
|
|
295
|
+
`reconcile-closure supports EXECUTING, VERIFYING, DIAGNOSING, CORRECTING, or REVIEWING tasks whose objective is already satisfied; found ${state.phase}`,
|
|
81
296
|
[stateRel],
|
|
82
297
|
);
|
|
83
298
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
`REVIEWING reconciliation requires authorized completion recovery: ${first.message ?? "unauthorized"}`,
|
|
97
|
-
[stateRel, receiptRel],
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
if (recovery.rebound) {
|
|
101
|
-
state = recovery.state;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const freshness = await classifyLoadedWorkState({ target, state, contractFile: contractRel });
|
|
106
|
-
if (freshness.status !== "REVALIDATION_REQUIRED" || !freshness.reasons.includes("REPOSITORY_CHANGED")) {
|
|
107
|
-
throw reconcileError(
|
|
108
|
-
"E_RECONCILE_NOT_STALE",
|
|
109
|
-
`work-state checkpoint is ${freshness.status === "FRESH" ? "fresh" : "not revalidation-required"}; no reconciliation required`,
|
|
110
|
-
[stateRel, contractRel],
|
|
111
|
-
);
|
|
112
|
-
}
|
|
113
|
-
const unsupported = freshness.reasons.filter((reason) => !RECONCILABLE_DRIFT.has(reason));
|
|
114
|
-
if (unsupported.length > 0) {
|
|
115
|
-
throw reconcileError(
|
|
116
|
-
"E_RECONCILE_UNSUPPORTED_DRIFT",
|
|
117
|
-
`reconcile-closure only reconciles repository fingerprint drift; unresolved drift: ${unsupported.join(", ")}`,
|
|
118
|
-
[stateRel, contractRel],
|
|
119
|
-
);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const ledger = await validateEventLedger(target, packageRoot, { taskId });
|
|
123
|
-
if (!ledger.valid) {
|
|
124
|
-
const first = ledger.errors[0];
|
|
125
|
-
throw reconcileError(
|
|
126
|
-
"E_RECONCILE_LEDGER_INVALID",
|
|
127
|
-
`append-only event ledger must be valid before reconciliation: ${first?.message ?? "invalid ledger"}`,
|
|
128
|
-
[eventsRel],
|
|
129
|
-
);
|
|
130
|
-
}
|
|
299
|
+
state = await validateReconciliationCheckpoint({
|
|
300
|
+
target,
|
|
301
|
+
packageRoot,
|
|
302
|
+
taskId,
|
|
303
|
+
state,
|
|
304
|
+
stateRel,
|
|
305
|
+
contractRel,
|
|
306
|
+
eventsRel,
|
|
307
|
+
receiptRel,
|
|
308
|
+
authorityContext,
|
|
309
|
+
runtimeContext,
|
|
310
|
+
});
|
|
131
311
|
|
|
132
312
|
const contract = await readContract(target, packageRoot, { taskId });
|
|
133
313
|
const verificationItem = (contract.value.verification ?? []).find((item) => {
|
|
@@ -169,6 +349,24 @@ export async function runReconcileClosure({
|
|
|
169
349
|
const repository = await currentRepositoryFingerprint(target);
|
|
170
350
|
const previous = state.repositoryFingerprint;
|
|
171
351
|
|
|
352
|
+
const rebindDiagnosis = Boolean(state.undiagnosedDiagnosingRebind);
|
|
353
|
+
if (rebindDiagnosis) {
|
|
354
|
+
delete state.undiagnosedDiagnosingRebind;
|
|
355
|
+
await appendProtocolEvent(target, {
|
|
356
|
+
taskId,
|
|
357
|
+
event: "DIAGNOSTIC_CASE_RECORDED",
|
|
358
|
+
details: reconcileReboundDiagnosticCase({
|
|
359
|
+
taskId,
|
|
360
|
+
verificationCycle: state.verificationCycle ?? 1,
|
|
361
|
+
checkId,
|
|
362
|
+
requirement,
|
|
363
|
+
command: argv.join(" "),
|
|
364
|
+
executionId: execution.execution.executionId,
|
|
365
|
+
repositoryHead: repository.head,
|
|
366
|
+
}),
|
|
367
|
+
}, packageRoot, { taskId });
|
|
368
|
+
}
|
|
369
|
+
|
|
172
370
|
await appendProtocolEvent(target, {
|
|
173
371
|
taskId,
|
|
174
372
|
event: RECONCILE_EVENT,
|
|
@@ -219,3 +417,19 @@ export async function runReconcileClosure({
|
|
|
219
417
|
event: RECONCILE_EVENT,
|
|
220
418
|
};
|
|
221
419
|
}
|
|
420
|
+
|
|
421
|
+
/** Validate the reconcile arguments exactly as before; shared by the phase-independent input gate. */
|
|
422
|
+
function requireReconcileInputs({ taskId, checkId, requirement, argv } = {}) {
|
|
423
|
+
if (typeof taskId !== "string" || !taskId.trim()) {
|
|
424
|
+
throw reconcileError("E_TASK_REQUIRED", "reconcile-closure requires --task", []);
|
|
425
|
+
}
|
|
426
|
+
if (typeof checkId !== "string" || !checkId.trim()) {
|
|
427
|
+
throw reconcileError("E_RECONCILE_REQUIREMENT_UNKNOWN", "reconcile-closure requires --id", []);
|
|
428
|
+
}
|
|
429
|
+
if (typeof requirement !== "string" || !requirement.trim()) {
|
|
430
|
+
throw reconcileError("E_RECONCILE_REQUIREMENT_UNKNOWN", "reconcile-closure requires --requirement", []);
|
|
431
|
+
}
|
|
432
|
+
if (!Array.isArray(argv) || argv.length === 0) {
|
|
433
|
+
throw reconcileError("E_RECONCILE_EVIDENCE_FAILED", "reconcile-closure requires -- followed by the evidence command argv", []);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { ledgerEventsOfTypes } from "./ledger-event-collection.js";
|
|
2
|
+
import { ledgerRelationSet } from "./ledger-relations.js";
|
|
1
3
|
import {
|
|
2
4
|
LEGACY_RECOVERY_MIGRATION_EVENT,
|
|
3
5
|
isLegacyRecoveryEventShape,
|
|
@@ -6,6 +8,7 @@ import {
|
|
|
6
8
|
const RECOVERY_EVENT_TYPES = new Set([
|
|
7
9
|
"TASK_RECOVERY_RECORDED",
|
|
8
10
|
"OPERATOR_RECOVERY_RECORDED",
|
|
11
|
+
"TASK_ABANDONED",
|
|
9
12
|
LEGACY_RECOVERY_MIGRATION_EVENT,
|
|
10
13
|
]);
|
|
11
14
|
|
|
@@ -22,56 +25,65 @@ function recoveryIdOf(event) {
|
|
|
22
25
|
: null;
|
|
23
26
|
}
|
|
24
27
|
|
|
25
|
-
|
|
28
|
+
function recoveryCandidate(event, events, seenRecoveryIds, activeCycle, errors) {
|
|
29
|
+
// The known legacy defect signature is never an owning recovery cycle by
|
|
30
|
+
// itself. It remains non-owning historical evidence; ownership comes
|
|
31
|
+
// exclusively from its valid LEGACY_RECOVERY_MIGRATION_RECORDED event,
|
|
32
|
+
// which is validated (binding, uniqueness) by the ledger validator.
|
|
33
|
+
if (!event.details?.recoveryId && isLegacyRecoveryEventShape(event)) return null;
|
|
34
|
+
// A migration event only counts as a canonical recovery cycle when it
|
|
35
|
+
// binds an actual legacy event present earlier in this ledger.
|
|
36
|
+
if (event.event === LEGACY_RECOVERY_MIGRATION_EVENT
|
|
37
|
+
&& !events.some((candidate) => candidate.seq === event.details?.legacyEventSeq
|
|
38
|
+
&& isLegacyRecoveryEventShape(candidate))) {
|
|
39
|
+
errors.push(recoveryError(
|
|
40
|
+
`Legacy migration event at seq ${event?.seq ?? "unknown"} does not bind a legacy recovery event in this ledger`,
|
|
41
|
+
));
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
const recoveryId = recoveryIdOf(event);
|
|
45
|
+
if (!recoveryId) {
|
|
46
|
+
errors.push(recoveryError(`Recovery event at seq ${event?.seq ?? "unknown"} has no recoveryId`));
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
if (seenRecoveryIds.has(recoveryId)) {
|
|
50
|
+
errors.push(recoveryError(`Recovery event at seq ${event.seq} reuses recovery id ${recoveryId}`));
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
if (activeCycle) {
|
|
54
|
+
errors.push(recoveryError(
|
|
55
|
+
`Recovery ${recoveryId} was recorded while recovery ${activeCycle.recoveryId} is unresolved`,
|
|
56
|
+
));
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
recoveryId,
|
|
62
|
+
recoveryEventSeq: event.seq,
|
|
63
|
+
resumedEventSeq: null,
|
|
64
|
+
active: true,
|
|
65
|
+
event,
|
|
66
|
+
resumedEvent: null,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function classifyHistory(events, { retainCycles = true, onRecovery = null } = {}) {
|
|
26
71
|
const recoveries = [];
|
|
27
72
|
const completedRecoveries = [];
|
|
28
73
|
const errors = [];
|
|
29
|
-
const seenRecoveryIds =
|
|
74
|
+
const seenRecoveryIds = ledgerRelationSet();
|
|
30
75
|
let activeCycle = null;
|
|
76
|
+
let completedRecoveryCount = 0;
|
|
31
77
|
|
|
32
|
-
|
|
78
|
+
const relevant = Array.isArray(events) ? events : ledgerEventsOfTypes(events, [...RECOVERY_EVENT_TYPES, "TASK_RECOVERY_RESUMED"]);
|
|
79
|
+
for (const event of relevant) {
|
|
33
80
|
if (RECOVERY_EVENT_TYPES.has(event?.event)) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (
|
|
39
|
-
|
|
40
|
-
// binds an actual legacy event present earlier in this ledger.
|
|
41
|
-
if (event.event === LEGACY_RECOVERY_MIGRATION_EVENT
|
|
42
|
-
&& !events.some((candidate) => candidate.seq === event.details?.legacyEventSeq
|
|
43
|
-
&& isLegacyRecoveryEventShape(candidate))) {
|
|
44
|
-
errors.push(recoveryError(
|
|
45
|
-
`Legacy migration event at seq ${event?.seq ?? "unknown"} does not bind a legacy recovery event in this ledger`,
|
|
46
|
-
));
|
|
47
|
-
continue;
|
|
48
|
-
}
|
|
49
|
-
const recoveryId = recoveryIdOf(event);
|
|
50
|
-
if (!recoveryId) {
|
|
51
|
-
errors.push(recoveryError(`Recovery event at seq ${event?.seq ?? "unknown"} has no recoveryId`));
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
if (seenRecoveryIds.has(recoveryId)) {
|
|
55
|
-
errors.push(recoveryError(`Recovery event at seq ${event.seq} reuses recovery id ${recoveryId}`));
|
|
56
|
-
continue;
|
|
57
|
-
}
|
|
58
|
-
if (activeCycle) {
|
|
59
|
-
errors.push(recoveryError(
|
|
60
|
-
`Recovery ${recoveryId} was recorded while recovery ${activeCycle.recoveryId} is unresolved`,
|
|
61
|
-
));
|
|
62
|
-
continue;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
activeCycle = {
|
|
66
|
-
recoveryId,
|
|
67
|
-
recoveryEventSeq: event.seq,
|
|
68
|
-
resumedEventSeq: null,
|
|
69
|
-
active: true,
|
|
70
|
-
event,
|
|
71
|
-
resumedEvent: null,
|
|
72
|
-
};
|
|
73
|
-
seenRecoveryIds.add(recoveryId);
|
|
74
|
-
recoveries.push(activeCycle);
|
|
81
|
+
const candidate = recoveryCandidate(event, events, seenRecoveryIds, activeCycle, errors);
|
|
82
|
+
if (!candidate) continue;
|
|
83
|
+
activeCycle = candidate;
|
|
84
|
+
seenRecoveryIds.add(activeCycle.recoveryId);
|
|
85
|
+
if (retainCycles) recoveries.push(activeCycle);
|
|
86
|
+
if (onRecovery) onRecovery(activeCycle);
|
|
75
87
|
continue;
|
|
76
88
|
}
|
|
77
89
|
|
|
@@ -94,23 +106,31 @@ export function classifyRecoveryHistory(events = []) {
|
|
|
94
106
|
activeCycle.resumedEventSeq = event.seq;
|
|
95
107
|
activeCycle.resumedEvent = event;
|
|
96
108
|
activeCycle.active = false;
|
|
97
|
-
|
|
109
|
+
completedRecoveryCount += 1;
|
|
110
|
+
if (retainCycles) completedRecoveries.push(activeCycle);
|
|
98
111
|
activeCycle = null;
|
|
99
112
|
}
|
|
100
113
|
|
|
101
114
|
const valid = errors.length === 0;
|
|
102
115
|
return {
|
|
103
|
-
recoveries,
|
|
116
|
+
...(retainCycles ? { recoveries } : {}),
|
|
104
117
|
activeRecoveryId: valid ? activeCycle?.recoveryId ?? null : null,
|
|
105
118
|
activeRecovery: valid && activeCycle
|
|
106
119
|
? { recoveryId: activeCycle.recoveryId, event: activeCycle.event }
|
|
107
120
|
: null,
|
|
108
|
-
completedRecoveries,
|
|
121
|
+
...(retainCycles ? { completedRecoveries } : { completedRecoveryCount }),
|
|
109
122
|
valid,
|
|
110
123
|
errors,
|
|
111
124
|
};
|
|
112
125
|
}
|
|
113
126
|
|
|
127
|
+
/** Public evidence keeps its historical arrays. Internal classification can stream claims. */
|
|
128
|
+
export function classifyRecoveryHistory(events = []) { return classifyHistory(events); }
|
|
129
|
+
|
|
130
|
+
export function summarizeRecoveryHistory(events, onRecovery) {
|
|
131
|
+
return classifyHistory(events, { retainCycles: false, onRecovery });
|
|
132
|
+
}
|
|
133
|
+
|
|
114
134
|
export function resolveRecoveryHistory(events = []) {
|
|
115
135
|
return classifyRecoveryHistory(events);
|
|
116
136
|
}
|