@cassiomc1/forgeloop 1.13.0 → 2.0.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENT_COMPATIBILITY.md +8 -0
- package/CONTRIBUTING.md +11 -7
- package/DOCS_INDEX.md +37 -4
- package/ENG/c-development-eng.md +2 -2
- package/ENG/cpp-development-eng.md +2 -2
- package/ENG/dotnet-aspnetcore-development-eng.md +7 -1
- package/ENG/flutter-development-eng.md +4 -2
- package/ENG/go-development-eng.md +2 -2
- package/ENG/java-development-eng.md +3 -3
- package/ENG/nodejs-backend-development-eng.md +10 -6
- package/ENG/php-development-eng.md +3 -3
- package/ENG/python-development-eng.md +205 -0
- package/ENG/rust-development-eng.md +42 -1
- package/ENG/sec-code-eng.md +7 -7
- package/ENG/sql-development-eng.md +4 -4
- package/ENG/swift-development-eng.md +5 -4
- package/ENG/test-code-eng.md +1 -1
- package/ENG/typescript-development-eng.md +4 -4
- package/EXECUTION_STATE.md +12 -0
- package/GUIDE_ROUTER.md +6 -0
- package/LICENSE +133 -21
- package/LOOP_ENGINEERING.md +28 -2
- package/LOOP_SYSTEM_DESIGN.md +10 -10
- package/ORCHESTRATOR_INTEGRATION.md +9 -5
- package/PROTOCOL_INTEGRATION.md +55 -2
- package/README.md +63 -59
- package/TERMINOLOGY.md +2 -0
- package/THIRD_PARTY_NOTICES.md +3 -4
- package/THREAT_MODEL.md +140 -1
- package/completions/_forgeloop +29 -3
- package/completions/forgeloop.bash +55 -3
- package/completions/forgeloop.fish +183 -1
- package/docs/ADVISORY_CONTEXT.md +25 -0
- package/docs/AGENT_BROWSER_ADAPTER.md +81 -0
- package/docs/AGENT_BROWSER_VERIFICATION.md +6 -0
- package/docs/AGENT_PROTOCOL_SUMMARY.md +74 -39
- package/docs/AGENT_SKILL.md +66 -0
- package/docs/ARTIFACT_REFERENCE.md +171 -35
- package/docs/AUDIT_UX.md +46 -0
- package/docs/BROWSER_VERIFICATION.md +136 -0
- package/docs/CLI_REFERENCE.md +512 -8
- package/docs/CODE_ATTESTATION.md +2 -2
- package/docs/DOCUMENTATION_GUIDE.md +34 -11
- package/docs/GETTING_STARTED.md +3 -3
- package/docs/JEV_BENCHMARKS.md +31 -0
- package/docs/MODEL_ROUTING.md +37 -0
- package/docs/OPENSRC_ADAPTER.md +241 -0
- package/docs/PACKAGE_CONTENTS.md +46 -18
- package/docs/PROVIDERS.md +126 -0
- package/docs/PROVIDER_ARCHITECTURE.md +199 -0
- package/docs/RECIPES.md +9 -0
- package/docs/RELEASE_CHECKLIST.md +116 -12
- package/docs/SECURITY_REVIEW.md +71 -0
- package/docs/SELF_HOSTED_TESTS.md +121 -0
- package/docs/SEMANTIC_DECISION_PLANE.md +71 -0
- package/docs/TEST_INTELLIGENCE.md +29 -0
- package/docs/TEST_PRUNING.md +14 -0
- package/docs/TROUBLESHOOTING.md +198 -1
- package/docs/UNIVERSAL_INTEGRATION.md +31 -0
- package/docs/assets/diagrams/forgeloop-code-attestation-flow.html +2 -2
- package/docs/assets/diagrams/forgeloop-code-attestation-flow.receipt.json +5 -5
- package/docs/assets/diagrams/forgeloop-code-attestation-flow.svg +1 -1
- package/docs/assets/diagrams/forgeloop-engineering-flow.html +39 -26
- package/docs/assets/diagrams/forgeloop-engineering-flow.receipt.json +6 -6
- package/docs/assets/diagrams/forgeloop-engineering-flow.svg +26 -26
- package/docs/assets/diagrams/forgeloop-verification-trust-flow.html +2 -1
- package/docs/assets/diagrams/forgeloop-verification-trust-flow.receipt.json +5 -5
- package/docs/assets/diagrams/forgeloop-verification-trust-flow.svg +1 -1
- package/docs/diagrams/README.md +13 -9
- package/docs/diagrams/forgeloop-code-attestation-flow.workflow.json +1 -1
- package/docs/diagrams/forgeloop-engineering-flow.workflow.json +24 -19
- package/docs/diagrams/forgeloop-verification-trust-flow.workflow.json +1 -0
- package/docs/diagrams/reviews/forgeloop-code-attestation-flow.review.json +4 -4
- package/docs/diagrams/reviews/forgeloop-engineering-flow.review.json +4 -4
- package/docs/diagrams/reviews/forgeloop-verification-trust-flow.review.json +4 -4
- package/docs/documentation-manifest.json +774 -16
- package/docs/protocol-requirements.json +24 -0
- package/package.json +43 -11
- package/schemas/config.schema.json +14 -0
- package/schemas/context-plan.schema.json +18 -0
- package/schemas/semantic-decision.schema.json +46 -0
- package/schemas/task-bundle.schema.json +7 -2
- package/schemas/test-utility.schema.json +44 -0
- package/scripts/CI_VALIDATORS.md +6 -6
- package/scripts/benchmark-jev.mjs +5 -0
- package/scripts/benchmark-test-intelligence.mjs +4 -0
- package/scripts/generate-agent-protocol-summary.mjs +6 -3
- package/scripts/generate-forgeloop-skill.mjs +133 -0
- package/scripts/jev-smoke.mjs +19 -0
- package/scripts/run-mcp-clean-tests.mjs +30 -0
- package/skills/forgeloop/README.md +9 -0
- package/skills/forgeloop/SKILL.md +77 -0
- package/skills/forgeloop/references/lifecycle.md +9 -0
- package/skills/forgeloop/references/recovery.md +7 -0
- package/skills/forgeloop/references/verification.md +7 -0
- package/src/adapters/agent-browser/assertions.js +47 -0
- package/src/adapters/agent-browser/commands.js +54 -0
- package/src/adapters/agent-browser/index.js +3 -0
- package/src/adapters/agent-browser/locator.js +40 -0
- package/src/adapters/agent-browser/process.js +215 -0
- package/src/adapters/agent-browser/provider.js +316 -0
- package/src/adapters/emulated-services/constants.js +24 -0
- package/src/adapters/emulated-services/index.js +7 -0
- package/src/adapters/emulated-services/process.js +162 -0
- package/src/adapters/emulated-services/provider.js +289 -0
- package/src/adapters/opensrc/normalize.js +90 -0
- package/src/adapters/opensrc/process.js +248 -0
- package/src/adapters/opensrc/provider.js +338 -0
- package/src/adapters/opensrc/search.js +264 -0
- package/src/adapters/temporary-paths.js +18 -0
- package/src/adapters/typesafe/client.js +28 -0
- package/src/adapters/typesafe/engine.js +63 -0
- package/src/adapters/typesafe/normalize.js +41 -0
- package/src/cli.js +218 -174
- package/src/commands/action-show.js +5 -2
- package/src/commands/advance.js +5 -1
- package/src/commands/baseline.js +13 -11
- package/src/commands/checkpoint-revalidate.js +192 -0
- package/src/commands/complete.js +19 -3
- package/src/commands/context-plan.js +38 -0
- package/src/commands/contract-create.js +266 -0
- package/src/commands/contract-revise.js +236 -0
- package/src/commands/decision-show.js +14 -0
- package/src/commands/decision-status.js +22 -0
- package/src/commands/discover.js +41 -0
- package/src/commands/doctor.js +51 -13
- package/src/commands/gate-record.js +194 -0
- package/src/commands/gate-revalidate.js +135 -0
- package/src/commands/handoff-list.js +6 -1
- package/src/commands/handoff-show.js +6 -1
- package/src/commands/migrate-protocol.js +2 -2
- package/src/commands/model-route.js +40 -0
- package/src/commands/next.js +7 -1
- package/src/commands/policy-diff.js +25 -4
- package/src/commands/progress.js +17 -11
- package/src/commands/record-diagnosis.js +3 -0
- package/src/commands/route.js +146 -18
- package/src/commands/semantic-plan.js +41 -0
- package/src/commands/status.js +6 -1
- package/src/commands/storage-backup.js +15 -0
- package/src/commands/storage-migrate.js +5 -0
- package/src/commands/storage-migration-resume.js +5 -0
- package/src/commands/storage-migration-status.js +44 -0
- package/src/commands/storage-restore-resume.js +7 -0
- package/src/commands/storage-restore.js +8 -0
- package/src/commands/task-abandon.js +224 -0
- package/src/commands/task-list.js +8 -43
- package/src/commands/task-migrate-contract-bootstrap-repair.js +288 -0
- package/src/commands/task-migrate.js +3 -3
- package/src/commands/task-recover.js +10 -2
- package/src/commands/task-repair-contract-bootstrap.js +263 -0
- package/src/commands/task-scope.js +18 -2
- package/src/commands/task-show.js +16 -2
- package/src/commands/test-inventory.js +5 -0
- package/src/commands/test-prune-plan.js +5 -0
- package/src/commands/test-prune-probe.js +5 -0
- package/src/commands/test-utility.js +5 -0
- package/src/commands/validate-protocol.js +39 -15
- package/src/commands/validate-receipt.js +8 -1
- package/src/config/guides.json +4 -0
- package/src/core/action-ledger-projection.js +10 -3
- package/src/core/actions.js +38 -75
- package/src/core/activation.js +11 -3
- package/src/core/approvals.js +17 -51
- package/src/core/artifact-registry.js +83 -2
- package/src/core/artifacts.js +81 -27
- package/src/core/attachment-paths.js +45 -0
- package/src/core/attestation-coverage.js +6 -1
- package/src/core/attestation-verifier.js +21 -2
- package/src/core/attestation.js +17 -6
- package/src/core/audit-ux.js +514 -0
- package/src/core/audit.js +6 -1
- package/src/core/browser-verification/constants.js +149 -0
- package/src/core/browser-verification/normalize.js +254 -0
- package/src/core/browser-verification/provider.js +519 -0
- package/src/core/browser-verification/service.js +115 -0
- package/src/core/bundles.js +160 -66
- package/src/core/checkpoint-revalidation.js +321 -0
- package/src/core/cli-command-definitions.js +350 -7
- package/src/core/code-manifest.js +9 -3
- package/src/core/command-executors.js +274 -108
- package/src/core/command-input.js +136 -44
- package/src/core/completion-artifacts.js +85 -34
- package/src/core/completion-ownership.js +19 -8
- package/src/core/completion-recovery-rebind.js +11 -1
- package/src/core/completion.js +64 -15
- package/src/core/config.js +3 -0
- package/src/core/conformance.js +2 -1
- package/src/core/context-compiler/budget.js +9 -0
- package/src/core/context-compiler/candidates.js +39 -0
- package/src/core/context-compiler/compiler.js +63 -0
- package/src/core/context-compiler/fingerprint.js +11 -0
- package/src/core/context-compiler/policy.js +13 -0
- package/src/core/context-compiler/result.js +23 -0
- package/src/core/continuity-lint.js +13 -2
- package/src/core/continuity-reconciliation.js +16 -3
- package/src/core/continuity.js +16 -8
- package/src/core/contract-bootstrap-recovery.js +656 -0
- package/src/core/contract-revision.js +209 -0
- package/src/core/decision/artifact.js +70 -0
- package/src/core/decision/benchmarks.js +103 -0
- package/src/core/decision/cache.js +27 -0
- package/src/core/decision/constants.js +58 -0
- package/src/core/decision/cutover.js +34 -0
- package/src/core/decision/engine.js +22 -0
- package/src/core/decision/errors.js +68 -0
- package/src/core/decision/events.js +104 -0
- package/src/core/decision/freshness.js +19 -0
- package/src/core/decision/normalizers/index.js +115 -0
- package/src/core/decision/policy.js +18 -0
- package/src/core/decision/projection.js +16 -0
- package/src/core/decision/question-registry.js +201 -0
- package/src/core/decision/request.js +26 -0
- package/src/core/decision/resolver.js +138 -0
- package/src/core/decision/result.js +58 -0
- package/src/core/decision/service.js +181 -0
- package/src/core/decision/state-builder.js +65 -0
- package/src/core/decision/task-bindings.js +35 -0
- package/src/core/decision/test-provider.js +32 -0
- package/src/core/decision/thresholds.js +15 -0
- package/src/core/diagnosis-model.js +11 -0
- package/src/core/diagnosis.js +10 -1
- package/src/core/diagnostic-projection.js +29 -12
- package/src/core/diagnostic-record.js +28 -3
- package/src/core/efficiency.js +6 -1
- package/src/core/error-codes.js +278 -0
- package/src/core/events.js +535 -147
- package/src/core/evidence-readiness.js +9 -0
- package/src/core/execution-prerequisites.js +36 -13
- package/src/core/execution-profile.js +63 -38
- package/src/core/execution.js +26 -40
- package/src/core/filesystem.js +11 -0
- package/src/core/gate-artifact.js +105 -19
- package/src/core/gate-provenance.js +125 -0
- package/src/core/handoff.js +26 -14
- package/src/core/history.js +39 -35
- package/src/core/hypothesis-projection.js +32 -0
- package/src/core/information-gain-projection.js +104 -100
- package/src/core/inspect.js +12 -3
- package/src/core/integration-invocation-policy.js +37 -4
- package/src/core/integration-resources.js +119 -87
- package/src/core/ledger-event-collection.js +189 -0
- package/src/core/ledger-relations.js +23 -0
- package/src/core/model-router/constants.js +10 -0
- package/src/core/model-router/policy.js +103 -0
- package/src/core/model-router/router.js +37 -0
- package/src/core/native-storage.js +12 -0
- package/src/core/next-action-continuity.js +31 -9
- package/src/core/next-action-model.js +58 -0
- package/src/core/next-action-phases.js +131 -43
- package/src/core/next-action-refresh.js +43 -9
- package/src/core/next-action-review-phase.js +9 -4
- package/src/core/next-action-verification-phase.js +2 -1
- package/src/core/next-action.js +56 -9
- package/src/core/phase.js +266 -103
- package/src/core/policy-engine.js +12 -13
- package/src/core/preflight-consistency.js +89 -62
- package/src/core/preflight-loaders.js +48 -9
- package/src/core/preflight-model.js +6 -1
- package/src/core/preflight.js +29 -12
- package/src/core/progress.js +21 -10
- package/src/core/protocol-info.js +68 -0
- package/src/core/protocol-migration.js +7 -6
- package/src/core/protocol.js +20 -0
- package/src/core/reconcile-closure.js +281 -67
- package/src/core/recovery-history.js +67 -47
- package/src/core/reflection.js +72 -77
- package/src/core/responsibility.js +25 -13
- package/src/core/resumability.js +154 -44
- package/src/core/route-artifact.js +28 -4
- package/src/core/router.js +67 -1
- package/src/core/runtime-context.js +123 -61
- package/src/core/schema-validation.js +11 -3
- package/src/core/security-review/constants.js +64 -0
- package/src/core/security-review/normalize.js +245 -0
- package/src/core/security-review/provider.js +204 -0
- package/src/core/security-review/service.js +134 -0
- package/src/core/semantic-planning/constants.js +19 -0
- package/src/core/semantic-planning/projection.js +94 -0
- package/src/core/semantic-planning/service.js +15 -0
- package/src/core/settlement.js +12 -1
- package/src/core/signing/bundle-file.js +53 -0
- package/src/core/signing/bundle-reference.js +15 -0
- package/src/core/signing/sigstore.js +60 -33
- package/src/core/signing/statement-file.js +27 -0
- package/src/core/sources.js +37 -0
- package/src/core/storage-runtime-registry.js +41 -0
- package/src/core/structural-quality/artifacts.js +20 -14
- package/src/core/structural-quality/service.js +81 -56
- package/src/core/target-layout.js +1 -1
- package/src/core/task-claim-state.js +249 -15
- package/src/core/task-command.js +21 -1
- package/src/core/task-conflict-inspection.js +133 -102
- package/src/core/task-descriptor.js +29 -22
- package/src/core/task-discovery.js +288 -90
- package/src/core/task-lock.js +66 -306
- package/src/core/task-migration-validation.js +5 -5
- package/src/core/task-migration.js +17 -287
- package/src/core/task-paths.js +13 -0
- package/src/core/task-recovery.js +1 -0
- package/src/core/task-snapshot.js +51 -8
- package/src/core/task-storage-migration.js +18 -0
- package/src/core/templates.js +23 -8
- package/src/core/test-intelligence/benchmarks.js +68 -0
- package/src/core/test-intelligence/inventory.js +73 -0
- package/src/core/test-intelligence/prune.js +94 -0
- package/src/core/test-intelligence/semantic-state.js +15 -0
- package/src/core/test-intelligence/service.js +40 -0
- package/src/core/test-intelligence/utility.js +50 -0
- package/src/core/trace.js +164 -105
- package/src/core/trajectory-evaluation.js +15 -3
- package/src/core/trajectory-metrics.js +47 -32
- package/src/core/transaction.js +16 -287
- package/src/core/verification-scope.js +20 -12
- package/src/core/work-state.js +100 -59
- package/src/integration.d.ts +492 -0
- package/src/integration.js +54 -0
- package/src/providers/README.md +47 -0
- package/src/providers/capabilities.js +46 -0
- package/src/providers/errors.js +15 -0
- package/src/providers/index.js +29 -0
- package/src/providers/json-snapshot.js +105 -0
- package/src/providers/registry.js +152 -0
- package/src/repository-index/binary-manager.js +7 -5
- package/src/storage/artifact-bytes.js +22 -0
- package/src/storage/attachment-binding.js +11 -0
- package/src/storage/attachment-files.js +82 -0
- package/src/storage/attachment-references.js +36 -0
- package/src/storage/backup.js +53 -0
- package/src/storage/capabilities.js +31 -0
- package/src/storage/connection-owner.js +119 -0
- package/src/storage/connection.js +376 -0
- package/src/storage/existing-project-scope.js +45 -0
- package/src/storage/export-destination.js +44 -0
- package/src/storage/exporter.js +284 -0
- package/src/storage/file-durability.js +46 -0
- package/src/storage/importer.js +461 -0
- package/src/storage/index.js +74 -0
- package/src/storage/leases.js +63 -0
- package/src/storage/ledger-event-snapshot.js +150 -0
- package/src/storage/ledger-relations.js +220 -0
- package/src/storage/legacy-maintenance-boundary.js +18 -0
- package/src/storage/legacy-signature.js +115 -0
- package/src/storage/maintenance-handoff.js +129 -0
- package/src/storage/maintenance-owner.js +42 -0
- package/src/storage/maintenance.js +310 -0
- package/src/storage/metadata-json.js +35 -0
- package/src/storage/migration-activate.js +59 -0
- package/src/storage/migration-archive.js +57 -0
- package/src/storage/migration-attachment-root.js +61 -0
- package/src/storage/migration-candidate.js +398 -0
- package/src/storage/migration-publication.js +143 -0
- package/src/storage/migration-rollback-admission.js +54 -0
- package/src/storage/migration-rollback-preparation.js +132 -0
- package/src/storage/migration-rollback-publication.js +183 -0
- package/src/storage/migration-rollback-release.js +66 -0
- package/src/storage/migration-rollback-source-stage.js +103 -0
- package/src/storage/migration-rollback.js +43 -0
- package/src/storage/migration-source-partition.js +56 -0
- package/src/storage/migration-source.js +255 -0
- package/src/storage/migration-terminal.js +39 -0
- package/src/storage/migration-validation.js +75 -0
- package/src/storage/migration.js +43 -0
- package/src/storage/operational-attachments.js +49 -0
- package/src/storage/operational-context.js +50 -0
- package/src/storage/project-backup.js +98 -0
- package/src/storage/project-bootstrap.js +24 -0
- package/src/storage/project-boundary.js +79 -0
- package/src/storage/project-read-snapshot.js +18 -0
- package/src/storage/project-replacement.js +137 -0
- package/src/storage/project-restore.js +203 -0
- package/src/storage/record-definitions.js +45 -0
- package/src/storage/repository.js +507 -0
- package/src/storage/restore-journal.js +62 -0
- package/src/storage/restore-layout.js +30 -0
- package/src/storage/restore-preparation.js +84 -0
- package/src/storage/restore-replacement-archive.js +162 -0
- package/src/storage/restore-replacement-owner.js +31 -0
- package/src/storage/restore-replacement-preparation.js +128 -0
- package/src/storage/restore-replacement.js +142 -0
- package/src/storage/restore-terminal.js +32 -0
- package/src/storage/runtime.js +31 -0
- package/src/storage/schema.js +214 -0
- package/src/storage/singleton-import.js +84 -0
- package/src/storage/snapshot-continuity.js +16 -0
- package/src/storage/snapshot.js +36 -0
- package/src/storage/storage-marker.js +79 -0
- package/src/storage/task-guards.js +107 -0
- package/src/storage/topology.js +42 -0
- package/src/storage/transaction.js +179 -0
- package/src/storage/unit-of-work.js +663 -0
- package/src/storage/windows-process-incarnation.js +206 -0
- package/.forgeloop/forgeloop.gitignore +0 -4
- package/LICENSE-DOCS.md +0 -13
- package/src/core/transaction-maintenance.js +0 -70
package/src/core/reflection.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { ledgerEventsOfTypes } from "./ledger-event-collection.js";
|
|
2
|
+
import { ledgerRelationMap, ledgerRelationSet, ledgerGroupedSet } from "./ledger-relations.js";
|
|
1
3
|
import { buildTaskTrace } from "./trace.js";
|
|
2
4
|
import { readEvents } from "./events.js";
|
|
3
5
|
import { findTaskById } from "./task-discovery.js";
|
|
4
|
-
import { projectHypothesisStates } from "./hypothesis-projection.js";
|
|
6
|
+
import { projectHypothesisStates, summarizeOpenHypotheses } from "./hypothesis-projection.js";
|
|
5
7
|
import {
|
|
6
8
|
buildInformationGainProjection,
|
|
7
9
|
evaluateStructuredDiagnosticStall,
|
|
@@ -64,11 +66,9 @@ export function evaluateInterventionEffectiveness(trace, surfacesByCycle) {
|
|
|
64
66
|
}
|
|
65
67
|
|
|
66
68
|
function failedRequirementSurfacesFromEvents(events = [], state = null) {
|
|
67
|
-
const byCycle =
|
|
69
|
+
const byCycle = ledgerGroupedSet();
|
|
68
70
|
const record = (cycle, requirement) => {
|
|
69
|
-
if (
|
|
70
|
-
if (!byCycle.has(cycle)) byCycle.set(cycle, new Set());
|
|
71
|
-
byCycle.get(cycle).add(requirement);
|
|
71
|
+
if (requirement) byCycle.add(cycle, requirement);
|
|
72
72
|
};
|
|
73
73
|
for (const event of events) {
|
|
74
74
|
if (event.event !== "VERIFICATION_RECORDED") continue;
|
|
@@ -95,88 +95,83 @@ function signatureSetsByCycle(trace) {
|
|
|
95
95
|
return byCycle;
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
// Canonical failure-signature hashes for the active verification cycle.
|
|
103
|
-
const activeFailureSignatures = [...new Set(
|
|
104
|
-
taskEvents
|
|
105
|
-
.filter((event) => event.event === "VERIFICATION_RECORDED"
|
|
106
|
-
&& ["failed", "blocked"].includes(event.details?.status)
|
|
107
|
-
&& (cycle === null || event.details?.verificationCycle === cycle))
|
|
108
|
-
.map((event) => {
|
|
109
|
-
const d = event.details;
|
|
110
|
-
return computeFailureSignature({
|
|
111
|
-
requirement: d.requirement ?? d.id ?? d.checkId,
|
|
112
|
-
status: d.status,
|
|
113
|
-
exitCode: Number.isInteger(d.exitCode) ? d.exitCode : null,
|
|
114
|
-
failureToken: typeof d.failureToken === "string" && d.failureToken
|
|
115
|
-
? d.failureToken
|
|
116
|
-
: (typeof d.details?.failureToken === "string" ? d.details.failureToken : null),
|
|
117
|
-
});
|
|
118
|
-
}),
|
|
119
|
-
)].sort();
|
|
120
|
-
|
|
121
|
-
const activeFailedRequirements = [...new Set(
|
|
122
|
-
[
|
|
123
|
-
...taskEvents
|
|
124
|
-
.filter((event) => event.event === "VERIFICATION_RECORDED"
|
|
125
|
-
&& ["failed", "blocked"].includes(event.details?.status)
|
|
126
|
-
&& (cycle === null || event.details?.verificationCycle === cycle))
|
|
127
|
-
.map((event) => event.details?.requirement ?? event.details?.id ?? event.details?.checkId),
|
|
128
|
-
...((state?.checks ?? []))
|
|
129
|
-
.filter((check) => ["failed", "blocked"].includes(check.status)
|
|
130
|
-
&& (cycle === null || check.details?.verificationCycle === cycle))
|
|
131
|
-
.map((check) => check.requirement ?? check.id ?? check.checkId),
|
|
132
|
-
].filter(Boolean),
|
|
133
|
-
)].sort();
|
|
98
|
+
function* contextEvents(events, types, taskId) {
|
|
99
|
+
for (const event of ledgerEventsOfTypes(events, types)) if (!taskId || event.taskId === taskId) yield event;
|
|
100
|
+
}
|
|
134
101
|
|
|
135
|
-
|
|
136
|
-
|
|
102
|
+
function contextFailureSignature(details) {
|
|
103
|
+
return computeFailureSignature({ requirement: details.requirement ?? details.id ?? details.checkId,
|
|
104
|
+
status: details.status, exitCode: Number.isInteger(details.exitCode) ? details.exitCode : null,
|
|
105
|
+
failureToken: typeof details.failureToken === "string" && details.failureToken
|
|
106
|
+
? details.failureToken : (typeof details.details?.failureToken === "string" ? details.details.failureToken : null) });
|
|
107
|
+
}
|
|
137
108
|
|
|
138
|
-
|
|
139
|
-
|
|
109
|
+
function activeFailure(details, cycle, observedCycle) {
|
|
110
|
+
return ["failed", "blocked"].includes(details?.status) && (cycle === null || observedCycle === cycle);
|
|
111
|
+
}
|
|
140
112
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
113
|
+
function activeContextFailures(events, state, cycle) {
|
|
114
|
+
const signatures = new Set();
|
|
115
|
+
const requirements = new Set();
|
|
116
|
+
for (const event of contextEvents(events, ["VERIFICATION_RECORDED"], state?.taskId)) {
|
|
117
|
+
const details = event.details;
|
|
118
|
+
if (!activeFailure(details, cycle, details?.verificationCycle)) continue;
|
|
119
|
+
signatures.add(contextFailureSignature(details));
|
|
120
|
+
const requirement = details.requirement ?? details.id ?? details.checkId;
|
|
121
|
+
if (requirement) requirements.add(requirement);
|
|
122
|
+
}
|
|
123
|
+
for (const check of state?.checks ?? []) {
|
|
124
|
+
if (!activeFailure(check, cycle, check.details?.verificationCycle)) continue;
|
|
125
|
+
const requirement = check.requirement ?? check.id ?? check.checkId;
|
|
126
|
+
if (requirement) requirements.add(requirement);
|
|
127
|
+
}
|
|
128
|
+
return { activeFailureSignatures: [...signatures].sort(), activeFailedRequirements: [...requirements].sort() };
|
|
129
|
+
}
|
|
149
130
|
|
|
150
|
-
|
|
151
|
-
|
|
131
|
+
function contextInterventions(events, taskId) {
|
|
132
|
+
const groups = ledgerRelationMap();
|
|
133
|
+
const lastCycles = ledgerRelationMap();
|
|
134
|
+
const invalidCycles = ledgerRelationSet();
|
|
135
|
+
let latestIntervention = null;
|
|
136
|
+
for (const event of contextEvents(events, ["INTERVENTION_RECORDED"], taskId)) {
|
|
137
|
+
latestIntervention = event.details?.intervention?.id ?? null;
|
|
152
138
|
const fingerprint = event.details?.interventionSemanticFingerprint;
|
|
153
139
|
if (!fingerprint) continue;
|
|
154
|
-
|
|
155
|
-
|
|
140
|
+
groups.set(fingerprint, (groups.get(fingerprint) ?? 0) + 1);
|
|
141
|
+
const cycle = Number(event.details?.verificationCycle ?? 1);
|
|
142
|
+
if (!Number.isFinite(cycle)) invalidCycles.add(fingerprint);
|
|
143
|
+
else lastCycles.set(fingerprint, Math.max(lastCycles.get(fingerprint) ?? -Infinity, cycle));
|
|
156
144
|
}
|
|
145
|
+
return { groups, lastCycles, invalidCycles, latestIntervention };
|
|
146
|
+
}
|
|
157
147
|
|
|
148
|
+
export function deriveDiagnosticContext(events = [], state = null) {
|
|
149
|
+
const cycle = state?.verificationCycle ?? null;
|
|
150
|
+
const failures = activeContextFailures(events, state, cycle);
|
|
151
|
+
const openHypotheses = summarizeOpenHypotheses(events, state?.taskId).sort();
|
|
152
|
+
const relevant = { *[Symbol.iterator]() {
|
|
153
|
+
yield* contextEvents(events, ["VERIFICATION_RECORDED"], state?.taskId);
|
|
154
|
+
} };
|
|
155
|
+
const surfacesByCycle = failedRequirementSurfacesFromEvents(relevant, state);
|
|
156
|
+
const cycles = new Set();
|
|
157
|
+
for (const event of contextEvents(events, ["VERIFICATION_STARTED"], state?.taskId)) {
|
|
158
|
+
if (Number.isInteger(event.details?.verificationCycle)) cycles.add(event.details.verificationCycle);
|
|
159
|
+
}
|
|
160
|
+
const completedCycles = [...cycles].sort((a, b) => a - b);
|
|
161
|
+
const { groups, lastCycles, invalidCycles, latestIntervention } = contextInterventions(events, state?.taskId);
|
|
158
162
|
const doNotRepeat = [];
|
|
159
|
-
for (const [fingerprint,
|
|
160
|
-
if (
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
163
|
+
for (const [fingerprint, countOfInterventions] of groups) {
|
|
164
|
+
if (countOfInterventions < 2 || invalidCycles.has(fingerprint)) continue;
|
|
165
|
+
const lastCycle = lastCycles.get(fingerprint);
|
|
166
|
+
let count = 0;
|
|
167
|
+
let latestCycle = null;
|
|
168
|
+
for (const completed of completedCycles) if (completed > lastCycle) { count++; latestCycle = completed; }
|
|
169
|
+
if (count < 2) continue;
|
|
170
|
+
const firstSurface = [...(surfacesByCycle.values(lastCycle))].sort();
|
|
171
|
+
const latestSurface = [...(surfacesByCycle.values(latestCycle))].sort();
|
|
172
|
+
if (JSON.stringify(firstSurface) === JSON.stringify(latestSurface)) doNotRepeat.push(fingerprint);
|
|
169
173
|
}
|
|
170
|
-
doNotRepeat.sort();
|
|
171
|
-
|
|
172
|
-
return {
|
|
173
|
-
activeFailureSignatures,
|
|
174
|
-
activeFailedRequirements,
|
|
175
|
-
openHypotheses,
|
|
176
|
-
latestIntervention,
|
|
177
|
-
nextExperiment: null,
|
|
178
|
-
doNotRepeat,
|
|
179
|
-
};
|
|
174
|
+
return { ...failures, openHypotheses, latestIntervention, nextExperiment: null, doNotRepeat: doNotRepeat.sort() };
|
|
180
175
|
}
|
|
181
176
|
|
|
182
177
|
export async function buildTaskReflection({ target, packageRoot, taskId = null } = {}) {
|
|
@@ -10,7 +10,9 @@ import { resolveTaskClaimState } from "./task-claim-state.js";
|
|
|
10
10
|
import { normalizeWriteClaims } from "./task-scope.js";
|
|
11
11
|
import { taskResponsibilityPath } from "./task-paths.js";
|
|
12
12
|
import { ensureWithin, fileExists } from "./filesystem.js";
|
|
13
|
-
import {
|
|
13
|
+
import { getOperationalStore, operationalArtifactExists } from "../storage/operational-context.js";
|
|
14
|
+
import { withNativeReadScope } from "./native-storage.js";
|
|
15
|
+
import { appendProtocolEvent, withEventLedgerAudit } from "./events.js";
|
|
14
16
|
import { withTaskTransaction } from "./transaction.js";
|
|
15
17
|
import { assertTaskMutationAllowed } from "./task-claim-state.js";
|
|
16
18
|
import { assertWorkspaceBinding } from "./workspace-binding.js";
|
|
@@ -118,17 +120,19 @@ export async function validateResponsibilityContract(value, packageRoot = getPac
|
|
|
118
120
|
}
|
|
119
121
|
|
|
120
122
|
export async function readResponsibility(target, { taskId, packageRoot = getPackageRoot() } = {}) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
123
|
+
return withNativeReadScope(target, async () => {
|
|
124
|
+
const relativePath = taskResponsibilityPath(taskId);
|
|
125
|
+
if (!(operationalArtifactExists(target, relativePath) ?? await fileExists(ensureWithin(target, relativePath)))) return null;
|
|
126
|
+
try {
|
|
127
|
+
const artifact = await readJsonArtifact(target, relativePath, "responsibility", packageRoot);
|
|
128
|
+
const value = await validateResponsibilityContract(artifact.value, packageRoot);
|
|
129
|
+
if (value.taskId !== taskId) throw responsibilityError("E_RESPONSIBILITY_INVALID", "Responsibility taskId does not match its task namespace", [relativePath]);
|
|
130
|
+
return { ...artifact, value };
|
|
131
|
+
} catch (error) {
|
|
132
|
+
if (error.code === "E_RESPONSIBILITY_INVALID") throw error;
|
|
133
|
+
throw responsibilityError("E_RESPONSIBILITY_INVALID", `Responsibility contract is invalid: ${error.message}`, [relativePath]);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
132
136
|
}
|
|
133
137
|
|
|
134
138
|
async function currentResponsibilityInputs(target, { taskId, packageRoot }) {
|
|
@@ -173,6 +177,14 @@ export function validateResponsibilityChecks(responsibility, state) {
|
|
|
173
177
|
}
|
|
174
178
|
|
|
175
179
|
export async function resolveResponsibilityStatus(target, { taskId, packageRoot = getPackageRoot() } = {}) {
|
|
180
|
+
const read = () => resolveLoadedResponsibilityStatus(target, { taskId, packageRoot });
|
|
181
|
+
return withNativeReadScope(target, () => {
|
|
182
|
+
if (getOperationalStore(target)) return withEventLedgerAudit(target, packageRoot, { taskId }, read);
|
|
183
|
+
return read();
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async function resolveLoadedResponsibilityStatus(target, { taskId, packageRoot }) {
|
|
176
188
|
const artifact = await readResponsibility(target, { taskId, packageRoot });
|
|
177
189
|
if (!artifact) return { status: "NOT_APPLICABLE", taskId, path: taskResponsibilityPath(taskId), responsibility: null, errors: [] };
|
|
178
190
|
let inputs;
|
|
@@ -245,7 +257,7 @@ export async function setResponsibilityContract(target, options = {}) {
|
|
|
245
257
|
return withTaskTransaction({ target, taskId, packageRoot, operation: "responsibility-set", recordCommitEvent: true }, async () => {
|
|
246
258
|
await assertTaskMutationAllowed(target, { taskId, packageRoot });
|
|
247
259
|
await assertWorkspaceBinding(target, { taskId, packageRoot, operation: "responsibility-set" });
|
|
248
|
-
if (await fileExists(ensureWithin(target, relativePath))) {
|
|
260
|
+
if (operationalArtifactExists(target, relativePath) ?? await fileExists(ensureWithin(target, relativePath))) {
|
|
249
261
|
throw responsibilityError("E_RESPONSIBILITY_INVALID", "Responsibility contract is immutable during a pass", [relativePath]);
|
|
250
262
|
}
|
|
251
263
|
const state = await readWorkState(target, { packageRoot, taskId });
|
package/src/core/resumability.js
CHANGED
|
@@ -4,6 +4,12 @@ import { createWorkState, initializeWorkState, readWorkState, mutateWorkState }
|
|
|
4
4
|
|
|
5
5
|
const DEFAULT_PENDING_STEPS = ["planning", "implementation", "verification"];
|
|
6
6
|
|
|
7
|
+
const CURRENT_ROUTE_CHECKPOINT_PHASES = new Set(["ROUTED"]);
|
|
8
|
+
|
|
9
|
+
export function routeCheckpointMustMatchCurrentRoute(phase) {
|
|
10
|
+
return CURRENT_ROUTE_CHECKPOINT_PHASES.has(phase);
|
|
11
|
+
}
|
|
12
|
+
|
|
7
13
|
/**
|
|
8
14
|
* Resume phase derived from the highest lifecycle milestone already recorded in
|
|
9
15
|
* a validated ledger. Recreating a checkpoint at ROUTED for a task whose ledger
|
|
@@ -16,9 +22,88 @@ const RESUME_PHASE_BY_MILESTONE = Object.freeze({
|
|
|
16
22
|
EXECUTION_STARTED: "EXECUTING",
|
|
17
23
|
VERIFICATION_STARTED: "VERIFYING",
|
|
18
24
|
VERIFICATION_RECORDED: "VERIFYING",
|
|
25
|
+
REVIEW_STARTED: "REVIEWING",
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const RESUME_STEPS_BY_PHASE = Object.freeze({
|
|
29
|
+
CONTRACT_READY: {
|
|
30
|
+
completedSteps: ["contract"],
|
|
31
|
+
pendingSteps: ["route", ...DEFAULT_PENDING_STEPS],
|
|
32
|
+
},
|
|
33
|
+
ROUTED: {
|
|
34
|
+
completedSteps: ["contract", "route"],
|
|
35
|
+
pendingSteps: [...DEFAULT_PENDING_STEPS],
|
|
36
|
+
},
|
|
37
|
+
PLANNED: {
|
|
38
|
+
completedSteps: ["contract", "route", "planning"],
|
|
39
|
+
pendingSteps: [...DEFAULT_PENDING_STEPS.filter((step) => step !== "planning")],
|
|
40
|
+
},
|
|
41
|
+
EXECUTING: {
|
|
42
|
+
completedSteps: ["contract", "route", "planning", "implementation"],
|
|
43
|
+
pendingSteps: ["verification"],
|
|
44
|
+
},
|
|
45
|
+
VERIFYING: {
|
|
46
|
+
completedSteps: ["contract", "route", "planning", "implementation"],
|
|
47
|
+
pendingSteps: ["verification"],
|
|
48
|
+
},
|
|
49
|
+
REVIEWING: {
|
|
50
|
+
completedSteps: ["contract", "route", "planning", "implementation", "verification"],
|
|
51
|
+
pendingSteps: [],
|
|
52
|
+
},
|
|
19
53
|
});
|
|
20
54
|
|
|
21
|
-
|
|
55
|
+
function cycleEvents(events) {
|
|
56
|
+
const CYCLE_EVENT_NAMES = new Set([
|
|
57
|
+
"VERIFICATION_STARTED",
|
|
58
|
+
"VERIFICATION_RECORDED",
|
|
59
|
+
"DIAGNOSIS_RECORDED",
|
|
60
|
+
"DIAGNOSTIC_CASE_RECORDED",
|
|
61
|
+
]);
|
|
62
|
+
const cycles = events
|
|
63
|
+
.filter((event) => CYCLE_EVENT_NAMES.has(event.event))
|
|
64
|
+
.map((event) => event.details?.verificationCycle)
|
|
65
|
+
.filter((cycle) => Number.isInteger(cycle) && cycle >= 1);
|
|
66
|
+
return cycles.at(-1);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Canonical reconstruction projection: for a resumed phase derived from a
|
|
71
|
+
* validated ledger, returns the single source of truth for completedSteps and
|
|
72
|
+
* pendingSteps. Unknown phases fall back to the ROUTED projection, matching the
|
|
73
|
+
* historical default resume checkpoint.
|
|
74
|
+
*/
|
|
75
|
+
export function resumeStepsForPhase(phase) {
|
|
76
|
+
return RESUME_STEPS_BY_PHASE[phase] ?? RESUME_STEPS_BY_PHASE.ROUTED;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Canonical verification cycle derived from the ledger when history proves
|
|
81
|
+
* verification has started; undefined when the ledger has no cycle metadata.
|
|
82
|
+
*/
|
|
83
|
+
export function resumeVerificationCycleForPhase(events) {
|
|
84
|
+
return cycleEvents(events);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Canonical work-state identity fields for reconstruction from a validated
|
|
89
|
+
* ledger: resumed phase, completedSteps, pendingSteps, and verificationCycle.
|
|
90
|
+
* This is the one projection shared by ensureResumableState and
|
|
91
|
+
* contract-create reconstruction so later phases cannot be reconstructed with
|
|
92
|
+
* contradictory steps.
|
|
93
|
+
*/
|
|
94
|
+
export function buildResumableWorkStateFields({ events, resumedPhase }) {
|
|
95
|
+
const steps = resumeStepsForPhase(resumedPhase);
|
|
96
|
+
return {
|
|
97
|
+
phase: resumedPhase,
|
|
98
|
+
completedSteps: [...steps.completedSteps],
|
|
99
|
+
pendingSteps: [...steps.pendingSteps],
|
|
100
|
+
...(events && events.length > 0 && cycleEvents(events) !== undefined
|
|
101
|
+
? { verificationCycle: cycleEvents(events) }
|
|
102
|
+
: {}),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export async function deriveResumePhaseFromLedger(target, packageRoot, taskId) {
|
|
22
107
|
let ledger;
|
|
23
108
|
try {
|
|
24
109
|
ledger = await validateEventLedger(target, packageRoot, { taskId });
|
|
@@ -32,71 +117,37 @@ async function deriveResumePhaseFromLedger(target, packageRoot, taskId) {
|
|
|
32
117
|
let derived = null;
|
|
33
118
|
for (const event of scoped) {
|
|
34
119
|
const phase = positions[event.event];
|
|
35
|
-
if (
|
|
36
|
-
if (!derived) {
|
|
37
|
-
derived = phase;
|
|
38
|
-
continue;
|
|
39
|
-
}
|
|
40
|
-
if (phase === "VERIFYING") derived = "VERIFYING";
|
|
120
|
+
if (phase) derived = phase;
|
|
41
121
|
}
|
|
42
122
|
return derived;
|
|
43
123
|
}
|
|
44
124
|
|
|
45
|
-
function deriveVerificationCycleFromLedger(events) {
|
|
46
|
-
const cycleEvents = new Set([
|
|
47
|
-
"VERIFICATION_STARTED",
|
|
48
|
-
"VERIFICATION_RECORDED",
|
|
49
|
-
"DIAGNOSIS_RECORDED",
|
|
50
|
-
"DIAGNOSTIC_CASE_RECORDED",
|
|
51
|
-
]);
|
|
52
|
-
const cycles = events
|
|
53
|
-
.filter((event) => cycleEvents.has(event.event))
|
|
54
|
-
.map((event) => event.details?.verificationCycle)
|
|
55
|
-
.filter((cycle) => Number.isInteger(cycle) && cycle >= 1);
|
|
56
|
-
return cycles.at(-1);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function resumeSteps(phase) {
|
|
60
|
-
if (phase === "EXECUTING" || phase === "VERIFYING") {
|
|
61
|
-
return {
|
|
62
|
-
completedSteps: ["contract", "route", "planning", "implementation"],
|
|
63
|
-
pendingSteps: ["verification"],
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
return {
|
|
67
|
-
completedSteps: ["contract", "route"],
|
|
68
|
-
pendingSteps: [...DEFAULT_PENDING_STEPS],
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
|
|
72
125
|
export async function ensureResumableState({ target, packageRoot, contract, route, taskId, statePath }) {
|
|
73
126
|
if (!contract || !route) return null;
|
|
74
127
|
const existing = await readWorkState(target, { packageRoot, taskId, statePath });
|
|
75
128
|
if (existing) return existing;
|
|
76
129
|
|
|
77
130
|
const resumedPhase = await deriveResumePhaseFromLedger(target, packageRoot, taskId) ?? "ROUTED";
|
|
78
|
-
let
|
|
131
|
+
let events = [];
|
|
79
132
|
try {
|
|
80
133
|
const ledger = await validateEventLedger(target, packageRoot, { taskId });
|
|
81
134
|
if (ledger.valid) {
|
|
82
|
-
|
|
83
|
-
(ledger.events ?? []).filter((event) => !taskId || event.taskId === taskId),
|
|
84
|
-
);
|
|
135
|
+
events = (ledger.events ?? []).filter((event) => !taskId || event.taskId === taskId);
|
|
85
136
|
}
|
|
86
137
|
} catch {
|
|
87
|
-
|
|
138
|
+
events = [];
|
|
88
139
|
}
|
|
89
|
-
const
|
|
140
|
+
const projection = buildResumableWorkStateFields({ events, resumedPhase });
|
|
90
141
|
const state = createWorkState({
|
|
91
142
|
taskId: contract.value.taskId,
|
|
92
143
|
contractFingerprint: contract.fingerprint,
|
|
93
144
|
routeFingerprint: route.fingerprint,
|
|
94
145
|
repositoryFingerprint: await currentRepositoryFingerprint(target),
|
|
95
|
-
phase:
|
|
146
|
+
phase: projection.phase,
|
|
96
147
|
selectedGuides: route.value.guides,
|
|
97
|
-
completedSteps:
|
|
98
|
-
pendingSteps:
|
|
99
|
-
...(verificationCycle !== undefined ? { verificationCycle } : {}),
|
|
148
|
+
completedSteps: projection.completedSteps,
|
|
149
|
+
pendingSteps: projection.pendingSteps,
|
|
150
|
+
...(projection.verificationCycle !== undefined ? { verificationCycle: projection.verificationCycle } : {}),
|
|
100
151
|
checks: [],
|
|
101
152
|
failures: [],
|
|
102
153
|
blockers: [],
|
|
@@ -105,6 +156,65 @@ export async function ensureResumableState({ target, packageRoot, contract, rout
|
|
|
105
156
|
return initializeWorkState(target, state, { packageRoot, taskId, statePath });
|
|
106
157
|
}
|
|
107
158
|
|
|
159
|
+
function routeSyncError(code, message) {
|
|
160
|
+
const error = new Error(message);
|
|
161
|
+
error.code = code;
|
|
162
|
+
return error;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function sameStringList(left, right) {
|
|
166
|
+
const a = [...(left ?? [])].sort();
|
|
167
|
+
const b = [...(right ?? [])].sort();
|
|
168
|
+
return a.length === b.length && a.every((value, index) => value === b[index]);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Rebinds an existing ROUTED checkpoint to an already-persisted route.
|
|
173
|
+
*
|
|
174
|
+
* Only the route-bound identity fields are updated; contract, repository,
|
|
175
|
+
* steps, checks, evidence, and phase are preserved. Same-identity reruns are
|
|
176
|
+
* a no-op. Every other situation fails closed.
|
|
177
|
+
*/
|
|
178
|
+
export async function synchronizePersistedRouteState({ target, packageRoot, taskId, route, contract = null, statePath } = {}) {
|
|
179
|
+
if (!route || !route.value || typeof route.fingerprint !== "string") {
|
|
180
|
+
throw routeSyncError("E_ROUTE_STALE", "A persisted route artifact is required to synchronize checkpoint identity");
|
|
181
|
+
}
|
|
182
|
+
const state = await readWorkState(target, { packageRoot, taskId, statePath });
|
|
183
|
+
if (!state) return null;
|
|
184
|
+
if (state.phase !== "ROUTED") {
|
|
185
|
+
throw routeSyncError(
|
|
186
|
+
"E_ROUTE_PHASE_UNSUPPORTED",
|
|
187
|
+
`Route checkpoint synchronization supports phase ROUTED, found ${state.phase}`,
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
if (taskId && state.taskId !== taskId) {
|
|
191
|
+
throw routeSyncError("E_ROUTE_STALE", "Work state does not belong to the current route task");
|
|
192
|
+
}
|
|
193
|
+
if (contract && state.contractFingerprint !== contract.fingerprint) {
|
|
194
|
+
// Contract evolution with regenerated routing is a pre-existing flow whose
|
|
195
|
+
// stale checkpoint is recovered through the sanctioned clear-state and
|
|
196
|
+
// preflight recreation path. Never rebind route identity across a contract
|
|
197
|
+
// boundary here; leave the checkpoint untouched.
|
|
198
|
+
return state;
|
|
199
|
+
}
|
|
200
|
+
if (route.value.contractFingerprint !== undefined && state.contractFingerprint !== route.value.contractFingerprint) {
|
|
201
|
+
return state;
|
|
202
|
+
}
|
|
203
|
+
if (state.routeFingerprint === route.fingerprint && sameStringList(state.selectedGuides, route.value.guides)) {
|
|
204
|
+
return state;
|
|
205
|
+
}
|
|
206
|
+
return mutateWorkState(target, {
|
|
207
|
+
expectedRevision: state.revision ?? 0,
|
|
208
|
+
packageRoot,
|
|
209
|
+
taskId,
|
|
210
|
+
statePath,
|
|
211
|
+
}, () => ({
|
|
212
|
+
...state,
|
|
213
|
+
routeFingerprint: route.fingerprint,
|
|
214
|
+
selectedGuides: [...route.value.guides],
|
|
215
|
+
}));
|
|
216
|
+
}
|
|
217
|
+
|
|
108
218
|
export async function synchronizePreflightState({
|
|
109
219
|
target,
|
|
110
220
|
packageRoot,
|
|
@@ -1,17 +1,27 @@
|
|
|
1
1
|
import { assertRouteInvariants } from "./router.js";
|
|
2
2
|
import { ARTIFACT_PATHS, readJsonArtifact, writeJsonArtifact } from "./artifacts.js";
|
|
3
3
|
import { readContract } from "./contract.js";
|
|
4
|
-
import { ensureResumableState } from "./resumability.js";
|
|
4
|
+
import { ensureResumableState, synchronizePersistedRouteState } from "./resumability.js";
|
|
5
|
+
import { readWorkState } from "./work-state.js";
|
|
5
6
|
import { taskArtifactPath } from "./task-paths.js";
|
|
7
|
+
import { getTaskTransaction, withTaskTransaction } from "./transaction.js";
|
|
8
|
+
|
|
9
|
+
|
|
6
10
|
|
|
7
11
|
export async function persistRoute(target, route, packageRoot, options = {}) {
|
|
12
|
+
if (!options.taskId) return persistRouteInTransaction(target, route, packageRoot, options);
|
|
13
|
+
return withTaskTransaction({ target, taskId: options.taskId, packageRoot, operation: "route" }, () => persistRouteInTransaction(target, route, packageRoot, options));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function persistRouteInTransaction(target, route, packageRoot, options) {
|
|
8
17
|
assertRouteInvariants(route);
|
|
9
|
-
const { contractFingerprint, ...writeOptions } = options;
|
|
18
|
+
const { contractFingerprint: suppliedFingerprint, ...writeOptions } = options;
|
|
19
|
+
let contractFingerprint = suppliedFingerprint;
|
|
10
20
|
let contractArtifact = null;
|
|
11
|
-
if (contractFingerprint === undefined) {
|
|
21
|
+
if (contractFingerprint === undefined || !options.taskId) {
|
|
12
22
|
try {
|
|
13
23
|
contractArtifact = await readContract(target, packageRoot, options);
|
|
14
|
-
contractFingerprint = contractArtifact.fingerprint;
|
|
24
|
+
if (contractFingerprint === undefined) contractFingerprint = contractArtifact.fingerprint;
|
|
15
25
|
} catch (error) {
|
|
16
26
|
if (error.code !== "ARTIFACT_MISSING") throw error;
|
|
17
27
|
}
|
|
@@ -21,6 +31,17 @@ export async function persistRoute(target, route, packageRoot, options = {}) {
|
|
|
21
31
|
: { ...route, contractFingerprint };
|
|
22
32
|
assertRouteInvariants(value);
|
|
23
33
|
const taskId = options.taskId ?? contractArtifact?.value?.taskId ?? null;
|
|
34
|
+
if (taskId && !(await getTaskTransaction(target))) {
|
|
35
|
+
return withTaskTransaction({ target, taskId, packageRoot, operation: "route" }, () => persistRouteInTransaction(target, route, packageRoot, { ...options, taskId }));
|
|
36
|
+
}
|
|
37
|
+
let existingState = null;
|
|
38
|
+
if (taskId) {
|
|
39
|
+
try {
|
|
40
|
+
existingState = await readWorkState(target, { packageRoot, taskId });
|
|
41
|
+
} catch {
|
|
42
|
+
existingState = null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
24
45
|
const relPath = options.routePath ?? options.routeFile ?? options.relativePath ?? (taskId ? taskArtifactPath(taskId, "route") : ARTIFACT_PATHS.route);
|
|
25
46
|
const artifact = await writeJsonArtifact(
|
|
26
47
|
target,
|
|
@@ -33,6 +54,9 @@ export async function persistRoute(target, route, packageRoot, options = {}) {
|
|
|
33
54
|
if (contractArtifact && contractArtifact.fingerprint === artifact.value.contractFingerprint) {
|
|
34
55
|
await ensureResumableState({ target, packageRoot, contract: contractArtifact, route: artifact, taskId });
|
|
35
56
|
}
|
|
57
|
+
if (existingState?.phase === "ROUTED") {
|
|
58
|
+
await synchronizePersistedRouteState({ target, packageRoot, taskId, route: artifact, contract: contractArtifact });
|
|
59
|
+
}
|
|
36
60
|
return artifact;
|
|
37
61
|
}
|
|
38
62
|
|
package/src/core/router.js
CHANGED
|
@@ -313,6 +313,64 @@ function reasonForSignal(prefix, signal) {
|
|
|
313
313
|
return `${prefix}_${signal.toUpperCase().replaceAll("-", "_")}`;
|
|
314
314
|
}
|
|
315
315
|
|
|
316
|
+
const SECURITY_TRUST_BOUNDARY_RISKS = Object.freeze([
|
|
317
|
+
"untrusted-input", "personal-data", "secrets", "external-service", "publication",
|
|
318
|
+
]);
|
|
319
|
+
|
|
320
|
+
// Canonical deterministic safety floor. These are the route reason codes the
|
|
321
|
+
// router assigns when a trust-boundary signal selects the security guide. The
|
|
322
|
+
// semantic exclusion policy reads protection from this same source, so a
|
|
323
|
+
// mandatory safety guide can never drift out of protection (for example the
|
|
324
|
+
// previously missing external-service boundary).
|
|
325
|
+
const MANDATORY_SAFETY_REASONS = Object.freeze(new Set([
|
|
326
|
+
"SURFACE_AUTH",
|
|
327
|
+
...SECURITY_TRUST_BOUNDARY_RISKS.map((risk) => reasonForSignal("RISK", risk)),
|
|
328
|
+
]));
|
|
329
|
+
|
|
330
|
+
export function isMandatorySafetyReason(reason) {
|
|
331
|
+
return MANDATORY_SAFETY_REASONS.has(reason);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export function isMandatorySafetyGuide(guide, reasons) {
|
|
335
|
+
return guide === "security"
|
|
336
|
+
&& Array.isArray(reasons)
|
|
337
|
+
&& reasons.some((reason) => isMandatorySafetyReason(reason));
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function applySemanticGuideRecommendation(selected, excluded, recommendation) {
|
|
341
|
+
const guideDecision = recommendation?.guideRelevance
|
|
342
|
+
?? (recommendation?.rankedIds || recommendation?.excludedIds ? recommendation : null);
|
|
343
|
+
if (!guideDecision) return;
|
|
344
|
+
const excludedIds = new Set(Array.isArray(guideDecision.excludedIds) ? guideDecision.excludedIds : []);
|
|
345
|
+
const confidenceById = guideDecision.confidenceById ?? {};
|
|
346
|
+
for (const guide of [...selected.keys()]) {
|
|
347
|
+
const reasons = selected.get(guide);
|
|
348
|
+
if (isMandatorySafetyGuide(guide, reasons)) {
|
|
349
|
+
if (!reasons.includes("MANDATORY_SAFETY_GUIDE")) reasons.push("MANDATORY_SAFETY_GUIDE");
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
if (!excludedIds.has(guide)) {
|
|
353
|
+
if (!reasons.includes("JEV_RELEVANT")) reasons.push("JEV_RELEVANT");
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
if ((confidenceById[guide] ?? 0) < 0.75) {
|
|
357
|
+
if (!reasons.includes("JEV_LOW_CONFIDENCE_RETAINED")) reasons.push("JEV_LOW_CONFIDENCE_RETAINED");
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
selected.delete(guide);
|
|
361
|
+
excluded[guide] = ["JEV_EXCLUDED"];
|
|
362
|
+
}
|
|
363
|
+
const ranked = Array.isArray(guideDecision.rankedIds) ? guideDecision.rankedIds : [];
|
|
364
|
+
const order = new Map(ranked.map((guide, index) => [guide, index]));
|
|
365
|
+
const entries = [...selected.entries()].sort((left, right) => {
|
|
366
|
+
const leftRank = order.has(left[0]) ? order.get(left[0]) : ranked.length + [...selected.keys()].indexOf(left[0]);
|
|
367
|
+
const rightRank = order.has(right[0]) ? order.get(right[0]) : ranked.length + [...selected.keys()].indexOf(right[0]);
|
|
368
|
+
return leftRank - rightRank;
|
|
369
|
+
});
|
|
370
|
+
selected.clear();
|
|
371
|
+
for (const [guide, reasons] of entries) selected.set(guide, reasons);
|
|
372
|
+
}
|
|
373
|
+
|
|
316
374
|
function normalizeArray(value, name, allowed) {
|
|
317
375
|
if (value === undefined) return [];
|
|
318
376
|
if (!Array.isArray(value)) throw new RouteInputError(`${name} must be an array`);
|
|
@@ -440,7 +498,7 @@ export function evaluateRoute(input = {}, profileOptions = {}) {
|
|
|
440
498
|
}
|
|
441
499
|
|
|
442
500
|
for (const risk of normalized.risks) {
|
|
443
|
-
if (
|
|
501
|
+
if (SECURITY_TRUST_BOUNDARY_RISKS.includes(risk)) {
|
|
444
502
|
add("security", reasonForSignal("RISK", risk));
|
|
445
503
|
}
|
|
446
504
|
if (["critical-path", "performance"].includes(risk)) {
|
|
@@ -481,12 +539,19 @@ export function evaluateRoute(input = {}, profileOptions = {}) {
|
|
|
481
539
|
excluded[guide] = [exclusionReasonForGuide(guide, projectEvidence, normalized.workType)];
|
|
482
540
|
}
|
|
483
541
|
|
|
542
|
+
applySemanticGuideRecommendation(
|
|
543
|
+
selected,
|
|
544
|
+
excluded,
|
|
545
|
+
profileOptions.semanticRecommendation ?? null,
|
|
546
|
+
);
|
|
547
|
+
|
|
484
548
|
const guides = [...selected.keys()];
|
|
485
549
|
const result = {
|
|
486
550
|
schemaVersion: ROUTING_SCHEMA_VERSION,
|
|
487
551
|
protocolVersion: PROTOCOL_VERSION,
|
|
488
552
|
input: normalized,
|
|
489
553
|
primary: Object.prototype.hasOwnProperty.call(PRIMARY_GUIDES, normalized.workType)
|
|
554
|
+
&& guides.includes(PRIMARY_GUIDES[normalized.workType])
|
|
490
555
|
? PRIMARY_GUIDES[normalized.workType]
|
|
491
556
|
: guides[0] ?? null,
|
|
492
557
|
guides,
|
|
@@ -499,6 +564,7 @@ export function evaluateRoute(input = {}, profileOptions = {}) {
|
|
|
499
564
|
configuredProfile: profileOptions.configuredProfile ?? input.configuredProfile ?? "auto",
|
|
500
565
|
requestedProfile: profileOptions.requestedProfile
|
|
501
566
|
?? (Object.prototype.hasOwnProperty.call(input, "executionProfile") ? input.executionProfile : null),
|
|
567
|
+
semanticRecommendation: profileOptions.semanticRecommendation ?? null,
|
|
502
568
|
}),
|
|
503
569
|
};
|
|
504
570
|
return assertRouteInvariants(result);
|