@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/actions.js
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
|
|
4
1
|
import { getTaskTransaction, withTaskTransaction } from "./transaction.js";
|
|
5
|
-
import { appendProtocolEvent,
|
|
2
|
+
import { appendProtocolEvent, iterateEvents } from "./events.js";
|
|
6
3
|
import {
|
|
7
4
|
canonicalActionFingerprint,
|
|
8
5
|
actionRequiresIdempotency,
|
|
@@ -22,8 +19,7 @@ import {
|
|
|
22
19
|
E_ACTION_STATE_MISMATCH,
|
|
23
20
|
E_ACTION_VERIFICATION_REQUIRED,
|
|
24
21
|
} from "./error-codes.js";
|
|
25
|
-
import {
|
|
26
|
-
import { taskActionPath, taskDirectory, TASK_ARTIFACT_FILES } from "./task-paths.js";
|
|
22
|
+
import { requireNativeStore, withNativeReadScope } from "./native-storage.js";
|
|
27
23
|
|
|
28
24
|
const STATE_EVENT_NAMES = Object.freeze({
|
|
29
25
|
AUTHORIZED: "ACTION_AUTHORIZED",
|
|
@@ -41,61 +37,26 @@ function actionError(code, message) {
|
|
|
41
37
|
return error;
|
|
42
38
|
}
|
|
43
39
|
|
|
44
|
-
async function readActionFile(target,
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
text = await readFile(absolute, "utf8");
|
|
51
|
-
} catch (error) {
|
|
52
|
-
if (error?.code === "ENOENT") return null;
|
|
53
|
-
throw error;
|
|
54
|
-
}
|
|
55
|
-
try {
|
|
56
|
-
return JSON.parse(text);
|
|
57
|
-
} catch (error) {
|
|
58
|
-
throw actionError(E_ACTION_INVALID, `durable action artifact is not valid JSON: ${relPath}`);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
async function writeActionFile(target, packageRoot, taskId, action) {
|
|
63
|
-
const relPath = taskActionPath(taskId, action.actionId);
|
|
64
|
-
await assertSafePath(target, relPath);
|
|
65
|
-
const serialized = `${JSON.stringify(action, null, 2)}\n`;
|
|
66
|
-
const activeTransaction = (await getTaskTransaction(target));
|
|
67
|
-
if (activeTransaction) {
|
|
68
|
-
await activeTransaction.stageText(relPath, serialized);
|
|
69
|
-
} else {
|
|
70
|
-
const absolute = ensureWithin(target, relPath);
|
|
71
|
-
await mkdir(path.dirname(absolute), { recursive: true });
|
|
72
|
-
await writeFile(absolute, serialized, "utf8");
|
|
73
|
-
}
|
|
40
|
+
async function readActionFile(target, taskId, actionId) {
|
|
41
|
+
return withNativeReadScope(target, () => requireNativeStore(
|
|
42
|
+
target,
|
|
43
|
+
actionError,
|
|
44
|
+
"Actions require canonical SQLite storage; migrate legacy operational state explicitly",
|
|
45
|
+
).readAction(taskId, actionId));
|
|
74
46
|
}
|
|
75
47
|
|
|
76
|
-
function
|
|
77
|
-
|
|
48
|
+
async function writeActionFile(target, action) {
|
|
49
|
+
const transaction = await getTaskTransaction(target);
|
|
50
|
+
if (!transaction) throw actionError("E_STORAGE_TRANSACTION_INVALID", "Action persistence requires an active task transaction");
|
|
51
|
+
return transaction.stageAction(action);
|
|
78
52
|
}
|
|
79
53
|
|
|
80
|
-
async function listActionFiles(target,
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
entries = await readdir(absoluteDir);
|
|
87
|
-
} catch (error) {
|
|
88
|
-
if (error?.code === "ENOENT") return [];
|
|
89
|
-
throw error;
|
|
90
|
-
}
|
|
91
|
-
const actions = [];
|
|
92
|
-
for (const entry of entries) {
|
|
93
|
-
if (!entry.endsWith(".json")) continue;
|
|
94
|
-
const parsed = await readActionFile(target, packageRoot, taskId, entry.replace(/\.json$/, ""));
|
|
95
|
-
if (parsed) actions.push(parsed);
|
|
96
|
-
}
|
|
97
|
-
actions.sort((left, right) => String(left.createdAt).localeCompare(String(right.createdAt)));
|
|
98
|
-
return actions;
|
|
54
|
+
async function listActionFiles(target, taskId) {
|
|
55
|
+
return withNativeReadScope(target, () => requireNativeStore(
|
|
56
|
+
target,
|
|
57
|
+
actionError,
|
|
58
|
+
"Action listing requires canonical SQLite storage; migrate legacy operational state explicitly",
|
|
59
|
+
).listActions(taskId).sort((left, right) => String(left.createdAt).localeCompare(String(right.createdAt))));
|
|
99
60
|
}
|
|
100
61
|
|
|
101
62
|
function assertProposeInput(input) {
|
|
@@ -152,7 +113,7 @@ export async function proposeAction(target, { packageRoot, taskId, input }) {
|
|
|
152
113
|
);
|
|
153
114
|
}
|
|
154
115
|
|
|
155
|
-
const existingById = await readActionFile(target,
|
|
116
|
+
const existingById = await readActionFile(target, taskId, input.actionId);
|
|
156
117
|
if (existingById) {
|
|
157
118
|
if (existingById.actionFingerprint === identityFingerprint) {
|
|
158
119
|
validateActionArtifact(existingById);
|
|
@@ -185,7 +146,7 @@ export async function proposeAction(target, { packageRoot, taskId, input }) {
|
|
|
185
146
|
};
|
|
186
147
|
validateActionArtifact(action);
|
|
187
148
|
|
|
188
|
-
await writeActionFile(target,
|
|
149
|
+
await writeActionFile(target, action);
|
|
189
150
|
await appendProtocolEvent(target, {
|
|
190
151
|
taskId,
|
|
191
152
|
event: "ACTION_PROPOSED",
|
|
@@ -207,25 +168,28 @@ export async function proposeAction(target, { packageRoot, taskId, input }) {
|
|
|
207
168
|
);
|
|
208
169
|
}
|
|
209
170
|
|
|
210
|
-
export async function readAction(target, {
|
|
211
|
-
const action = await readActionFile(target,
|
|
171
|
+
export async function readAction(target, { taskId, actionId }) {
|
|
172
|
+
const action = await readActionFile(target, taskId, actionId);
|
|
212
173
|
if (!action) {
|
|
213
174
|
throw actionError(E_ACTION_NOT_FOUND, `durable action ${actionId} does not exist for task ${taskId}`);
|
|
214
175
|
}
|
|
215
176
|
return validateActionArtifact(action);
|
|
216
177
|
}
|
|
217
178
|
|
|
218
|
-
export async function listActions(target, {
|
|
219
|
-
const actions = await listActionFiles(target,
|
|
179
|
+
export async function listActions(target, { taskId }) {
|
|
180
|
+
const actions = await listActionFiles(target, taskId);
|
|
220
181
|
return actions.map((action) => validateActionArtifact(action));
|
|
221
182
|
}
|
|
222
183
|
|
|
223
|
-
export async function findActionByIdempotencyKey(target, {
|
|
184
|
+
export async function findActionByIdempotencyKey(target, { taskId, idempotencyKey }) {
|
|
224
185
|
if (typeof idempotencyKey !== "string" || !idempotencyKey) {
|
|
225
186
|
throw actionError(E_ACTION_INVALID, "idempotencyKey must be a non-empty string");
|
|
226
187
|
}
|
|
227
|
-
const
|
|
228
|
-
|
|
188
|
+
const found = await withNativeReadScope(target, () => requireNativeStore(
|
|
189
|
+
target,
|
|
190
|
+
actionError,
|
|
191
|
+
"Action listing requires canonical SQLite storage; migrate legacy operational state explicitly",
|
|
192
|
+
).findActionByIdempotencyKey(taskId, idempotencyKey));
|
|
229
193
|
return found ? validateActionArtifact(found) : null;
|
|
230
194
|
}
|
|
231
195
|
|
|
@@ -289,7 +253,7 @@ async function applyTransition(target, {
|
|
|
289
253
|
next.commitResultCode = details.commitResultCode;
|
|
290
254
|
}
|
|
291
255
|
validateActionArtifact(next);
|
|
292
|
-
await writeActionFile(target,
|
|
256
|
+
await writeActionFile(target, next);
|
|
293
257
|
|
|
294
258
|
const baseDetails = {
|
|
295
259
|
actionId: next.actionId,
|
|
@@ -413,13 +377,12 @@ export async function transitionVerifiedAction(target, {
|
|
|
413
377
|
}
|
|
414
378
|
|
|
415
379
|
export async function detectOrphanActions(target, { packageRoot, taskId }) {
|
|
416
|
-
const actions = await listActionFiles(target,
|
|
417
|
-
const
|
|
418
|
-
const proposedFingerprints = new Set(
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
);
|
|
380
|
+
const actions = await listActionFiles(target, taskId);
|
|
381
|
+
const candidates = new Set(actions.map(action => action.actionFingerprint));
|
|
382
|
+
const proposedFingerprints = new Set();
|
|
383
|
+
for await (const event of iterateEvents(target, packageRoot, { taskId })) {
|
|
384
|
+
if (event.event === "ACTION_PROPOSED" && candidates.has(event.details?.actionFingerprint)) proposedFingerprints.add(event.details?.actionFingerprint);
|
|
385
|
+
}
|
|
423
386
|
return actions
|
|
424
387
|
.filter((action) => !proposedFingerprints.has(action.actionFingerprint))
|
|
425
388
|
.map((action) => action.actionId);
|
|
@@ -431,7 +394,7 @@ export async function detectOrphanActions(target, { packageRoot, taskId }) {
|
|
|
431
394
|
* authorization, reconciliation, verification) and compared to its artifact.
|
|
432
395
|
*/
|
|
433
396
|
export async function validateActionLedgerConsistency(target, { packageRoot, taskId }) {
|
|
434
|
-
const actions = await listActionFiles(target,
|
|
397
|
+
const actions = await listActionFiles(target, taskId);
|
|
435
398
|
const { projectActionLedger } = await import("./action-ledger-projection.js");
|
|
436
399
|
const issues = [];
|
|
437
400
|
for (const action of actions) {
|
package/src/core/activation.js
CHANGED
|
@@ -3,8 +3,14 @@ import { randomUUID } from "node:crypto";
|
|
|
3
3
|
import { ARTIFACT_PATHS, writeJsonArtifact } from "./artifacts.js";
|
|
4
4
|
import { PROTOCOL_VERSION } from "./protocol.js";
|
|
5
5
|
import { sessionArtifactPath } from "./task-paths.js";
|
|
6
|
+
import { getOperationalStore } from "../storage/operational-context.js";
|
|
7
|
+
import { withTaskTransaction } from "./transaction.js";
|
|
6
8
|
|
|
7
9
|
export async function activateSession(target, packageRoot, options = {}) {
|
|
10
|
+
if (!getOperationalStore(target)) {
|
|
11
|
+
const { withProjectStorage } = await import("../storage/project-boundary.js");
|
|
12
|
+
return withProjectStorage(target, () => activateSession(target, packageRoot, options));
|
|
13
|
+
}
|
|
8
14
|
const sessionId = options.sessionId ?? randomUUID();
|
|
9
15
|
const value = {
|
|
10
16
|
schemaVersion: 1,
|
|
@@ -14,7 +20,9 @@ export async function activateSession(target, packageRoot, options = {}) {
|
|
|
14
20
|
createdAt: options.createdAt ?? new Date().toISOString(),
|
|
15
21
|
};
|
|
16
22
|
const relativePath = sessionArtifactPath(sessionId);
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
23
|
+
return withTaskTransaction({ target, taskId: sessionId, operation: "activate-session", packageRoot }, async () => {
|
|
24
|
+
const written = await writeJsonArtifact(target, relativePath, value, "activation", packageRoot);
|
|
25
|
+
await writeJsonArtifact(target, ARTIFACT_PATHS.session, value, "activation", packageRoot);
|
|
26
|
+
return { ...written.value, path: relativePath };
|
|
27
|
+
});
|
|
20
28
|
}
|
package/src/core/approvals.js
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
|
|
4
1
|
import { getTaskTransaction, withTaskTransaction } from "./transaction.js";
|
|
5
2
|
import { appendProtocolEvent } from "./events.js";
|
|
6
3
|
import { canonicalFingerprint } from "./artifacts.js";
|
|
@@ -16,11 +13,10 @@ import {
|
|
|
16
13
|
E_APPROVAL_INVALID,
|
|
17
14
|
E_APPROVAL_STALE,
|
|
18
15
|
} from "./error-codes.js";
|
|
19
|
-
import { assertSafePath, ensureWithin } from "./filesystem.js";
|
|
20
16
|
import { isTrustedHostAuthorityContext } from "./capability-policy.js";
|
|
21
|
-
import { taskApprovalPath, taskDirectory, TASK_ARTIFACT_FILES } from "./task-paths.js";
|
|
22
17
|
import { readAction } from "./actions.js";
|
|
23
18
|
import { readWorkState } from "./work-state.js";
|
|
19
|
+
import { requireNativeStore, withNativeReadScope } from "./native-storage.js";
|
|
24
20
|
|
|
25
21
|
function approvalError(code, message) {
|
|
26
22
|
const error = new Error(message);
|
|
@@ -29,55 +25,25 @@ function approvalError(code, message) {
|
|
|
29
25
|
}
|
|
30
26
|
|
|
31
27
|
async function readApprovalFile(target, taskId, approvalId) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
} catch (error) {
|
|
38
|
-
if (error?.code === "ENOENT") return null;
|
|
39
|
-
throw error;
|
|
40
|
-
}
|
|
41
|
-
try {
|
|
42
|
-
return JSON.parse(text);
|
|
43
|
-
} catch {
|
|
44
|
-
throw approvalError(E_APPROVAL_INVALID, `approval artifact is not valid JSON: ${relPath}`);
|
|
45
|
-
}
|
|
28
|
+
return withNativeReadScope(target, () => requireNativeStore(
|
|
29
|
+
target,
|
|
30
|
+
approvalError,
|
|
31
|
+
"Approvals require canonical SQLite storage; migrate legacy operational state explicitly",
|
|
32
|
+
).readApproval(taskId, approvalId));
|
|
46
33
|
}
|
|
47
34
|
|
|
48
|
-
async function writeApprovalFile(target,
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const activeTransaction = (await getTaskTransaction(target));
|
|
53
|
-
if (activeTransaction) {
|
|
54
|
-
await activeTransaction.stageText(relPath, serialized);
|
|
55
|
-
} else {
|
|
56
|
-
const absolute = ensureWithin(target, relPath);
|
|
57
|
-
await mkdir(path.dirname(absolute), { recursive: true });
|
|
58
|
-
await writeFile(absolute, serialized, "utf8");
|
|
59
|
-
}
|
|
35
|
+
async function writeApprovalFile(target, approval) {
|
|
36
|
+
const transaction = await getTaskTransaction(target);
|
|
37
|
+
if (!transaction) throw approvalError("E_STORAGE_TRANSACTION_INVALID", "Approval persistence requires an active task transaction");
|
|
38
|
+
return transaction.stageApproval(approval);
|
|
60
39
|
}
|
|
61
40
|
|
|
62
41
|
async function listApprovalFiles(target, taskId) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
entries = await readdir(absoluteDir);
|
|
69
|
-
} catch (error) {
|
|
70
|
-
if (error?.code === "ENOENT") return [];
|
|
71
|
-
throw error;
|
|
72
|
-
}
|
|
73
|
-
const approvals = [];
|
|
74
|
-
for (const entry of entries) {
|
|
75
|
-
if (!entry.endsWith(".json")) continue;
|
|
76
|
-
const parsed = await readApprovalFile(target, taskId, entry.replace(/\.json$/, ""));
|
|
77
|
-
if (parsed) approvals.push(parsed);
|
|
78
|
-
}
|
|
79
|
-
approvals.sort((left, right) => String(left.requestedAt).localeCompare(String(right.requestedAt)));
|
|
80
|
-
return approvals;
|
|
42
|
+
return withNativeReadScope(target, () => requireNativeStore(
|
|
43
|
+
target,
|
|
44
|
+
approvalError,
|
|
45
|
+
"Approval listing requires canonical SQLite storage; migrate legacy operational state explicitly",
|
|
46
|
+
).listApprovals(taskId).sort((left, right) => String(left.requestedAt).localeCompare(String(right.requestedAt))));
|
|
81
47
|
}
|
|
82
48
|
|
|
83
49
|
function approvalBindingFields(approval) {
|
|
@@ -207,7 +173,7 @@ export async function requestApproval(target, { packageRoot, taskId, input }) {
|
|
|
207
173
|
};
|
|
208
174
|
validateApprovalArtifact(approval);
|
|
209
175
|
|
|
210
|
-
await writeApprovalFile(target,
|
|
176
|
+
await writeApprovalFile(target, approval);
|
|
211
177
|
await appendProtocolEvent(target, {
|
|
212
178
|
taskId,
|
|
213
179
|
event: "APPROVAL_REQUESTED",
|
|
@@ -295,7 +261,7 @@ export async function resolveApproval(target, {
|
|
|
295
261
|
reason: reason ?? current.reason ?? null,
|
|
296
262
|
};
|
|
297
263
|
validateApprovalArtifact(resolved);
|
|
298
|
-
await writeApprovalFile(target,
|
|
264
|
+
await writeApprovalFile(target, resolved);
|
|
299
265
|
|
|
300
266
|
const details = {
|
|
301
267
|
approvalId: resolved.approvalId,
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { TASK_STATE_ROOT, TASK_ARTIFACT_FILES, PROJECT_ARTIFACT_PATHS, SESSIONS_ROOT } from "./task-paths.js";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Canonical registry of ForgeLoop artifacts, defining
|
|
4
|
+
* Canonical registry of ForgeLoop artifacts, defining logical identities,
|
|
5
5
|
* schema bindings, ownership, mutability, trust classifications, and scopes.
|
|
6
6
|
*
|
|
7
7
|
* Schema field shapes remain strictly owned by JSON schemas in `schemas/`.
|
|
8
8
|
*/
|
|
9
|
-
|
|
9
|
+
const ARTIFACT_DEFINITIONS = Object.freeze({
|
|
10
10
|
descriptor: Object.freeze({
|
|
11
11
|
key: "descriptor",
|
|
12
12
|
scope: "TASK",
|
|
@@ -391,4 +391,85 @@ export const ARTIFACT_REGISTRY = Object.freeze({
|
|
|
391
391
|
isPersisted: true,
|
|
392
392
|
description: "Optional external signing-provider bundle; existence alone never establishes a valid signature.",
|
|
393
393
|
}),
|
|
394
|
+
semanticDecisions: Object.freeze({
|
|
395
|
+
key: "semanticDecisions",
|
|
396
|
+
scope: "TASK",
|
|
397
|
+
path: `${TASK_STATE_ROOT}/<task-key>/${TASK_ARTIFACT_FILES.decisions}/<decision-id>.json`,
|
|
398
|
+
schema: "semantic-decision",
|
|
399
|
+
owner: "PROTOCOL_COMPILED",
|
|
400
|
+
mutability: "IMMUTABLE_ONCE_WRITTEN",
|
|
401
|
+
trustRole: "SEMANTIC_DECISION",
|
|
402
|
+
isPublic: true,
|
|
403
|
+
isPersisted: true,
|
|
404
|
+
description: "Fingerprint-bound, normalized Jev semantic decision; never lifecycle, evidence, ownership, installation, or completion authority.",
|
|
405
|
+
}),
|
|
406
|
+
testUtility: Object.freeze({
|
|
407
|
+
key: "testUtility",
|
|
408
|
+
scope: "TASK",
|
|
409
|
+
path: `${TASK_STATE_ROOT}/<task-key>/${TASK_ARTIFACT_FILES.testUtility}`,
|
|
410
|
+
schema: "test-utility",
|
|
411
|
+
owner: "PROTOCOL_COMPILED",
|
|
412
|
+
mutability: "OVERWRITTEN_ON_ANALYSIS",
|
|
413
|
+
trustRole: "NON_EVIDENCE_TEST_ANALYSIS",
|
|
414
|
+
isPublic: true,
|
|
415
|
+
isPersisted: true,
|
|
416
|
+
description: "Deterministic test inventory and bounded utility analysis; never completion evidence or deletion authority.",
|
|
417
|
+
}),
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
const DATABASE_PATH = ".forgeloop/state.sqlite";
|
|
421
|
+
const RECORD_TABLES = Object.freeze({ descriptor: "tasks", state: "tasks", events: "events", actions: "actions", approvals: "approvals", executions: "executions", session: "sessions" });
|
|
422
|
+
const ARTIFACT_KINDS = Object.freeze({ gates: "gate", evaluations: "evaluation", handoffs: "handoff", semanticDecisions: "decision", codeManifest: "attestation", attestationStatement: "attestation" });
|
|
423
|
+
|
|
424
|
+
function canonicalStorage(artifact) {
|
|
425
|
+
if (artifact.scope === "PROJECT") return Object.freeze({ backend: "FILE", path: artifact.path });
|
|
426
|
+
if (artifact.key === "attestationBundle") {
|
|
427
|
+
return Object.freeze({ backend: "SQLITE_REFERENCED_FILE", path: DATABASE_PATH, table: "attachment_references", attachmentPath: ".forgeloop/attachments/objects/<sha256>" });
|
|
428
|
+
}
|
|
429
|
+
const table = RECORD_TABLES[artifact.key] ?? "task_artifacts";
|
|
430
|
+
return Object.freeze({ backend: "SQLITE", path: DATABASE_PATH, table,
|
|
431
|
+
...(table === "task_artifacts" ? { kind: ARTIFACT_KINDS[artifact.key] ?? artifact.key } : {}),
|
|
432
|
+
...(artifact.key === "descriptor" ? { column: "descriptor_json" } : {}),
|
|
433
|
+
...(artifact.key === "state" ? { column: "state_json" } : {}),
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/** `path` stays a historical logical alias; it is never a live-file guarantee. */
|
|
438
|
+
export const ARTIFACT_REGISTRY = Object.freeze(Object.fromEntries(
|
|
439
|
+
Object.entries(ARTIFACT_DEFINITIONS).map(([key, artifact]) => [key, Object.freeze({
|
|
440
|
+
...artifact,
|
|
441
|
+
logicalPath: artifact.path,
|
|
442
|
+
canonicalStorage: canonicalStorage(artifact),
|
|
443
|
+
// Configuration and policy inputs are copied separately from database export.
|
|
444
|
+
exportPath: artifact.scope === "PROJECT" ? null : artifact.key === "attestationBundle" ? ".forgeloop/attachments/objects/<sha256>" : artifact.path,
|
|
445
|
+
...(artifact.key === "attestationBundle" ? { exportBindingPath: "export-index.json#attachments" } : {}),
|
|
446
|
+
})]),
|
|
447
|
+
));
|
|
448
|
+
|
|
449
|
+
function portableSingletonMapping(key) {
|
|
450
|
+
return Object.freeze({
|
|
451
|
+
key, kind: ARTIFACT_REGISTRY[key].canonicalStorage.kind, filename: TASK_ARTIFACT_FILES[key],
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function portableCollectionMapping([key, registryKey]) {
|
|
456
|
+
return Object.freeze({
|
|
457
|
+
kind: ARTIFACT_REGISTRY[registryKey].canonicalStorage.kind, directory: TASK_ARTIFACT_FILES[key],
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/** Ordered interchange mappings; bundle-specific filenames remain in bundles.js. */
|
|
462
|
+
export const PORTABLE_ARTIFACT_MAPPINGS = Object.freeze({
|
|
463
|
+
singletons: Object.freeze([
|
|
464
|
+
"contract", "route", "preflight", "continuity", "receipt", "policySnapshot",
|
|
465
|
+
"recovery", "workspaceBinding", "responsibility", "verificationScope", "usage", "testUtility",
|
|
466
|
+
].map(portableSingletonMapping)),
|
|
467
|
+
collections: Object.freeze([
|
|
468
|
+
["gates", "gates"],
|
|
469
|
+
["handoffs", "handoffs"],
|
|
470
|
+
["decisions", "semanticDecisions"],
|
|
471
|
+
["attestations", "codeManifest"],
|
|
472
|
+
["evaluations", "evaluations"],
|
|
473
|
+
["structuralQuality", "structuralQuality"],
|
|
474
|
+
].map(portableCollectionMapping)),
|
|
394
475
|
});
|
package/src/core/artifacts.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { ARTIFACT_REGISTRY } from "./artifact-registry.js";
|
|
2
|
+
import { needsExistingProjectScope, withExistingProjectScope, assertNativeWritePath, isOperationalArtifactPath } from "../storage/existing-project-scope.js";
|
|
1
3
|
import { createHash } from "node:crypto";
|
|
2
4
|
|
|
3
5
|
import { assertSafePath, ensureWithin, fileExists, readBytes, writeFileAtomic } from "./filesystem.js";
|
|
@@ -6,6 +8,12 @@ import { assertJsonBytes, assertJsonLimits } from "./json-safety.js";
|
|
|
6
8
|
import { assertSchema, readSchema } from "./schema-validation.js";
|
|
7
9
|
import { getPackageRoot } from "./templates.js";
|
|
8
10
|
import { getTaskTransaction, withTaskTransaction } from "./transaction.js";
|
|
11
|
+
import { getOperationalStore, readOperationalText } from "../storage/operational-context.js";
|
|
12
|
+
import { withNativeReadScope } from "./native-storage.js";
|
|
13
|
+
|
|
14
|
+
const OPERATIONAL_SCHEMAS = new Set(Object.values(ARTIFACT_REGISTRY)
|
|
15
|
+
.filter(artifact => artifact.scope !== "PROJECT" && artifact.schema)
|
|
16
|
+
.map(artifact => artifact.schema));
|
|
9
17
|
|
|
10
18
|
export const ARTIFACT_PATHS = Object.freeze({
|
|
11
19
|
contract: ".forgeloop/current-contract.json",
|
|
@@ -63,12 +71,43 @@ function artifactError(code, relativePath, error) {
|
|
|
63
71
|
);
|
|
64
72
|
}
|
|
65
73
|
|
|
74
|
+
/** Validate already captured canonical bytes after their native read scope closes. */
|
|
75
|
+
export async function parseCapturedJsonArtifact(text, relativePath, schemaName, packageRoot = getPackageRoot()) {
|
|
76
|
+
if (text === null) throw new ArtifactError("ARTIFACT_MISSING", `Artifact is missing: ${relativePath}`, [relativePath]);
|
|
77
|
+
try {
|
|
78
|
+
assertJsonBytes(text, relativePath);
|
|
79
|
+
const value = JSON.parse(text);
|
|
80
|
+
assertJsonLimits(value, relativePath);
|
|
81
|
+
assertSchema(value, await readSchema(schemaName, packageRoot), relativePath);
|
|
82
|
+
return { value, path: relativePath, fingerprint: canonicalFingerprint(value) };
|
|
83
|
+
} catch (error) { throw artifactError(error.code === "JSON_LIMIT_EXCEEDED" ? error.code : "ARTIFACT_INVALID", relativePath, error); }
|
|
84
|
+
}
|
|
85
|
+
|
|
66
86
|
export async function readJsonArtifact(
|
|
67
87
|
target,
|
|
68
88
|
relativePath,
|
|
69
89
|
schemaName,
|
|
70
90
|
packageRoot = getPackageRoot(),
|
|
71
91
|
) {
|
|
92
|
+
return withNativeReadScope(target, async () => {
|
|
93
|
+
const operational = readOperationalText(target, relativePath);
|
|
94
|
+
if (operational.selected) {
|
|
95
|
+
return parseCapturedJsonArtifact(operational.text, relativePath, schemaName, packageRoot);
|
|
96
|
+
}
|
|
97
|
+
if (isOperationalArtifactPath(relativePath)) {
|
|
98
|
+
try { await assertSafePath(target, relativePath); }
|
|
99
|
+
catch (error) { throw artifactError("ARTIFACT_PATH_INVALID", relativePath, error); }
|
|
100
|
+
if (!(await fileExists(ensureWithin(target, relativePath)))) {
|
|
101
|
+
throw new ArtifactError("ARTIFACT_MISSING", `Artifact is missing: ${relativePath}`, [relativePath]);
|
|
102
|
+
}
|
|
103
|
+
throw new ArtifactError("E_STORAGE_MIGRATION_REQUIRED", "Operational JSON reads require canonical SQLite storage; migrate legacy state explicitly", [relativePath]);
|
|
104
|
+
}
|
|
105
|
+
return readPortableJsonArtifact(target, relativePath, schemaName, packageRoot);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Explicit file inspection for migration inputs, portable exports and configuration. */
|
|
110
|
+
export async function readPortableJsonArtifact(target, relativePath, schemaName, packageRoot = getPackageRoot()) {
|
|
72
111
|
try {
|
|
73
112
|
await assertSafePath(target, relativePath);
|
|
74
113
|
} catch (error) {
|
|
@@ -76,9 +115,7 @@ export async function readJsonArtifact(
|
|
|
76
115
|
}
|
|
77
116
|
|
|
78
117
|
const artifactPath = ensureWithin(target, relativePath);
|
|
79
|
-
|
|
80
|
-
const stagedText = transaction ? await transaction.readText(relativePath) : null;
|
|
81
|
-
if (stagedText === null && !(await fileExists(artifactPath))) {
|
|
118
|
+
if (!(await fileExists(artifactPath))) {
|
|
82
119
|
throw new ArtifactError(
|
|
83
120
|
"ARTIFACT_MISSING",
|
|
84
121
|
`Artifact is missing: ${relativePath}`,
|
|
@@ -86,14 +123,9 @@ export async function readJsonArtifact(
|
|
|
86
123
|
);
|
|
87
124
|
}
|
|
88
125
|
|
|
89
|
-
let value;
|
|
90
126
|
try {
|
|
91
|
-
const bytes =
|
|
92
|
-
|
|
93
|
-
value = JSON.parse(bytes.toString("utf8"));
|
|
94
|
-
assertJsonLimits(value, relativePath);
|
|
95
|
-
const schema = await readSchema(schemaName, packageRoot);
|
|
96
|
-
assertSchema(value, schema, relativePath);
|
|
127
|
+
const bytes = await readBytes(artifactPath);
|
|
128
|
+
return await parseCapturedJsonArtifact(bytes, relativePath, schemaName, packageRoot);
|
|
97
129
|
} catch (error) {
|
|
98
130
|
throw artifactError(
|
|
99
131
|
["JSON_LIMIT_EXCEEDED", "ARTIFACT_PATH_INVALID"].includes(error.code)
|
|
@@ -103,12 +135,6 @@ export async function readJsonArtifact(
|
|
|
103
135
|
error,
|
|
104
136
|
);
|
|
105
137
|
}
|
|
106
|
-
|
|
107
|
-
return {
|
|
108
|
-
value,
|
|
109
|
-
path: relativePath,
|
|
110
|
-
fingerprint: canonicalFingerprint(value),
|
|
111
|
-
};
|
|
112
138
|
}
|
|
113
139
|
|
|
114
140
|
export async function writeJsonArtifact(
|
|
@@ -119,17 +145,49 @@ export async function writeJsonArtifact(
|
|
|
119
145
|
packageRoot = getPackageRoot(),
|
|
120
146
|
{ dryRun = false, taskId = null, operation = "write-artifact" } = {},
|
|
121
147
|
) {
|
|
148
|
+
if (OPERATIONAL_SCHEMAS.has(schemaName) && !isOperationalArtifactPath(relativePath)) {
|
|
149
|
+
throw new ArtifactError("E_STORAGE_OPERATION_UNSUPPORTED", "Operational artifacts require canonical SQLite identities; use explicit portable export", [relativePath]);
|
|
150
|
+
}
|
|
151
|
+
if (!getOperationalStore(target) && isOperationalArtifactPath(relativePath)) {
|
|
152
|
+
await assertSafePath(target, relativePath);
|
|
153
|
+
assertSecretFree(value);
|
|
154
|
+
assertSchema(value, await readSchema(schemaName, packageRoot), relativePath);
|
|
155
|
+
assertJsonLimits(value, relativePath);
|
|
156
|
+
assertJsonBytes(`${JSON.stringify(value, null, 2)}\n`, relativePath);
|
|
157
|
+
const { withProjectStorage } = await import("../storage/project-boundary.js");
|
|
158
|
+
return withProjectStorage(target, () => writeJsonArtifact(target, relativePath, value, schemaName, packageRoot, { dryRun, taskId, operation }), { readOnly: dryRun });
|
|
159
|
+
}
|
|
160
|
+
if (await needsExistingProjectScope(target)) {
|
|
161
|
+
return withExistingProjectScope(target, () => writeJsonArtifact(target, relativePath, value, schemaName, packageRoot, { dryRun, taskId, operation }), { readOnly: dryRun });
|
|
162
|
+
}
|
|
163
|
+
assertNativeWritePath(target, relativePath);
|
|
164
|
+
const store = getOperationalStore(target);
|
|
165
|
+
if (store?.recognizes(relativePath)) {
|
|
166
|
+
assertSecretFree(value);
|
|
167
|
+
assertSchema(value, await readSchema(schemaName, packageRoot), relativePath);
|
|
168
|
+
assertJsonLimits(value, relativePath);
|
|
169
|
+
const text = `${JSON.stringify(value, null, 2)}\n`;
|
|
170
|
+
assertJsonBytes(text, relativePath);
|
|
171
|
+
if (!dryRun) {
|
|
172
|
+
const scopedTaskId = taskId ?? value.taskId ?? store.transaction?.taskId;
|
|
173
|
+
if (!scopedTaskId) throw new ArtifactError("E_TASK_REQUIRED", "Operational artifact requires a task identity", [relativePath]);
|
|
174
|
+
await withTaskTransaction({ target, taskId: scopedTaskId, operation, packageRoot }, tx => tx.stageJsonRecord(relativePath, text, schemaName));
|
|
175
|
+
}
|
|
176
|
+
return { path: relativePath, fingerprint: canonicalFingerprint(value), value };
|
|
177
|
+
}
|
|
178
|
+
return writePortableJsonArtifact(target, relativePath, value, schemaName, packageRoot, { dryRun });
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** Explicit interchange/configuration output; never canonical operational persistence. */
|
|
182
|
+
export async function writePortableJsonArtifact(target, relativePath, value, schemaName, packageRoot = getPackageRoot(), { dryRun = false } = {}) {
|
|
183
|
+
if (isOperationalArtifactPath(relativePath)) throw new ArtifactError("E_STORAGE_OPERATION_UNSUPPORTED", "Portable output cannot replace a canonical operational namespace", [relativePath]);
|
|
184
|
+
assertNativeWritePath(target, relativePath);
|
|
185
|
+
if (!dryRun && await getTaskTransaction(target)) throw new ArtifactError("E_STORAGE_TRANSACTION_INVALID", "Portable output requires no active operational transaction", [relativePath]);
|
|
122
186
|
try {
|
|
123
187
|
await assertSafePath(target, relativePath);
|
|
124
188
|
} catch (error) {
|
|
125
189
|
throw artifactError("ARTIFACT_PATH_INVALID", relativePath, error);
|
|
126
190
|
}
|
|
127
|
-
const activeTransaction = (await getTaskTransaction(target));
|
|
128
|
-
if (!activeTransaction && taskId && !dryRun) {
|
|
129
|
-
return withTaskTransaction({ target, taskId, operation, packageRoot }, async () => (
|
|
130
|
-
writeJsonArtifact(target, relativePath, value, schemaName, packageRoot, { dryRun, taskId, operation })
|
|
131
|
-
));
|
|
132
|
-
}
|
|
133
191
|
try {
|
|
134
192
|
const artifactPath = ensureWithin(target, relativePath);
|
|
135
193
|
assertSecretFree(value);
|
|
@@ -138,11 +196,7 @@ export async function writeJsonArtifact(
|
|
|
138
196
|
assertJsonLimits(value, relativePath);
|
|
139
197
|
const serialized = `${JSON.stringify(value, null, 2)}\n`;
|
|
140
198
|
assertJsonBytes(serialized, relativePath);
|
|
141
|
-
|
|
142
|
-
await activeTransaction.stageText(relativePath, serialized);
|
|
143
|
-
} else {
|
|
144
|
-
await writeFileAtomic(artifactPath, serialized, { dryRun });
|
|
145
|
-
}
|
|
199
|
+
await writeFileAtomic(artifactPath, serialized, { dryRun });
|
|
146
200
|
return { path: relativePath, fingerprint: canonicalFingerprint(value), value };
|
|
147
201
|
} catch (error) {
|
|
148
202
|
throw artifactError(
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { getOperationalStore, readOperationalText } from "../storage/operational-context.js";
|
|
2
|
+
import { validateAttachmentReference } from "../storage/attachment-binding.js";
|
|
3
|
+
import { taskStorageKey } from "./task-identity.js";
|
|
4
|
+
import { taskAttestationBundlePath, taskAttestationStatementPath } from "./task-paths.js";
|
|
5
|
+
import { legacyAttachmentReferenceId, signatureReferencePrefix } from "./signing/bundle-reference.js";
|
|
6
|
+
|
|
7
|
+
function resolveCanonicalSignature(target, taskId, store) {
|
|
8
|
+
const source = readOperationalText(target, taskAttestationStatementPath(taskId));
|
|
9
|
+
if (source.text === null) return null;
|
|
10
|
+
const prefix = signatureReferencePrefix(source.text);
|
|
11
|
+
// At most two distinct objects are enough to detect ambiguity. Repeated
|
|
12
|
+
// bindings to identical bytes do not make selection ambiguous.
|
|
13
|
+
const rows = store.observe(`signature:${taskId}:${prefix}`, () => store.db.prepare(
|
|
14
|
+
"SELECT MIN(reference_id) AS reference_id, path, size, sha256 FROM attachment_references WHERE task_id = ? AND reference_id >= ? AND reference_id < ? GROUP BY path, size, sha256 ORDER BY path LIMIT 2",
|
|
15
|
+
).all(taskId, prefix, `${prefix}~`));
|
|
16
|
+
const paths = new Set(rows.map(row => validateAttachmentReference({ taskId, referenceId: row.reference_id, path: row.path, size: row.size, sha256: row.sha256 }).path));
|
|
17
|
+
for (const reference of store.attachments.values()) {
|
|
18
|
+
if (reference.taskId === taskId && reference.referenceId.startsWith(prefix)) paths.add(validateAttachmentReference(reference).path);
|
|
19
|
+
}
|
|
20
|
+
if (paths.size > 1) {
|
|
21
|
+
throw Object.assign(new Error("Multiple signature bundles bind this statement; supply an explicit bundle path"), { code: "E_ATTESTATION_SIGNATURE_INVALID" });
|
|
22
|
+
}
|
|
23
|
+
return paths.values().next().value ?? null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Preserve legacy logical references while locating external SQLite assets. */
|
|
27
|
+
export function resolveAttestationBundlePath(target, taskId, suppliedPath = null) {
|
|
28
|
+
const legacy = taskAttestationBundlePath(taskId);
|
|
29
|
+
const store = getOperationalStore(target);
|
|
30
|
+
if (store && suppliedPath) {
|
|
31
|
+
const referenceId = legacyAttachmentReferenceId(suppliedPath);
|
|
32
|
+
const row = store.observe(`attachment-alias:${JSON.stringify([taskId, referenceId])}`, () => store.db.prepare(
|
|
33
|
+
"SELECT * FROM attachment_references WHERE task_id = ? AND reference_id = ?",
|
|
34
|
+
).get(taskId, referenceId) ?? null);
|
|
35
|
+
const staged = store.attachments.get(JSON.stringify([taskId, referenceId]));
|
|
36
|
+
const reference = staged ?? (row ? { taskId: row.task_id, referenceId: row.reference_id, path: row.path, size: row.size, sha256: row.sha256 } : null);
|
|
37
|
+
if (reference) return validateAttachmentReference(reference).path;
|
|
38
|
+
}
|
|
39
|
+
if (store && (!suppliedPath || suppliedPath === legacy)) {
|
|
40
|
+
const canonical = resolveCanonicalSignature(target, taskId, store);
|
|
41
|
+
if (canonical) return canonical;
|
|
42
|
+
return `.forgeloop/attachments/${taskStorageKey(taskId)}/attestations/statement.sigstore.json`;
|
|
43
|
+
}
|
|
44
|
+
return suppliedPath ?? legacy;
|
|
45
|
+
}
|
|
@@ -6,6 +6,7 @@ import { resolveRevisionProvider } from "./revision/provider.js";
|
|
|
6
6
|
import { REVISION_PROVIDERS } from "./revision/registry.js";
|
|
7
7
|
import { assertAttestationStatementBindings, verifyCodeManifestContent } from "./attestation-verifier.js";
|
|
8
8
|
import { taskAttestationBundlePath } from "./task-paths.js";
|
|
9
|
+
import { withProjectReadSnapshot } from "../storage/project-read-snapshot.js";
|
|
9
10
|
|
|
10
11
|
function pathMatchesExclusion(pathValue, exclusions) {
|
|
11
12
|
return exclusions.some((pattern) => {
|
|
@@ -24,7 +25,11 @@ function isProtocolMetadataPath(value) {
|
|
|
24
25
|
return normalized === ".forgeloop" || normalized.startsWith(".forgeloop/");
|
|
25
26
|
}
|
|
26
27
|
|
|
27
|
-
export async function evaluateAttestationCoverage({
|
|
28
|
+
export async function evaluateAttestationCoverage(options = {}) {
|
|
29
|
+
return withProjectReadSnapshot(options.target, () => evaluateSelectedAttestationCoverage(options));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function evaluateSelectedAttestationCoverage({
|
|
28
33
|
target,
|
|
29
34
|
packageRoot = getPackageRoot(),
|
|
30
35
|
revisionProvider = "git",
|