@cassiomc1/forgeloop 1.14.0 → 2.0.0-rc.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +11 -7
- package/DOCS_INDEX.md +2 -1
- package/ENG/c-development-eng.md +2 -2
- package/ENG/cpp-development-eng.md +2 -2
- package/ENG/dotnet-aspnetcore-development-eng.md +7 -1
- package/ENG/flutter-development-eng.md +4 -2
- package/ENG/go-development-eng.md +2 -2
- package/ENG/java-development-eng.md +3 -3
- package/ENG/nodejs-backend-development-eng.md +8 -4
- package/ENG/php-development-eng.md +3 -3
- package/ENG/python-development-eng.md +205 -0
- package/ENG/rust-development-eng.md +42 -1
- package/ENG/sql-development-eng.md +4 -4
- package/ENG/swift-development-eng.md +5 -4
- package/ENG/test-code-eng.md +1 -1
- package/ENG/typescript-development-eng.md +4 -4
- package/GUIDE_ROUTER.md +6 -0
- package/LICENSE +133 -21
- package/LOOP_SYSTEM_DESIGN.md +10 -10
- package/README.md +30 -40
- package/THIRD_PARTY_NOTICES.md +3 -4
- package/completions/_forgeloop +11 -3
- package/completions/forgeloop.bash +19 -3
- package/completions/forgeloop.fish +61 -1
- package/docs/AGENT_PROTOCOL_SUMMARY.md +50 -40
- package/docs/ARTIFACT_REFERENCE.md +50 -37
- package/docs/CLI_REFERENCE.md +147 -3
- package/docs/DOCUMENTATION_GUIDE.md +6 -4
- package/docs/GETTING_STARTED.md +3 -3
- package/docs/PACKAGE_CONTENTS.md +14 -13
- package/docs/RELEASE_CHECKLIST.md +110 -10
- package/docs/SELF_HOSTED_TESTS.md +121 -0
- package/docs/TROUBLESHOOTING.md +33 -0
- package/docs/documentation-manifest.json +25 -12
- package/package.json +15 -10
- package/schemas/task-bundle.schema.json +7 -2
- package/scripts/generate-agent-protocol-summary.mjs +2 -2
- package/scripts/run-mcp-clean-tests.mjs +30 -0
- package/src/adapters/agent-browser/provider.js +14 -11
- package/src/adapters/emulated-services/provider.js +11 -4
- package/src/adapters/temporary-paths.js +18 -0
- package/src/cli.js +146 -210
- package/src/commands/action-show.js +5 -2
- package/src/commands/advance.js +5 -1
- package/src/commands/baseline.js +13 -11
- package/src/commands/checkpoint-revalidate.js +24 -8
- package/src/commands/complete.js +19 -3
- package/src/commands/contract-create.js +3 -1
- package/src/commands/doctor.js +36 -13
- package/src/commands/gate-record.js +20 -31
- package/src/commands/gate-revalidate.js +11 -13
- package/src/commands/handoff-list.js +6 -1
- package/src/commands/handoff-show.js +6 -1
- package/src/commands/migrate-protocol.js +2 -2
- package/src/commands/model-route.js +9 -1
- package/src/commands/next.js +7 -1
- package/src/commands/policy-diff.js +25 -4
- package/src/commands/progress.js +17 -11
- package/src/commands/record-diagnosis.js +3 -0
- package/src/commands/semantic-plan.js +26 -2
- package/src/commands/status.js +6 -1
- package/src/commands/storage-backup.js +15 -0
- package/src/commands/storage-migrate.js +5 -0
- package/src/commands/storage-migration-resume.js +5 -0
- package/src/commands/storage-migration-status.js +44 -0
- package/src/commands/storage-restore-resume.js +7 -0
- package/src/commands/storage-restore.js +8 -0
- package/src/commands/task-abandon.js +1 -1
- package/src/commands/task-list.js +8 -43
- package/src/commands/task-migrate.js +3 -3
- package/src/commands/task-recover.js +10 -2
- package/src/commands/task-scope.js +18 -2
- package/src/commands/task-show.js +16 -2
- package/src/commands/validate-protocol.js +29 -14
- package/src/commands/validate-receipt.js +8 -1
- package/src/config/guides.json +4 -0
- package/src/core/action-ledger-projection.js +10 -3
- package/src/core/actions.js +38 -75
- package/src/core/activation.js +11 -3
- package/src/core/approvals.js +17 -51
- package/src/core/artifact-registry.js +59 -2
- package/src/core/artifacts.js +81 -27
- package/src/core/attachment-paths.js +45 -0
- package/src/core/attestation-coverage.js +6 -1
- package/src/core/attestation-verifier.js +21 -2
- package/src/core/attestation.js +17 -6
- package/src/core/audit.js +6 -1
- package/src/core/bundles.js +160 -66
- package/src/core/checkpoint-revalidation.js +12 -10
- package/src/core/cli-command-definitions.js +109 -7
- package/src/core/code-manifest.js +9 -3
- package/src/core/command-executors.js +182 -126
- package/src/core/command-input.js +21 -1
- package/src/core/completion-artifacts.js +85 -36
- package/src/core/completion-ownership.js +19 -8
- package/src/core/completion-recovery-rebind.js +11 -1
- package/src/core/completion.js +60 -9
- package/src/core/conformance.js +2 -1
- package/src/core/continuity-lint.js +13 -2
- package/src/core/continuity-reconciliation.js +16 -3
- package/src/core/continuity.js +16 -8
- package/src/core/contract-bootstrap-recovery.js +42 -41
- package/src/core/contract-revision.js +8 -9
- package/src/core/decision/artifact.js +2 -1
- package/src/core/decision/events.js +9 -6
- package/src/core/decision/resolver.js +19 -11
- package/src/core/decision/service.js +63 -38
- package/src/core/decision/task-bindings.js +6 -1
- package/src/core/diagnosis-model.js +11 -0
- package/src/core/diagnosis.js +10 -1
- package/src/core/diagnostic-projection.js +29 -12
- package/src/core/diagnostic-record.js +28 -3
- package/src/core/efficiency.js +6 -1
- package/src/core/events.js +316 -97
- package/src/core/execution-prerequisites.js +22 -13
- package/src/core/execution.js +26 -40
- package/src/core/filesystem.js +11 -0
- package/src/core/gate-artifact.js +105 -19
- package/src/core/gate-provenance.js +6 -5
- package/src/core/handoff.js +26 -14
- package/src/core/history.js +39 -35
- package/src/core/hypothesis-projection.js +32 -0
- package/src/core/information-gain-projection.js +104 -100
- package/src/core/inspect.js +12 -3
- package/src/core/integration-invocation-policy.js +10 -0
- package/src/core/integration-resources.js +41 -34
- package/src/core/ledger-event-collection.js +189 -0
- package/src/core/ledger-relations.js +23 -0
- package/src/core/native-storage.js +12 -0
- package/src/core/next-action-continuity.js +31 -9
- package/src/core/next-action-phases.js +1 -1
- package/src/core/next-action-review-phase.js +2 -2
- package/src/core/next-action-verification-phase.js +2 -1
- package/src/core/next-action.js +21 -2
- package/src/core/phase.js +138 -93
- package/src/core/policy-engine.js +12 -13
- package/src/core/preflight-consistency.js +72 -59
- package/src/core/preflight-loaders.js +19 -12
- package/src/core/preflight-model.js +6 -1
- package/src/core/preflight.js +29 -12
- package/src/core/progress.js +21 -10
- package/src/core/protocol-info.js +3 -0
- package/src/core/protocol-migration.js +7 -6
- package/src/core/reconcile-closure.js +162 -24
- package/src/core/recovery-history.js +66 -47
- package/src/core/reflection.js +72 -77
- package/src/core/responsibility.js +25 -13
- package/src/core/route-artifact.js +13 -3
- package/src/core/runtime-context.js +5 -0
- package/src/core/schema-validation.js +8 -3
- package/src/core/settlement.js +12 -1
- package/src/core/signing/bundle-file.js +53 -0
- package/src/core/signing/bundle-reference.js +15 -0
- package/src/core/signing/sigstore.js +60 -33
- package/src/core/signing/statement-file.js +27 -0
- package/src/core/storage-runtime-registry.js +41 -0
- package/src/core/structural-quality/artifacts.js +20 -14
- package/src/core/structural-quality/service.js +81 -56
- package/src/core/target-layout.js +1 -1
- package/src/core/task-claim-state.js +52 -18
- package/src/core/task-command.js +21 -1
- package/src/core/task-conflict-inspection.js +112 -107
- package/src/core/task-descriptor.js +29 -22
- package/src/core/task-discovery.js +288 -90
- package/src/core/task-lock.js +66 -306
- package/src/core/task-migration-validation.js +5 -5
- package/src/core/task-migration.js +17 -287
- package/src/core/task-snapshot.js +51 -8
- package/src/core/task-storage-migration.js +18 -0
- package/src/core/templates.js +20 -8
- package/src/core/test-intelligence/prune.js +5 -1
- package/src/core/trace.js +154 -99
- package/src/core/trajectory-evaluation.js +15 -3
- package/src/core/trajectory-metrics.js +47 -32
- package/src/core/transaction.js +16 -288
- package/src/core/verification-scope.js +20 -12
- package/src/core/work-state.js +100 -59
- package/src/repository-index/binary-manager.js +15 -7
- package/src/storage/artifact-bytes.js +22 -0
- package/src/storage/attachment-binding.js +11 -0
- package/src/storage/attachment-files.js +82 -0
- package/src/storage/attachment-references.js +36 -0
- package/src/storage/backup.js +53 -0
- package/src/storage/capabilities.js +31 -0
- package/src/storage/connection-owner.js +119 -0
- package/src/storage/connection.js +376 -0
- package/src/storage/existing-project-scope.js +45 -0
- package/src/storage/export-destination.js +44 -0
- package/src/storage/exporter.js +284 -0
- package/src/storage/file-durability.js +46 -0
- package/src/storage/importer.js +461 -0
- package/src/storage/index.js +74 -0
- package/src/storage/leases.js +63 -0
- package/src/storage/ledger-event-snapshot.js +150 -0
- package/src/storage/ledger-relations.js +220 -0
- package/src/storage/legacy-maintenance-boundary.js +18 -0
- package/src/storage/legacy-signature.js +115 -0
- package/src/storage/maintenance-handoff.js +129 -0
- package/src/storage/maintenance-owner.js +42 -0
- package/src/storage/maintenance.js +310 -0
- package/src/storage/metadata-json.js +35 -0
- package/src/storage/migration-activate.js +59 -0
- package/src/storage/migration-archive.js +57 -0
- package/src/storage/migration-attachment-root.js +61 -0
- package/src/storage/migration-candidate.js +398 -0
- package/src/storage/migration-publication.js +143 -0
- package/src/storage/migration-rollback-admission.js +54 -0
- package/src/storage/migration-rollback-preparation.js +132 -0
- package/src/storage/migration-rollback-publication.js +183 -0
- package/src/storage/migration-rollback-release.js +66 -0
- package/src/storage/migration-rollback-source-stage.js +103 -0
- package/src/storage/migration-rollback.js +43 -0
- package/src/storage/migration-source-partition.js +56 -0
- package/src/storage/migration-source.js +255 -0
- package/src/storage/migration-terminal.js +39 -0
- package/src/storage/migration-validation.js +75 -0
- package/src/storage/migration.js +43 -0
- package/src/storage/operational-attachments.js +49 -0
- package/src/storage/operational-context.js +50 -0
- package/src/storage/project-backup.js +98 -0
- package/src/storage/project-bootstrap.js +24 -0
- package/src/storage/project-boundary.js +79 -0
- package/src/storage/project-read-snapshot.js +18 -0
- package/src/storage/project-replacement.js +137 -0
- package/src/storage/project-restore.js +203 -0
- package/src/storage/record-definitions.js +45 -0
- package/src/storage/repository.js +507 -0
- package/src/storage/restore-execution-provenance.js +26 -0
- package/src/storage/restore-journal.js +62 -0
- package/src/storage/restore-layout.js +30 -0
- package/src/storage/restore-preparation.js +84 -0
- package/src/storage/restore-replacement-archive.js +162 -0
- package/src/storage/restore-replacement-owner.js +31 -0
- package/src/storage/restore-replacement-preparation.js +128 -0
- package/src/storage/restore-replacement.js +142 -0
- package/src/storage/restore-terminal.js +32 -0
- package/src/storage/runtime.js +31 -0
- package/src/storage/schema.js +214 -0
- package/src/storage/singleton-import.js +84 -0
- package/src/storage/snapshot-continuity.js +16 -0
- package/src/storage/snapshot.js +36 -0
- package/src/storage/storage-marker.js +79 -0
- package/src/storage/task-guards.js +107 -0
- package/src/storage/topology.js +42 -0
- package/src/storage/transaction.js +179 -0
- package/src/storage/unit-of-work.js +667 -0
- package/src/storage/windows-process-incarnation.js +206 -0
- package/.forgeloop/forgeloop.gitignore +0 -4
- package/LICENSE-DOCS.md +0 -13
- package/src/core/transaction-maintenance.js +0 -70
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 });
|
|
@@ -4,17 +4,24 @@ import { readContract } from "./contract.js";
|
|
|
4
4
|
import { ensureResumableState, synchronizePersistedRouteState } from "./resumability.js";
|
|
5
5
|
import { readWorkState } from "./work-state.js";
|
|
6
6
|
import { taskArtifactPath } from "./task-paths.js";
|
|
7
|
+
import { getTaskTransaction, withTaskTransaction } from "./transaction.js";
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
|
|
10
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) {
|
|
11
17
|
assertRouteInvariants(route);
|
|
12
|
-
const { contractFingerprint, ...writeOptions } = options;
|
|
18
|
+
const { contractFingerprint: suppliedFingerprint, ...writeOptions } = options;
|
|
19
|
+
let contractFingerprint = suppliedFingerprint;
|
|
13
20
|
let contractArtifact = null;
|
|
14
|
-
if (contractFingerprint === undefined) {
|
|
21
|
+
if (contractFingerprint === undefined || !options.taskId) {
|
|
15
22
|
try {
|
|
16
23
|
contractArtifact = await readContract(target, packageRoot, options);
|
|
17
|
-
contractFingerprint = contractArtifact.fingerprint;
|
|
24
|
+
if (contractFingerprint === undefined) contractFingerprint = contractArtifact.fingerprint;
|
|
18
25
|
} catch (error) {
|
|
19
26
|
if (error.code !== "ARTIFACT_MISSING") throw error;
|
|
20
27
|
}
|
|
@@ -24,6 +31,9 @@ export async function persistRoute(target, route, packageRoot, options = {}) {
|
|
|
24
31
|
: { ...route, contractFingerprint };
|
|
25
32
|
assertRouteInvariants(value);
|
|
26
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
|
+
}
|
|
27
37
|
let existingState = null;
|
|
28
38
|
if (taskId) {
|
|
29
39
|
try {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { attachStorageConnectionOwner } from "./storage-runtime-registry.js";
|
|
1
2
|
import {
|
|
2
3
|
E_VERIFICATION_ISOLATION_UNAVAILABLE,
|
|
3
4
|
isVerificationExecutionAdapter,
|
|
@@ -220,5 +221,9 @@ export function createForgeLoopContext(options = {}) {
|
|
|
220
221
|
if (options?.securityReviewProviders !== undefined) {
|
|
221
222
|
configureSecurityReviewProviders(context, options.securityReviewProviders);
|
|
222
223
|
}
|
|
224
|
+
if (options?.persistentStorage !== undefined && typeof options.persistentStorage !== "boolean") {
|
|
225
|
+
throw Object.assign(new Error("persistentStorage must be a boolean"), { code: "E_STORAGE_RUNTIME_CONFIGURATION_INVALID" });
|
|
226
|
+
}
|
|
227
|
+
if (options?.persistentStorage === true) context.close = attachStorageConnectionOwner(context);
|
|
223
228
|
return Object.freeze(context);
|
|
224
229
|
}
|
|
@@ -123,7 +123,6 @@ function validate(value, schema, location, errors, rootSchema = schema) {
|
|
|
123
123
|
return candidateErrors.length === 0;
|
|
124
124
|
});
|
|
125
125
|
if (matches.length !== 1) errors.push(`${location}: expected exactly one matching schema`);
|
|
126
|
-
return;
|
|
127
126
|
}
|
|
128
127
|
|
|
129
128
|
if (schema.type && !typeMatches(value, schema.type)) {
|
|
@@ -213,6 +212,12 @@ export async function readSchema(name, packageRoot = getPackageRoot()) {
|
|
|
213
212
|
return schema;
|
|
214
213
|
}
|
|
215
214
|
|
|
215
|
+
function schemaHealthVersion(schema, name) {
|
|
216
|
+
const versions = schema?.properties?.schemaVersion?.enum ?? null;
|
|
217
|
+
const version = schema?.properties?.schemaVersion?.const ?? (name === "task-bundle" && JSON.stringify(versions) === "[1,2]" ? 2 : null);
|
|
218
|
+
return version;
|
|
219
|
+
}
|
|
220
|
+
|
|
216
221
|
export async function inspectSchemaHealth(packageRoot = getPackageRoot()) {
|
|
217
222
|
const schemas = [];
|
|
218
223
|
for (const name of SHIPPED_SCHEMA_NAMES) {
|
|
@@ -223,13 +228,13 @@ export async function inspectSchemaHealth(packageRoot = getPackageRoot()) {
|
|
|
223
228
|
assertJsonBytes(bytes, filename);
|
|
224
229
|
const schema = JSON.parse(bytes.toString("utf8"));
|
|
225
230
|
assertJsonLimits(schema, filename);
|
|
226
|
-
const version = schema
|
|
231
|
+
const version = schemaHealthVersion(schema, name);
|
|
227
232
|
let status = "valid";
|
|
228
233
|
let error = null;
|
|
229
234
|
if (!schema || typeof schema !== "object" || Array.isArray(schema)) {
|
|
230
235
|
status = "invalid";
|
|
231
236
|
error = "schema root must be an object";
|
|
232
|
-
} else if (version !== 1) {
|
|
237
|
+
} else if (version !== 1 && !(name === "task-bundle" && version === 2)) {
|
|
233
238
|
status = "unsupported-version";
|
|
234
239
|
error = `schemaVersion ${version ?? "missing"} is not supported`;
|
|
235
240
|
}
|
package/src/core/settlement.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getTaskTransaction, withTaskTransaction } from "./transaction.js";
|
|
1
2
|
import { readContract } from "./contract.js";
|
|
2
3
|
import { appendProtocolEvent, validateEventLedger } from "./events.js";
|
|
3
4
|
import {
|
|
@@ -18,7 +19,17 @@ export {
|
|
|
18
19
|
normalizeDecisionText,
|
|
19
20
|
};
|
|
20
21
|
|
|
21
|
-
export async function recordDecisionCriterion({
|
|
22
|
+
export async function recordDecisionCriterion(options) {
|
|
23
|
+
normalizeDecisionCriterionInput(options);
|
|
24
|
+
if (await getTaskTransaction(options.target)) return recordCriterion(options);
|
|
25
|
+
const contract = await readContract(options.target, options.packageRoot, { taskId: options.taskId, contractPath: options.contractPath });
|
|
26
|
+
const taskId = options.taskId ?? contract?.value?.taskId;
|
|
27
|
+
if (!taskId) return recordCriterion(options);
|
|
28
|
+
return withTaskTransaction({ target: options.target, packageRoot: options.packageRoot,
|
|
29
|
+
taskId, operation: "record-decision-criterion" }, () => recordCriterion({ ...options, taskId }));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function recordCriterion({
|
|
22
33
|
target,
|
|
23
34
|
packageRoot,
|
|
24
35
|
decision,
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { isOperationalArtifactPath, needsExistingProjectScope, withExistingProjectScope } from "../../storage/existing-project-scope.js";
|
|
2
|
+
import { constants } from "node:fs";
|
|
3
|
+
import { mkdtemp, open, rm } from "node:fs/promises";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { assertSafePath, ensureWithin } from "../filesystem.js";
|
|
7
|
+
import { getOperationalStore, readOperationalText } from "../../storage/operational-context.js";
|
|
8
|
+
import { signatureReferenceId } from "./bundle-reference.js";
|
|
9
|
+
|
|
10
|
+
/** External signing finishes before immutable bytes enter the prepared commit. */
|
|
11
|
+
export async function withSigningBundleFile(target, destinationPath, statementPath, callback) {
|
|
12
|
+
if (await needsExistingProjectScope(target)) {
|
|
13
|
+
return withExistingProjectScope(target, () => withSigningBundleFile(target, destinationPath, statementPath, callback));
|
|
14
|
+
}
|
|
15
|
+
const store = getOperationalStore(target);
|
|
16
|
+
if (!store) {
|
|
17
|
+
if (isOperationalArtifactPath(destinationPath)) throw Object.assign(new Error("Operational signature bundles require canonical SQLite preparation"), { code: "E_STORAGE_OPERATION_UNSUPPORTED" });
|
|
18
|
+
await assertSafePath(target, destinationPath);
|
|
19
|
+
await callback(ensureWithin(target, destinationPath));
|
|
20
|
+
return { path: destinationPath };
|
|
21
|
+
}
|
|
22
|
+
const transaction = store.transaction;
|
|
23
|
+
if (!transaction || store.db.isTransaction) {
|
|
24
|
+
throw Object.assign(new Error("SQLite signing requires an active preparation outside the native writer"), { code: "E_ATTESTATION_SIGNATURE_INVALID" });
|
|
25
|
+
}
|
|
26
|
+
const source = readOperationalText(target, statementPath);
|
|
27
|
+
if (!source.selected || source.text === null) {
|
|
28
|
+
throw Object.assign(new Error("SQLite signing requires a canonical statement"), { code: "E_ATTESTATION_STATEMENT_INVALID" });
|
|
29
|
+
}
|
|
30
|
+
const directory = await mkdtemp(path.join(os.tmpdir(), "forgeloop-signing-bundle-"));
|
|
31
|
+
try {
|
|
32
|
+
const filename = path.join(directory, "bundle.json");
|
|
33
|
+
await callback(filename);
|
|
34
|
+
if (readOperationalText(target, statementPath).text !== source.text) {
|
|
35
|
+
throw Object.assign(new Error("Canonical statement changed during signing"), { code: "E_ATTESTATION_STATEMENT_INVALID" });
|
|
36
|
+
}
|
|
37
|
+
let handle;
|
|
38
|
+
try {
|
|
39
|
+
await assertSafePath(directory, "bundle.json");
|
|
40
|
+
handle = await open(filename, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
41
|
+
} catch {
|
|
42
|
+
throw Object.assign(new Error("Signer did not produce a safe signature bundle"), { code: "E_ATTESTATION_SIGNATURE_INVALID" });
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
if (!(await handle.stat()).isFile()) throw new Error("Signer output must be a regular file");
|
|
46
|
+
const attachment = await transaction.stageAttachment({
|
|
47
|
+
referenceId: signatureReferenceId(source.text),
|
|
48
|
+
readable: handle.createReadStream({ autoClose: false }),
|
|
49
|
+
});
|
|
50
|
+
return { path: attachment.path, attachment };
|
|
51
|
+
} finally { await handle.close(); }
|
|
52
|
+
} finally { await rm(directory, { recursive: true, force: true }); }
|
|
53
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
/** Bind signature discovery to the exact statement bytes, not mutable filenames. */
|
|
4
|
+
export function signatureReferencePrefix(statementText) {
|
|
5
|
+
return `sigstore:${createHash("sha256").update(statementText, "utf8").digest("hex")}:`;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function signatureReferenceId(statementText) {
|
|
9
|
+
return `${signatureReferencePrefix(statementText)}${randomUUID()}`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Historical logical paths remain addressable without changing ledger bytes. */
|
|
13
|
+
export function legacyAttachmentReferenceId(relativePath) {
|
|
14
|
+
return `legacy-path:${createHash("sha256").update(relativePath, "utf8").digest("hex")}`;
|
|
15
|
+
}
|
|
@@ -1,13 +1,30 @@
|
|
|
1
|
+
import { needsExistingProjectScope, withExistingProjectScope } from "../../storage/existing-project-scope.js";
|
|
1
2
|
import { execFile } from "node:child_process";
|
|
2
3
|
import { promisify } from "node:util";
|
|
3
4
|
|
|
4
5
|
import path from "node:path";
|
|
5
6
|
import { ensureWithin, assertSafePath } from "../filesystem.js";
|
|
7
|
+
import { withSigningStatementFile } from "./statement-file.js";
|
|
8
|
+
import { withSigningBundleFile } from "./bundle-file.js";
|
|
9
|
+
import { getOperationalStore } from "../../storage/operational-context.js";
|
|
10
|
+
import { withOperationalAttachmentFile } from "../../storage/operational-attachments.js";
|
|
6
11
|
|
|
7
12
|
const execFileAsync = promisify(execFile);
|
|
8
13
|
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
9
14
|
const MAX_OUTPUT_BYTES = 8 * 1024 * 1024;
|
|
10
15
|
|
|
16
|
+
async function withExistingSigningReadScope(target, read) {
|
|
17
|
+
if (await needsExistingProjectScope(target)) return withExistingProjectScope(target, read, { readOnly: true });
|
|
18
|
+
return read();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function signingTaskId(target, statementPath) {
|
|
22
|
+
const store = getOperationalStore(target);
|
|
23
|
+
const taskKey = /^\.forgeloop\/task-state\/([a-f0-9]{64})\/attestations\/statement\.json$/u.exec(statementPath)?.[1];
|
|
24
|
+
const taskId = store && taskKey ? store.taskRow(taskKey)?.task_id : null;
|
|
25
|
+
return taskId;
|
|
26
|
+
}
|
|
27
|
+
|
|
11
28
|
export function createSigstoreSigningProvider({
|
|
12
29
|
command = "cosign",
|
|
13
30
|
execFileImpl = execFileAsync,
|
|
@@ -21,7 +38,7 @@ export function createSigstoreSigningProvider({
|
|
|
21
38
|
killSignal: "SIGTERM",
|
|
22
39
|
maxBuffer: MAX_OUTPUT_BYTES,
|
|
23
40
|
});
|
|
24
|
-
|
|
41
|
+
const provider = {
|
|
25
42
|
name: "sigstore",
|
|
26
43
|
async detect() {
|
|
27
44
|
try {
|
|
@@ -43,13 +60,13 @@ export function createSigstoreSigningProvider({
|
|
|
43
60
|
try {
|
|
44
61
|
await assertSafePath(target, statementPath);
|
|
45
62
|
await assertSafePath(target, destinationPath);
|
|
46
|
-
await run([
|
|
63
|
+
const signed = await withSigningBundleFile(target, destinationPath, statementPath, bundleAbsolute => withSigningStatementFile(target, statementPath, statementAbsolute => run([
|
|
47
64
|
"attest-blob",
|
|
48
|
-
"--statement",
|
|
49
|
-
"--bundle",
|
|
65
|
+
"--statement", statementAbsolute,
|
|
66
|
+
"--bundle", bundleAbsolute,
|
|
50
67
|
"--yes",
|
|
51
|
-
], { cwd: path.resolve(target) });
|
|
52
|
-
return { status: "VALID",
|
|
68
|
+
], { cwd: path.resolve(target) })));
|
|
69
|
+
return { status: "VALID", ...signed };
|
|
53
70
|
} catch (error) {
|
|
54
71
|
const unavailable = error.code === "ENOENT" || error.cause?.code === "ENOENT";
|
|
55
72
|
const timedOut = error.code === "ETIMEDOUT" || error.killed === true || error.signal === "SIGTERM";
|
|
@@ -67,33 +84,42 @@ export function createSigstoreSigningProvider({
|
|
|
67
84
|
async verify({ target, statementPath, bundlePath, policy = {} } = {}) {
|
|
68
85
|
if (!bundlePath) return { status: "UNSIGNED", signer: null };
|
|
69
86
|
try {
|
|
70
|
-
await
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
87
|
+
return await withExistingSigningReadScope(target, async () => {
|
|
88
|
+
await assertSafePath(target, statementPath);
|
|
89
|
+
await assertSafePath(target, bundlePath);
|
|
90
|
+
const taskId = signingTaskId(target, statementPath);
|
|
91
|
+
const statementAbsolute = ensureWithin(target, statementPath);
|
|
92
|
+
const bundleAbsolute = ensureWithin(target, bundlePath);
|
|
93
|
+
const args = [
|
|
94
|
+
"verify-blob-attestation",
|
|
95
|
+
"--statement", statementAbsolute,
|
|
96
|
+
"--bundle", bundleAbsolute,
|
|
97
|
+
];
|
|
98
|
+
const identities = Array.isArray(policy.identities)
|
|
99
|
+
? [...new Set(policy.identities.filter((value) => typeof value === "string" && value.length > 0))]
|
|
100
|
+
: [];
|
|
101
|
+
if (policy.identity) args.push("--certificate-identity", policy.identity);
|
|
102
|
+
else if (identities.length === 1) args.push("--certificate-identity", identities[0]);
|
|
103
|
+
else if (identities.length > 1) {
|
|
104
|
+
const exactAlternatives = identities
|
|
105
|
+
.map((value) => value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&"))
|
|
106
|
+
.join("|");
|
|
107
|
+
args.push("--certificate-identity-regexp", `^(?:${exactAlternatives})$`);
|
|
108
|
+
}
|
|
109
|
+
if (policy.issuer) args.push("--certificate-oidc-issuer", policy.issuer);
|
|
110
|
+
if (policy.trustedRoot) {
|
|
111
|
+
await assertSafePath(target, policy.trustedRoot);
|
|
112
|
+
args.push("--trusted-root", ensureWithin(target, policy.trustedRoot));
|
|
113
|
+
}
|
|
114
|
+
await withSigningStatementFile(target, statementPath, statementFilename => {
|
|
115
|
+
args[args.indexOf("--statement") + 1] = statementFilename;
|
|
116
|
+
return withOperationalAttachmentFile(target, bundlePath, taskId, bundleFilename => {
|
|
117
|
+
args[args.indexOf("--bundle") + 1] = bundleFilename;
|
|
118
|
+
return run(args, { cwd: path.resolve(target) });
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
return { status: "VALID", signer: policy.identity ?? (identities.length === 1 ? identities[0] : null) };
|
|
122
|
+
});
|
|
97
123
|
} catch (error) {
|
|
98
124
|
if (error.code === "E_ATTESTATION_SIGNER_UNAVAILABLE") throw error;
|
|
99
125
|
const unavailable = error.code === "ENOENT" || error.cause?.code === "ENOENT";
|
|
@@ -126,6 +152,7 @@ export function createSigstoreSigningProvider({
|
|
|
126
152
|
}
|
|
127
153
|
},
|
|
128
154
|
};
|
|
155
|
+
return provider;
|
|
129
156
|
}
|
|
130
157
|
|
|
131
158
|
/**
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { needsExistingProjectScope, withExistingProjectScope } from "../../storage/existing-project-scope.js";
|
|
2
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { assertSafePath, ensureWithin, writeFileAtomic } from "../filesystem.js";
|
|
6
|
+
import { getOperationalStore, readOperationalText } from "../../storage/operational-context.js";
|
|
7
|
+
|
|
8
|
+
/** Present selected canonical bytes to external signers without a legacy mirror. */
|
|
9
|
+
export async function withSigningStatementFile(target, statementPath, callback) {
|
|
10
|
+
if (await needsExistingProjectScope(target)) {
|
|
11
|
+
return withExistingProjectScope(target, () => withSigningStatementFile(target, statementPath, callback), { readOnly: true });
|
|
12
|
+
}
|
|
13
|
+
await assertSafePath(target, statementPath);
|
|
14
|
+
const source = readOperationalText(target, statementPath);
|
|
15
|
+
if (!source.selected) return callback(ensureWithin(target, statementPath));
|
|
16
|
+
const invalid = message => Object.assign(new Error(message), { code: "E_ATTESTATION_STATEMENT_INVALID" });
|
|
17
|
+
if (source.text === null) throw invalid("Selected attestation statement is missing");
|
|
18
|
+
if (getOperationalStore(target).db.isTransaction) throw invalid("External signing cannot run inside a SQLite write transaction");
|
|
19
|
+
const directory = await mkdtemp(path.join(os.tmpdir(), "forgeloop-signing-statement-"));
|
|
20
|
+
try {
|
|
21
|
+
const filename = path.join(directory, "statement.json");
|
|
22
|
+
await writeFileAtomic(filename, source.text);
|
|
23
|
+
const result = await callback(filename);
|
|
24
|
+
if (readOperationalText(target, statementPath).text !== source.text) throw invalid("Attestation statement changed during external signing");
|
|
25
|
+
return result;
|
|
26
|
+
} finally { await rm(directory, { recursive: true, force: true }); }
|
|
27
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const owners = new WeakMap();
|
|
2
|
+
function closed(message) { return Object.assign(new Error(message), { code: "E_STORAGE_RUNTIME_CLOSED" }); }
|
|
3
|
+
|
|
4
|
+
/** Register lazy ownership without importing a SQLite implementation. */
|
|
5
|
+
export function attachStorageConnectionOwner(context) {
|
|
6
|
+
let owner;
|
|
7
|
+
let loading;
|
|
8
|
+
let closing = false;
|
|
9
|
+
let closePromise;
|
|
10
|
+
const pending = new Set();
|
|
11
|
+
const getOwner = () => loading ??= import("../storage/connection-owner.js").then(module => {
|
|
12
|
+
owner = module.createStorageConnectionOwner();
|
|
13
|
+
return owner;
|
|
14
|
+
});
|
|
15
|
+
const proxy = {
|
|
16
|
+
run(target, callback, options) {
|
|
17
|
+
if (closing) return Promise.reject(closed("Persistent storage runtime is closing or closed"));
|
|
18
|
+
const request = getOwner().then(value => value.run(target, callback, options));
|
|
19
|
+
pending.add(request);
|
|
20
|
+
const finished = () => { pending.delete(request); };
|
|
21
|
+
request.then(finished, finished);
|
|
22
|
+
return request;
|
|
23
|
+
},
|
|
24
|
+
close() {
|
|
25
|
+
if (owner?.isActive()) return Promise.reject(closed("Close the runtime outside an active command"));
|
|
26
|
+
if (closePromise) return closePromise;
|
|
27
|
+
closing = true;
|
|
28
|
+
closePromise = (async () => {
|
|
29
|
+
await Promise.all([...pending].map(request => request.catch(() => undefined)));
|
|
30
|
+
if (loading) await (await loading).close();
|
|
31
|
+
})();
|
|
32
|
+
return closePromise;
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
owners.set(context, proxy);
|
|
36
|
+
return proxy.close;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function getStorageConnectionOwner(context) {
|
|
40
|
+
return context && typeof context === "object" ? owners.get(context) ?? null : null;
|
|
41
|
+
}
|