@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
|
@@ -1,11 +1,26 @@
|
|
|
1
|
+
import { attachStorageConnectionOwner } from "./storage-runtime-registry.js";
|
|
1
2
|
import {
|
|
2
3
|
E_VERIFICATION_ISOLATION_UNAVAILABLE,
|
|
3
4
|
isVerificationExecutionAdapter,
|
|
4
5
|
normalizeVerificationExecutionPolicy,
|
|
5
6
|
} from "./verification-execution.js";
|
|
6
7
|
import { STRUCTURAL_QUALITY_PROVIDER_ID_PATTERN } from "./structural-quality/constants.js";
|
|
7
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
E_ADVISORY_CONTEXT_PROVIDER_INVALID,
|
|
10
|
+
E_BROWSER_VERIFICATION_PROVIDER_INVALID,
|
|
11
|
+
E_SECURITY_REVIEW_PROVIDER_INVALID,
|
|
12
|
+
} from "./error-codes.js";
|
|
8
13
|
import { assertAdvisoryContextProviderIdentity } from "./advisory-context/provider.js";
|
|
14
|
+
import {
|
|
15
|
+
BROWSER_VERIFICATION_PROVIDER_ID_PATTERN,
|
|
16
|
+
assertBrowserVerificationProvider,
|
|
17
|
+
assertBrowserVerificationProviderIdentity,
|
|
18
|
+
} from "./browser-verification/provider.js";
|
|
19
|
+
import {
|
|
20
|
+
SECURITY_REVIEW_PROVIDER_ID_PATTERN,
|
|
21
|
+
assertSecurityReviewProvider,
|
|
22
|
+
assertSecurityReviewProviderIdentity,
|
|
23
|
+
} from "./security-review/provider.js";
|
|
9
24
|
|
|
10
25
|
export const AUTHORITY_TRUST_MODES = Object.freeze(["NONE", "HOST_ATTESTED"]);
|
|
11
26
|
|
|
@@ -83,6 +98,100 @@ export function resolveAuthorityContext(options = {}) {
|
|
|
83
98
|
return hasDirectAuthority ? createAuthorityContext({ ...source, trustMode: "NONE" }) : createAuthorityContext();
|
|
84
99
|
}
|
|
85
100
|
|
|
101
|
+
function isProviderObject(value) {
|
|
102
|
+
return value && typeof value === "object" && !Array.isArray(value);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function providerEntries(configured, label, code) {
|
|
106
|
+
if (!configured || typeof configured !== "object" || Array.isArray(configured)) {
|
|
107
|
+
const error = new Error(`${label} must be an object or Map`);
|
|
108
|
+
error.code = code;
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
return configured instanceof Map ? [...configured.entries()] : Object.entries(configured);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function registerProviders(configured, { label, code, idError, validateId, providerError, validateProvider }) {
|
|
115
|
+
const providers = {};
|
|
116
|
+
for (const [id, provider] of providerEntries(configured, label, code)) {
|
|
117
|
+
if (!validateId(id)) {
|
|
118
|
+
const error = new Error(idError(id));
|
|
119
|
+
error.code = code;
|
|
120
|
+
throw error;
|
|
121
|
+
}
|
|
122
|
+
if (typeof provider !== "function" && !isProviderObject(provider)) {
|
|
123
|
+
const error = new Error(providerError(id));
|
|
124
|
+
error.code = code;
|
|
125
|
+
throw error;
|
|
126
|
+
}
|
|
127
|
+
if (typeof provider !== "function") validateProvider(provider, id);
|
|
128
|
+
providers[id] = provider;
|
|
129
|
+
}
|
|
130
|
+
return Object.freeze(providers);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function configureUsageProvider(context, provider) {
|
|
134
|
+
if (!provider
|
|
135
|
+
|| typeof provider !== "object"
|
|
136
|
+
|| Array.isArray(provider)
|
|
137
|
+
|| typeof provider.getTaskUsage !== "function") {
|
|
138
|
+
const error = new Error("Usage provider must expose getTaskUsage({ projectPath, taskId })");
|
|
139
|
+
error.code = "E_USAGE_INVALID";
|
|
140
|
+
throw error;
|
|
141
|
+
}
|
|
142
|
+
context.usageProvider = provider;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function configureStructuralQualityProviders(context, configured) {
|
|
146
|
+
context.structuralQualityProviders = registerProviders(configured, {
|
|
147
|
+
label: "structuralQualityProviders",
|
|
148
|
+
code: "E_STRUCTURAL_QUALITY_PROVIDER_INVALID",
|
|
149
|
+
idError: id => `Invalid or reserved structural-quality provider ID: ${id}`,
|
|
150
|
+
validateId: id => STRUCTURAL_QUALITY_PROVIDER_ID_PATTERN.test(id) && id !== "sentrux",
|
|
151
|
+
providerError: id => `Structural-quality provider ${id} must be an object or factory`,
|
|
152
|
+
validateProvider: (provider, id) => {},
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function configureAdvisoryContextProviders(context, configured) {
|
|
157
|
+
context.advisoryContextProviders = registerProviders(configured, {
|
|
158
|
+
label: "advisoryContextProviders",
|
|
159
|
+
code: E_ADVISORY_CONTEXT_PROVIDER_INVALID,
|
|
160
|
+
idError: id => `Invalid advisory-context provider ID: ${id}`,
|
|
161
|
+
validateId: id => /^[a-z0-9][a-z0-9_-]*$/.test(id),
|
|
162
|
+
providerError: id => `Advisory-context provider ${id} must be an object or factory`,
|
|
163
|
+
validateProvider: assertAdvisoryContextProviderIdentity,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function configureBrowserVerificationProviders(context, configured) {
|
|
168
|
+
context.browserVerificationProviders = registerProviders(configured, {
|
|
169
|
+
label: "browserVerificationProviders",
|
|
170
|
+
code: E_BROWSER_VERIFICATION_PROVIDER_INVALID,
|
|
171
|
+
idError: id => `Invalid browser-verification provider ID: ${id}`,
|
|
172
|
+
validateId: id => BROWSER_VERIFICATION_PROVIDER_ID_PATTERN.test(id),
|
|
173
|
+
providerError: id => `Browser-verification provider ${id} must be an object with verify() or a factory`,
|
|
174
|
+
validateProvider: (provider, id) => {
|
|
175
|
+
assertBrowserVerificationProviderIdentity(provider, { expectedId: id });
|
|
176
|
+
assertBrowserVerificationProvider(provider, { label: `browser-verification provider "${id}"` });
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function configureSecurityReviewProviders(context, configured) {
|
|
182
|
+
context.securityReviewProviders = registerProviders(configured, {
|
|
183
|
+
label: "securityReviewProviders",
|
|
184
|
+
code: E_SECURITY_REVIEW_PROVIDER_INVALID,
|
|
185
|
+
idError: id => `Invalid security-review provider ID: ${id}`,
|
|
186
|
+
validateId: id => SECURITY_REVIEW_PROVIDER_ID_PATTERN.test(id),
|
|
187
|
+
providerError: id => `Security-review provider ${id} must be an object with review() or a factory`,
|
|
188
|
+
validateProvider: (provider, id) => {
|
|
189
|
+
assertSecurityReviewProviderIdentity(provider, { expectedId: id });
|
|
190
|
+
assertSecurityReviewProvider(provider, { label: `security-review provider "${id}"` });
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
86
195
|
export function createForgeLoopContext(options = {}) {
|
|
87
196
|
const authorityContext = createAuthorityContext(options);
|
|
88
197
|
const context = { authorityContext };
|
|
@@ -98,70 +207,23 @@ export function createForgeLoopContext(options = {}) {
|
|
|
98
207
|
context.verificationExecutionPolicy = normalizeVerificationExecutionPolicy(options.verificationExecutionPolicy);
|
|
99
208
|
}
|
|
100
209
|
if (options?.usageProvider !== undefined) {
|
|
101
|
-
|
|
102
|
-
|| typeof options.usageProvider !== "object"
|
|
103
|
-
|| Array.isArray(options.usageProvider)
|
|
104
|
-
|| typeof options.usageProvider.getTaskUsage !== "function") {
|
|
105
|
-
const error = new Error("Usage provider must expose getTaskUsage({ projectPath, taskId })");
|
|
106
|
-
error.code = "E_USAGE_INVALID";
|
|
107
|
-
throw error;
|
|
108
|
-
}
|
|
109
|
-
context.usageProvider = options.usageProvider;
|
|
210
|
+
configureUsageProvider(context, options.usageProvider);
|
|
110
211
|
}
|
|
111
212
|
if (options?.structuralQualityProviders !== undefined) {
|
|
112
|
-
|
|
113
|
-
? Object.fromEntries(options.structuralQualityProviders.entries())
|
|
114
|
-
: options.structuralQualityProviders;
|
|
115
|
-
if (!configured || typeof configured !== "object" || Array.isArray(configured)) {
|
|
116
|
-
const error = new Error("structuralQualityProviders must be an object or Map");
|
|
117
|
-
error.code = "E_STRUCTURAL_QUALITY_PROVIDER_INVALID";
|
|
118
|
-
throw error;
|
|
119
|
-
}
|
|
120
|
-
const providers = {};
|
|
121
|
-
for (const [id, provider] of Object.entries(configured)) {
|
|
122
|
-
if (!STRUCTURAL_QUALITY_PROVIDER_ID_PATTERN.test(id) || id === "sentrux") {
|
|
123
|
-
const error = new Error(`Invalid or reserved structural-quality provider ID: ${id}`);
|
|
124
|
-
error.code = "E_STRUCTURAL_QUALITY_PROVIDER_INVALID";
|
|
125
|
-
throw error;
|
|
126
|
-
}
|
|
127
|
-
if (typeof provider !== "function"
|
|
128
|
-
&& (!provider || typeof provider !== "object" || Array.isArray(provider))) {
|
|
129
|
-
const error = new Error(`Structural-quality provider ${id} must be an object or factory`);
|
|
130
|
-
error.code = "E_STRUCTURAL_QUALITY_PROVIDER_INVALID";
|
|
131
|
-
throw error;
|
|
132
|
-
}
|
|
133
|
-
providers[id] = provider;
|
|
134
|
-
}
|
|
135
|
-
context.structuralQualityProviders = Object.freeze(providers);
|
|
213
|
+
configureStructuralQualityProviders(context, options.structuralQualityProviders);
|
|
136
214
|
}
|
|
137
215
|
if (options?.advisoryContextProviders !== undefined) {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
if (!/^[a-z0-9][a-z0-9_-]*$/.test(id)) {
|
|
149
|
-
const error = new Error(`Invalid advisory-context provider ID: ${id}`);
|
|
150
|
-
error.code = E_ADVISORY_CONTEXT_PROVIDER_INVALID;
|
|
151
|
-
throw error;
|
|
152
|
-
}
|
|
153
|
-
if (typeof provider !== "function"
|
|
154
|
-
&& (!provider || typeof provider !== "object" || Array.isArray(provider))) {
|
|
155
|
-
const error = new Error(`Advisory-context provider ${id} must be an object or factory`);
|
|
156
|
-
error.code = E_ADVISORY_CONTEXT_PROVIDER_INVALID;
|
|
157
|
-
throw error;
|
|
158
|
-
}
|
|
159
|
-
if (typeof provider !== "function") {
|
|
160
|
-
assertAdvisoryContextProviderIdentity(provider, id);
|
|
161
|
-
}
|
|
162
|
-
providers[id] = provider;
|
|
163
|
-
}
|
|
164
|
-
context.advisoryContextProviders = Object.freeze(providers);
|
|
216
|
+
configureAdvisoryContextProviders(context, options.advisoryContextProviders);
|
|
217
|
+
}
|
|
218
|
+
if (options?.browserVerificationProviders !== undefined) {
|
|
219
|
+
configureBrowserVerificationProviders(context, options.browserVerificationProviders);
|
|
220
|
+
}
|
|
221
|
+
if (options?.securityReviewProviders !== undefined) {
|
|
222
|
+
configureSecurityReviewProviders(context, options.securityReviewProviders);
|
|
223
|
+
}
|
|
224
|
+
if (options?.persistentStorage !== undefined && typeof options.persistentStorage !== "boolean") {
|
|
225
|
+
throw Object.assign(new Error("persistentStorage must be a boolean"), { code: "E_STORAGE_RUNTIME_CONFIGURATION_INVALID" });
|
|
165
226
|
}
|
|
227
|
+
if (options?.persistentStorage === true) context.close = attachStorageConnectionOwner(context);
|
|
166
228
|
return Object.freeze(context);
|
|
167
229
|
}
|
|
@@ -55,6 +55,9 @@ export const SHIPPED_SCHEMA_NAMES = Object.freeze([
|
|
|
55
55
|
"execution-profile-benchmark-run",
|
|
56
56
|
"execution-profile-benchmark-aggregate",
|
|
57
57
|
"structural-quality",
|
|
58
|
+
"semantic-decision",
|
|
59
|
+
"context-plan",
|
|
60
|
+
"test-utility",
|
|
58
61
|
]);
|
|
59
62
|
|
|
60
63
|
export class SchemaValidationError extends Error {
|
|
@@ -120,7 +123,6 @@ function validate(value, schema, location, errors, rootSchema = schema) {
|
|
|
120
123
|
return candidateErrors.length === 0;
|
|
121
124
|
});
|
|
122
125
|
if (matches.length !== 1) errors.push(`${location}: expected exactly one matching schema`);
|
|
123
|
-
return;
|
|
124
126
|
}
|
|
125
127
|
|
|
126
128
|
if (schema.type && !typeMatches(value, schema.type)) {
|
|
@@ -210,6 +212,12 @@ export async function readSchema(name, packageRoot = getPackageRoot()) {
|
|
|
210
212
|
return schema;
|
|
211
213
|
}
|
|
212
214
|
|
|
215
|
+
function schemaHealthVersion(schema, name) {
|
|
216
|
+
const versions = schema?.properties?.schemaVersion?.enum ?? null;
|
|
217
|
+
const version = schema?.properties?.schemaVersion?.const ?? (name === "task-bundle" && JSON.stringify(versions) === "[1,2]" ? 2 : null);
|
|
218
|
+
return version;
|
|
219
|
+
}
|
|
220
|
+
|
|
213
221
|
export async function inspectSchemaHealth(packageRoot = getPackageRoot()) {
|
|
214
222
|
const schemas = [];
|
|
215
223
|
for (const name of SHIPPED_SCHEMA_NAMES) {
|
|
@@ -220,13 +228,13 @@ export async function inspectSchemaHealth(packageRoot = getPackageRoot()) {
|
|
|
220
228
|
assertJsonBytes(bytes, filename);
|
|
221
229
|
const schema = JSON.parse(bytes.toString("utf8"));
|
|
222
230
|
assertJsonLimits(schema, filename);
|
|
223
|
-
const version = schema
|
|
231
|
+
const version = schemaHealthVersion(schema, name);
|
|
224
232
|
let status = "valid";
|
|
225
233
|
let error = null;
|
|
226
234
|
if (!schema || typeof schema !== "object" || Array.isArray(schema)) {
|
|
227
235
|
status = "invalid";
|
|
228
236
|
error = "schema root must be an object";
|
|
229
|
-
} else if (version !== 1) {
|
|
237
|
+
} else if (version !== 1 && !(name === "task-bundle" && version === 2)) {
|
|
230
238
|
status = "unsupported-version";
|
|
231
239
|
error = `schemaVersion ${version ?? "missing"} is not supported`;
|
|
232
240
|
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { E_SECURITY_REVIEW_REQUEST_INVALID } from "../error-codes.js";
|
|
2
|
+
|
|
3
|
+
export const SECURITY_REVIEW_LIMITS = Object.freeze({
|
|
4
|
+
maxProjectPathChars: 4096,
|
|
5
|
+
maxTaskIdChars: 128,
|
|
6
|
+
maxReviewIdChars: 128,
|
|
7
|
+
maxRequirementChars: 2000,
|
|
8
|
+
maxRequirements: 64,
|
|
9
|
+
maxPaths: 128,
|
|
10
|
+
maxPathChars: 512,
|
|
11
|
+
maxCategories: 32,
|
|
12
|
+
maxCategoryChars: 64,
|
|
13
|
+
maxRevisionChars: 256,
|
|
14
|
+
maxFindings: 256,
|
|
15
|
+
maxFindingIdChars: 128,
|
|
16
|
+
maxSeverityChars: 16,
|
|
17
|
+
maxTitleChars: 512,
|
|
18
|
+
maxSummaryChars: 4096,
|
|
19
|
+
maxRuleIdChars: 128,
|
|
20
|
+
maxConfidenceChars: 16,
|
|
21
|
+
maxDiagnosticChars: 4000,
|
|
22
|
+
maxDiagnostics: 64,
|
|
23
|
+
maxResultChars: 524288,
|
|
24
|
+
maxSnapshotDepth: 32,
|
|
25
|
+
maxSnapshotNodes: 4096,
|
|
26
|
+
maxSnapshotChars: 524288,
|
|
27
|
+
maxDurationMs: 86_400_000,
|
|
28
|
+
defaultTimeoutMs: 30_000,
|
|
29
|
+
maxTimeoutMs: 120_000,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
export const SECURITY_REVIEW_SCOPES = Object.freeze(["FULL", "CHANGED", "SELECTED"]);
|
|
33
|
+
export const SECURITY_REVIEW_SEVERITIES = Object.freeze(["INFO", "LOW", "MEDIUM", "HIGH", "CRITICAL"]);
|
|
34
|
+
export const SECURITY_REVIEW_CONFIDENCE = Object.freeze(["LOW", "MEDIUM", "HIGH"]);
|
|
35
|
+
export const SECURITY_REVIEW_CATEGORIES = Object.freeze([
|
|
36
|
+
"DEPENDENCY", "SECRETS", "INJECTION", "AUTHENTICATION", "AUTHORIZATION",
|
|
37
|
+
"CRYPTOGRAPHY", "CONFIGURATION", "NETWORK", "FILESYSTEM", "SUPPLY_CHAIN",
|
|
38
|
+
"UNSAFE_EXECUTION", "OTHER",
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
export const SECURITY_REVIEW_TRUST = Object.freeze({
|
|
42
|
+
authority: "OBSERVATION",
|
|
43
|
+
evidenceAuthority: "NONE",
|
|
44
|
+
actionability: "NON_EXECUTABLE",
|
|
45
|
+
trustRole: "NON_EVIDENCE_SECURITY_REVIEW",
|
|
46
|
+
persisted: false,
|
|
47
|
+
lifecycleAuthority: false,
|
|
48
|
+
completionAuthority: false,
|
|
49
|
+
evidenceRequiresForgeLoopValidation: true,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
export const SECURITY_REVIEW_BLOCKED_RESULT_FIELDS = Object.freeze([
|
|
53
|
+
"complete", "completed", "nextAction", "releaseClaims", "mutationAllowed", "taskPhase",
|
|
54
|
+
"lifecycleState", "receipt", "evidence", "evidenceStatus", "check", "gate", "satisfied",
|
|
55
|
+
"ownership", "writeClaims", "event", "events", "transaction", "installation", "command",
|
|
56
|
+
"commandArgv", "executable", "shell", "environment", "credentials", "secrets",
|
|
57
|
+
]);
|
|
58
|
+
|
|
59
|
+
export function securityReviewRequestError(message, details = null) {
|
|
60
|
+
const error = new Error(message);
|
|
61
|
+
error.code = E_SECURITY_REVIEW_REQUEST_INVALID;
|
|
62
|
+
error.details = details;
|
|
63
|
+
return error;
|
|
64
|
+
}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import {
|
|
2
|
+
E_SECURITY_REVIEW_OUTPUT_LIMIT,
|
|
3
|
+
E_SECURITY_REVIEW_RESULT_INVALID,
|
|
4
|
+
} from "../error-codes.js";
|
|
5
|
+
import { deepFreeze, normalizePortableText } from "../portable-context.js";
|
|
6
|
+
import {
|
|
7
|
+
SECURITY_REVIEW_BLOCKED_RESULT_FIELDS,
|
|
8
|
+
SECURITY_REVIEW_CATEGORIES,
|
|
9
|
+
SECURITY_REVIEW_CONFIDENCE,
|
|
10
|
+
SECURITY_REVIEW_LIMITS,
|
|
11
|
+
SECURITY_REVIEW_SCOPES,
|
|
12
|
+
SECURITY_REVIEW_SEVERITIES,
|
|
13
|
+
SECURITY_REVIEW_TRUST,
|
|
14
|
+
} from "./constants.js";
|
|
15
|
+
|
|
16
|
+
function resultError(message, details = null, code = E_SECURITY_REVIEW_RESULT_INVALID) {
|
|
17
|
+
const error = new Error(message);
|
|
18
|
+
error.code = code;
|
|
19
|
+
error.details = details;
|
|
20
|
+
return error;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function limitError(message, details = null) {
|
|
24
|
+
return resultError(message, details, E_SECURITY_REVIEW_OUTPUT_LIMIT);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function text(label, value, maxLength, { optional = false } = {}) {
|
|
28
|
+
try {
|
|
29
|
+
return normalizePortableText(value, { label, maxLength, optional });
|
|
30
|
+
} catch {
|
|
31
|
+
throw resultError(`${label} is invalid.`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function consumeSnapshotBudget(value, label, state, depth) {
|
|
36
|
+
if (depth > SECURITY_REVIEW_LIMITS.maxSnapshotDepth) {
|
|
37
|
+
throw limitError(`${label} exceeds the maximum result nesting depth.`);
|
|
38
|
+
}
|
|
39
|
+
state.nodes += 1;
|
|
40
|
+
if (state.nodes > SECURITY_REVIEW_LIMITS.maxSnapshotNodes) {
|
|
41
|
+
throw limitError("Security review result exceeds its maximum snapshot node count.");
|
|
42
|
+
}
|
|
43
|
+
if (typeof value === "string") {
|
|
44
|
+
state.chars += value.length;
|
|
45
|
+
if (state.chars > SECURITY_REVIEW_LIMITS.maxSnapshotChars) {
|
|
46
|
+
throw limitError("Security review result exceeds its maximum snapshot character count.");
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function strictSnapshotArray(value, label, seen, state, depth) {
|
|
52
|
+
seen.add(value);
|
|
53
|
+
try {
|
|
54
|
+
const output = [];
|
|
55
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
56
|
+
if (!Object.prototype.hasOwnProperty.call(value, index)) throw resultError(`${label} contains a sparse array.`);
|
|
57
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
|
|
58
|
+
if (!descriptor || descriptor.get || descriptor.set) throw resultError(`${label}[${index}] is an accessor.`);
|
|
59
|
+
output.push(strictSnapshot(value[index], `${label}[${index}]`, seen, state, depth + 1));
|
|
60
|
+
}
|
|
61
|
+
return output;
|
|
62
|
+
} finally {
|
|
63
|
+
seen.delete(value);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function strictSnapshotObject(value, label, seen, state, depth) {
|
|
68
|
+
const prototype = Object.getPrototypeOf(value);
|
|
69
|
+
if (value instanceof Date || value instanceof Map || value instanceof Set || value instanceof Promise
|
|
70
|
+
|| (prototype !== Object.prototype && prototype !== null)) {
|
|
71
|
+
throw resultError(`${label} contains a non-plain object.`);
|
|
72
|
+
}
|
|
73
|
+
seen.add(value);
|
|
74
|
+
try {
|
|
75
|
+
const output = {};
|
|
76
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
77
|
+
if (typeof key !== "string") throw resultError(`${label} contains a symbol key.`);
|
|
78
|
+
if (key === "toJSON") throw resultError(`${label} contains a custom toJSON method.`);
|
|
79
|
+
state.chars += key.length;
|
|
80
|
+
if (state.chars > SECURITY_REVIEW_LIMITS.maxSnapshotChars) {
|
|
81
|
+
throw limitError("Security review result exceeds its maximum snapshot character count.");
|
|
82
|
+
}
|
|
83
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
84
|
+
if (!descriptor || descriptor.get || descriptor.set) throw resultError(`${label}.${key} is an accessor.`);
|
|
85
|
+
output[key] = strictSnapshot(value[key], `${label}.${key}`, seen, state, depth + 1);
|
|
86
|
+
}
|
|
87
|
+
return output;
|
|
88
|
+
} finally {
|
|
89
|
+
seen.delete(value);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function strictSnapshot(value, label = "security review result", seen = new Set(), state = { nodes: 0, chars: 0 }, depth = 0) {
|
|
94
|
+
consumeSnapshotBudget(value, label, state, depth);
|
|
95
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return value;
|
|
96
|
+
if (typeof value === "number") {
|
|
97
|
+
if (!Number.isFinite(value)) throw resultError(`${label} contains a non-finite number.`);
|
|
98
|
+
return value;
|
|
99
|
+
}
|
|
100
|
+
if (typeof value !== "object") throw resultError(`${label} contains an unsupported value.`);
|
|
101
|
+
if (seen.has(value)) throw resultError(`${label} contains a circular reference.`);
|
|
102
|
+
return Array.isArray(value)
|
|
103
|
+
? strictSnapshotArray(value, label, seen, state, depth)
|
|
104
|
+
: strictSnapshotObject(value, label, seen, state, depth);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function rejectUnknown(source, allowed, label) {
|
|
108
|
+
for (const key of Reflect.ownKeys(source)) {
|
|
109
|
+
if (typeof key !== "string" || !allowed.includes(key)) {
|
|
110
|
+
throw resultError(`${label} contains an unsupported field.`, { field: typeof key === "string" ? key : String(key) });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function assertSafeText(label, value, maxLength) {
|
|
116
|
+
const normalized = text(label, value, maxLength);
|
|
117
|
+
if (/(?:authorization\s*:\s*bearer|cookie\s*:|(?:token|secret|password)=|-----begin|file:\/\/(?:\/|%2f)|(?:^|\/)Users\/|(?:^|\/)home\/)/i.test(normalized)) {
|
|
118
|
+
throw resultError(`${label} contains sensitive or non-portable content.`);
|
|
119
|
+
}
|
|
120
|
+
return normalized;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function normalizePath(value, label) {
|
|
124
|
+
const path = assertSafeText(label, value, SECURITY_REVIEW_LIMITS.maxPathChars).replaceAll("\\", "/");
|
|
125
|
+
if (path.startsWith("/") || /^[a-z]:\//i.test(path) || path.startsWith("//") || /^file:/i.test(path)
|
|
126
|
+
|| path.split("/").includes("..") || path === "." || path.endsWith("/") || path.includes("//")) {
|
|
127
|
+
throw resultError(`${label} must be a project-relative non-escaping path.`);
|
|
128
|
+
}
|
|
129
|
+
return path;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function assertFindingScope(path, scope, requestedPaths, index) {
|
|
133
|
+
if (scope === "FULL" || requestedPaths.length === 0) return;
|
|
134
|
+
const inScope = requestedPaths.some((root) => path === root || path.startsWith(`${root}/`));
|
|
135
|
+
if (!inScope) {
|
|
136
|
+
throw resultError(`findings[${index}].path is outside the requested ${scope.toLowerCase()} scope.`, {
|
|
137
|
+
path,
|
|
138
|
+
requestedPaths,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function normalizeFinding(raw, index, scope, requestedPaths) {
|
|
144
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) throw resultError(`findings[${index}] is invalid.`);
|
|
145
|
+
rejectUnknown(raw, ["id", "category", "severity", "title", "summary", "path", "line", "ruleId", "confidence"], `findings[${index}]`);
|
|
146
|
+
const finding = {
|
|
147
|
+
id: assertSafeText(`findings[${index}].id`, raw.id, SECURITY_REVIEW_LIMITS.maxFindingIdChars),
|
|
148
|
+
category: assertSafeText(`findings[${index}].category`, raw.category, SECURITY_REVIEW_LIMITS.maxCategoryChars),
|
|
149
|
+
severity: assertSafeText(`findings[${index}].severity`, raw.severity, SECURITY_REVIEW_LIMITS.maxSeverityChars),
|
|
150
|
+
title: assertSafeText(`findings[${index}].title`, raw.title, SECURITY_REVIEW_LIMITS.maxTitleChars),
|
|
151
|
+
summary: assertSafeText(`findings[${index}].summary`, raw.summary, SECURITY_REVIEW_LIMITS.maxSummaryChars),
|
|
152
|
+
};
|
|
153
|
+
if (!SECURITY_REVIEW_CATEGORIES.includes(finding.category)) throw resultError(`findings[${index}].category is unsupported.`);
|
|
154
|
+
if (!SECURITY_REVIEW_SEVERITIES.includes(finding.severity)) throw resultError(`findings[${index}].severity is unsupported.`);
|
|
155
|
+
if (raw.path !== undefined) {
|
|
156
|
+
finding.path = normalizePath(raw.path, `findings[${index}].path`);
|
|
157
|
+
assertFindingScope(finding.path, scope, requestedPaths, index);
|
|
158
|
+
}
|
|
159
|
+
if (raw.line !== undefined) {
|
|
160
|
+
if (!Number.isInteger(raw.line) || raw.line < 1 || raw.line > 10_000_000) throw resultError(`findings[${index}].line is invalid.`);
|
|
161
|
+
finding.line = raw.line;
|
|
162
|
+
}
|
|
163
|
+
if (raw.ruleId !== undefined) finding.ruleId = assertSafeText(`findings[${index}].ruleId`, raw.ruleId, SECURITY_REVIEW_LIMITS.maxRuleIdChars);
|
|
164
|
+
if (raw.confidence !== undefined) {
|
|
165
|
+
finding.confidence = assertSafeText(`findings[${index}].confidence`, raw.confidence, SECURITY_REVIEW_LIMITS.maxConfidenceChars);
|
|
166
|
+
if (!SECURITY_REVIEW_CONFIDENCE.includes(finding.confidence)) throw resultError(`findings[${index}].confidence is unsupported.`);
|
|
167
|
+
}
|
|
168
|
+
return deepFreeze(finding);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function normalizeDiagnostics(raw) {
|
|
172
|
+
if (raw === undefined) return [];
|
|
173
|
+
if (!Array.isArray(raw) || raw.length > SECURITY_REVIEW_LIMITS.maxDiagnostics) throw limitError("diagnostics exceeds its limit.");
|
|
174
|
+
return raw.map((entry, index) => assertSafeText(`diagnostics[${index}]`, entry, SECURITY_REVIEW_LIMITS.maxDiagnosticChars));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function deriveSummary(findings) {
|
|
178
|
+
const bySeverity = Object.fromEntries(SECURITY_REVIEW_SEVERITIES.map((severity) => [severity, 0]));
|
|
179
|
+
const byCategory = Object.fromEntries(SECURITY_REVIEW_CATEGORIES.map((category) => [category, 0]));
|
|
180
|
+
for (const finding of findings) {
|
|
181
|
+
bySeverity[finding.severity] += 1;
|
|
182
|
+
byCategory[finding.category] += 1;
|
|
183
|
+
}
|
|
184
|
+
return deepFreeze({ total: findings.length, bySeverity: deepFreeze(bySeverity), byCategory: deepFreeze(byCategory) });
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function normalizeSummary(raw, expected) {
|
|
188
|
+
if (raw === undefined) return expected;
|
|
189
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) throw resultError("summary is invalid.");
|
|
190
|
+
rejectUnknown(raw, ["total", "bySeverity", "byCategory"], "summary");
|
|
191
|
+
if (JSON.stringify(raw) !== JSON.stringify(expected)) throw resultError("summary does not match normalized findings.");
|
|
192
|
+
return expected;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function normalizeRequestedPaths(requestedPaths) {
|
|
196
|
+
if (!Array.isArray(requestedPaths)) throw resultError("requestedPaths must be an array.");
|
|
197
|
+
const normalized = requestedPaths.map((path, index) => normalizePath(path, `requestedPaths[${index}]`));
|
|
198
|
+
if (new Set(normalized).size !== normalized.length) throw resultError("requestedPaths must not contain duplicates.");
|
|
199
|
+
return normalized;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function normalizeSecurityReviewResult(raw, {
|
|
203
|
+
provider, taskId, reviewId, scope, requestedPaths = [],
|
|
204
|
+
} = {}) {
|
|
205
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) throw resultError("security review result must be an object.");
|
|
206
|
+
if (!SECURITY_REVIEW_SCOPES.includes(scope)) throw resultError("scope is unsupported.");
|
|
207
|
+
const normalizedRequestedPaths = normalizeRequestedPaths(requestedPaths);
|
|
208
|
+
if (scope === "SELECTED" && normalizedRequestedPaths.length === 0) {
|
|
209
|
+
throw resultError("SELECTED scope requires requestedPaths.");
|
|
210
|
+
}
|
|
211
|
+
const snapshot = strictSnapshot(raw);
|
|
212
|
+
for (const field of SECURITY_REVIEW_BLOCKED_RESULT_FIELDS) {
|
|
213
|
+
if (Object.prototype.hasOwnProperty.call(snapshot, field)) throw resultError("security review result contains a reserved authority field.", { field });
|
|
214
|
+
}
|
|
215
|
+
rejectUnknown(snapshot, ["findings", "diagnostics", "durationMs", "summary"], "security review result");
|
|
216
|
+
if (!Array.isArray(snapshot.findings)) throw resultError("findings must be an array.");
|
|
217
|
+
if (snapshot.findings.length > SECURITY_REVIEW_LIMITS.maxFindings) throw limitError("Too many findings.", { count: snapshot.findings.length });
|
|
218
|
+
const findings = snapshot.findings.map((finding, index) => normalizeFinding(finding, index, scope, normalizedRequestedPaths));
|
|
219
|
+
const ids = findings.map((finding) => finding.id);
|
|
220
|
+
if (new Set(ids).size !== ids.length) throw resultError("findings must not contain duplicate IDs.");
|
|
221
|
+
const summary = deriveSummary(findings);
|
|
222
|
+
normalizeSummary(snapshot.summary, summary);
|
|
223
|
+
let durationMs;
|
|
224
|
+
if (snapshot.durationMs !== undefined) {
|
|
225
|
+
if (!Number.isInteger(snapshot.durationMs) || snapshot.durationMs < 0 || snapshot.durationMs > SECURITY_REVIEW_LIMITS.maxDurationMs) {
|
|
226
|
+
throw resultError("durationMs is invalid.");
|
|
227
|
+
}
|
|
228
|
+
durationMs = snapshot.durationMs;
|
|
229
|
+
}
|
|
230
|
+
const result = {
|
|
231
|
+
taskId: assertSafeText("taskId", taskId, SECURITY_REVIEW_LIMITS.maxTaskIdChars),
|
|
232
|
+
reviewId: assertSafeText("reviewId", reviewId, SECURITY_REVIEW_LIMITS.maxReviewIdChars),
|
|
233
|
+
scope,
|
|
234
|
+
requestedPaths: Object.freeze(normalizedRequestedPaths),
|
|
235
|
+
provider: deepFreeze({ id: provider.id, ...(provider.version === undefined ? {} : { version: provider.version }) }),
|
|
236
|
+
findings: Object.freeze(findings),
|
|
237
|
+
summary,
|
|
238
|
+
diagnostics: Object.freeze(normalizeDiagnostics(snapshot.diagnostics)),
|
|
239
|
+
...(durationMs === undefined ? {} : { durationMs }),
|
|
240
|
+
...SECURITY_REVIEW_TRUST,
|
|
241
|
+
};
|
|
242
|
+
const serialized = JSON.stringify(result);
|
|
243
|
+
if (serialized.length > SECURITY_REVIEW_LIMITS.maxResultChars) throw limitError("Security review result exceeds its size limit.", { chars: serialized.length });
|
|
244
|
+
return deepFreeze(result);
|
|
245
|
+
}
|