@cassiomc1/forgeloop 1.14.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/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 +28 -39
- 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 +81 -10
- package/docs/SELF_HOSTED_TESTS.md +121 -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 +71 -29
- 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 +7 -5
- package/src/storage/artifact-bytes.js +22 -0
- package/src/storage/attachment-binding.js +11 -0
- package/src/storage/attachment-files.js +82 -0
- package/src/storage/attachment-references.js +36 -0
- package/src/storage/backup.js +53 -0
- package/src/storage/capabilities.js +31 -0
- package/src/storage/connection-owner.js +119 -0
- package/src/storage/connection.js +376 -0
- package/src/storage/existing-project-scope.js +45 -0
- package/src/storage/export-destination.js +44 -0
- package/src/storage/exporter.js +284 -0
- package/src/storage/file-durability.js +46 -0
- package/src/storage/importer.js +461 -0
- package/src/storage/index.js +74 -0
- package/src/storage/leases.js +63 -0
- package/src/storage/ledger-event-snapshot.js +150 -0
- package/src/storage/ledger-relations.js +220 -0
- package/src/storage/legacy-maintenance-boundary.js +18 -0
- package/src/storage/legacy-signature.js +115 -0
- package/src/storage/maintenance-handoff.js +129 -0
- package/src/storage/maintenance-owner.js +42 -0
- package/src/storage/maintenance.js +310 -0
- package/src/storage/metadata-json.js +35 -0
- package/src/storage/migration-activate.js +59 -0
- package/src/storage/migration-archive.js +57 -0
- package/src/storage/migration-attachment-root.js +61 -0
- package/src/storage/migration-candidate.js +398 -0
- package/src/storage/migration-publication.js +143 -0
- package/src/storage/migration-rollback-admission.js +54 -0
- package/src/storage/migration-rollback-preparation.js +132 -0
- package/src/storage/migration-rollback-publication.js +183 -0
- package/src/storage/migration-rollback-release.js +66 -0
- package/src/storage/migration-rollback-source-stage.js +103 -0
- package/src/storage/migration-rollback.js +43 -0
- package/src/storage/migration-source-partition.js +56 -0
- package/src/storage/migration-source.js +255 -0
- package/src/storage/migration-terminal.js +39 -0
- package/src/storage/migration-validation.js +75 -0
- package/src/storage/migration.js +43 -0
- package/src/storage/operational-attachments.js +49 -0
- package/src/storage/operational-context.js +50 -0
- package/src/storage/project-backup.js +98 -0
- package/src/storage/project-bootstrap.js +24 -0
- package/src/storage/project-boundary.js +79 -0
- package/src/storage/project-read-snapshot.js +18 -0
- package/src/storage/project-replacement.js +137 -0
- package/src/storage/project-restore.js +203 -0
- package/src/storage/record-definitions.js +45 -0
- package/src/storage/repository.js +507 -0
- package/src/storage/restore-journal.js +62 -0
- package/src/storage/restore-layout.js +30 -0
- package/src/storage/restore-preparation.js +84 -0
- package/src/storage/restore-replacement-archive.js +162 -0
- package/src/storage/restore-replacement-owner.js +31 -0
- package/src/storage/restore-replacement-preparation.js +128 -0
- package/src/storage/restore-replacement.js +142 -0
- package/src/storage/restore-terminal.js +32 -0
- package/src/storage/runtime.js +31 -0
- package/src/storage/schema.js +214 -0
- package/src/storage/singleton-import.js +84 -0
- package/src/storage/snapshot-continuity.js +16 -0
- package/src/storage/snapshot.js +36 -0
- package/src/storage/storage-marker.js +79 -0
- package/src/storage/task-guards.js +107 -0
- package/src/storage/topology.js +42 -0
- package/src/storage/transaction.js +179 -0
- package/src/storage/unit-of-work.js +663 -0
- package/src/storage/windows-process-incarnation.js +206 -0
- package/.forgeloop/forgeloop.gitignore +0 -4
- package/LICENSE-DOCS.md +0 -13
- package/src/core/transaction-maintenance.js +0 -70
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withProjectReadSnapshot } from "../storage/project-read-snapshot.js";
|
|
1
2
|
import { createHash } from "node:crypto";
|
|
2
3
|
|
|
3
4
|
import { canonicalFingerprint } from "./artifacts.js";
|
|
@@ -8,7 +9,8 @@ import { assertRevisionProvider, resolveRevisionProvider } from "./revision/prov
|
|
|
8
9
|
import { REVISION_PROVIDERS } from "./revision/registry.js";
|
|
9
10
|
import { assertSigningProvider, resolveSigningProvider } from "./signing/provider.js";
|
|
10
11
|
import { SIGNING_PROVIDERS } from "./signing/registry.js";
|
|
11
|
-
import {
|
|
12
|
+
import { taskAttestationStatementPath } from "./task-paths.js";
|
|
13
|
+
import { resolveAttestationBundlePath } from "./attachment-paths.js";
|
|
12
14
|
import { ensureWithin, fileExists } from "./filesystem.js";
|
|
13
15
|
import { E_ATTESTATION_CONFIGURATION_INVALID } from "./error-codes.js";
|
|
14
16
|
|
|
@@ -130,6 +132,23 @@ export async function verifyAttestation({
|
|
|
130
132
|
signingProvider = null,
|
|
131
133
|
trustedRoot = null,
|
|
132
134
|
requireSignature = false,
|
|
135
|
+
} = {}) {
|
|
136
|
+
const options = { target, packageRoot, taskId, revision, bundlePath, identity, issuer, revisionProvider, signingProvider, trustedRoot, requireSignature };
|
|
137
|
+
return withProjectReadSnapshot(target, () => verifySelectedAttestation(options));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function verifySelectedAttestation({
|
|
141
|
+
target,
|
|
142
|
+
packageRoot = getPackageRoot(),
|
|
143
|
+
taskId,
|
|
144
|
+
revision = null,
|
|
145
|
+
bundlePath = null,
|
|
146
|
+
identity = null,
|
|
147
|
+
issuer = null,
|
|
148
|
+
revisionProvider = null,
|
|
149
|
+
signingProvider = null,
|
|
150
|
+
trustedRoot = null,
|
|
151
|
+
requireSignature = false,
|
|
133
152
|
} = {}) {
|
|
134
153
|
let effectiveRequireSignature = requireSignature === true;
|
|
135
154
|
let effectiveSigningProvider = signingProvider;
|
|
@@ -146,7 +165,6 @@ export async function verifyAttestation({
|
|
|
146
165
|
} catch (error) {
|
|
147
166
|
if (error.code !== "ARTIFACT_MISSING") configurationError = error;
|
|
148
167
|
}
|
|
149
|
-
if (effectiveRequireSignature && !effectiveBundlePath) effectiveBundlePath = taskAttestationBundlePath(taskId);
|
|
150
168
|
const result = {
|
|
151
169
|
schemaVersion: 1,
|
|
152
170
|
status: "INVALID",
|
|
@@ -166,6 +184,7 @@ export async function verifyAttestation({
|
|
|
166
184
|
return result;
|
|
167
185
|
}
|
|
168
186
|
try {
|
|
187
|
+
if (effectiveBundlePath || effectiveRequireSignature) effectiveBundlePath = resolveAttestationBundlePath(target, taskId, effectiveBundlePath);
|
|
169
188
|
const statementArtifact = await readAttestationStatement({ target, packageRoot, taskId });
|
|
170
189
|
const manifestArtifact = await readCodeManifest({ target, packageRoot, taskId });
|
|
171
190
|
await validateAttestationStatement(statementArtifact.value, packageRoot);
|
package/src/core/attestation.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { withTaskTransaction } from "./transaction.js";
|
|
2
|
+
import { withProjectReadSnapshot } from "../storage/project-read-snapshot.js";
|
|
1
3
|
import { canonicalFingerprint, readJsonArtifact, writeJsonArtifact } from "./artifacts.js";
|
|
2
4
|
import { assertSchema, readSchema } from "./schema-validation.js";
|
|
3
5
|
import { assertSecretFree } from "./receipt.js";
|
|
@@ -6,10 +8,11 @@ import { readCodeManifest, validateCodeManifestBindings } from "./code-manifest.
|
|
|
6
8
|
import {
|
|
7
9
|
taskAttestationStatementPath,
|
|
8
10
|
taskAttestationStatementHistoryPath,
|
|
9
|
-
taskAttestationBundlePath,
|
|
10
11
|
} from "./task-paths.js";
|
|
11
12
|
import { appendProtocolEvent } from "./events.js";
|
|
12
13
|
import { ensureWithin, fileExists } from "./filesystem.js";
|
|
14
|
+
import { operationalArtifactExists } from "../storage/operational-context.js";
|
|
15
|
+
import { resolveAttestationBundlePath } from "./attachment-paths.js";
|
|
13
16
|
import { E_ATTESTATION_CONFIGURATION_INVALID } from "./error-codes.js";
|
|
14
17
|
|
|
15
18
|
const STATEMENT_TYPE = "https://in-toto.io/Statement/v1";
|
|
@@ -136,8 +139,13 @@ export async function readAttestationStatement({ target, packageRoot = getPackag
|
|
|
136
139
|
|
|
137
140
|
export async function writeAttestationStatement({ target, packageRoot = getPackageRoot(), taskId, statement } = {}) {
|
|
138
141
|
await validateAttestationStatement(statement, packageRoot);
|
|
142
|
+
return withTaskTransaction({ target, taskId, packageRoot, operation: "attestation-create" }, () =>
|
|
143
|
+
writeValidatedAttestationStatement({ target, packageRoot, taskId, statement }));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async function writeValidatedAttestationStatement({ target, packageRoot, taskId, statement }) {
|
|
139
147
|
const relativePath = taskAttestationStatementPath(taskId);
|
|
140
|
-
if (await fileExists(ensureWithin(target, relativePath))) {
|
|
148
|
+
if (operationalArtifactExists(target, relativePath) ?? await fileExists(ensureWithin(target, relativePath))) {
|
|
141
149
|
let previous;
|
|
142
150
|
try {
|
|
143
151
|
previous = await readAttestationStatement({ target, packageRoot, taskId });
|
|
@@ -150,7 +158,7 @@ export async function writeAttestationStatement({ target, packageRoot = getPacka
|
|
|
150
158
|
throw attestationError("E_ATTESTATION_STATEMENT_INVALID", "Attestation statement is immutable within a verification cycle", [relativePath]);
|
|
151
159
|
}
|
|
152
160
|
const historyPath = taskAttestationStatementHistoryPath(taskId, previousCycle);
|
|
153
|
-
if (!(await fileExists(ensureWithin(target, historyPath)))) {
|
|
161
|
+
if (!(operationalArtifactExists(target, historyPath) ?? await fileExists(ensureWithin(target, historyPath)))) {
|
|
154
162
|
await writeJsonArtifact(target, historyPath, previous.value, "in-toto-statement", packageRoot, { taskId, operation: "attestation-statement-history" });
|
|
155
163
|
}
|
|
156
164
|
}
|
|
@@ -164,7 +172,11 @@ export async function writeAttestationStatement({ target, packageRoot = getPacka
|
|
|
164
172
|
return { path: relativePath, fingerprint: artifact.fingerprint, statement };
|
|
165
173
|
}
|
|
166
174
|
|
|
167
|
-
export async function resolveAttestationStatus(
|
|
175
|
+
export async function resolveAttestationStatus(options = {}) {
|
|
176
|
+
return withProjectReadSnapshot(options.target, () => resolveSelectedAttestationStatus(options));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function resolveSelectedAttestationStatus({ target, packageRoot = getPackageRoot(), taskId, revisionProvider = null, bundlePath = null, requireSignature = false, signingProvider = null, signerPolicy = {} } = {}) {
|
|
168
180
|
const result = {
|
|
169
181
|
taskId,
|
|
170
182
|
status: "MISSING",
|
|
@@ -192,7 +204,6 @@ export async function resolveAttestationStatus({ target, packageRoot = getPackag
|
|
|
192
204
|
...(configuredSigning.policy ?? {}),
|
|
193
205
|
...signerPolicy,
|
|
194
206
|
};
|
|
195
|
-
if (effectiveRequireSignature && !effectiveBundlePath) effectiveBundlePath = taskAttestationBundlePath(taskId);
|
|
196
207
|
result.signature = effectiveRequireSignature ? "REQUIRED" : "NOT_CHECKED";
|
|
197
208
|
if ((config.attestation?.mode ?? "off") === "off" && !bundlePath && !effectiveRequireSignature) {
|
|
198
209
|
result.status = "DISABLED";
|
|
@@ -208,8 +219,8 @@ export async function resolveAttestationStatus({ target, packageRoot = getPackag
|
|
|
208
219
|
}
|
|
209
220
|
}
|
|
210
221
|
result.signature = effectiveRequireSignature ? "REQUIRED" : "NOT_CHECKED";
|
|
211
|
-
if (effectiveRequireSignature && !effectiveBundlePath) effectiveBundlePath = taskAttestationBundlePath(taskId);
|
|
212
222
|
try {
|
|
223
|
+
if (effectiveBundlePath || effectiveRequireSignature) effectiveBundlePath = resolveAttestationBundlePath(target, taskId, effectiveBundlePath);
|
|
213
224
|
const statement = await readAttestationStatement({ target, packageRoot, taskId });
|
|
214
225
|
const manifest = await readCodeManifest({ target, packageRoot, taskId });
|
|
215
226
|
const provider = revisionProvider ?? manifest.value.capture.revisionProvider;
|
package/src/core/audit.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withProjectReadSnapshot } from "../storage/project-read-snapshot.js";
|
|
1
2
|
import { ARTIFACT_PATHS } from "./artifacts.js";
|
|
2
3
|
import { evaluateCompletion } from "./completion.js";
|
|
3
4
|
import { readManifest } from "./manifest.js";
|
|
@@ -108,7 +109,11 @@ async function evaluateLocalAttestation({ target, packageRoot, taskId, completio
|
|
|
108
109
|
return { ...base, status: "VALID" };
|
|
109
110
|
}
|
|
110
111
|
|
|
111
|
-
export async function evaluateAudit({
|
|
112
|
+
export async function evaluateAudit(options = {}) {
|
|
113
|
+
return withProjectReadSnapshot(options.target, () => evaluateSelectedAudit(options));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function evaluateSelectedAudit({
|
|
112
117
|
target,
|
|
113
118
|
packageRoot,
|
|
114
119
|
strict = false,
|
package/src/core/bundles.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { readdir } from "node:fs/promises";
|
|
2
|
+
import { createReadStream } from "node:fs";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import { resolveAttestationBundlePath } from "./attachment-paths.js";
|
|
5
|
+
import { withOperationalAttachmentFile } from "../storage/operational-attachments.js";
|
|
6
|
+
import { getOperationalStore, operationalArtifactExists, listOperationalArtifactNames } from "../storage/operational-context.js";
|
|
2
7
|
|
|
3
|
-
import { ARTIFACT_PATHS, canonicalFingerprint, readJsonArtifact,
|
|
8
|
+
import { ARTIFACT_PATHS, canonicalFingerprint, readJsonArtifact, readPortableJsonArtifact, writePortableJsonArtifact } from "./artifacts.js";
|
|
4
9
|
import { validateContract } from "./contract.js";
|
|
5
10
|
import { assertSafePath, ensureWithin, fileExists, readBytes, writeFileAtomic } from "./filesystem.js";
|
|
6
11
|
import { PROTOCOL_VERSION } from "./protocol.js";
|
|
@@ -12,11 +17,13 @@ import { taskArtifactPath, taskDirectory, taskStructuralQualityDirectory } from
|
|
|
12
17
|
import { resolveTaskClaimState } from "./task-claim-state.js";
|
|
13
18
|
import { E_TASK_CLAIM_OWNERSHIP_INCONSISTENT } from "./error-codes.js";
|
|
14
19
|
import { listActions } from "./actions.js";
|
|
20
|
+
import { validateActionArtifact } from "./action-model.js";
|
|
15
21
|
import { validateCanonicalHandoff } from "./handoff.js";
|
|
16
22
|
import { validateVerificationScope } from "./verification-scope.js";
|
|
17
23
|
import { validateResponsibilityContract } from "./responsibility.js";
|
|
18
24
|
import { validateWorkspaceBinding } from "./workspace-binding.js";
|
|
19
25
|
import { validateCodeManifest, validateCodeManifestBindings } from "./code-manifest.js";
|
|
26
|
+
import { withNativeReadScope } from "./native-storage.js";
|
|
20
27
|
import { assertAttestationStatementBindings } from "./attestation-verifier.js";
|
|
21
28
|
import { validateAttestationStatement } from "./attestation.js";
|
|
22
29
|
import {
|
|
@@ -27,7 +34,16 @@ import {
|
|
|
27
34
|
} from "./structural-quality/artifacts.js";
|
|
28
35
|
import { normalizeStructuralQualityConfig, structuralQualityPolicyFingerprint } from "./structural-quality/policy.js";
|
|
29
36
|
|
|
30
|
-
export const BUNDLE_SCHEMA_VERSION =
|
|
37
|
+
export const BUNDLE_SCHEMA_VERSION = 2;
|
|
38
|
+
|
|
39
|
+
async function bundleFileBinding(target, relativePath, name) {
|
|
40
|
+
if (typeof name !== "string" || name.includes("\\") || name.includes("\u0000") || name.split("/").some(part => !part || part === "." || part === "..")) throw Object.assign(new Error("Invalid portable bundle artifact path"), { code: "E_BUNDLE_PATH_INVALID" });
|
|
41
|
+
const filename = await assertSafePath(target, relativePath);
|
|
42
|
+
const hash = createHash("sha256");
|
|
43
|
+
let size = 0;
|
|
44
|
+
for await (const bytes of createReadStream(filename)) { hash.update(bytes); size += bytes.length; }
|
|
45
|
+
return { path: name, size, sha256: hash.digest("hex") };
|
|
46
|
+
}
|
|
31
47
|
const BUNDLE_ROOT = ".forgeloop/tasks";
|
|
32
48
|
|
|
33
49
|
function safeTaskId(taskId) {
|
|
@@ -161,10 +177,32 @@ function assertBundledCodeManifestBindings({ loaded, ledger, taskId }) {
|
|
|
161
177
|
}
|
|
162
178
|
}
|
|
163
179
|
|
|
180
|
+
/** Export sources may be explicit legacy inputs; selected native authority never falls back. */
|
|
181
|
+
function readBundleSourceJson(target,sourcePath,schemaName,packageRoot){
|
|
182
|
+
const reader=getOperationalStore(target)?readJsonArtifact:readPortableJsonArtifact;
|
|
183
|
+
return reader(target,sourcePath,schemaName,packageRoot);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Read-only legacy export inspection; operational action listing remains SQLite-only. */
|
|
187
|
+
async function readBundleSourceActions(target,taskId,packageRoot){
|
|
188
|
+
if(getOperationalStore(target))return listActions(target,{taskId,packageRoot});
|
|
189
|
+
const directory=`${taskDirectory(taskId)}/actions`;
|
|
190
|
+
const absolute=await assertSafePath(target,directory);
|
|
191
|
+
if(!(await fileExists(absolute)))return [];
|
|
192
|
+
const actions=[];
|
|
193
|
+
for(const name of (await readdir(absolute)).filter(name=>name.endsWith(".json")).sort()){
|
|
194
|
+
const artifact=await readPortableJsonArtifact(target,`${directory}/${name}`,"action",packageRoot);
|
|
195
|
+
const action=validateActionArtifact(artifact.value);
|
|
196
|
+
if(action.taskId!==taskId || `${action.actionId}.json`!==name)throw bundleBindingError("E_BUNDLE_TASK_MISMATCH","Legacy export action identity differs from its source path");
|
|
197
|
+
actions.push(action);
|
|
198
|
+
}
|
|
199
|
+
return actions.sort((left,right)=>String(left.createdAt).localeCompare(String(right.createdAt)));
|
|
200
|
+
}
|
|
201
|
+
|
|
164
202
|
async function copyJson(target, sourcePath, destinationPath, schemaName, packageRoot, artifacts, relativeName) {
|
|
165
203
|
try {
|
|
166
|
-
const value = await
|
|
167
|
-
await
|
|
204
|
+
const value = await readBundleSourceJson(target, sourcePath, schemaName, packageRoot);
|
|
205
|
+
await writePortableJsonArtifact(target, destinationPath, value.value, schemaName, packageRoot);
|
|
168
206
|
artifacts.push(relativeName);
|
|
169
207
|
return value;
|
|
170
208
|
} catch (error) {
|
|
@@ -176,37 +214,52 @@ async function copyJson(target, sourcePath, destinationPath, schemaName, package
|
|
|
176
214
|
async function copyOptionalJson(target, taskPath, legacyPath, destinationPath, schemaName, packageRoot, artifacts, relativeName) {
|
|
177
215
|
const candidates = [taskPath, legacyPath].filter(Boolean);
|
|
178
216
|
for (const sourcePath of candidates) {
|
|
179
|
-
if (
|
|
217
|
+
if (getOperationalStore(target) && sourcePath === legacyPath) continue;
|
|
218
|
+
if (!(operationalArtifactExists(target, sourcePath) ?? await fileExists(ensureWithin(target, sourcePath)))) continue;
|
|
180
219
|
return copyJson(target, sourcePath, destinationPath, schemaName, packageRoot, artifacts, relativeName);
|
|
181
220
|
}
|
|
182
221
|
return null;
|
|
183
222
|
}
|
|
184
223
|
|
|
185
|
-
async function copyRawFile(target, sourcePath, destinationPath, artifacts, relativeName) {
|
|
186
|
-
|
|
187
|
-
await
|
|
188
|
-
await
|
|
224
|
+
async function copyRawFile(target, sourcePath, destinationPath, artifacts, relativeName, taskId) {
|
|
225
|
+
const canonical = getOperationalStore(target) && sourcePath.startsWith(".forgeloop/attachments/objects/");
|
|
226
|
+
if (!canonical && !(await fileExists(ensureWithin(target, sourcePath)))) return false;
|
|
227
|
+
await assertSafePath(target, sourcePath);
|
|
228
|
+
const destination = await assertSafePath(target, destinationPath);
|
|
229
|
+
await withOperationalAttachmentFile(target, sourcePath, taskId, source => writeFileAtomic(destination, createReadStream(source)));
|
|
189
230
|
artifacts.push(relativeName);
|
|
190
231
|
return true;
|
|
191
232
|
}
|
|
192
233
|
|
|
193
234
|
async function tryReadJson(target, taskPath, legacyPath, schemaName, packageRoot) {
|
|
194
235
|
try {
|
|
195
|
-
return await
|
|
236
|
+
return await readBundleSourceJson(target, taskPath, schemaName, packageRoot);
|
|
196
237
|
} catch (error) {
|
|
197
|
-
if (error.code === "ARTIFACT_MISSING" && legacyPath) {
|
|
198
|
-
return await
|
|
238
|
+
if (error.code === "ARTIFACT_MISSING" && legacyPath && !getOperationalStore(target)) {
|
|
239
|
+
return await readBundleSourceJson(target, legacyPath, schemaName, packageRoot);
|
|
199
240
|
}
|
|
200
241
|
throw error;
|
|
201
242
|
}
|
|
202
243
|
}
|
|
203
244
|
|
|
204
245
|
export async function exportTaskBundle(target, taskId, packageRoot) {
|
|
246
|
+
return withNativeReadScope(target, async () => {
|
|
247
|
+
const store = getOperationalStore(target);
|
|
248
|
+
if (!store) return exportSelectedTaskBundle(target, taskId, packageRoot);
|
|
249
|
+
const [{ withStorageSnapshot }, { withOperationalStore }, { operationalContext }] = await Promise.all([
|
|
250
|
+
import("../storage/snapshot.js"), import("../storage/unit-of-work.js"), import("../storage/operational-context.js"),
|
|
251
|
+
]);
|
|
252
|
+
return withStorageSnapshot(store.db, snapshot => operationalContext.run(null,
|
|
253
|
+
() => withOperationalStore({ db: snapshot, target }, () => exportSelectedTaskBundle(target, taskId, packageRoot))));
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
async function exportSelectedTaskBundle(target, taskId, packageRoot) {
|
|
205
258
|
safeTaskId(taskId);
|
|
206
259
|
const directory = bundleDirectory(taskId);
|
|
207
260
|
const artifacts = [];
|
|
208
261
|
|
|
209
|
-
if (await fileExists(ensureWithin(target, taskArtifactPath(taskId, "descriptor")))) {
|
|
262
|
+
if (operationalArtifactExists(target, taskArtifactPath(taskId, "descriptor")) ?? await fileExists(ensureWithin(target, taskArtifactPath(taskId, "descriptor")))) {
|
|
210
263
|
const claimProjection = await resolveTaskClaimState(target, { taskId, packageRoot });
|
|
211
264
|
if (!claimProjection.valid) {
|
|
212
265
|
const error = new Error(`Task ${taskId} claim ownership is inconsistent and cannot be exported safely`);
|
|
@@ -255,7 +308,7 @@ export async function exportTaskBundle(target, taskId, packageRoot) {
|
|
|
255
308
|
error.code = "E_BUNDLE_TASK_MISMATCH";
|
|
256
309
|
throw error;
|
|
257
310
|
}
|
|
258
|
-
await
|
|
311
|
+
await writePortableJsonArtifact(target, `${directory}/${destinationName}`, source.value, schemaName, packageRoot);
|
|
259
312
|
artifacts.push(destinationName);
|
|
260
313
|
}
|
|
261
314
|
|
|
@@ -295,12 +348,12 @@ export async function exportTaskBundle(target, taskId, packageRoot) {
|
|
|
295
348
|
const qualityBaseline = await readStructuralQualityBaseline(target, taskId, packageRoot);
|
|
296
349
|
const qualityEvaluations = await listStructuralQualityEvaluations(target, taskId, packageRoot);
|
|
297
350
|
if (qualityBaseline) {
|
|
298
|
-
await
|
|
351
|
+
await writePortableJsonArtifact(target, `${directory}/structural-quality/baseline.json`, qualityBaseline.value, "structural-quality", packageRoot);
|
|
299
352
|
artifacts.push("structural-quality/baseline.json");
|
|
300
353
|
}
|
|
301
354
|
for (const evaluation of qualityEvaluations) {
|
|
302
355
|
const destination = `structural-quality/evaluations/cycle-${evaluation.value.verificationCycle}-attempt-${evaluation.value.attempt}.json`;
|
|
303
|
-
await
|
|
356
|
+
await writePortableJsonArtifact(target, `${directory}/${destination}`, evaluation.value, "structural-quality", packageRoot);
|
|
304
357
|
artifacts.push(destination);
|
|
305
358
|
}
|
|
306
359
|
|
|
@@ -330,22 +383,23 @@ export async function exportTaskBundle(target, taskId, packageRoot) {
|
|
|
330
383
|
}
|
|
331
384
|
|
|
332
385
|
const handoffDirectory = taskArtifactPath(taskId, "handoffs");
|
|
333
|
-
|
|
334
|
-
|
|
386
|
+
const selectedHandoffs = listOperationalArtifactNames(target, taskId, "handoffs");
|
|
387
|
+
if (selectedHandoffs !== null || await fileExists(ensureWithin(target, handoffDirectory))) {
|
|
388
|
+
const handoffEntries = selectedHandoffs !== null ? selectedHandoffs.map(name => ({ name, isFile: () => true })) : await readdir(ensureWithin(target, handoffDirectory), { withFileTypes: true });
|
|
335
389
|
for (const entry of handoffEntries
|
|
336
390
|
.filter((item) => item.isFile() && /^handoff-[A-Za-z0-9_-]+\.json$/u.test(item.name))
|
|
337
391
|
.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
338
392
|
const sourcePath = `${handoffDirectory}/${entry.name}`;
|
|
339
|
-
const handoff = await
|
|
393
|
+
const handoff = await readBundleSourceJson(target, sourcePath, "handoff-envelope", packageRoot);
|
|
340
394
|
await validateCanonicalHandoff(target, handoff.value, { taskId, packageRoot });
|
|
341
395
|
const destination = `handoffs/${entry.name}`;
|
|
342
|
-
await
|
|
396
|
+
await writePortableJsonArtifact(target, `${directory}/${destination}`, handoff.value, "handoff-envelope", packageRoot);
|
|
343
397
|
artifacts.push(destination);
|
|
344
398
|
}
|
|
345
399
|
}
|
|
346
400
|
|
|
347
|
-
const rawAttestationBundle =
|
|
348
|
-
await copyRawFile(target, rawAttestationBundle, `${directory}/attestations/statement.sigstore.json`, artifacts, "attestations/statement.sigstore.json");
|
|
401
|
+
const rawAttestationBundle = resolveAttestationBundlePath(target, taskId);
|
|
402
|
+
await copyRawFile(target, rawAttestationBundle, `${directory}/attestations/statement.sigstore.json`, artifacts, "attestations/statement.sigstore.json", taskId);
|
|
349
403
|
if (exportedManifest?.value) {
|
|
350
404
|
exportedManifest.value = await validateCodeManifest(exportedManifest.value, packageRoot);
|
|
351
405
|
if (exportedManifest.value.taskId !== taskId) {
|
|
@@ -366,10 +420,10 @@ export async function exportTaskBundle(target, taskId, packageRoot) {
|
|
|
366
420
|
if (exportedManifest?.value && exportedStatement?.value) {
|
|
367
421
|
assertAttestationStatementBindings(exportedStatement.value, exportedManifest.value, taskId, canonicalFingerprint(exportedManifest.value));
|
|
368
422
|
}
|
|
369
|
-
const actionArtifacts = await
|
|
423
|
+
const actionArtifacts = await readBundleSourceActions(target,taskId,packageRoot);
|
|
370
424
|
for (const action of actionArtifacts) {
|
|
371
425
|
const destination = `${directory}/actions/${action.actionId}.json`;
|
|
372
|
-
await
|
|
426
|
+
await writePortableJsonArtifact(target, destination, action, "action", packageRoot);
|
|
373
427
|
artifacts.push(`actions/${action.actionId}.json`);
|
|
374
428
|
}
|
|
375
429
|
|
|
@@ -380,38 +434,11 @@ export async function exportTaskBundle(target, taskId, packageRoot) {
|
|
|
380
434
|
for (const executionRef of executionRefs) {
|
|
381
435
|
const execution = await readExecutionArtifact({ target, executionRef, packageRoot, taskId });
|
|
382
436
|
const destination = `${directory}/executions/${execution.value.executionId}.json`;
|
|
383
|
-
await
|
|
437
|
+
await writePortableJsonArtifact(target, destination, execution.value, "execution", packageRoot);
|
|
384
438
|
artifacts.push(`executions/${execution.value.executionId}.json`);
|
|
385
439
|
}
|
|
386
440
|
|
|
387
|
-
|
|
388
|
-
let eventsPath = ensureWithin(target, taskArtifactPath(taskId, "events"));
|
|
389
|
-
if (!(await fileExists(eventsPath))) {
|
|
390
|
-
eventsPath = ensureWithin(target, ARTIFACT_PATHS.events);
|
|
391
|
-
}
|
|
392
|
-
if (await fileExists(eventsPath)) {
|
|
393
|
-
await assertSafePath(target, `${directory}/events.ndjson`);
|
|
394
|
-
await writeFileAtomic(ensureWithin(target, `${directory}/events.ndjson`), await readBytes(eventsPath));
|
|
395
|
-
artifacts.push("events.ndjson");
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
// Gates
|
|
399
|
-
let gateDirectory = ensureWithin(target, `${taskDirectory(taskId)}/gates`);
|
|
400
|
-
if (!(await fileExists(gateDirectory))) {
|
|
401
|
-
gateDirectory = ensureWithin(target, ARTIFACT_PATHS.gates);
|
|
402
|
-
}
|
|
403
|
-
if (await fileExists(gateDirectory)) {
|
|
404
|
-
const entries = await readdir(gateDirectory, { withFileTypes: true });
|
|
405
|
-
for (const entry of entries.filter((item) => item.isFile() && item.name.endsWith(".json")).sort((left, right) => left.name.localeCompare(right.name))) {
|
|
406
|
-
const gateName = entry.name.slice(0, -5);
|
|
407
|
-
const sourcePath = `${gateDirectory.replace(target + "/", "")}/${entry.name}`;
|
|
408
|
-
const destinationPath = `${directory}/gates/${entry.name}`;
|
|
409
|
-
const gate = await readJsonArtifact(target, sourcePath, "gate", packageRoot);
|
|
410
|
-
if (gate.value.taskId !== taskId) continue;
|
|
411
|
-
await writeJsonArtifact(target, destinationPath, gate.value, "gate", packageRoot);
|
|
412
|
-
artifacts.push(`gates/${gateName}.json`);
|
|
413
|
-
}
|
|
414
|
-
}
|
|
441
|
+
await exportBundleLedgerAndGates(target, taskId, packageRoot, directory, artifacts);
|
|
415
442
|
|
|
416
443
|
artifacts.sort();
|
|
417
444
|
const manifest = {
|
|
@@ -419,14 +446,17 @@ export async function exportTaskBundle(target, taskId, packageRoot) {
|
|
|
419
446
|
protocolVersion: PROTOCOL_VERSION,
|
|
420
447
|
taskId,
|
|
421
448
|
artifacts,
|
|
449
|
+
files: [],
|
|
422
450
|
};
|
|
423
|
-
await
|
|
451
|
+
for (const artifact of artifacts) manifest.files.push(await bundleFileBinding(target, `${directory}/${artifact}`, artifact));
|
|
452
|
+
await writePortableJsonArtifact(target, `${directory}/bundle.json`, manifest, "task-bundle", packageRoot);
|
|
424
453
|
return { ...manifest, path: `${directory}/bundle.json` };
|
|
425
454
|
}
|
|
426
455
|
|
|
427
456
|
export async function readTaskBundle(target, taskId, packageRoot) {
|
|
428
457
|
const directory = bundleDirectory(taskId);
|
|
429
458
|
const manifest = await readJsonArtifact(target, `${directory}/bundle.json`, "task-bundle", packageRoot);
|
|
459
|
+
await verifyBundleFileBindings(target, directory, manifest);
|
|
430
460
|
const loaded = {};
|
|
431
461
|
const mappings = {
|
|
432
462
|
"contract.json": ["contract", "current-contract"],
|
|
@@ -450,18 +480,7 @@ export async function readTaskBundle(target, taskId, packageRoot) {
|
|
|
450
480
|
for (const artifact of manifest.value.artifacts) {
|
|
451
481
|
const qualityKind = structuralQualityBundleKind(artifact);
|
|
452
482
|
if (qualityKind) {
|
|
453
|
-
|
|
454
|
-
validateStructuralQualityArtifact(qualityArtifact.value, artifact);
|
|
455
|
-
if (qualityArtifact.value.taskId !== taskId) {
|
|
456
|
-
throw bundleBindingError("E_BUNDLE_TASK_MISMATCH", `Structural-quality ${qualityKind} taskId does not match its bundle task`);
|
|
457
|
-
}
|
|
458
|
-
loaded.structuralQuality ??= { baseline: null, evaluations: [] };
|
|
459
|
-
if (qualityKind === "baseline") {
|
|
460
|
-
if (loaded.structuralQuality.baseline) throw bundleBindingError("E_STRUCTURAL_QUALITY_EVIDENCE_STALE", "A bundle cannot contain more than one structural-quality baseline");
|
|
461
|
-
loaded.structuralQuality.baseline = qualityArtifact.value;
|
|
462
|
-
} else {
|
|
463
|
-
loaded.structuralQuality.evaluations.push(qualityArtifact.value);
|
|
464
|
-
}
|
|
483
|
+
await loadBundleQualityArtifact(target, directory, artifact, packageRoot, taskId, loaded, qualityKind);
|
|
465
484
|
continue;
|
|
466
485
|
}
|
|
467
486
|
if (artifact.startsWith("executions/") && artifact.endsWith(".json")) {
|
|
@@ -501,6 +520,9 @@ export async function readTaskBundle(target, taskId, packageRoot) {
|
|
|
501
520
|
}
|
|
502
521
|
if (mapping[1] === "continuity") {
|
|
503
522
|
assertContinuitySemantics(loadedArtifact.value);
|
|
523
|
+
if (loadedArtifact.value.taskId !== taskId) {
|
|
524
|
+
throw bundleBindingError("E_BUNDLE_TASK_MISMATCH", "Continuity taskId does not match its bundle task");
|
|
525
|
+
}
|
|
504
526
|
}
|
|
505
527
|
if (mapping[1] === "workspace-binding" && loadedArtifact.value.taskId !== taskId) {
|
|
506
528
|
const error = new Error("Workspace binding taskId does not match its bundle task");
|
|
@@ -586,3 +608,75 @@ export async function readTaskBundle(target, taskId, packageRoot) {
|
|
|
586
608
|
}
|
|
587
609
|
return { manifest: manifest.value, artifacts: loaded };
|
|
588
610
|
}
|
|
611
|
+
|
|
612
|
+
async function exportBundleLedgerAndGates(target, taskId, packageRoot, directory, artifacts) {
|
|
613
|
+
// Events
|
|
614
|
+
const selectedStore = getOperationalStore(target);
|
|
615
|
+
if (selectedStore) {
|
|
616
|
+
async function* ledgerBytes() {
|
|
617
|
+
for (const row of selectedStore.db.prepare("SELECT event_json FROM events WHERE task_id = ? ORDER BY seq").iterate(taskId)) yield `${JSON.stringify(JSON.parse(row.event_json))}\n`;
|
|
618
|
+
}
|
|
619
|
+
await writeFileAtomic(await assertSafePath(target, `${directory}/events.ndjson`), ledgerBytes());
|
|
620
|
+
artifacts.push("events.ndjson");
|
|
621
|
+
} else {
|
|
622
|
+
let eventsPath = ensureWithin(target, taskArtifactPath(taskId, "events"));
|
|
623
|
+
if (!(await fileExists(eventsPath))) {
|
|
624
|
+
eventsPath = ensureWithin(target, ARTIFACT_PATHS.events);
|
|
625
|
+
}
|
|
626
|
+
if (await fileExists(eventsPath)) {
|
|
627
|
+
await assertSafePath(target, `${directory}/events.ndjson`);
|
|
628
|
+
await writeFileAtomic(ensureWithin(target, `${directory}/events.ndjson`), await readBytes(eventsPath));
|
|
629
|
+
artifacts.push("events.ndjson");
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// Gates
|
|
634
|
+
let gateRelativePath = `${taskDirectory(taskId)}/gates`;
|
|
635
|
+
let gateDirectory = ensureWithin(target, gateRelativePath);
|
|
636
|
+
const selectedGates = listOperationalArtifactNames(target, taskId, "gates");
|
|
637
|
+
if (selectedGates === null && !(await fileExists(gateDirectory))) {
|
|
638
|
+
gateRelativePath = ARTIFACT_PATHS.gates;
|
|
639
|
+
gateDirectory = ensureWithin(target, gateRelativePath);
|
|
640
|
+
}
|
|
641
|
+
if (selectedGates !== null || await fileExists(gateDirectory)) {
|
|
642
|
+
const entries = selectedGates !== null ? selectedGates.map(name => ({ name, isFile: () => true })) : await readdir(gateDirectory, { withFileTypes: true });
|
|
643
|
+
for (const entry of entries.filter((item) => item.isFile() && item.name.endsWith(".json")).sort((left, right) => left.name.localeCompare(right.name))) {
|
|
644
|
+
const gateName = entry.name.slice(0, -5);
|
|
645
|
+
const sourcePath = `${gateRelativePath}/${entry.name}`;
|
|
646
|
+
const destinationPath = `${directory}/gates/${entry.name}`;
|
|
647
|
+
const gate = await readBundleSourceJson(target, sourcePath, "gate", packageRoot);
|
|
648
|
+
if (gate.value.taskId !== taskId) continue;
|
|
649
|
+
await writePortableJsonArtifact(target, destinationPath, gate.value, "gate", packageRoot);
|
|
650
|
+
artifacts.push(`gates/${gateName}.json`);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
async function verifyBundleFileBindings(target, directory, manifest) {
|
|
657
|
+
if (manifest.value.schemaVersion === 2) {
|
|
658
|
+
const listed = manifest.value.files.map(file => file.path);
|
|
659
|
+
if (new Set(listed).size !== listed.length || canonicalFingerprint([...listed].sort()) !== canonicalFingerprint([...manifest.value.artifacts].sort())) {
|
|
660
|
+
throw Object.assign(new Error("Bundle file bindings disagree with included artifacts"), { code: "E_BUNDLE_DIGEST_INVALID" });
|
|
661
|
+
}
|
|
662
|
+
for (const binding of manifest.value.files) {
|
|
663
|
+
const actual = await bundleFileBinding(target, `${directory}/${binding.path}`, binding.path);
|
|
664
|
+
if (actual.size !== binding.size || actual.sha256 !== binding.sha256) throw Object.assign(new Error(`Bundle bytes disagree with manifest: ${binding.path}`), { code: "E_BUNDLE_DIGEST_INVALID" });
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
async function loadBundleQualityArtifact(target, directory, artifact, packageRoot, taskId, loaded, qualityKind) {
|
|
670
|
+
const qualityArtifact = await readJsonArtifact(target, `${directory}/${artifact}`, "structural-quality", packageRoot);
|
|
671
|
+
validateStructuralQualityArtifact(qualityArtifact.value, artifact);
|
|
672
|
+
if (qualityArtifact.value.taskId !== taskId) {
|
|
673
|
+
throw bundleBindingError("E_BUNDLE_TASK_MISMATCH", `Structural-quality ${qualityKind} taskId does not match its bundle task`);
|
|
674
|
+
}
|
|
675
|
+
loaded.structuralQuality ??= { baseline: null, evaluations: [] };
|
|
676
|
+
if (qualityKind === "baseline") {
|
|
677
|
+
if (loaded.structuralQuality.baseline) throw bundleBindingError("E_STRUCTURAL_QUALITY_EVIDENCE_STALE", "A bundle cannot contain more than one structural-quality baseline");
|
|
678
|
+
loaded.structuralQuality.baseline = qualityArtifact.value;
|
|
679
|
+
} else {
|
|
680
|
+
loaded.structuralQuality.evaluations.push(qualityArtifact.value);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ledgerEventAt, ledgerEventsOfTypes } from "./ledger-event-collection.js";
|
|
1
2
|
import { canonicalFingerprint } from "./artifacts.js";
|
|
2
3
|
import {
|
|
3
4
|
resolveCanonicalRouteEvolution,
|
|
@@ -175,7 +176,7 @@ export function resolveCheckpointRevalidationBoundary(events = [], revalidationE
|
|
|
175
176
|
&& candidate?.taskId === revalidationEvent?.taskId
|
|
176
177
|
&& candidate?.event === CHECKPOINT_REVALIDATED_EVENT));
|
|
177
178
|
if (index < 0) return null;
|
|
178
|
-
const commitEvent = events
|
|
179
|
+
const commitEvent = ledgerEventAt(events, index + 1);
|
|
179
180
|
if (!commitEvent
|
|
180
181
|
|| revalidationEvent?.event !== CHECKPOINT_REVALIDATED_EVENT
|
|
181
182
|
|| commitEvent.event !== "TRANSACTION_COMMITTED"
|
|
@@ -194,9 +195,10 @@ export function resolveCheckpointRevalidationBoundary(events = [], revalidationE
|
|
|
194
195
|
|
|
195
196
|
export function validateCheckpointRevalidationEventBindings(events = []) {
|
|
196
197
|
const errors = [];
|
|
197
|
-
|
|
198
|
-
for (
|
|
199
|
-
const
|
|
198
|
+
let previousEvent;
|
|
199
|
+
for (const event of ledgerEventsOfTypes(events, [CHECKPOINT_REVALIDATED_EVENT])) {
|
|
200
|
+
const prior = previousEvent;
|
|
201
|
+
previousEvent = event;
|
|
200
202
|
let details;
|
|
201
203
|
try {
|
|
202
204
|
details = assertCheckpointRevalidatedDetails(event.details);
|
|
@@ -207,7 +209,7 @@ export function validateCheckpointRevalidationEventBindings(events = []) {
|
|
|
207
209
|
if (!resolveCheckpointRevalidationBoundary(events, event)) {
|
|
208
210
|
errors.push(bindingError(`event ${event.seq} must be immediately followed by its checkpoint-revalidate transaction commit`));
|
|
209
211
|
}
|
|
210
|
-
const previous =
|
|
212
|
+
const previous = prior?.details;
|
|
211
213
|
if (previous) {
|
|
212
214
|
if (!sameRepositoryFingerprint(details.previousRepositoryFingerprint, previous.repositoryFingerprint)) {
|
|
213
215
|
errors.push(bindingError(`event ${event.seq} is disconnected from the previous checkpoint revalidation repository`));
|
|
@@ -222,7 +224,7 @@ export function validateCheckpointRevalidationEventBindings(events = []) {
|
|
|
222
224
|
const contractEvolved = details.contractFingerprint !== previous.contractFingerprint
|
|
223
225
|
&& resolveCanonicalContractEvolution(events, {
|
|
224
226
|
taskId: event.taskId,
|
|
225
|
-
sourceSeq:
|
|
227
|
+
sourceSeq: prior.seq,
|
|
226
228
|
sourceContractFingerprint: previous.contractFingerprint,
|
|
227
229
|
targetContractFingerprint: details.contractFingerprint,
|
|
228
230
|
});
|
|
@@ -233,7 +235,7 @@ export function validateCheckpointRevalidationEventBindings(events = []) {
|
|
|
233
235
|
const evolved = details.previousStateRevision > previous.revalidatedStateRevision
|
|
234
236
|
&& routeEvolutionAfter(
|
|
235
237
|
events,
|
|
236
|
-
|
|
238
|
+
prior,
|
|
237
239
|
previous.routeFingerprint,
|
|
238
240
|
details.routeFingerprint,
|
|
239
241
|
details.contractFingerprint,
|
|
@@ -294,9 +296,9 @@ function validateLatestCheckpointBinding(state, latest) {
|
|
|
294
296
|
|
|
295
297
|
export function validateCheckpointRevalidationCurrentBinding(state, events = []) {
|
|
296
298
|
const errors = [];
|
|
297
|
-
|
|
298
|
-
const
|
|
299
|
-
|
|
299
|
+
let latest;
|
|
300
|
+
for (const event of ledgerEventsOfTypes(events, [CHECKPOINT_REVALIDATED_EVENT])) {
|
|
301
|
+
latest = event;
|
|
300
302
|
const contractError = validateCurrentContractBinding(state, events, event);
|
|
301
303
|
if (contractError) errors.push(contractError);
|
|
302
304
|
const routeError = validateCurrentRouteBinding(state, events, event);
|