@intentius/chant 0.13.1 → 0.15.1
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/dist/audit/catalog.d.ts +45 -5
- package/dist/audit/catalog.d.ts.map +1 -1
- package/dist/audit/proof.d.ts +3 -0
- package/dist/audit/proof.d.ts.map +1 -1
- package/dist/audit/report-model.d.ts +4 -1
- package/dist/audit/report-model.d.ts.map +1 -1
- package/dist/audit/report.d.ts +3 -0
- package/dist/audit/report.d.ts.map +1 -1
- package/dist/audit/rules-doc.d.ts +8 -2
- package/dist/audit/rules-doc.d.ts.map +1 -1
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/doctor.d.ts.map +1 -1
- package/dist/cli/commands/lexicon-rolling-upgrade.d.ts +37 -0
- package/dist/cli/commands/lexicon-rolling-upgrade.d.ts.map +1 -0
- package/dist/cli/commands/lexicon-surface-diff.d.ts +48 -0
- package/dist/cli/commands/lexicon-surface-diff.d.ts.map +1 -0
- package/dist/cli/commands/lint.d.ts.map +1 -1
- package/dist/cli/commands/pinned-upgrade.d.ts +41 -0
- package/dist/cli/commands/pinned-upgrade.d.ts.map +1 -0
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/components.d.ts +39 -0
- package/dist/cli/handlers/components.d.ts.map +1 -0
- package/dist/cli/handlers/dev.d.ts +3 -0
- package/dist/cli/handlers/dev.d.ts.map +1 -1
- package/dist/cli/handlers/graph.d.ts +4 -2
- package/dist/cli/handlers/graph.d.ts.map +1 -1
- package/dist/cli/handlers/misc.d.ts.map +1 -1
- package/dist/cli/handlers/run.d.ts +49 -0
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +35 -0
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/codegen/fetch.d.ts +22 -0
- package/dist/codegen/fetch.d.ts.map +1 -1
- package/dist/codegen/lexicon-regen.d.ts +110 -0
- package/dist/codegen/lexicon-regen.d.ts.map +1 -0
- package/dist/codegen/pinned-upgrade.d.ts +153 -0
- package/dist/codegen/pinned-upgrade.d.ts.map +1 -0
- package/dist/codegen/rolling-upgrade.d.ts +138 -0
- package/dist/codegen/rolling-upgrade.d.ts.map +1 -0
- package/dist/codegen/surface-snapshot.d.ts +173 -0
- package/dist/codegen/surface-snapshot.d.ts.map +1 -0
- package/dist/codegen/version-bump.d.ts +21 -0
- package/dist/codegen/version-bump.d.ts.map +1 -0
- package/dist/components/auto-release.d.ts +93 -0
- package/dist/components/auto-release.d.ts.map +1 -0
- package/dist/components/builders.d.ts +37 -0
- package/dist/components/builders.d.ts.map +1 -0
- package/dist/components/capability-plugin-loader.d.ts +111 -0
- package/dist/components/capability-plugin-loader.d.ts.map +1 -0
- package/dist/components/capability-plugin-schema.d.ts +24 -0
- package/dist/components/capability-plugin-schema.d.ts.map +1 -0
- package/dist/components/capability-plugin.d.ts +85 -0
- package/dist/components/capability-plugin.d.ts.map +1 -0
- package/dist/components/capability.d.ts +89 -0
- package/dist/components/capability.d.ts.map +1 -0
- package/dist/components/cli-support.d.ts +204 -0
- package/dist/components/cli-support.d.ts.map +1 -0
- package/dist/components/component.d.ts +179 -0
- package/dist/components/component.d.ts.map +1 -0
- package/dist/components/config-defaults.d.ts +41 -0
- package/dist/components/config-defaults.d.ts.map +1 -0
- package/dist/components/discover.d.ts +60 -0
- package/dist/components/discover.d.ts.map +1 -0
- package/dist/components/driver-output.d.ts +20 -0
- package/dist/components/driver-output.d.ts.map +1 -0
- package/dist/components/driver.d.ts +209 -0
- package/dist/components/driver.d.ts.map +1 -0
- package/dist/components/index.d.ts +22 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/manifest-persistence.d.ts +79 -0
- package/dist/components/manifest-persistence.d.ts.map +1 -0
- package/dist/components/pilots/alb-ecs.pilot.d.ts +27 -0
- package/dist/components/pilots/alb-ecs.pilot.d.ts.map +1 -0
- package/dist/components/pilots/dynamodb.pilot.d.ts +22 -0
- package/dist/components/pilots/dynamodb.pilot.d.ts.map +1 -0
- package/dist/components/pilots/jar-emr.pilot.d.ts +37 -0
- package/dist/components/pilots/jar-emr.pilot.d.ts.map +1 -0
- package/dist/components/pilots/lambda.pilot.d.ts +35 -0
- package/dist/components/pilots/lambda.pilot.d.ts.map +1 -0
- package/dist/components/pilots/neo4j-fanout.pilot.d.ts +17 -0
- package/dist/components/pilots/neo4j-fanout.pilot.d.ts.map +1 -0
- package/dist/components/pilots/project.d.ts +13 -0
- package/dist/components/pilots/project.d.ts.map +1 -0
- package/dist/components/presets/ecs-fargate.d.ts +59 -0
- package/dist/components/presets/ecs-fargate.d.ts.map +1 -0
- package/dist/components/presets/index.d.ts +29 -0
- package/dist/components/presets/index.d.ts.map +1 -0
- package/dist/components/presets/lambda.d.ts +35 -0
- package/dist/components/presets/lambda.d.ts.map +1 -0
- package/dist/components/presets/single-host-compose.d.ts +43 -0
- package/dist/components/presets/single-host-compose.d.ts.map +1 -0
- package/dist/components/registry.d.ts +29 -0
- package/dist/components/registry.d.ts.map +1 -0
- package/dist/components/starter-plugin.d.ts +51 -0
- package/dist/components/starter-plugin.d.ts.map +1 -0
- package/dist/components/temporal-codegen-loader.d.ts +27 -0
- package/dist/components/temporal-codegen-loader.d.ts.map +1 -0
- package/dist/components/verbs/__tests__/mock-cloud-executor.d.ts +35 -0
- package/dist/components/verbs/__tests__/mock-cloud-executor.d.ts.map +1 -0
- package/dist/components/verbs/__tests__/mock-process-runner.d.ts +45 -0
- package/dist/components/verbs/__tests__/mock-process-runner.d.ts.map +1 -0
- package/dist/components/verbs/__tests__/mock-sbom-generator.d.ts +33 -0
- package/dist/components/verbs/__tests__/mock-sbom-generator.d.ts.map +1 -0
- package/dist/components/verbs/bom-writer.d.ts +155 -0
- package/dist/components/verbs/bom-writer.d.ts.map +1 -0
- package/dist/components/verbs/build-archive.d.ts +205 -0
- package/dist/components/verbs/build-archive.d.ts.map +1 -0
- package/dist/components/verbs/build.d.ts +150 -0
- package/dist/components/verbs/build.d.ts.map +1 -0
- package/dist/components/verbs/cloud-executor.d.ts +88 -0
- package/dist/components/verbs/cloud-executor.d.ts.map +1 -0
- package/dist/components/verbs/component-bom.d.ts +103 -0
- package/dist/components/verbs/component-bom.d.ts.map +1 -0
- package/dist/components/verbs/index.d.ts +49 -0
- package/dist/components/verbs/index.d.ts.map +1 -0
- package/dist/components/verbs/license-policy.d.ts +56 -0
- package/dist/components/verbs/license-policy.d.ts.map +1 -0
- package/dist/components/verbs/lockfile-sbom-generator.d.ts +81 -0
- package/dist/components/verbs/lockfile-sbom-generator.d.ts.map +1 -0
- package/dist/components/verbs/process-runner.d.ts +64 -0
- package/dist/components/verbs/process-runner.d.ts.map +1 -0
- package/dist/components/verbs/reproducibility.d.ts +80 -0
- package/dist/components/verbs/reproducibility.d.ts.map +1 -0
- package/dist/components/verbs/sbom-generator.d.ts +157 -0
- package/dist/components/verbs/sbom-generator.d.ts.map +1 -0
- package/dist/components/verbs/sbom.d.ts +79 -0
- package/dist/components/verbs/sbom.d.ts.map +1 -0
- package/dist/components/verbs/shell.d.ts +35 -0
- package/dist/components/verbs/shell.d.ts.map +1 -0
- package/dist/components/verbs/sign.d.ts +222 -0
- package/dist/components/verbs/sign.d.ts.map +1 -0
- package/dist/components/verbs/stub.d.ts +18 -0
- package/dist/components/verbs/stub.d.ts.map +1 -0
- package/dist/components/verbs/tool-sbom-generator.d.ts +51 -0
- package/dist/components/verbs/tool-sbom-generator.d.ts.map +1 -0
- package/dist/components/verbs/verify.d.ts +106 -0
- package/dist/components/verbs/verify.d.ts.map +1 -0
- package/dist/components/verbs/vex.d.ts +65 -0
- package/dist/components/verbs/vex.d.ts.map +1 -0
- package/dist/components/verbs/vuln-gate.d.ts +91 -0
- package/dist/components/verbs/vuln-gate.d.ts.map +1 -0
- package/dist/components/verbs/vuln-scan.d.ts +116 -0
- package/dist/components/verbs/vuln-scan.d.ts.map +1 -0
- package/dist/components/verbs/wait-verify.d.ts +85 -0
- package/dist/components/verbs/wait-verify.d.ts.map +1 -0
- package/dist/config.d.ts +205 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/lexicon.d.ts +78 -0
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/build-ledger-store.d.ts +121 -0
- package/dist/lifecycle/build-ledger-store.d.ts.map +1 -0
- package/dist/lifecycle/build-ledger.d.ts +225 -0
- package/dist/lifecycle/build-ledger.d.ts.map +1 -0
- package/dist/lifecycle/git.d.ts +84 -0
- package/dist/lifecycle/git.d.ts.map +1 -1
- package/dist/lifecycle/index.d.ts +5 -0
- package/dist/lifecycle/index.d.ts.map +1 -1
- package/dist/lifecycle/oras-referrer-lookup.d.ts +41 -0
- package/dist/lifecycle/oras-referrer-lookup.d.ts.map +1 -0
- package/dist/lifecycle/release-ledger.d.ts +122 -0
- package/dist/lifecycle/release-ledger.d.ts.map +1 -0
- package/dist/lifecycle/status.d.ts +150 -0
- package/dist/lifecycle/status.d.ts.map +1 -0
- package/dist/lint/component-checks.d.ts +95 -0
- package/dist/lint/component-checks.d.ts.map +1 -0
- package/dist/lint/engine.d.ts +23 -0
- package/dist/lint/engine.d.ts.map +1 -1
- package/dist/lint/presets/strict.json +8 -1
- package/dist/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-a.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-a.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-b.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-b.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp001/fail/orphan-lib.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp001/fail/orphan-lib.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp001/pass/emr-job.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp001/pass/emr-job.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp001/pass/jar-lib.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp001/pass/jar-lib.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp002/fail/search-service.component.d.ts +10 -0
- package/dist/lint/rules/__fixtures__/comp/comp002/fail/search-service.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp002/pass/jar-lib.component.d.ts +3 -0
- package/dist/lint/rules/__fixtures__/comp/comp002/pass/jar-lib.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp002/pass/search-service.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp002/pass/search-service.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp003/fail/migrator.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp003/fail/migrator.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp003/pass/migrator.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp003/pass/migrator.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp003/pass/orders-table.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp003/pass/orders-table.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp004/fail/neo4j-cluster.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp004/fail/neo4j-cluster.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp005/fail/search-service.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp005/fail/search-service.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp005/pass/search-service.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp005/pass/search-service.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp006/fail/legacy-tool.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp006/fail/legacy-tool.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp006/pass/legacy-tool.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp006/pass/legacy-tool.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp007/fail/inventory-table.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp007/fail/inventory-table.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp007/fail/orders-table.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp007/fail/orders-table.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp007/pass/orders-table.component.d.ts +3 -0
- package/dist/lint/rules/__fixtures__/comp/comp007/pass/orders-table.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp007/pass/search-service.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp007/pass/search-service.component.d.ts.map +1 -0
- package/dist/lint/rules/comp/comp001-publishes-never-applies.d.ts +20 -0
- package/dist/lint/rules/comp/comp001-publishes-never-applies.d.ts.map +1 -0
- package/dist/lint/rules/comp/comp002-dangling-wiring-ref.d.ts +28 -0
- package/dist/lint/rules/comp/comp002-dangling-wiring-ref.d.ts.map +1 -0
- package/dist/lint/rules/comp/comp003-mutating-no-rollback.d.ts +39 -0
- package/dist/lint/rules/comp/comp003-mutating-no-rollback.d.ts.map +1 -0
- package/dist/lint/rules/comp/comp004-gate-needs-temporal.d.ts +37 -0
- package/dist/lint/rules/comp/comp004-gate-needs-temporal.d.ts.map +1 -0
- package/dist/lint/rules/comp/comp005-capability-kind-is-noun.d.ts +43 -0
- package/dist/lint/rules/comp/comp005-capability-kind-is-noun.d.ts.map +1 -0
- package/dist/lint/rules/comp/comp006-shell-needs-reason.d.ts +22 -0
- package/dist/lint/rules/comp/comp006-shell-needs-reason.d.ts.map +1 -0
- package/dist/lint/rules/comp/comp007-composition-sprawl.d.ts +33 -0
- package/dist/lint/rules/comp/comp007-composition-sprawl.d.ts.map +1 -0
- package/dist/lint/rules/comp/index.d.ts +18 -0
- package/dist/lint/rules/comp/index.d.ts.map +1 -0
- package/dist/lint/rules/comp/support.d.ts +71 -0
- package/dist/lint/rules/comp/support.d.ts.map +1 -0
- package/dist/lsp/lexicon-providers.d.ts.map +1 -1
- package/dist/op/activity-registry.d.ts +11 -6
- package/dist/op/activity-registry.d.ts.map +1 -1
- package/dist/op/activity-runtime.d.ts +24 -0
- package/dist/op/activity-runtime.d.ts.map +1 -0
- package/dist/op/builders.d.ts +59 -0
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/index.d.ts +2 -1
- package/dist/op/index.d.ts.map +1 -1
- package/dist/ownership.d.ts +20 -14
- package/dist/ownership.d.ts.map +1 -1
- package/package.json +14 -1
- package/src/audit/catalog.test.ts +74 -22
- package/src/audit/catalog.ts +74 -471
- package/src/audit/proof.ts +4 -2
- package/src/audit/report-html.test.ts +14 -7
- package/src/audit/report-model.ts +20 -9
- package/src/audit/report.test.ts +36 -13
- package/src/audit/report.ts +2 -0
- package/src/audit/rules-doc.test.ts +8 -6
- package/src/audit/rules-doc.ts +13 -5
- package/src/cli/commands/audit.ts +23 -18
- package/src/cli/commands/component-lint.test.ts +158 -0
- package/src/cli/commands/doctor.test.ts +116 -2
- package/src/cli/commands/doctor.ts +46 -0
- package/src/cli/commands/lexicon-rolling-upgrade.ts +198 -0
- package/src/cli/commands/lexicon-surface-diff.test.ts +104 -0
- package/src/cli/commands/lexicon-surface-diff.ts +185 -0
- package/src/cli/commands/lint.ts +142 -1
- package/src/cli/commands/pinned-upgrade.ts +204 -0
- package/src/cli/handlers/build.ts +58 -1
- package/src/cli/handlers/components.test.ts +504 -0
- package/src/cli/handlers/components.ts +404 -0
- package/src/cli/handlers/dev.ts +60 -1
- package/src/cli/handlers/graph.ts +47 -2
- package/src/cli/handlers/misc.ts +45 -1
- package/src/cli/handlers/run.test.ts +967 -3
- package/src/cli/handlers/run.ts +618 -11
- package/src/cli/main.test.ts +66 -0
- package/src/cli/main.ts +105 -2
- package/src/cli/registry.ts +35 -0
- package/src/codegen/fetch.test.ts +84 -3
- package/src/codegen/fetch.ts +76 -2
- package/src/codegen/lexicon-regen.test.ts +298 -0
- package/src/codegen/lexicon-regen.ts +545 -0
- package/src/codegen/pinned-upgrade.test.ts +399 -0
- package/src/codegen/pinned-upgrade.ts +423 -0
- package/src/codegen/rolling-upgrade.test.ts +333 -0
- package/src/codegen/rolling-upgrade.ts +320 -0
- package/src/codegen/surface-snapshot.test.ts +461 -0
- package/src/codegen/surface-snapshot.ts +546 -0
- package/src/codegen/version-bump.test.ts +47 -0
- package/src/codegen/version-bump.ts +39 -0
- package/src/components/README.md +130 -0
- package/src/components/SPRAWL-VALIDATION.md +181 -0
- package/src/components/__fixtures__/alb-ecs-service.json +51 -0
- package/src/components/__fixtures__/dynamodb-infra.json +32 -0
- package/src/components/__fixtures__/emr-job-consumer.json +26 -0
- package/src/components/__fixtures__/jar-lib-producer.json +16 -0
- package/src/components/__fixtures__/lambda-image-processor.json +28 -0
- package/src/components/__fixtures__/neo4j-fanout.json +34 -0
- package/src/components/__fixtures__/single-host-compose.json +29 -0
- package/src/components/auto-release.test.ts +217 -0
- package/src/components/auto-release.ts +216 -0
- package/src/components/builders.test.ts +45 -0
- package/src/components/builders.ts +71 -0
- package/src/components/capability-plugin-loader.ts +218 -0
- package/src/components/capability-plugin-schema.ts +56 -0
- package/src/components/capability-plugin.test.ts +235 -0
- package/src/components/capability-plugin.ts +108 -0
- package/src/components/capability.ts +117 -0
- package/src/components/cli-support.test.ts +579 -0
- package/src/components/cli-support.ts +481 -0
- package/src/components/component-schema.test.ts +265 -0
- package/src/components/component.schema.json +235 -0
- package/src/components/component.test.ts +251 -0
- package/src/components/component.ts +255 -0
- package/src/components/config-defaults.test.ts +263 -0
- package/src/components/config-defaults.ts +145 -0
- package/src/components/discover.test.ts +352 -0
- package/src/components/discover.ts +167 -0
- package/src/components/driver-output.ts +71 -0
- package/src/components/driver.test.ts +638 -0
- package/src/components/driver.ts +690 -0
- package/src/components/index.ts +113 -0
- package/src/components/manifest-persistence.test.ts +152 -0
- package/src/components/manifest-persistence.ts +164 -0
- package/src/components/pilots/README.md +33 -0
- package/src/components/pilots/alb-ecs.pilot.ts +63 -0
- package/src/components/pilots/dynamodb.pilot.ts +59 -0
- package/src/components/pilots/jar-emr.pilot.ts +69 -0
- package/src/components/pilots/lambda.pilot.ts +55 -0
- package/src/components/pilots/neo4j-fanout.pilot.ts +92 -0
- package/src/components/pilots/pilots-lint-clean.test.ts +78 -0
- package/src/components/pilots/pilots.test.ts +126 -0
- package/src/components/pilots/project.ts +13 -0
- package/src/components/presets/comp007-reduction.test.ts +122 -0
- package/src/components/presets/ecs-fargate.test.ts +84 -0
- package/src/components/presets/ecs-fargate.ts +103 -0
- package/src/components/presets/index.ts +32 -0
- package/src/components/presets/lambda.test.ts +35 -0
- package/src/components/presets/lambda.ts +53 -0
- package/src/components/presets/single-host-compose.test.ts +65 -0
- package/src/components/presets/single-host-compose.ts +64 -0
- package/src/components/registry.test.ts +112 -0
- package/src/components/registry.ts +37 -0
- package/src/components/starter-plugin.ts +105 -0
- package/src/components/temporal-codegen-loader.ts +41 -0
- package/src/components/verbs/__fixtures__/package-lock.json +45 -0
- package/src/components/verbs/__fixtures__/pom.xml +37 -0
- package/src/components/verbs/__fixtures__/schemas/cyclonedx-1.5.schema.json +3800 -0
- package/src/components/verbs/__fixtures__/schemas/cyclonedx-spdx-license.schema.json +621 -0
- package/src/components/verbs/__fixtures__/schemas/jsf-0.82.schema.json +240 -0
- package/src/components/verbs/__fixtures__/schemas/spdx-2.3.schema.json +740 -0
- package/src/components/verbs/__tests__/mock-cloud-executor.ts +102 -0
- package/src/components/verbs/__tests__/mock-process-runner.ts +79 -0
- package/src/components/verbs/__tests__/mock-sbom-generator.ts +85 -0
- package/src/components/verbs/bom-writer.test.ts +331 -0
- package/src/components/verbs/bom-writer.ts +386 -0
- package/src/components/verbs/build-archive.test.ts +251 -0
- package/src/components/verbs/build-archive.ts +296 -0
- package/src/components/verbs/build.test.ts +222 -0
- package/src/components/verbs/build.ts +252 -0
- package/src/components/verbs/cloud-executor.ts +181 -0
- package/src/components/verbs/component-bom.test.ts +219 -0
- package/src/components/verbs/component-bom.ts +189 -0
- package/src/components/verbs/index.ts +49 -0
- package/src/components/verbs/license-policy.ts +115 -0
- package/src/components/verbs/lockfile-sbom-generator.test.ts +195 -0
- package/src/components/verbs/lockfile-sbom-generator.ts +236 -0
- package/src/components/verbs/process-runner.test.ts +79 -0
- package/src/components/verbs/process-runner.ts +113 -0
- package/src/components/verbs/reproducibility.test.ts +24 -0
- package/src/components/verbs/reproducibility.ts +100 -0
- package/src/components/verbs/sbom-generator.test.ts +96 -0
- package/src/components/verbs/sbom-generator.ts +209 -0
- package/src/components/verbs/sbom.test.ts +184 -0
- package/src/components/verbs/sbom.ts +152 -0
- package/src/components/verbs/shell.test.ts +36 -0
- package/src/components/verbs/shell.ts +51 -0
- package/src/components/verbs/sign.test.ts +210 -0
- package/src/components/verbs/sign.ts +350 -0
- package/src/components/verbs/stub.ts +34 -0
- package/src/components/verbs/tool-sbom-generator.test.ts +194 -0
- package/src/components/verbs/tool-sbom-generator.ts +192 -0
- package/src/components/verbs/verify.test.ts +159 -0
- package/src/components/verbs/verify.ts +179 -0
- package/src/components/verbs/vex.ts +182 -0
- package/src/components/verbs/vuln-gate.test.ts +201 -0
- package/src/components/verbs/vuln-gate.ts +170 -0
- package/src/components/verbs/vuln-scan.test.ts +145 -0
- package/src/components/verbs/vuln-scan.ts +266 -0
- package/src/components/verbs/wait-verify.test.ts +111 -0
- package/src/components/verbs/wait-verify.ts +171 -0
- package/src/config.test.ts +74 -1
- package/src/config.ts +219 -0
- package/src/index.ts +32 -0
- package/src/lexicon.ts +88 -0
- package/src/lifecycle/build-ledger-store.test.ts +217 -0
- package/src/lifecycle/build-ledger-store.ts +165 -0
- package/src/lifecycle/build-ledger.test.ts +349 -0
- package/src/lifecycle/build-ledger.ts +305 -0
- package/src/lifecycle/git.test.ts +118 -0
- package/src/lifecycle/git.ts +149 -14
- package/src/lifecycle/index.ts +5 -0
- package/src/lifecycle/oras-referrer-lookup.test.ts +116 -0
- package/src/lifecycle/oras-referrer-lookup.ts +110 -0
- package/src/lifecycle/release-ledger.test.ts +209 -0
- package/src/lifecycle/release-ledger.ts +185 -0
- package/src/lifecycle/status.test.ts +330 -0
- package/src/lifecycle/status.ts +278 -0
- package/src/lint/component-checks.ts +148 -0
- package/src/lint/config.test.ts +21 -0
- package/src/lint/engine.ts +11 -2
- package/src/lint/presets/relaxed.json +8 -1
- package/src/lint/presets/strict.json +8 -1
- package/src/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-a.component.ts +9 -0
- package/src/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-b.component.ts +9 -0
- package/src/lint/rules/__fixtures__/comp/comp001/fail/orphan-lib.component.ts +11 -0
- package/src/lint/rules/__fixtures__/comp/comp001/pass/emr-job.component.ts +13 -0
- package/src/lint/rules/__fixtures__/comp/comp001/pass/jar-lib.component.ts +11 -0
- package/src/lint/rules/__fixtures__/comp/comp002/fail/search-service.component.ts +27 -0
- package/src/lint/rules/__fixtures__/comp/comp002/pass/jar-lib.component.ts +10 -0
- package/src/lint/rules/__fixtures__/comp/comp002/pass/search-service.component.ts +23 -0
- package/src/lint/rules/__fixtures__/comp/comp003/fail/migrator.component.ts +10 -0
- package/src/lint/rules/__fixtures__/comp/comp003/pass/migrator.component.ts +19 -0
- package/src/lint/rules/__fixtures__/comp/comp003/pass/orders-table.component.ts +10 -0
- package/src/lint/rules/__fixtures__/comp/comp004/fail/neo4j-cluster.component.ts +20 -0
- package/src/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.ts +23 -0
- package/src/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.ts +10 -0
- package/src/lint/rules/__fixtures__/comp/comp005/fail/search-service.component.ts +14 -0
- package/src/lint/rules/__fixtures__/comp/comp005/pass/search-service.component.ts +18 -0
- package/src/lint/rules/__fixtures__/comp/comp006/fail/legacy-tool.component.ts +10 -0
- package/src/lint/rules/__fixtures__/comp/comp006/pass/legacy-tool.component.ts +14 -0
- package/src/lint/rules/__fixtures__/comp/comp007/fail/inventory-table.component.ts +13 -0
- package/src/lint/rules/__fixtures__/comp/comp007/fail/orders-table.component.ts +13 -0
- package/src/lint/rules/__fixtures__/comp/comp007/pass/orders-table.component.ts +12 -0
- package/src/lint/rules/__fixtures__/comp/comp007/pass/search-service.component.ts +18 -0
- package/src/lint/rules/comp/comp.test.ts +378 -0
- package/src/lint/rules/comp/comp001-publishes-never-applies.ts +78 -0
- package/src/lint/rules/comp/comp002-dangling-wiring-ref.ts +94 -0
- package/src/lint/rules/comp/comp003-mutating-no-rollback.ts +115 -0
- package/src/lint/rules/comp/comp004-gate-needs-temporal.ts +66 -0
- package/src/lint/rules/comp/comp005-capability-kind-is-noun.ts +113 -0
- package/src/lint/rules/comp/comp006-shell-needs-reason.ts +55 -0
- package/src/lint/rules/comp/comp007-composition-sprawl.ts +71 -0
- package/src/lint/rules/comp/index.ts +37 -0
- package/src/lint/rules/comp/support.ts +173 -0
- package/src/lsp/lexicon-providers.test.ts +55 -0
- package/src/lsp/lexicon-providers.ts +13 -3
- package/src/op/activity-registry.test.ts +38 -0
- package/src/op/activity-registry.ts +31 -12
- package/src/op/activity-runtime.ts +73 -0
- package/src/op/builders.ts +99 -0
- package/src/op/index.ts +3 -1
- package/src/ownership.test.ts +24 -26
- package/src/ownership.ts +26 -38
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests the real, deep-scan `SbomGenerator` backend (#610,
|
|
3
|
+
* ./tool-sbom-generator.ts) against a `MockProcessRunner`
|
|
4
|
+
* (./__tests__/mock-process-runner.ts) — no live `syft`/`docker buildx`/
|
|
5
|
+
* `cyclonedx-maven`/`cdxgen` invoked. Asserts the command each artifact type
|
|
6
|
+
* dispatches to, that output is parsed (package count), and that a missing
|
|
7
|
+
* tool surfaces `ToolNotAvailableError` rather than a silent/empty SBOM.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
11
|
+
import { tmpdir } from "node:os";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
14
|
+
import { createToolSbomGenerator } from "./tool-sbom-generator";
|
|
15
|
+
import { ToolNotAvailableError } from "./process-runner";
|
|
16
|
+
import { createMockProcessRunner } from "./__tests__/mock-process-runner";
|
|
17
|
+
|
|
18
|
+
const dirs: string[] = [];
|
|
19
|
+
function tempDir(): string {
|
|
20
|
+
const dir = mkdtempSync(join(tmpdir(), "chant-tool-sbom-"));
|
|
21
|
+
dirs.push(dir);
|
|
22
|
+
return dir;
|
|
23
|
+
}
|
|
24
|
+
afterEach(() => {
|
|
25
|
+
while (dirs.length) rmSync(dirs.pop()!, { recursive: true, force: true });
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const CYCLONEDX_DOC = JSON.stringify({ bomFormat: "CycloneDX", components: [{ name: "left-pad" }, { name: "express" }] });
|
|
29
|
+
const SPDX_DOC = JSON.stringify({ spdxVersion: "SPDX-2.3", packages: [{ name: "left-pad" }] });
|
|
30
|
+
|
|
31
|
+
describe("createToolSbomGenerator — forDir (#610)", () => {
|
|
32
|
+
it("scans with syft when available, requesting the SPDX format by default", async () => {
|
|
33
|
+
const dir = tempDir();
|
|
34
|
+
const mock = createMockProcessRunner({ responses: { "cat ": SPDX_DOC } });
|
|
35
|
+
const generator = createToolSbomGenerator({ runner: mock.runner });
|
|
36
|
+
|
|
37
|
+
const doc = await generator.forDir({ path: dir });
|
|
38
|
+
|
|
39
|
+
expect(doc.format).toBe("spdx");
|
|
40
|
+
expect(doc.mediaType).toBe("application/spdx+json");
|
|
41
|
+
expect(doc.generator).toBe("syft");
|
|
42
|
+
expect(doc.packageCount).toBe(1);
|
|
43
|
+
const syftCall = mock.calls.find((c) => c.command.startsWith("syft "))!;
|
|
44
|
+
expect(syftCall.command).toContain(`syft '${dir}' -o spdx-json=`);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("requests CycloneDX output when format is cyclonedx", async () => {
|
|
48
|
+
const dir = tempDir();
|
|
49
|
+
const mock = createMockProcessRunner({ responses: { "cat ": CYCLONEDX_DOC } });
|
|
50
|
+
const generator = createToolSbomGenerator({ runner: mock.runner });
|
|
51
|
+
|
|
52
|
+
const doc = await generator.forDir({ path: dir, format: "cyclonedx" });
|
|
53
|
+
|
|
54
|
+
expect(doc.mediaType).toBe("application/vnd.cyclonedx+json");
|
|
55
|
+
expect(doc.packageCount).toBe(2);
|
|
56
|
+
const syftCall = mock.calls.find((c) => c.command.startsWith("syft "))!;
|
|
57
|
+
expect(syftCall.command).toContain("-o cyclonedx-json=");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("falls back to cdxgen when syft is unavailable", async () => {
|
|
61
|
+
const dir = tempDir();
|
|
62
|
+
const mock = createMockProcessRunner({
|
|
63
|
+
tools: { syft: false, cdxgen: true },
|
|
64
|
+
responses: { "cat ": CYCLONEDX_DOC },
|
|
65
|
+
});
|
|
66
|
+
const generator = createToolSbomGenerator({ runner: mock.runner });
|
|
67
|
+
|
|
68
|
+
const doc = await generator.forDir({ path: dir });
|
|
69
|
+
|
|
70
|
+
expect(doc.generator).toBe("cdxgen");
|
|
71
|
+
expect(doc.format).toBe("cyclonedx"); // cdxgen is CycloneDX-only, even though SPDX is the project default.
|
|
72
|
+
expect(mock.calls.some((c) => c.command.startsWith("cdxgen "))).toBe(true);
|
|
73
|
+
expect(mock.calls.some((c) => c.command.startsWith("syft "))).toBe(false);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("throws ToolNotAvailableError when neither syft nor cdxgen is installed", async () => {
|
|
77
|
+
const dir = tempDir();
|
|
78
|
+
const mock = createMockProcessRunner({ tools: { syft: false, cdxgen: false } });
|
|
79
|
+
const generator = createToolSbomGenerator({ runner: mock.runner });
|
|
80
|
+
|
|
81
|
+
await expect(generator.forDir({ path: dir })).rejects.toThrow(ToolNotAvailableError);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe("createToolSbomGenerator — forZip (#610)", () => {
|
|
86
|
+
it("always scans with syft", async () => {
|
|
87
|
+
const mock = createMockProcessRunner({ responses: { "cat ": CYCLONEDX_DOC } });
|
|
88
|
+
const generator = createToolSbomGenerator({ runner: mock.runner });
|
|
89
|
+
|
|
90
|
+
const doc = await generator.forZip({ zipPath: "archive/lambda.zip", digest: "sha256:aaa", format: "cyclonedx" });
|
|
91
|
+
|
|
92
|
+
expect(doc.generator).toBe("syft");
|
|
93
|
+
expect(doc.packageCount).toBe(2);
|
|
94
|
+
expect(mock.calls.some((c) => c.command.includes("archive/lambda.zip"))).toBe(true);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("throws ToolNotAvailableError when syft is unavailable (no fallback for zip)", async () => {
|
|
98
|
+
const mock = createMockProcessRunner({ tools: { syft: false } });
|
|
99
|
+
const generator = createToolSbomGenerator({ runner: mock.runner });
|
|
100
|
+
await expect(generator.forZip({ zipPath: "archive/lambda.zip", digest: "sha256:aaa" })).rejects.toThrow(
|
|
101
|
+
ToolNotAvailableError,
|
|
102
|
+
);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
describe("createToolSbomGenerator — forJar (#610)", () => {
|
|
107
|
+
it("scans with syft when no pom.xml sits alongside the jar", async () => {
|
|
108
|
+
const dir = tempDir();
|
|
109
|
+
const jarPath = join(dir, "app.jar");
|
|
110
|
+
const mock = createMockProcessRunner({ responses: { "cat ": CYCLONEDX_DOC } });
|
|
111
|
+
const generator = createToolSbomGenerator({ runner: mock.runner });
|
|
112
|
+
|
|
113
|
+
const doc = await generator.forJar({ jarPath, digest: "sha256:bbb", format: "cyclonedx" });
|
|
114
|
+
|
|
115
|
+
expect(doc.generator).toBe("syft");
|
|
116
|
+
expect(mock.calls.some((c) => c.command.startsWith("mvn "))).toBe(false);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("prefers cyclonedx-maven when a pom.xml sits alongside the jar and mvn is available", async () => {
|
|
120
|
+
const dir = tempDir();
|
|
121
|
+
writeFileSync(join(dir, "pom.xml"), "<project></project>");
|
|
122
|
+
const jarPath = join(dir, "app.jar");
|
|
123
|
+
const mock = createMockProcessRunner({ tools: { mvn: true }, responses: { "cat ": CYCLONEDX_DOC } });
|
|
124
|
+
const generator = createToolSbomGenerator({ runner: mock.runner });
|
|
125
|
+
|
|
126
|
+
const doc = await generator.forJar({ jarPath, digest: "sha256:bbb" });
|
|
127
|
+
|
|
128
|
+
expect(doc.generator).toBe("cyclonedx-maven");
|
|
129
|
+
expect(doc.format).toBe("cyclonedx"); // cyclonedx-maven always produces CycloneDX regardless of the requested default.
|
|
130
|
+
const mvnCall = mock.calls.find((c) => c.command.startsWith("mvn "))!;
|
|
131
|
+
expect(mvnCall.command).toContain("cyclonedx-maven-plugin");
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("falls back to syft when a pom.xml is present but mvn is not installed", async () => {
|
|
135
|
+
const dir = tempDir();
|
|
136
|
+
writeFileSync(join(dir, "pom.xml"), "<project></project>");
|
|
137
|
+
const jarPath = join(dir, "app.jar");
|
|
138
|
+
const mock = createMockProcessRunner({ tools: { mvn: false, syft: true }, responses: { "cat ": CYCLONEDX_DOC } });
|
|
139
|
+
const generator = createToolSbomGenerator({ runner: mock.runner });
|
|
140
|
+
|
|
141
|
+
const doc = await generator.forJar({ jarPath, digest: "sha256:bbb" });
|
|
142
|
+
expect(doc.generator).toBe("syft");
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
describe("createToolSbomGenerator — forImage (#610)", () => {
|
|
147
|
+
it("scans the saved tarball with syft when no Dockerfile sits next to it", async () => {
|
|
148
|
+
const dir = tempDir();
|
|
149
|
+
const imagePath = join(dir, "search.tar");
|
|
150
|
+
const mock = createMockProcessRunner({ responses: { "cat ": SPDX_DOC } });
|
|
151
|
+
const generator = createToolSbomGenerator({ runner: mock.runner });
|
|
152
|
+
|
|
153
|
+
const doc = await generator.forImage({ imagePath, digest: "sha256:ccc" });
|
|
154
|
+
|
|
155
|
+
expect(doc.generator).toBe("syft");
|
|
156
|
+
expect(mock.calls.some((c) => c.command.startsWith("docker buildx"))).toBe(false);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it("prefers BuildKit's --sbom attestation when a Dockerfile sits next to the tarball and docker is available", async () => {
|
|
160
|
+
const dir = tempDir();
|
|
161
|
+
writeFileSync(join(dir, "Dockerfile"), "FROM scratch");
|
|
162
|
+
const imagePath = join(dir, "search.tar");
|
|
163
|
+
const mock = createMockProcessRunner({ tools: { docker: true }, responses: { "cat ": SPDX_DOC } });
|
|
164
|
+
const generator = createToolSbomGenerator({ runner: mock.runner });
|
|
165
|
+
|
|
166
|
+
const doc = await generator.forImage({ imagePath, digest: "sha256:ccc" });
|
|
167
|
+
|
|
168
|
+
expect(doc.generator).toBe("buildkit");
|
|
169
|
+
expect(doc.format).toBe("spdx");
|
|
170
|
+
const buildxCall = mock.calls.find((c) => c.command.startsWith("docker buildx"))!;
|
|
171
|
+
expect(buildxCall.command).toContain("--sbom=true");
|
|
172
|
+
expect(buildxCall.command).toContain(dir);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it("falls back to syft when a Dockerfile is present but docker is unavailable", async () => {
|
|
176
|
+
const dir = tempDir();
|
|
177
|
+
writeFileSync(join(dir, "Dockerfile"), "FROM scratch");
|
|
178
|
+
const imagePath = join(dir, "search.tar");
|
|
179
|
+
const mock = createMockProcessRunner({ tools: { docker: false, syft: true }, responses: { "cat ": SPDX_DOC } });
|
|
180
|
+
const generator = createToolSbomGenerator({ runner: mock.runner });
|
|
181
|
+
|
|
182
|
+
const doc = await generator.forImage({ imagePath, digest: "sha256:ccc" });
|
|
183
|
+
expect(doc.generator).toBe("syft");
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it("throws ToolNotAvailableError when falling back to syft and syft is also unavailable", async () => {
|
|
187
|
+
const dir = tempDir();
|
|
188
|
+
const imagePath = join(dir, "search.tar");
|
|
189
|
+
const mock = createMockProcessRunner({ tools: { syft: false } });
|
|
190
|
+
const generator = createToolSbomGenerator({ runner: mock.runner });
|
|
191
|
+
|
|
192
|
+
await expect(generator.forImage({ imagePath, digest: "sha256:ccc" })).rejects.toThrow(ToolNotAvailableError);
|
|
193
|
+
});
|
|
194
|
+
});
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Real, deep-scan `SbomGenerator` backend (#610, epic #551's "deep external
|
|
3
|
+
* tool" half of the SBOM story — the counterpart to ./lockfile-sbom-generator.ts's
|
|
4
|
+
* hermetic, zero-dependency default from #613). Shells out to whichever
|
|
5
|
+
* external scanner fits the artifact type, through the injectable
|
|
6
|
+
* `ProcessRunner` (./process-runner.ts, itself mirroring
|
|
7
|
+
* ./cloud-executor.ts's `CloudExecutor` pattern):
|
|
8
|
+
*
|
|
9
|
+
* - `image` -> `docker buildx build --sbom=true` (BuildKit's native SPDX
|
|
10
|
+
* attestation) when a Dockerfile build context is available, otherwise a
|
|
11
|
+
* `syft <tarball>` scan of the already-built OCI-layout tarball;
|
|
12
|
+
* - `jar` -> `syft`, or `cyclonedx-maven` when the jar's project directory
|
|
13
|
+
* carries a `pom.xml` (a more Maven-native scan than a bytecode-level jar
|
|
14
|
+
* scan);
|
|
15
|
+
* - `zip` -> `syft <zip>`;
|
|
16
|
+
* - `dir` -> `syft <dir>`, falling back to `cdxgen <dir>` when `syft` is
|
|
17
|
+
* unavailable (`cdxgen`'s "wnat's on disk" mode covers ecosystems syft
|
|
18
|
+
* might not have detected).
|
|
19
|
+
*
|
|
20
|
+
* Every method resolves the specific tool it needs, checks availability via
|
|
21
|
+
* `ProcessRunner.available` first, and throws `ToolNotAvailableError`
|
|
22
|
+
* (./process-runner.ts) with an actionable install hint if the tool is
|
|
23
|
+
* missing — never a silent empty SBOM. Tests inject `MockProcessRunner`
|
|
24
|
+
* (./__tests__/mock-process-runner.ts) and assert on the constructed command
|
|
25
|
+
* line + parsed output; nothing here ever spawns a real process.
|
|
26
|
+
*
|
|
27
|
+
* This backend is **not** the process-wide default (see
|
|
28
|
+
* ./sbom-generator.ts's `notImplementedSbomGenerator` and
|
|
29
|
+
* ./lockfile-sbom-generator.ts's `lockfileSbomGenerator` module docs for why):
|
|
30
|
+
* a project opts in explicitly by constructing
|
|
31
|
+
* `createGenerateSbomCapability(createToolSbomGenerator())` when it wants the
|
|
32
|
+
* deeper, external-tool-backed scan (e.g. to see an image's base-layer OS
|
|
33
|
+
* packages, which no lockfile read can see).
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import { basename, dirname, join } from "node:path";
|
|
37
|
+
import { existsSync } from "node:fs";
|
|
38
|
+
import {
|
|
39
|
+
DEFAULT_SBOM_FORMAT,
|
|
40
|
+
SBOM_MEDIA_TYPES,
|
|
41
|
+
type GenerateDirSbomInput,
|
|
42
|
+
type GenerateImageSbomInput,
|
|
43
|
+
type GenerateJarSbomInput,
|
|
44
|
+
type GenerateZipSbomInput,
|
|
45
|
+
type SbomDocument,
|
|
46
|
+
type SbomFormat,
|
|
47
|
+
type SbomGenerator,
|
|
48
|
+
} from "./sbom-generator";
|
|
49
|
+
import { defaultProcessRunner, q, requireTool, type ProcessRunner } from "./process-runner";
|
|
50
|
+
|
|
51
|
+
/** `syft`'s CycloneDX/SPDX JSON output format flags, keyed by `SbomFormat`. */
|
|
52
|
+
const SYFT_FORMAT: Record<SbomFormat, string> = {
|
|
53
|
+
spdx: "spdx-json",
|
|
54
|
+
cyclonedx: "cyclonedx-json",
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/** `cdxgen` only emits CycloneDX; SPDX is not one of its output formats. */
|
|
58
|
+
const CDXGEN_FORMAT: SbomFormat = "cyclonedx";
|
|
59
|
+
|
|
60
|
+
export interface ToolSbomGeneratorOptions {
|
|
61
|
+
/** Injected process boundary. Defaults to the real, `child_process`-backed runner. */
|
|
62
|
+
runner?: ProcessRunner;
|
|
63
|
+
/** Directory `syft`/`cdxgen`/`cyclonedx-maven` write their output file into before this backend reads it back. Defaults to `node:os`'s tmpdir via each call's own scratch path — see `scratchPath`. */
|
|
64
|
+
workDir?: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Parse a package count out of a CycloneDX or SPDX JSON document, without requiring a full schema-aware parse — mirrors how ./lockfile-sbom-generator.ts's callers only need a count, not full re-validation. */
|
|
68
|
+
function countPackages(format: SbomFormat, bytes: string): number | undefined {
|
|
69
|
+
try {
|
|
70
|
+
const doc = JSON.parse(bytes) as Record<string, unknown>;
|
|
71
|
+
if (format === "cyclonedx" && Array.isArray(doc.components)) return doc.components.length;
|
|
72
|
+
if (format === "spdx" && Array.isArray(doc.packages)) return doc.packages.length;
|
|
73
|
+
} catch {
|
|
74
|
+
// Malformed/unexpected output — report no count rather than throwing;
|
|
75
|
+
// the SBOM bytes themselves are still returned to the caller as-is.
|
|
76
|
+
}
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Build a per-call scratch output path so concurrent scans never collide. */
|
|
81
|
+
function scratchPath(workDir: string | undefined, subject: string, format: SbomFormat): string {
|
|
82
|
+
const dir = workDir ?? "/tmp/chant-sbom";
|
|
83
|
+
const safeName = basename(subject).replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
84
|
+
return join(dir, `${safeName}.${Date.now()}.${format}.json`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Build a real, deep-scan `SbomGenerator` backend. Every method shells out
|
|
89
|
+
* through `options.runner` (default: the real `ProcessRunner`); tests should
|
|
90
|
+
* always pass a `MockProcessRunner` instead.
|
|
91
|
+
*/
|
|
92
|
+
export function createToolSbomGenerator(options: ToolSbomGeneratorOptions = {}): SbomGenerator {
|
|
93
|
+
const runner = options.runner ?? defaultProcessRunner();
|
|
94
|
+
|
|
95
|
+
async function runSyft(target: string, format: SbomFormat): Promise<{ bytes: string; generator: string }> {
|
|
96
|
+
await requireTool(runner, "syft", `scan ${target} for a software bill of materials`);
|
|
97
|
+
const out = scratchPath(options.workDir, target, format);
|
|
98
|
+
await runner.run(`syft ${q(target)} -o ${SYFT_FORMAT[format]}=${q(out)}`);
|
|
99
|
+
const { stdout } = await runner.run(`cat ${q(out)}`);
|
|
100
|
+
return { bytes: stdout, generator: "syft" };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
async forImage(input: GenerateImageSbomInput): Promise<SbomDocument> {
|
|
105
|
+
const format = input.format ?? DEFAULT_SBOM_FORMAT;
|
|
106
|
+
|
|
107
|
+
// Prefer BuildKit's own attestation when the image was built from a
|
|
108
|
+
// context on disk right next to the tarball (the common docker-build ->
|
|
109
|
+
// generate-sbom composition); otherwise fall back to scanning the
|
|
110
|
+
// already-saved tarball with syft, which needs no rebuild.
|
|
111
|
+
const buildContext = dirname(input.imagePath);
|
|
112
|
+
const hasDockerfile = existsSync(join(buildContext, "Dockerfile"));
|
|
113
|
+
|
|
114
|
+
if (hasDockerfile && (await runner.available("docker"))) {
|
|
115
|
+
const out = scratchPath(options.workDir, input.imagePath, format);
|
|
116
|
+
await runner.run(
|
|
117
|
+
`docker buildx build --sbom=true --output type=local,dest=${q(out)} ${q(buildContext)}`,
|
|
118
|
+
);
|
|
119
|
+
const { stdout } = await runner.run(`cat ${q(join(out, "sbom.spdx.json"))}`);
|
|
120
|
+
return {
|
|
121
|
+
format,
|
|
122
|
+
mediaType: SBOM_MEDIA_TYPES[format],
|
|
123
|
+
bytes: stdout,
|
|
124
|
+
packageCount: countPackages(format, stdout),
|
|
125
|
+
generator: "buildkit",
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const { bytes, generator } = await runSyft(input.imagePath, format);
|
|
130
|
+
return { format, mediaType: SBOM_MEDIA_TYPES[format], bytes, packageCount: countPackages(format, bytes), generator };
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
async forJar(input: GenerateJarSbomInput): Promise<SbomDocument> {
|
|
134
|
+
const format = input.format ?? DEFAULT_SBOM_FORMAT;
|
|
135
|
+
const projectDir = dirname(input.jarPath);
|
|
136
|
+
const hasPom = existsSync(join(projectDir, "pom.xml"));
|
|
137
|
+
|
|
138
|
+
if (hasPom && (await runner.available("mvn"))) {
|
|
139
|
+
await requireTool(runner, "mvn", `run cyclonedx-maven against ${projectDir}`);
|
|
140
|
+
const out = scratchPath(options.workDir, input.jarPath, "cyclonedx");
|
|
141
|
+
await runner.run(
|
|
142
|
+
`mvn -f ${q(join(projectDir, "pom.xml"))} org.cyclonedx:cyclonedx-maven-plugin:makeAggregateBom -DoutputFormat=json -DoutputName=${q(basename(out, ".json"))}`,
|
|
143
|
+
{ cwd: projectDir },
|
|
144
|
+
);
|
|
145
|
+
const { stdout } = await runner.run(`cat ${q(out)}`);
|
|
146
|
+
return {
|
|
147
|
+
format: "cyclonedx",
|
|
148
|
+
mediaType: SBOM_MEDIA_TYPES.cyclonedx,
|
|
149
|
+
bytes: stdout,
|
|
150
|
+
packageCount: countPackages("cyclonedx", stdout),
|
|
151
|
+
generator: "cyclonedx-maven",
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const { bytes, generator } = await runSyft(input.jarPath, format);
|
|
156
|
+
return { format, mediaType: SBOM_MEDIA_TYPES[format], bytes, packageCount: countPackages(format, bytes), generator };
|
|
157
|
+
},
|
|
158
|
+
|
|
159
|
+
async forZip(input: GenerateZipSbomInput): Promise<SbomDocument> {
|
|
160
|
+
const format = input.format ?? DEFAULT_SBOM_FORMAT;
|
|
161
|
+
const { bytes, generator } = await runSyft(input.zipPath, format);
|
|
162
|
+
return { format, mediaType: SBOM_MEDIA_TYPES[format], bytes, packageCount: countPackages(format, bytes), generator };
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
async forDir(input: GenerateDirSbomInput): Promise<SbomDocument> {
|
|
166
|
+
const format = input.format ?? DEFAULT_SBOM_FORMAT;
|
|
167
|
+
|
|
168
|
+
if (await runner.available("syft")) {
|
|
169
|
+
const { bytes, generator } = await runSyft(input.path, format);
|
|
170
|
+
return { format, mediaType: SBOM_MEDIA_TYPES[format], bytes, packageCount: countPackages(format, bytes), generator };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// syft absent — fall back to cdxgen (CycloneDX-only; requested SPDX is
|
|
174
|
+
// reported back as the CycloneDX doc's own format, never silently
|
|
175
|
+
// mislabeled as SPDX).
|
|
176
|
+
await requireTool(runner, "cdxgen", `scan ${input.path} for a software bill of materials (syft is also unavailable)`);
|
|
177
|
+
const out = scratchPath(options.workDir, input.path, CDXGEN_FORMAT);
|
|
178
|
+
await runner.run(`cdxgen -o ${q(out)} ${q(input.path)}`);
|
|
179
|
+
const { stdout } = await runner.run(`cat ${q(out)}`);
|
|
180
|
+
return {
|
|
181
|
+
format: CDXGEN_FORMAT,
|
|
182
|
+
mediaType: SBOM_MEDIA_TYPES[CDXGEN_FORMAT],
|
|
183
|
+
bytes: stdout,
|
|
184
|
+
packageCount: countPackages(CDXGEN_FORMAT, stdout),
|
|
185
|
+
generator: "cdxgen",
|
|
186
|
+
};
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** Process-wide, real-tool-backed `SbomGenerator`, ready to inject wherever a project opts into the deep-scan path over ./lockfile-sbom-generator.ts's hermetic default. Never constructed by a test. */
|
|
192
|
+
export const toolSbomGenerator: SbomGenerator = createToolSbomGenerator();
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests the `verify` gate (#622, ./verify.ts) against a `MockProcessRunner`
|
|
3
|
+
* (./__tests__/mock-process-runner.ts) — no live `cosign`, no real
|
|
4
|
+
* Rekor/Fulcio network call. Asserts: passes on a valid mock (signature +
|
|
5
|
+
* provenance both verify), fails on missing/invalid signature, fails on
|
|
6
|
+
* wrong identity, and handles a missing `cosign` binary gracefully (throws
|
|
7
|
+
* rather than silently passing the gate).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { describe, expect, it } from "vitest";
|
|
11
|
+
import {
|
|
12
|
+
buildVerifyAttestationArgs,
|
|
13
|
+
buildVerifySignatureArgs,
|
|
14
|
+
createVerifyCapability,
|
|
15
|
+
VerificationFailedError,
|
|
16
|
+
} from "./verify";
|
|
17
|
+
import { ToolNotAvailableError } from "./process-runner";
|
|
18
|
+
import { createMockProcessRunner } from "./__tests__/mock-process-runner";
|
|
19
|
+
|
|
20
|
+
const ctx = { env: "prod", component: "search-service" };
|
|
21
|
+
const DIGEST_REF = "123.dkr.ecr.us-east-1.amazonaws.com/search@sha256:" + "a".repeat(64);
|
|
22
|
+
|
|
23
|
+
const POLICY = {
|
|
24
|
+
expectedIssuer: "https://token.actions.githubusercontent.com",
|
|
25
|
+
expectedIdentity: "https://github.com/my-org/my-repo/.github/workflows/release.yml@refs/heads/main",
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
describe("verify — command construction", () => {
|
|
29
|
+
it("builds cosign verify with the expected issuer/identity flags", () => {
|
|
30
|
+
const cmd = buildVerifySignatureArgs({ imageRef: DIGEST_REF, policy: POLICY });
|
|
31
|
+
expect(cmd).toBe(
|
|
32
|
+
`cosign verify --certificate-oidc-issuer '${POLICY.expectedIssuer}' --certificate-identity '${POLICY.expectedIdentity}' '${DIGEST_REF}'`,
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("builds cosign verify-attestation with the slsaprovenance1 type", () => {
|
|
37
|
+
const cmd = buildVerifyAttestationArgs({ imageRef: DIGEST_REF, policy: POLICY });
|
|
38
|
+
expect(cmd).toBe(
|
|
39
|
+
`cosign verify-attestation --type slsaprovenance1 --certificate-oidc-issuer '${POLICY.expectedIssuer}' --certificate-identity '${POLICY.expectedIdentity}' '${DIGEST_REF}'`,
|
|
40
|
+
);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("uses --certificate-identity-regexp when identityIsRegexp is set", () => {
|
|
44
|
+
const cmd = buildVerifySignatureArgs({
|
|
45
|
+
imageRef: DIGEST_REF,
|
|
46
|
+
policy: { ...POLICY, expectedIdentity: ".*release.yml@refs/heads/.*", identityIsRegexp: true },
|
|
47
|
+
});
|
|
48
|
+
expect(cmd).toContain("--certificate-identity-regexp");
|
|
49
|
+
expect(cmd).not.toContain("--certificate-identity '");
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("uses --key instead of the identity policy flags when policy.key is set", () => {
|
|
53
|
+
const cmd = buildVerifySignatureArgs({ imageRef: DIGEST_REF, policy: { ...POLICY, key: "cosign.pub" } });
|
|
54
|
+
expect(cmd).toBe(`cosign verify --key 'cosign.pub' '${DIGEST_REF}'`);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe("verify — passes on a valid mock", () => {
|
|
59
|
+
it("verifies both signature and provenance by default", async () => {
|
|
60
|
+
const mock = createMockProcessRunner();
|
|
61
|
+
const capability = createVerifyCapability(mock.runner);
|
|
62
|
+
|
|
63
|
+
const output = await capability.run(ctx, { imageRef: DIGEST_REF, policy: POLICY });
|
|
64
|
+
|
|
65
|
+
expect(output).toEqual({ verified: true, checked: ["signature", "provenance"] });
|
|
66
|
+
expect(mock.calls.some((c) => c.command.startsWith("cosign verify --certificate"))).toBe(true);
|
|
67
|
+
expect(mock.calls.some((c) => c.command.startsWith("cosign verify-attestation"))).toBe(true);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("skips the provenance check when requireProvenance is false", async () => {
|
|
71
|
+
const mock = createMockProcessRunner();
|
|
72
|
+
const capability = createVerifyCapability(mock.runner);
|
|
73
|
+
|
|
74
|
+
const output = await capability.run(ctx, { imageRef: DIGEST_REF, policy: POLICY, requireProvenance: false });
|
|
75
|
+
|
|
76
|
+
expect(output).toEqual({ verified: true, checked: ["signature"] });
|
|
77
|
+
expect(mock.calls.some((c) => c.command.startsWith("cosign verify-attestation"))).toBe(false);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe("verify — FAILS the deploy on a missing/invalid signature", () => {
|
|
82
|
+
it("throws VerificationFailedError when cosign verify rejects (e.g. no signature found)", async () => {
|
|
83
|
+
const mock = createMockProcessRunner({
|
|
84
|
+
failures: { "cosign verify --certificate": "Error: no matching signatures" },
|
|
85
|
+
});
|
|
86
|
+
const capability = createVerifyCapability(mock.runner);
|
|
87
|
+
|
|
88
|
+
await expect(capability.run(ctx, { imageRef: DIGEST_REF, policy: POLICY })).rejects.toThrow(
|
|
89
|
+
VerificationFailedError,
|
|
90
|
+
);
|
|
91
|
+
await expect(capability.run(ctx, { imageRef: DIGEST_REF, policy: POLICY })).rejects.toThrow(
|
|
92
|
+
/signature verification failed/,
|
|
93
|
+
);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("never runs the provenance check once the signature check has already failed", async () => {
|
|
97
|
+
const mock = createMockProcessRunner({
|
|
98
|
+
failures: { "cosign verify --certificate": "Error: no matching signatures" },
|
|
99
|
+
});
|
|
100
|
+
const capability = createVerifyCapability(mock.runner);
|
|
101
|
+
|
|
102
|
+
await expect(capability.run(ctx, { imageRef: DIGEST_REF, policy: POLICY })).rejects.toThrow();
|
|
103
|
+
expect(mock.calls.some((c) => c.command.startsWith("cosign verify-attestation"))).toBe(false);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe("verify — FAILS the deploy on missing/invalid provenance", () => {
|
|
108
|
+
it("throws VerificationFailedError when cosign verify-attestation rejects", async () => {
|
|
109
|
+
const mock = createMockProcessRunner({
|
|
110
|
+
failures: { "cosign verify-attestation": "Error: no matching attestations" },
|
|
111
|
+
});
|
|
112
|
+
const capability = createVerifyCapability(mock.runner);
|
|
113
|
+
|
|
114
|
+
await expect(capability.run(ctx, { imageRef: DIGEST_REF, policy: POLICY })).rejects.toThrow(
|
|
115
|
+
VerificationFailedError,
|
|
116
|
+
);
|
|
117
|
+
await expect(capability.run(ctx, { imageRef: DIGEST_REF, policy: POLICY })).rejects.toThrow(
|
|
118
|
+
/provenance verification failed/,
|
|
119
|
+
);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe("verify — FAILS the deploy on wrong identity", () => {
|
|
124
|
+
it("surfaces cosign's own identity-mismatch failure as VerificationFailedError", async () => {
|
|
125
|
+
const mock = createMockProcessRunner({
|
|
126
|
+
failures: {
|
|
127
|
+
"cosign verify --certificate": "Error: none of the expected identities matched what was in the certificate",
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
const capability = createVerifyCapability(mock.runner);
|
|
131
|
+
|
|
132
|
+
await expect(
|
|
133
|
+
capability.run(ctx, {
|
|
134
|
+
imageRef: DIGEST_REF,
|
|
135
|
+
policy: { ...POLICY, expectedIdentity: "https://github.com/someone-else/other-repo/.github/workflows/x.yml@refs/heads/main" },
|
|
136
|
+
}),
|
|
137
|
+
).rejects.toThrow(/none of the expected identities matched/);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
describe("verify — cosign absence handled gracefully", () => {
|
|
142
|
+
it("throws ToolNotAvailableError rather than silently passing the gate when cosign is not installed", async () => {
|
|
143
|
+
const mock = createMockProcessRunner({ tools: { cosign: false } });
|
|
144
|
+
const capability = createVerifyCapability(mock.runner);
|
|
145
|
+
|
|
146
|
+
await expect(capability.run(ctx, { imageRef: DIGEST_REF, policy: POLICY })).rejects.toThrow(
|
|
147
|
+
ToolNotAvailableError,
|
|
148
|
+
);
|
|
149
|
+
// No verify/verify-attestation call was even attempted.
|
|
150
|
+
expect(mock.calls.some((c) => c.command.startsWith("cosign verify"))).toBe(false);
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
describe("verify — no rollback (read-only observation)", () => {
|
|
155
|
+
it("declares no rollback", () => {
|
|
156
|
+
const capability = createVerifyCapability(createMockProcessRunner().runner);
|
|
157
|
+
expect(capability.rollback).toBeUndefined();
|
|
158
|
+
});
|
|
159
|
+
});
|