@intentius/chant 0.13.0 → 0.15.0
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/fetch.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 +56 -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/fetch.test.ts +112 -8
- package/src/audit/fetch.ts +53 -8
- 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 +92 -0
- package/src/op/index.ts +3 -1
- package/src/ownership.test.ts +24 -26
- package/src/ownership.ts +26 -38
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component-level BOM aggregation (#614, epic #551 follow-up to #613).
|
|
3
|
+
*
|
|
4
|
+
* A component may produce more than one leaf BOM: a software SBOM
|
|
5
|
+
* (./sbom.ts's `generate-sbom`, over an image/jar/zip/dir) for its built
|
|
6
|
+
* artifact, and/or an IaC config-BOM (./config-bom.ts's `extract-config-bom`)
|
|
7
|
+
* for its synthesized template. Each leaf BOM already validates as its own
|
|
8
|
+
* standalone SPDX/CycloneDX document (#613). This module composes every leaf
|
|
9
|
+
* BOM a component's build archive carries into **one** component-level BOM —
|
|
10
|
+
* a real assembly (CycloneDX `metadata.component` + nested `components` +
|
|
11
|
+
* `compositions` + `dependencies`, or SPDX `documentDescribes`-equivalent
|
|
12
|
+
* `CONTAINS`/`DEPENDS_ON` relationships), not a re-implementation of the
|
|
13
|
+
* writer: `aggregateComponentBom` below reads each leaf BOM's own structural
|
|
14
|
+
* inventory back out (it does not re-parse the leaf's serialized bytes) and
|
|
15
|
+
* hands the result to ./bom-writer.ts's `writeBom`, which gained
|
|
16
|
+
* `BomInput.subDocuments` support for exactly this purpose.
|
|
17
|
+
*
|
|
18
|
+
* **Why not just concatenate/parse the leaf documents' bytes.** The leaf BOM
|
|
19
|
+
* bytes are the source of truth for each artifact independently, but this
|
|
20
|
+
* module needs each leaf's package list as structured data to nest it
|
|
21
|
+
* correctly in the assembly — reparsing SPDX/CycloneDX JSON back into
|
|
22
|
+
* `BomPackage[]` would be a second, format-aware reader this codebase does
|
|
23
|
+
* not otherwise need. Callers therefore pass each leaf's already-known
|
|
24
|
+
* `BomPackage[]` (what `generate-sbom`/`extract-config-bom` built before
|
|
25
|
+
* calling `writeBom` themselves) plus the archive entry it produced — see
|
|
26
|
+
* `ComponentBomLeaf` below. A caller assembling a component's archive
|
|
27
|
+
* end-to-end has both on hand already; nothing here re-derives them from
|
|
28
|
+
* scratch.
|
|
29
|
+
*
|
|
30
|
+
* **Single-artifact = 1:1, multi-artifact = a real assembly.** A component
|
|
31
|
+
* with exactly one leaf BOM produces a component BOM whose only "assembly"
|
|
32
|
+
* is itself — see `aggregateComponentBom`'s single-leaf fast path, which
|
|
33
|
+
* still runs through the same writer and produces a valid document, just
|
|
34
|
+
* with an empty `subDocuments` composition (nothing to nest). A component
|
|
35
|
+
* with two or more leaves gets `subDocuments` populated, the real multi-leaf
|
|
36
|
+
* case #614 asks for.
|
|
37
|
+
*
|
|
38
|
+
* **Release-level rollup is out of scope here** (deferred per #614's note) —
|
|
39
|
+
* this module aggregates *within* one component's archive, never across
|
|
40
|
+
* components/releases.
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
import type { BuildArchiveEntry, BuildArchiveManifest } from "./build-archive";
|
|
44
|
+
import { sbomEntries } from "./build-archive";
|
|
45
|
+
import { writeBom, type BomPackage, type BomSubDocument } from "./bom-writer";
|
|
46
|
+
import { DEFAULT_SBOM_FORMAT, type SbomDocument, type SbomFormat } from "./sbom-generator";
|
|
47
|
+
|
|
48
|
+
// ── inputs ───────────────────────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* One leaf BOM a component produced, ready to fold into the component-level
|
|
52
|
+
* aggregate. `entry` is the `sbom`-kind `BuildArchiveEntry` `generate-sbom`/
|
|
53
|
+
* `extract-config-bom` already wrote into the manifest (carries `bomKind`,
|
|
54
|
+
* `subjectDigest`, `packageCount`, `generator`); `packages` is the same
|
|
55
|
+
* `BomPackage[]` that capability built before calling `writeBom` for its own
|
|
56
|
+
* standalone document.
|
|
57
|
+
*/
|
|
58
|
+
export interface ComponentBomLeaf {
|
|
59
|
+
/** The `sbom`-kind manifest entry this leaf corresponds to (see `findSbomForSubject`/`findConfigBomForSubject`, ./build-archive.ts). */
|
|
60
|
+
entry: BuildArchiveEntry;
|
|
61
|
+
/** The leaf's own enumerated packages/components/resources. */
|
|
62
|
+
packages: BomPackage[];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface AggregateComponentBomInput {
|
|
66
|
+
/** Component name — becomes the aggregate BOM's subject name. */
|
|
67
|
+
component: string;
|
|
68
|
+
/** The build archive manifest this component's leaves live in — used to derive a stable subject id from `manifestDigest` and to validate that every leaf's `entry` actually belongs to this manifest. */
|
|
69
|
+
manifest: BuildArchiveManifest;
|
|
70
|
+
/** Every leaf BOM to compose. Order is preserved in the resulting `subDocuments`/nested components. Must be non-empty — a component with no BOM to aggregate has nothing for this function to do (see module doc's "skips cleanly" convention elsewhere in this directory). */
|
|
71
|
+
leaves: ComponentBomLeaf[];
|
|
72
|
+
/** BOM format for the aggregate document. Defaults to `DEFAULT_SBOM_FORMAT` (SPDX), same precedence convention as every other BOM-producing capability in this directory. */
|
|
73
|
+
format?: SbomFormat;
|
|
74
|
+
/** Clock override for deterministic tests. */
|
|
75
|
+
now?: () => Date;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface AggregateComponentBomOutput {
|
|
79
|
+
/** The composed component-level BOM document. */
|
|
80
|
+
bom: SbomDocument;
|
|
81
|
+
/** Total package/component count across every leaf (the sum `chant components status` surfaces, see ../../lifecycle/build-ledger.ts). */
|
|
82
|
+
totalPackageCount: number;
|
|
83
|
+
/** How many leaf BOMs were composed — 1 for a single-artifact component (still a valid document, just with nothing to nest), 2+ for a real multi-artifact assembly. */
|
|
84
|
+
leafCount: number;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Tool name recorded on the aggregate document — distinct from any individual leaf's own `generator` (e.g. "syft", "chant-config-bom-extractor"), since the aggregate is produced by this module, not by whichever tool generated a leaf. */
|
|
88
|
+
const COMPONENT_BOM_GENERATOR = "chant-component-bom-aggregator";
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Compose a component's leaf BOMs (a software SBOM and/or an IaC config-BOM,
|
|
92
|
+
* per `input.leaves`) into one component-level BOM via ./bom-writer.ts's
|
|
93
|
+
* assembly support (`BomInput.subDocuments`). Pure — no I/O, no manifest
|
|
94
|
+
* mutation; the caller decides whether/where to write the result to disk
|
|
95
|
+
* (mirroring `extract-config-bom`'s `outDir` convention) and whether to fold
|
|
96
|
+
* it back into the archive as its own entry.
|
|
97
|
+
*
|
|
98
|
+
* Throws if `input.leaves` is empty — aggregation over zero leaves is a
|
|
99
|
+
* caller bug (compose only when at least one BOM exists), not a case this
|
|
100
|
+
* function silently no-ops on.
|
|
101
|
+
*/
|
|
102
|
+
export function aggregateComponentBom(input: AggregateComponentBomInput): AggregateComponentBomOutput {
|
|
103
|
+
if (input.leaves.length === 0) {
|
|
104
|
+
throw new Error(
|
|
105
|
+
`aggregateComponentBom: component "${input.component}" has no leaf BOMs to aggregate — ` +
|
|
106
|
+
`only call this once at least one generate-sbom/extract-config-bom leaf exists`,
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const format = input.format ?? DEFAULT_SBOM_FORMAT;
|
|
111
|
+
const totalPackageCount = input.leaves.reduce((sum, l) => sum + l.packages.length, 0);
|
|
112
|
+
|
|
113
|
+
// Single leaf: the component BOM *is* that leaf's BOM, structurally — no
|
|
114
|
+
// sub-document nesting needed, its packages become the aggregate's own
|
|
115
|
+
// flat package list directly (still a real, standalone valid document).
|
|
116
|
+
if (input.leaves.length === 1) {
|
|
117
|
+
const leaf = input.leaves[0]!;
|
|
118
|
+
const doc = writeBom(
|
|
119
|
+
format,
|
|
120
|
+
{
|
|
121
|
+
subjectName: input.component,
|
|
122
|
+
subjectId: input.manifest.manifestDigest,
|
|
123
|
+
packages: leaf.packages,
|
|
124
|
+
generator: COMPONENT_BOM_GENERATOR,
|
|
125
|
+
},
|
|
126
|
+
input.now,
|
|
127
|
+
);
|
|
128
|
+
return {
|
|
129
|
+
bom: { ...doc, packageCount: totalPackageCount, generator: COMPONENT_BOM_GENERATOR },
|
|
130
|
+
totalPackageCount,
|
|
131
|
+
leafCount: 1,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Multi-leaf: a real assembly — each leaf becomes its own nested
|
|
136
|
+
// sub-document, keyed by which artifact digest it describes (its
|
|
137
|
+
// `subjectDigest`) so the assembly is traceable back to a specific
|
|
138
|
+
// BuildArchiveEntry, never flattened into one anonymous package list.
|
|
139
|
+
const subDocuments: BomSubDocument[] = input.leaves.map((leaf) => ({
|
|
140
|
+
subjectName: leafSubjectName(leaf),
|
|
141
|
+
subjectId: leaf.entry.subjectDigest ?? leaf.entry.digest,
|
|
142
|
+
packages: leaf.packages,
|
|
143
|
+
}));
|
|
144
|
+
|
|
145
|
+
const doc = writeBom(
|
|
146
|
+
format,
|
|
147
|
+
{
|
|
148
|
+
subjectName: input.component,
|
|
149
|
+
subjectId: input.manifest.manifestDigest,
|
|
150
|
+
packages: [],
|
|
151
|
+
generator: COMPONENT_BOM_GENERATOR,
|
|
152
|
+
subDocuments,
|
|
153
|
+
},
|
|
154
|
+
input.now,
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
bom: { ...doc, packageCount: totalPackageCount, generator: COMPONENT_BOM_GENERATOR },
|
|
159
|
+
totalPackageCount,
|
|
160
|
+
leafCount: input.leaves.length,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Human-readable sub-document name: the leaf kind plus the archive path of the BOM entry itself, so two leaves of the same `bomKind` (unlikely but not disallowed) remain distinguishable. */
|
|
165
|
+
function leafSubjectName(leaf: ComponentBomLeaf): string {
|
|
166
|
+
const kind = leaf.entry.bomKind ?? "software";
|
|
167
|
+
return `${kind}:${leaf.entry.path}`;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ── convenience: build leaves straight from a manifest ──────────────────────
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Convenience for the common case: given a manifest that already carries
|
|
174
|
+
* `sbom`-kind entries (written by `generate-sbom`/`extract-config-bom`) and
|
|
175
|
+
* the `BomPackage[]` each one was built from (keyed by archive path, since
|
|
176
|
+
* that's the one stable identifier a caller has for "which leaf is this"),
|
|
177
|
+
* assemble the `ComponentBomLeaf[]` `aggregateComponentBom` expects. Kept
|
|
178
|
+
* separate from `aggregateComponentBom` itself so a caller that already has
|
|
179
|
+
* `ComponentBomLeaf[]` in hand (e.g. straight from the capabilities that just
|
|
180
|
+
* ran) can skip this lookup entirely.
|
|
181
|
+
*/
|
|
182
|
+
export function componentBomLeavesFromManifest(
|
|
183
|
+
manifest: BuildArchiveManifest,
|
|
184
|
+
packagesByPath: Map<string, BomPackage[]>,
|
|
185
|
+
): ComponentBomLeaf[] {
|
|
186
|
+
return sbomEntries(manifest)
|
|
187
|
+
.filter((entry) => packagesByPath.has(entry.path))
|
|
188
|
+
.map((entry) => ({ entry, packages: packagesByPath.get(entry.path)! }));
|
|
189
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The starter verb set, grouped by family, per epic #551 and
|
|
3
|
+
* docs/components/capabilities.mdx. The pilot AWS leaves — `docker-build`,
|
|
4
|
+
* `publish-image`, `load-image-on-host`, `cfn-deploy`, `ecs-update-service`,
|
|
5
|
+
* `code-deploy`, `wait-for-stack`, `wait-steady-state`, `wait-cluster-healthy`
|
|
6
|
+
* — are real implementations over the injectable `CloudExecutor` (#557/#564,
|
|
7
|
+
* epic #551, see ./cloud-executor.ts); every other verb remains a typed stub
|
|
8
|
+
* (../capability.ts). `generate-sbom` (#606) is a real implementation over
|
|
9
|
+
* the injectable, artifact-type-keyed `SbomGenerator` (./sbom-generator.ts);
|
|
10
|
+
* `./lockfile-sbom-generator.ts` (#613) is a real, hermetic backend for it
|
|
11
|
+
* (lockfile/manifest parsing, no external tool), and `./tool-sbom-generator.ts`
|
|
12
|
+
* (#610) is a real, deep-scan backend for it (`syft`/`docker buildx --sbom`/
|
|
13
|
+
* `cyclonedx-maven`/`cdxgen`, shelling out through the injectable
|
|
14
|
+
* `ProcessRunner`, ./process-runner.ts). `extract-config-bom` (#613) parses a
|
|
15
|
+
* synthesized CloudFormation template, so it moved to the aws lexicon (#684);
|
|
16
|
+
* core keeps the agnostic SPDX/CycloneDX writer, ./bom-writer.ts, which both it
|
|
17
|
+
* and `generate-sbom` share. ./component-bom.ts
|
|
18
|
+
* (#614) composes a component's leaf BOMs into one component-level BOM over
|
|
19
|
+
* that same writer, and ./reproducibility.ts (#614) records per-artifact
|
|
20
|
+
* reproducibility + provenance on each `BuildArchiveEntry`. `publish-image`
|
|
21
|
+
* (#610) also gained a best-effort OCI-referrer attach step (`oras attach`,
|
|
22
|
+
* also via `ProcessRunner`) for a supplied SBOM/component BOM — see ./publish.ts.
|
|
23
|
+
* `sign`/`attest-provenance` (#622, ./sign.ts) are real implementations over
|
|
24
|
+
* the same injectable `ProcessRunner`: keyless-by-default `cosign sign`/
|
|
25
|
+
* `cosign attest` for signature + SLSA provenance, reusing #614's
|
|
26
|
+
* `ProvenanceLink` material. `verify` (#622, ./verify.ts) is the matching
|
|
27
|
+
* deploy-time gate — `cosign verify`/`verify-attestation` against a
|
|
28
|
+
* configured identity policy, mirroring ../../lint/policy.ts's `policyGate`.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
export * from "./build-archive";
|
|
32
|
+
export * from "./build";
|
|
33
|
+
export * from "./bom-writer";
|
|
34
|
+
export * from "./sbom-generator";
|
|
35
|
+
export * from "./sbom";
|
|
36
|
+
export * from "./lockfile-sbom-generator";
|
|
37
|
+
export * from "./tool-sbom-generator";
|
|
38
|
+
export * from "./component-bom";
|
|
39
|
+
export * from "./reproducibility";
|
|
40
|
+
export * from "./sign";
|
|
41
|
+
export * from "./verify";
|
|
42
|
+
export * from "./vuln-scan";
|
|
43
|
+
export * from "./vex";
|
|
44
|
+
export * from "./license-policy";
|
|
45
|
+
export * from "./vuln-gate";
|
|
46
|
+
export * from "./wait-verify";
|
|
47
|
+
export * from "./shell";
|
|
48
|
+
export * from "./cloud-executor";
|
|
49
|
+
export * from "./process-runner";
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* License policy over an SBOM's declared licenses (#626). The SBOM already
|
|
3
|
+
* enumerates every component and its license; this evaluates those against an
|
|
4
|
+
* allow/deny list (e.g. block a copyleft license in a proprietary product).
|
|
5
|
+
*
|
|
6
|
+
* **Report-only by default.** License posture is context-dependent — GPL is a
|
|
7
|
+
* problem for a proprietary binary but fine for a GPL project — so the gate
|
|
8
|
+
* (./vuln-gate.ts) does NOT block on license violations unless a project opts
|
|
9
|
+
* in (`failOnLicense: true`). Extraction reads both SPDX
|
|
10
|
+
* (`packages[].licenseConcluded`/`licenseDeclared`) and CycloneDX
|
|
11
|
+
* (`components[].licenses[]`) without a spec dependency.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { SbomDocument } from "./sbom-generator";
|
|
15
|
+
|
|
16
|
+
/** A package/component and the license the SBOM declares for it. */
|
|
17
|
+
export interface PackageLicense {
|
|
18
|
+
package: string;
|
|
19
|
+
/** SPDX license expression/id as the SBOM reports it, or `"NOASSERTION"`/`""` when none is declared. */
|
|
20
|
+
license: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface LicensePolicy {
|
|
24
|
+
/** If set, ONLY these licenses are permitted — anything else is a violation. */
|
|
25
|
+
allow?: string[];
|
|
26
|
+
/** These licenses are violations (takes precedence; useful without an allowlist). */
|
|
27
|
+
deny?: string[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** One package whose license violates the policy. */
|
|
31
|
+
export interface LicenseViolation {
|
|
32
|
+
package: string;
|
|
33
|
+
license: string;
|
|
34
|
+
/** Which rule it broke. */
|
|
35
|
+
reason: "denied" | "not-allowed";
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ── extraction ───────────────────────────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
interface SpdxDoc {
|
|
41
|
+
packages?: Array<{ name?: string; licenseConcluded?: string; licenseDeclared?: string }>;
|
|
42
|
+
}
|
|
43
|
+
interface CycloneDxDoc {
|
|
44
|
+
components?: Array<{
|
|
45
|
+
name?: string;
|
|
46
|
+
licenses?: Array<{ license?: { id?: string; name?: string }; expression?: string }>;
|
|
47
|
+
}>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Extract each package's declared license from an SBOM, dispatching on media type (SPDX vs CycloneDX). Never throws on shape — a package with no license reads as `"NOASSERTION"`. */
|
|
51
|
+
export function extractLicenses(sbom: SbomDocument): PackageLicense[] {
|
|
52
|
+
const doc = JSON.parse(sbom.bytes) as SpdxDoc & CycloneDxDoc;
|
|
53
|
+
if (sbom.format === "cyclonedx" || Array.isArray(doc.components)) {
|
|
54
|
+
return (doc.components ?? []).map((c) => {
|
|
55
|
+
const first = c.licenses?.[0];
|
|
56
|
+
const license = first?.expression ?? first?.license?.id ?? first?.license?.name ?? "NOASSERTION";
|
|
57
|
+
return { package: c.name ?? "unknown", license };
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
return (doc.packages ?? []).map((p) => ({
|
|
61
|
+
package: p.name ?? "unknown",
|
|
62
|
+
license: p.licenseConcluded && p.licenseConcluded !== "NOASSERTION" ? p.licenseConcluded : (p.licenseDeclared ?? "NOASSERTION"),
|
|
63
|
+
}));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ── evaluation ───────────────────────────────────────────────────────────────
|
|
67
|
+
|
|
68
|
+
/** Case-insensitive membership, so `"mit"` matches `"MIT"`. */
|
|
69
|
+
function includesCi(list: string[], value: string): boolean {
|
|
70
|
+
const v = value.toLowerCase();
|
|
71
|
+
return list.some((x) => x.toLowerCase() === v);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Split an SPDX license expression into its component license atoms, so a
|
|
76
|
+
* deny/allow list matches a license hidden inside an expression like
|
|
77
|
+
* `"GPL-3.0 OR MIT"` or `"(Apache-2.0 AND MIT)"` — an exact-string compare on
|
|
78
|
+
* the whole expression would miss `GPL-3.0` and let it through. Conservative:
|
|
79
|
+
* we only need the *set* of licenses referenced, so we split on `OR`/`AND`,
|
|
80
|
+
* drop parentheses, and strip any `WITH <exception>` suffix. A single license
|
|
81
|
+
* id returns itself unchanged.
|
|
82
|
+
*/
|
|
83
|
+
export function licenseAtoms(expr: string): string[] {
|
|
84
|
+
return expr
|
|
85
|
+
.replace(/[()]/g, " ")
|
|
86
|
+
.split(/\s+(?:OR|AND)\s+/i)
|
|
87
|
+
.map((a) => a.split(/\s+WITH\s+/i)[0].trim())
|
|
88
|
+
.filter(Boolean);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Evaluate an SBOM's licenses against `policy`. A package violates if ANY atom
|
|
93
|
+
* of its (possibly compound) SPDX license expression is in `deny`, or (when
|
|
94
|
+
* `allow` is set) any atom is not in `allow`. This is conservative on `OR`
|
|
95
|
+
* expressions — `"GPL-3.0 OR MIT"` counts as a `GPL-3.0` deny hit even though a
|
|
96
|
+
* consumer could choose MIT — because a policy gate should surface the presence
|
|
97
|
+
* of a denied license, not silently rely on the consumer picking the permissive
|
|
98
|
+
* branch. Packages with no declared license (`NOASSERTION`/empty) are not
|
|
99
|
+
* flagged — an undeclared license is not asserted to be denied.
|
|
100
|
+
*/
|
|
101
|
+
export function evaluateLicensePolicy(sbom: SbomDocument, policy: LicensePolicy): LicenseViolation[] {
|
|
102
|
+
const out: LicenseViolation[] = [];
|
|
103
|
+
for (const { package: pkg, license } of extractLicenses(sbom)) {
|
|
104
|
+
if (!license || license === "NOASSERTION") continue;
|
|
105
|
+
const atoms = licenseAtoms(license);
|
|
106
|
+
if (policy.deny && atoms.some((a) => includesCi(policy.deny!, a))) {
|
|
107
|
+
out.push({ package: pkg, license, reason: "denied" });
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (policy.allow && policy.allow.length > 0 && atoms.some((a) => !includesCi(policy.allow!, a))) {
|
|
111
|
+
out.push({ package: pkg, license, reason: "not-allowed" });
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return out;
|
|
115
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests the hermetic lockfile/manifest-derived `SbomGenerator` (#613) against
|
|
3
|
+
* fixture lockfiles (./__fixtures__/package-lock.json,
|
|
4
|
+
* ./__fixtures__/pom.xml) — no network, no `syft`/`docker`/`mvn` invoked.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { readFileSync, writeFileSync, mkdirSync, mkdtempSync, rmSync } from "node:fs";
|
|
8
|
+
import { tmpdir } from "node:os";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { describe, expect, it, afterEach } from "vitest";
|
|
11
|
+
import {
|
|
12
|
+
parseNpmPackageLock,
|
|
13
|
+
parsePomXml,
|
|
14
|
+
createLockfileSbomGenerator,
|
|
15
|
+
sbomOutputPath,
|
|
16
|
+
} from "./lockfile-sbom-generator";
|
|
17
|
+
import { SbomGeneratorNotImplementedError } from "./sbom-generator";
|
|
18
|
+
|
|
19
|
+
const FIXTURES_DIR = join(import.meta.dirname, "__fixtures__");
|
|
20
|
+
const NPM_LOCK = readFileSync(join(FIXTURES_DIR, "package-lock.json"), "utf-8");
|
|
21
|
+
const POM_XML = readFileSync(join(FIXTURES_DIR, "pom.xml"), "utf-8");
|
|
22
|
+
|
|
23
|
+
describe("parseNpmPackageLock (#613)", () => {
|
|
24
|
+
it("extracts every non-root package with its resolved version", () => {
|
|
25
|
+
const packages = parseNpmPackageLock(NPM_LOCK);
|
|
26
|
+
const names = packages.map((p) => p.name).sort();
|
|
27
|
+
expect(names).toEqual(["accepts", "body-parser", "express", "left-pad", "mocha"]);
|
|
28
|
+
const express = packages.find((p) => p.name === "express")!;
|
|
29
|
+
expect(express.version).toBe("4.19.2");
|
|
30
|
+
expect(express.type).toBe("npm");
|
|
31
|
+
expect(express.purl).toBe("pkg:npm/express@4.19.2");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("records declared dependency edges for DEPENDS_ON relationships", () => {
|
|
35
|
+
const packages = parseNpmPackageLock(NPM_LOCK);
|
|
36
|
+
const express = packages.find((p) => p.name === "express")!;
|
|
37
|
+
expect(express.dependsOn).toEqual(expect.arrayContaining(["accepts", "body-parser"]));
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("does not include the root project itself as a dependency", () => {
|
|
41
|
+
const packages = parseNpmPackageLock(NPM_LOCK);
|
|
42
|
+
expect(packages.find((p) => p.name === "fixture-search-service")).toBeUndefined();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("falls back to the v1 nested dependencies tree when packages is absent", () => {
|
|
46
|
+
const v1Lock = JSON.stringify({
|
|
47
|
+
name: "fixture-v1",
|
|
48
|
+
version: "1.0.0",
|
|
49
|
+
dependencies: {
|
|
50
|
+
"left-pad": { version: "1.3.0", requires: {} },
|
|
51
|
+
chalk: { version: "4.1.2", requires: { "left-pad": "1.3.0" } },
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
const packages = parseNpmPackageLock(v1Lock);
|
|
55
|
+
expect(packages.map((p) => p.name).sort()).toEqual(["chalk", "left-pad"]);
|
|
56
|
+
expect(packages.find((p) => p.name === "chalk")!.dependsOn).toEqual(["left-pad"]);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe("parsePomXml (#613)", () => {
|
|
61
|
+
it("extracts every declared <dependency> from the top-level <dependencies> block", () => {
|
|
62
|
+
const packages = parsePomXml(POM_XML);
|
|
63
|
+
const names = packages.map((p) => p.name).sort();
|
|
64
|
+
expect(names).toEqual([
|
|
65
|
+
"com.fasterxml.jackson.core:jackson-databind",
|
|
66
|
+
"org.apache.spark:spark-core_2.12",
|
|
67
|
+
"org.junit.jupiter:junit-jupiter",
|
|
68
|
+
]);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("does not pull dependencies declared only in <dependencyManagement>", () => {
|
|
72
|
+
const packages = parsePomXml(POM_XML);
|
|
73
|
+
expect(packages.find((p) => p.name.includes("should-not-appear"))).toBeUndefined();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("derives a maven purl from groupId/artifactId/version", () => {
|
|
77
|
+
const packages = parsePomXml(POM_XML);
|
|
78
|
+
const spark = packages.find((p) => p.name === "org.apache.spark:spark-core_2.12")!;
|
|
79
|
+
expect(spark.version).toBe("3.5.1");
|
|
80
|
+
expect(spark.type).toBe("maven");
|
|
81
|
+
expect(spark.purl).toBe("pkg:maven/org.apache.spark/spark-core_2.12@3.5.1");
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("returns an empty list when there is no <dependencies> block", () => {
|
|
85
|
+
expect(parsePomXml("<project></project>")).toEqual([]);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe("createLockfileSbomGenerator (#613) — hermetic SbomGenerator backend", () => {
|
|
90
|
+
let tmpDir: string;
|
|
91
|
+
|
|
92
|
+
afterEach(() => {
|
|
93
|
+
if (tmpDir) rmSync(tmpDir, { recursive: true, force: true });
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
function withNpmFixture(): string {
|
|
97
|
+
tmpDir = mkdtempSync(join(tmpdir(), "chant-sbom-npm-"));
|
|
98
|
+
writeFileSync(join(tmpDir, "package-lock.json"), NPM_LOCK);
|
|
99
|
+
return tmpDir;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function withMavenFixture(): string {
|
|
103
|
+
tmpDir = mkdtempSync(join(tmpdir(), "chant-sbom-maven-"));
|
|
104
|
+
writeFileSync(join(tmpDir, "pom.xml"), POM_XML);
|
|
105
|
+
return tmpDir;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
it("forDir scans a directory's package-lock.json and emits a valid SPDX doc by default", async () => {
|
|
109
|
+
const dir = withNpmFixture();
|
|
110
|
+
const generator = createLockfileSbomGenerator();
|
|
111
|
+
const doc = await generator.forDir({ path: dir });
|
|
112
|
+
expect(doc.format).toBe("spdx");
|
|
113
|
+
expect(doc.mediaType).toBe("application/spdx+json");
|
|
114
|
+
expect(doc.packageCount).toBe(5);
|
|
115
|
+
expect(doc.generator).toBe("chant-lockfile-sbom/package-lock.json");
|
|
116
|
+
const parsed = JSON.parse(doc.bytes);
|
|
117
|
+
expect(parsed.spdxVersion).toBe("SPDX-2.3");
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("forDir scans a directory's pom.xml when no package-lock.json is present", async () => {
|
|
121
|
+
const dir = withMavenFixture();
|
|
122
|
+
const generator = createLockfileSbomGenerator();
|
|
123
|
+
const doc = await generator.forDir({ path: dir });
|
|
124
|
+
expect(doc.packageCount).toBe(3);
|
|
125
|
+
expect(doc.generator).toBe("chant-lockfile-sbom/pom.xml");
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("honors an explicit cyclonedx format request", async () => {
|
|
129
|
+
const dir = withNpmFixture();
|
|
130
|
+
const generator = createLockfileSbomGenerator();
|
|
131
|
+
const doc = await generator.forDir({ path: dir, format: "cyclonedx" });
|
|
132
|
+
expect(doc.format).toBe("cyclonedx");
|
|
133
|
+
expect(doc.mediaType).toBe("application/vnd.cyclonedx+json");
|
|
134
|
+
expect(JSON.parse(doc.bytes).bomFormat).toBe("CycloneDX");
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("writes the SBOM to disk as sbom.<format>.json alongside the scanned directory", async () => {
|
|
138
|
+
const dir = withNpmFixture();
|
|
139
|
+
const generator = createLockfileSbomGenerator();
|
|
140
|
+
await generator.forDir({ path: dir });
|
|
141
|
+
const written = readFileSync(sbomOutputPath(dir, "spdx"), "utf-8");
|
|
142
|
+
expect(JSON.parse(written).spdxVersion).toBe("SPDX-2.3");
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("writes to a distinct outDir when configured, instead of alongside the scanned directory", async () => {
|
|
146
|
+
const dir = withNpmFixture();
|
|
147
|
+
const outDir = join(dir, "out");
|
|
148
|
+
const generator = createLockfileSbomGenerator({ outDir });
|
|
149
|
+
await generator.forDir({ path: dir });
|
|
150
|
+
const written = readFileSync(sbomOutputPath(outDir, "spdx"), "utf-8");
|
|
151
|
+
expect(JSON.parse(written).spdxVersion).toBe("SPDX-2.3");
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("forJar/forZip scan the artifact's containing directory for a lockfile", async () => {
|
|
155
|
+
const dir = withNpmFixture();
|
|
156
|
+
const generator = createLockfileSbomGenerator();
|
|
157
|
+
const jarDoc = await generator.forJar({ jarPath: join(dir, "lib.jar"), digest: "sha256:jar1" });
|
|
158
|
+
expect(jarDoc.packageCount).toBe(5);
|
|
159
|
+
const zipDoc = await generator.forZip({ zipPath: join(dir, "fn.zip"), digest: "sha256:zip1" });
|
|
160
|
+
expect(zipDoc.packageCount).toBe(5);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("forImage throws SbomGeneratorNotImplementedError — an image's base layers are outside a lockfile's scope", async () => {
|
|
164
|
+
const generator = createLockfileSbomGenerator();
|
|
165
|
+
await expect(generator.forImage({ imagePath: "x.tar", digest: "sha256:1" })).rejects.toBeInstanceOf(
|
|
166
|
+
SbomGeneratorNotImplementedError,
|
|
167
|
+
);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("throws a clear error when no supported lockfile/manifest is found", async () => {
|
|
171
|
+
tmpDir = mkdtempSync(join(tmpdir(), "chant-sbom-empty-"));
|
|
172
|
+
const generator = createLockfileSbomGenerator();
|
|
173
|
+
await expect(generator.forDir({ path: tmpDir })).rejects.toThrow(/no supported lockfile/);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("two generations from identical (path + content) input produce the same digest/bytes (content-addressed)", async () => {
|
|
177
|
+
// Use two fixed, identically-named subdirectories under one root so
|
|
178
|
+
// `path` (which feeds both subjectName and, absent a digest, subjectId)
|
|
179
|
+
// is identical across both scans — isolating the assertion to "same
|
|
180
|
+
// input, same output" rather than incidentally varying by directory
|
|
181
|
+
// identity (which mkdtempSync's random suffix would otherwise do).
|
|
182
|
+
const root = mkdtempSync(join(tmpdir(), "chant-sbom-determinism-"));
|
|
183
|
+
tmpDir = root;
|
|
184
|
+
const dirA = join(root, "component");
|
|
185
|
+
const dirB = join(root, "component"); // same path, scanned twice in sequence
|
|
186
|
+
mkdirSync(dirA, { recursive: true });
|
|
187
|
+
writeFileSync(join(dirA, "package-lock.json"), NPM_LOCK);
|
|
188
|
+
|
|
189
|
+
const fixedNow = () => new Date("2026-01-01T00:00:00.000Z");
|
|
190
|
+
const docA = await createLockfileSbomGenerator({ now: fixedNow }).forDir({ path: dirA });
|
|
191
|
+
const docB = await createLockfileSbomGenerator({ now: fixedNow }).forDir({ path: dirB });
|
|
192
|
+
|
|
193
|
+
expect(docA.bytes).toBe(docB.bytes);
|
|
194
|
+
});
|
|
195
|
+
});
|