@cassiomc1/forgeloop 1.13.0 → 2.0.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENT_COMPATIBILITY.md +8 -0
- package/CONTRIBUTING.md +11 -7
- package/DOCS_INDEX.md +37 -4
- package/ENG/c-development-eng.md +2 -2
- package/ENG/cpp-development-eng.md +2 -2
- package/ENG/dotnet-aspnetcore-development-eng.md +7 -1
- package/ENG/flutter-development-eng.md +4 -2
- package/ENG/go-development-eng.md +2 -2
- package/ENG/java-development-eng.md +3 -3
- package/ENG/nodejs-backend-development-eng.md +10 -6
- package/ENG/php-development-eng.md +3 -3
- package/ENG/python-development-eng.md +205 -0
- package/ENG/rust-development-eng.md +42 -1
- package/ENG/sec-code-eng.md +7 -7
- package/ENG/sql-development-eng.md +4 -4
- package/ENG/swift-development-eng.md +5 -4
- package/ENG/test-code-eng.md +1 -1
- package/ENG/typescript-development-eng.md +4 -4
- package/EXECUTION_STATE.md +12 -0
- package/GUIDE_ROUTER.md +6 -0
- package/LICENSE +133 -21
- package/LOOP_ENGINEERING.md +28 -2
- package/LOOP_SYSTEM_DESIGN.md +10 -10
- package/ORCHESTRATOR_INTEGRATION.md +9 -5
- package/PROTOCOL_INTEGRATION.md +55 -2
- package/README.md +63 -59
- package/TERMINOLOGY.md +2 -0
- package/THIRD_PARTY_NOTICES.md +3 -4
- package/THREAT_MODEL.md +140 -1
- package/completions/_forgeloop +29 -3
- package/completions/forgeloop.bash +55 -3
- package/completions/forgeloop.fish +183 -1
- package/docs/ADVISORY_CONTEXT.md +25 -0
- package/docs/AGENT_BROWSER_ADAPTER.md +81 -0
- package/docs/AGENT_BROWSER_VERIFICATION.md +6 -0
- package/docs/AGENT_PROTOCOL_SUMMARY.md +74 -39
- package/docs/AGENT_SKILL.md +66 -0
- package/docs/ARTIFACT_REFERENCE.md +171 -35
- package/docs/AUDIT_UX.md +46 -0
- package/docs/BROWSER_VERIFICATION.md +136 -0
- package/docs/CLI_REFERENCE.md +512 -8
- package/docs/CODE_ATTESTATION.md +2 -2
- package/docs/DOCUMENTATION_GUIDE.md +34 -11
- package/docs/GETTING_STARTED.md +3 -3
- package/docs/JEV_BENCHMARKS.md +31 -0
- package/docs/MODEL_ROUTING.md +37 -0
- package/docs/OPENSRC_ADAPTER.md +241 -0
- package/docs/PACKAGE_CONTENTS.md +46 -18
- package/docs/PROVIDERS.md +126 -0
- package/docs/PROVIDER_ARCHITECTURE.md +199 -0
- package/docs/RECIPES.md +9 -0
- package/docs/RELEASE_CHECKLIST.md +116 -12
- package/docs/SECURITY_REVIEW.md +71 -0
- package/docs/SELF_HOSTED_TESTS.md +121 -0
- package/docs/SEMANTIC_DECISION_PLANE.md +71 -0
- package/docs/TEST_INTELLIGENCE.md +29 -0
- package/docs/TEST_PRUNING.md +14 -0
- package/docs/TROUBLESHOOTING.md +198 -1
- package/docs/UNIVERSAL_INTEGRATION.md +31 -0
- package/docs/assets/diagrams/forgeloop-code-attestation-flow.html +2 -2
- package/docs/assets/diagrams/forgeloop-code-attestation-flow.receipt.json +5 -5
- package/docs/assets/diagrams/forgeloop-code-attestation-flow.svg +1 -1
- package/docs/assets/diagrams/forgeloop-engineering-flow.html +39 -26
- package/docs/assets/diagrams/forgeloop-engineering-flow.receipt.json +6 -6
- package/docs/assets/diagrams/forgeloop-engineering-flow.svg +26 -26
- package/docs/assets/diagrams/forgeloop-verification-trust-flow.html +2 -1
- package/docs/assets/diagrams/forgeloop-verification-trust-flow.receipt.json +5 -5
- package/docs/assets/diagrams/forgeloop-verification-trust-flow.svg +1 -1
- package/docs/diagrams/README.md +13 -9
- package/docs/diagrams/forgeloop-code-attestation-flow.workflow.json +1 -1
- package/docs/diagrams/forgeloop-engineering-flow.workflow.json +24 -19
- package/docs/diagrams/forgeloop-verification-trust-flow.workflow.json +1 -0
- package/docs/diagrams/reviews/forgeloop-code-attestation-flow.review.json +4 -4
- package/docs/diagrams/reviews/forgeloop-engineering-flow.review.json +4 -4
- package/docs/diagrams/reviews/forgeloop-verification-trust-flow.review.json +4 -4
- package/docs/documentation-manifest.json +774 -16
- package/docs/protocol-requirements.json +24 -0
- package/package.json +43 -11
- package/schemas/config.schema.json +14 -0
- package/schemas/context-plan.schema.json +18 -0
- package/schemas/semantic-decision.schema.json +46 -0
- package/schemas/task-bundle.schema.json +7 -2
- package/schemas/test-utility.schema.json +44 -0
- package/scripts/CI_VALIDATORS.md +6 -6
- package/scripts/benchmark-jev.mjs +5 -0
- package/scripts/benchmark-test-intelligence.mjs +4 -0
- package/scripts/generate-agent-protocol-summary.mjs +6 -3
- package/scripts/generate-forgeloop-skill.mjs +133 -0
- package/scripts/jev-smoke.mjs +19 -0
- package/scripts/run-mcp-clean-tests.mjs +30 -0
- package/skills/forgeloop/README.md +9 -0
- package/skills/forgeloop/SKILL.md +77 -0
- package/skills/forgeloop/references/lifecycle.md +9 -0
- package/skills/forgeloop/references/recovery.md +7 -0
- package/skills/forgeloop/references/verification.md +7 -0
- package/src/adapters/agent-browser/assertions.js +47 -0
- package/src/adapters/agent-browser/commands.js +54 -0
- package/src/adapters/agent-browser/index.js +3 -0
- package/src/adapters/agent-browser/locator.js +40 -0
- package/src/adapters/agent-browser/process.js +215 -0
- package/src/adapters/agent-browser/provider.js +316 -0
- package/src/adapters/emulated-services/constants.js +24 -0
- package/src/adapters/emulated-services/index.js +7 -0
- package/src/adapters/emulated-services/process.js +162 -0
- package/src/adapters/emulated-services/provider.js +289 -0
- package/src/adapters/opensrc/normalize.js +90 -0
- package/src/adapters/opensrc/process.js +248 -0
- package/src/adapters/opensrc/provider.js +338 -0
- package/src/adapters/opensrc/search.js +264 -0
- package/src/adapters/temporary-paths.js +18 -0
- package/src/adapters/typesafe/client.js +28 -0
- package/src/adapters/typesafe/engine.js +63 -0
- package/src/adapters/typesafe/normalize.js +41 -0
- package/src/cli.js +218 -174
- package/src/commands/action-show.js +5 -2
- package/src/commands/advance.js +5 -1
- package/src/commands/baseline.js +13 -11
- package/src/commands/checkpoint-revalidate.js +192 -0
- package/src/commands/complete.js +19 -3
- package/src/commands/context-plan.js +38 -0
- package/src/commands/contract-create.js +266 -0
- package/src/commands/contract-revise.js +236 -0
- package/src/commands/decision-show.js +14 -0
- package/src/commands/decision-status.js +22 -0
- package/src/commands/discover.js +41 -0
- package/src/commands/doctor.js +51 -13
- package/src/commands/gate-record.js +194 -0
- package/src/commands/gate-revalidate.js +135 -0
- package/src/commands/handoff-list.js +6 -1
- package/src/commands/handoff-show.js +6 -1
- package/src/commands/migrate-protocol.js +2 -2
- package/src/commands/model-route.js +40 -0
- package/src/commands/next.js +7 -1
- package/src/commands/policy-diff.js +25 -4
- package/src/commands/progress.js +17 -11
- package/src/commands/record-diagnosis.js +3 -0
- package/src/commands/route.js +146 -18
- package/src/commands/semantic-plan.js +41 -0
- package/src/commands/status.js +6 -1
- package/src/commands/storage-backup.js +15 -0
- package/src/commands/storage-migrate.js +5 -0
- package/src/commands/storage-migration-resume.js +5 -0
- package/src/commands/storage-migration-status.js +44 -0
- package/src/commands/storage-restore-resume.js +7 -0
- package/src/commands/storage-restore.js +8 -0
- package/src/commands/task-abandon.js +224 -0
- package/src/commands/task-list.js +8 -43
- package/src/commands/task-migrate-contract-bootstrap-repair.js +288 -0
- package/src/commands/task-migrate.js +3 -3
- package/src/commands/task-recover.js +10 -2
- package/src/commands/task-repair-contract-bootstrap.js +263 -0
- package/src/commands/task-scope.js +18 -2
- package/src/commands/task-show.js +16 -2
- package/src/commands/test-inventory.js +5 -0
- package/src/commands/test-prune-plan.js +5 -0
- package/src/commands/test-prune-probe.js +5 -0
- package/src/commands/test-utility.js +5 -0
- package/src/commands/validate-protocol.js +39 -15
- package/src/commands/validate-receipt.js +8 -1
- package/src/config/guides.json +4 -0
- package/src/core/action-ledger-projection.js +10 -3
- package/src/core/actions.js +38 -75
- package/src/core/activation.js +11 -3
- package/src/core/approvals.js +17 -51
- package/src/core/artifact-registry.js +83 -2
- package/src/core/artifacts.js +81 -27
- package/src/core/attachment-paths.js +45 -0
- package/src/core/attestation-coverage.js +6 -1
- package/src/core/attestation-verifier.js +21 -2
- package/src/core/attestation.js +17 -6
- package/src/core/audit-ux.js +514 -0
- package/src/core/audit.js +6 -1
- package/src/core/browser-verification/constants.js +149 -0
- package/src/core/browser-verification/normalize.js +254 -0
- package/src/core/browser-verification/provider.js +519 -0
- package/src/core/browser-verification/service.js +115 -0
- package/src/core/bundles.js +160 -66
- package/src/core/checkpoint-revalidation.js +321 -0
- package/src/core/cli-command-definitions.js +350 -7
- package/src/core/code-manifest.js +9 -3
- package/src/core/command-executors.js +274 -108
- package/src/core/command-input.js +136 -44
- package/src/core/completion-artifacts.js +85 -34
- package/src/core/completion-ownership.js +19 -8
- package/src/core/completion-recovery-rebind.js +11 -1
- package/src/core/completion.js +64 -15
- package/src/core/config.js +3 -0
- package/src/core/conformance.js +2 -1
- package/src/core/context-compiler/budget.js +9 -0
- package/src/core/context-compiler/candidates.js +39 -0
- package/src/core/context-compiler/compiler.js +63 -0
- package/src/core/context-compiler/fingerprint.js +11 -0
- package/src/core/context-compiler/policy.js +13 -0
- package/src/core/context-compiler/result.js +23 -0
- package/src/core/continuity-lint.js +13 -2
- package/src/core/continuity-reconciliation.js +16 -3
- package/src/core/continuity.js +16 -8
- package/src/core/contract-bootstrap-recovery.js +656 -0
- package/src/core/contract-revision.js +209 -0
- package/src/core/decision/artifact.js +70 -0
- package/src/core/decision/benchmarks.js +103 -0
- package/src/core/decision/cache.js +27 -0
- package/src/core/decision/constants.js +58 -0
- package/src/core/decision/cutover.js +34 -0
- package/src/core/decision/engine.js +22 -0
- package/src/core/decision/errors.js +68 -0
- package/src/core/decision/events.js +104 -0
- package/src/core/decision/freshness.js +19 -0
- package/src/core/decision/normalizers/index.js +115 -0
- package/src/core/decision/policy.js +18 -0
- package/src/core/decision/projection.js +16 -0
- package/src/core/decision/question-registry.js +201 -0
- package/src/core/decision/request.js +26 -0
- package/src/core/decision/resolver.js +138 -0
- package/src/core/decision/result.js +58 -0
- package/src/core/decision/service.js +181 -0
- package/src/core/decision/state-builder.js +65 -0
- package/src/core/decision/task-bindings.js +35 -0
- package/src/core/decision/test-provider.js +32 -0
- package/src/core/decision/thresholds.js +15 -0
- package/src/core/diagnosis-model.js +11 -0
- package/src/core/diagnosis.js +10 -1
- package/src/core/diagnostic-projection.js +29 -12
- package/src/core/diagnostic-record.js +28 -3
- package/src/core/efficiency.js +6 -1
- package/src/core/error-codes.js +278 -0
- package/src/core/events.js +535 -147
- package/src/core/evidence-readiness.js +9 -0
- package/src/core/execution-prerequisites.js +36 -13
- package/src/core/execution-profile.js +63 -38
- package/src/core/execution.js +26 -40
- package/src/core/filesystem.js +11 -0
- package/src/core/gate-artifact.js +105 -19
- package/src/core/gate-provenance.js +125 -0
- package/src/core/handoff.js +26 -14
- package/src/core/history.js +39 -35
- package/src/core/hypothesis-projection.js +32 -0
- package/src/core/information-gain-projection.js +104 -100
- package/src/core/inspect.js +12 -3
- package/src/core/integration-invocation-policy.js +37 -4
- package/src/core/integration-resources.js +119 -87
- package/src/core/ledger-event-collection.js +189 -0
- package/src/core/ledger-relations.js +23 -0
- package/src/core/model-router/constants.js +10 -0
- package/src/core/model-router/policy.js +103 -0
- package/src/core/model-router/router.js +37 -0
- package/src/core/native-storage.js +12 -0
- package/src/core/next-action-continuity.js +31 -9
- package/src/core/next-action-model.js +58 -0
- package/src/core/next-action-phases.js +131 -43
- package/src/core/next-action-refresh.js +43 -9
- package/src/core/next-action-review-phase.js +9 -4
- package/src/core/next-action-verification-phase.js +2 -1
- package/src/core/next-action.js +56 -9
- package/src/core/phase.js +266 -103
- package/src/core/policy-engine.js +12 -13
- package/src/core/preflight-consistency.js +89 -62
- package/src/core/preflight-loaders.js +48 -9
- package/src/core/preflight-model.js +6 -1
- package/src/core/preflight.js +29 -12
- package/src/core/progress.js +21 -10
- package/src/core/protocol-info.js +68 -0
- package/src/core/protocol-migration.js +7 -6
- package/src/core/protocol.js +20 -0
- package/src/core/reconcile-closure.js +281 -67
- package/src/core/recovery-history.js +67 -47
- package/src/core/reflection.js +72 -77
- package/src/core/responsibility.js +25 -13
- package/src/core/resumability.js +154 -44
- package/src/core/route-artifact.js +28 -4
- package/src/core/router.js +67 -1
- package/src/core/runtime-context.js +123 -61
- package/src/core/schema-validation.js +11 -3
- package/src/core/security-review/constants.js +64 -0
- package/src/core/security-review/normalize.js +245 -0
- package/src/core/security-review/provider.js +204 -0
- package/src/core/security-review/service.js +134 -0
- package/src/core/semantic-planning/constants.js +19 -0
- package/src/core/semantic-planning/projection.js +94 -0
- package/src/core/semantic-planning/service.js +15 -0
- package/src/core/settlement.js +12 -1
- package/src/core/signing/bundle-file.js +53 -0
- package/src/core/signing/bundle-reference.js +15 -0
- package/src/core/signing/sigstore.js +60 -33
- package/src/core/signing/statement-file.js +27 -0
- package/src/core/sources.js +37 -0
- package/src/core/storage-runtime-registry.js +41 -0
- package/src/core/structural-quality/artifacts.js +20 -14
- package/src/core/structural-quality/service.js +81 -56
- package/src/core/target-layout.js +1 -1
- package/src/core/task-claim-state.js +249 -15
- package/src/core/task-command.js +21 -1
- package/src/core/task-conflict-inspection.js +133 -102
- package/src/core/task-descriptor.js +29 -22
- package/src/core/task-discovery.js +288 -90
- package/src/core/task-lock.js +66 -306
- package/src/core/task-migration-validation.js +5 -5
- package/src/core/task-migration.js +17 -287
- package/src/core/task-paths.js +13 -0
- package/src/core/task-recovery.js +1 -0
- package/src/core/task-snapshot.js +51 -8
- package/src/core/task-storage-migration.js +18 -0
- package/src/core/templates.js +23 -8
- package/src/core/test-intelligence/benchmarks.js +68 -0
- package/src/core/test-intelligence/inventory.js +73 -0
- package/src/core/test-intelligence/prune.js +94 -0
- package/src/core/test-intelligence/semantic-state.js +15 -0
- package/src/core/test-intelligence/service.js +40 -0
- package/src/core/test-intelligence/utility.js +50 -0
- package/src/core/trace.js +164 -105
- package/src/core/trajectory-evaluation.js +15 -3
- package/src/core/trajectory-metrics.js +47 -32
- package/src/core/transaction.js +16 -287
- package/src/core/verification-scope.js +20 -12
- package/src/core/work-state.js +100 -59
- package/src/integration.d.ts +492 -0
- package/src/integration.js +54 -0
- package/src/providers/README.md +47 -0
- package/src/providers/capabilities.js +46 -0
- package/src/providers/errors.js +15 -0
- package/src/providers/index.js +29 -0
- package/src/providers/json-snapshot.js +105 -0
- package/src/providers/registry.js +152 -0
- package/src/repository-index/binary-manager.js +7 -5
- package/src/storage/artifact-bytes.js +22 -0
- package/src/storage/attachment-binding.js +11 -0
- package/src/storage/attachment-files.js +82 -0
- package/src/storage/attachment-references.js +36 -0
- package/src/storage/backup.js +53 -0
- package/src/storage/capabilities.js +31 -0
- package/src/storage/connection-owner.js +119 -0
- package/src/storage/connection.js +376 -0
- package/src/storage/existing-project-scope.js +45 -0
- package/src/storage/export-destination.js +44 -0
- package/src/storage/exporter.js +284 -0
- package/src/storage/file-durability.js +46 -0
- package/src/storage/importer.js +461 -0
- package/src/storage/index.js +74 -0
- package/src/storage/leases.js +63 -0
- package/src/storage/ledger-event-snapshot.js +150 -0
- package/src/storage/ledger-relations.js +220 -0
- package/src/storage/legacy-maintenance-boundary.js +18 -0
- package/src/storage/legacy-signature.js +115 -0
- package/src/storage/maintenance-handoff.js +129 -0
- package/src/storage/maintenance-owner.js +42 -0
- package/src/storage/maintenance.js +310 -0
- package/src/storage/metadata-json.js +35 -0
- package/src/storage/migration-activate.js +59 -0
- package/src/storage/migration-archive.js +57 -0
- package/src/storage/migration-attachment-root.js +61 -0
- package/src/storage/migration-candidate.js +398 -0
- package/src/storage/migration-publication.js +143 -0
- package/src/storage/migration-rollback-admission.js +54 -0
- package/src/storage/migration-rollback-preparation.js +132 -0
- package/src/storage/migration-rollback-publication.js +183 -0
- package/src/storage/migration-rollback-release.js +66 -0
- package/src/storage/migration-rollback-source-stage.js +103 -0
- package/src/storage/migration-rollback.js +43 -0
- package/src/storage/migration-source-partition.js +56 -0
- package/src/storage/migration-source.js +255 -0
- package/src/storage/migration-terminal.js +39 -0
- package/src/storage/migration-validation.js +75 -0
- package/src/storage/migration.js +43 -0
- package/src/storage/operational-attachments.js +49 -0
- package/src/storage/operational-context.js +50 -0
- package/src/storage/project-backup.js +98 -0
- package/src/storage/project-bootstrap.js +24 -0
- package/src/storage/project-boundary.js +79 -0
- package/src/storage/project-read-snapshot.js +18 -0
- package/src/storage/project-replacement.js +137 -0
- package/src/storage/project-restore.js +203 -0
- package/src/storage/record-definitions.js +45 -0
- package/src/storage/repository.js +507 -0
- package/src/storage/restore-journal.js +62 -0
- package/src/storage/restore-layout.js +30 -0
- package/src/storage/restore-preparation.js +84 -0
- package/src/storage/restore-replacement-archive.js +162 -0
- package/src/storage/restore-replacement-owner.js +31 -0
- package/src/storage/restore-replacement-preparation.js +128 -0
- package/src/storage/restore-replacement.js +142 -0
- package/src/storage/restore-terminal.js +32 -0
- package/src/storage/runtime.js +31 -0
- package/src/storage/schema.js +214 -0
- package/src/storage/singleton-import.js +84 -0
- package/src/storage/snapshot-continuity.js +16 -0
- package/src/storage/snapshot.js +36 -0
- package/src/storage/storage-marker.js +79 -0
- package/src/storage/task-guards.js +107 -0
- package/src/storage/topology.js +42 -0
- package/src/storage/transaction.js +179 -0
- package/src/storage/unit-of-work.js +663 -0
- package/src/storage/windows-process-incarnation.js +206 -0
- package/.forgeloop/forgeloop.gitignore +0 -4
- package/LICENSE-DOCS.md +0 -13
- package/src/core/transaction-maintenance.js +0 -70
package/src/core/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
|
+
}
|