@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,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capability contract — the typed leaf behavior components compose from.
|
|
3
|
+
*
|
|
4
|
+
* A capability is a verb ("docker-build", "cfn-deploy", "wait-steady-state"),
|
|
5
|
+
* never a noun (never named after the component that happens to use it).
|
|
6
|
+
* Registered once by `kind`, dispatched by the orchestrator, composed by an
|
|
7
|
+
* unbounded number of components. See:
|
|
8
|
+
* https://intentius.io/chant/components/capabilities/
|
|
9
|
+
*
|
|
10
|
+
* This module defines the interface and registry only. Verb implementations
|
|
11
|
+
* live under `./verbs/*` as typed stubs — no cloud calls, no side effects.
|
|
12
|
+
* Cloud implementations are a later phase (see epic #551, issue #554).
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Ambient information a capability's `run`/`rollback` receives, independent of
|
|
17
|
+
* its typed `input`. Deliberately minimal for this phase: the orchestrator
|
|
18
|
+
* (interpret-mode driver, #556) will thread through the resolved environment,
|
|
19
|
+
* a logger, and step-output wiring (`@Phase.output`) once it exists. Kept as
|
|
20
|
+
* an extensible interface so a later phase can widen it without breaking the
|
|
21
|
+
* `Capability` signature.
|
|
22
|
+
*/
|
|
23
|
+
export interface DeployContext {
|
|
24
|
+
/** Target environment name (e.g. "dev", "staging", "prod"). */
|
|
25
|
+
env: string;
|
|
26
|
+
/** Component name this run belongs to, for logging/attribution. */
|
|
27
|
+
component: string;
|
|
28
|
+
/** Arbitrary environment config resolved by the orchestrator (registry URLs, cluster names, ...). */
|
|
29
|
+
vars?: Record<string, unknown>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* A typed leaf behavior. `kind` is the verb string components reference in
|
|
34
|
+
* their composition (`{ kind: "cfn-deploy", ... }`); `run` performs the
|
|
35
|
+
* operation; `rollback` is the optional paired compensation the orchestrator
|
|
36
|
+
* calls, in reverse step order, on saga unwind.
|
|
37
|
+
*
|
|
38
|
+
* Typed `In`/`Out` let a composition wire one step's output into the next
|
|
39
|
+
* step's input (`imageRef: "@Publish.digest"`) and let lint check the wiring
|
|
40
|
+
* before anything runs.
|
|
41
|
+
*/
|
|
42
|
+
export interface Capability<In = unknown, Out = unknown> {
|
|
43
|
+
/** The verb this capability implements — e.g. "docker-build", "cfn-deploy". Never a component name. */
|
|
44
|
+
readonly kind: string;
|
|
45
|
+
/** Perform the operation. */
|
|
46
|
+
run(ctx: DeployContext, input: In): Promise<Out>;
|
|
47
|
+
/** Optional paired compensation, invoked in reverse order on saga rollback. */
|
|
48
|
+
rollback?(ctx: DeployContext, input: In): Promise<void>;
|
|
49
|
+
/**
|
|
50
|
+
* How this verb relates to rollback, for the COMP003 composition check.
|
|
51
|
+
* Usually derivable and left unset: a capability with a `rollback` method is
|
|
52
|
+
* treated as `"native"`, everything else as `"none-by-design"` (build/publish/
|
|
53
|
+
* wait — nothing to compensate). Set it explicitly to `"needs-opt-out"` on a
|
|
54
|
+
* *mutating* verb that has no rollback and no safe undo (e.g. `s3-sync`,
|
|
55
|
+
* `run-migration`), so COMP003 requires the component to acknowledge the
|
|
56
|
+
* compensation gap. See ../lint/rules/comp/comp003-mutating-no-rollback.ts.
|
|
57
|
+
*/
|
|
58
|
+
readonly rollbackPolicy?: RollbackPolicy;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** A capability's relationship to rollback — see `Capability.rollbackPolicy`. */
|
|
62
|
+
export type RollbackPolicy = "native" | "none-by-design" | "needs-opt-out";
|
|
63
|
+
|
|
64
|
+
/** Extract a capability's `In` type. */
|
|
65
|
+
export type CapabilityInput<C> = C extends Capability<infer In, unknown> ? In : never;
|
|
66
|
+
/** Extract a capability's `Out` type. */
|
|
67
|
+
export type CapabilityOutput<C> = C extends Capability<unknown, infer Out> ? Out : never;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Thrown by a stub `run`/`rollback` — the verb is specified and typed but has
|
|
71
|
+
* no cloud implementation yet. Distinguishes "not implemented" from a runtime
|
|
72
|
+
* failure so callers (and tests) can assert on it specifically.
|
|
73
|
+
*/
|
|
74
|
+
export class CapabilityNotImplementedError extends Error {
|
|
75
|
+
constructor(public readonly kind: string) {
|
|
76
|
+
super(`capability "${kind}" is not implemented`);
|
|
77
|
+
this.name = "CapabilityNotImplementedError";
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Resolves capabilities by `kind`. One registry instance is the composition
|
|
83
|
+
* root the orchestrator dispatches through; `createCapabilityRegistry` builds
|
|
84
|
+
* one pre-seeded with the starter verb set (see `./verbs`).
|
|
85
|
+
*/
|
|
86
|
+
export class CapabilityRegistry {
|
|
87
|
+
private readonly capabilities = new Map<string, Capability<never, unknown>>();
|
|
88
|
+
|
|
89
|
+
/** Register a capability. Throws if `kind` is already registered — a capability is a verb, registered once. */
|
|
90
|
+
register<In, Out>(capability: Capability<In, Out>): this {
|
|
91
|
+
if (this.capabilities.has(capability.kind)) {
|
|
92
|
+
throw new Error(`capability "${capability.kind}" is already registered`);
|
|
93
|
+
}
|
|
94
|
+
this.capabilities.set(capability.kind, capability as Capability<never, unknown>);
|
|
95
|
+
return this;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Resolve a capability by `kind`. Throws a friendly error listing known kinds if absent. */
|
|
99
|
+
resolve(kind: string): Capability<never, unknown> {
|
|
100
|
+
const capability = this.capabilities.get(kind);
|
|
101
|
+
if (!capability) {
|
|
102
|
+
const known = [...this.capabilities.keys()].sort().join(", ");
|
|
103
|
+
throw new Error(`no capability registered for kind "${kind}" (known: ${known})`);
|
|
104
|
+
}
|
|
105
|
+
return capability;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** True if a capability is registered for `kind`. */
|
|
109
|
+
has(kind: string): boolean {
|
|
110
|
+
return this.capabilities.has(kind);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** All registered kinds, sorted. */
|
|
114
|
+
kinds(): string[] {
|
|
115
|
+
return [...this.capabilities.keys()].sort();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,579 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the CLI-facing component helpers (#560, epic #551): `listComponents`
|
|
3
|
+
* (backs `chant list --components`), `describeComponent` (backs
|
|
4
|
+
* `chant describe <name> --components`), `computeComponentGraph` (backs
|
|
5
|
+
* `chant graph --components`), and `runComponents` (backs `chant run
|
|
6
|
+
* --components <name|all>`, #585). Mirrors `../cli/commands/list.test.ts`'s
|
|
7
|
+
* style of exercising the pure command function directly against a temp dir,
|
|
8
|
+
* rather than going through the CLI arg-parsing/handler dispatch layer.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { describe, test, expect, beforeEach, afterEach } from "vitest";
|
|
12
|
+
import { mkdir, writeFile, rm } from "node:fs/promises";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { tmpdir } from "node:os";
|
|
15
|
+
import Ajv2020 from "ajv/dist/2020";
|
|
16
|
+
import componentSchema from "./component.schema.json";
|
|
17
|
+
import { listComponents, describeComponent, computeComponentGraph, runComponents, findComponentGate } from "./cli-support";
|
|
18
|
+
import { CapabilityRegistry, type DeployContext } from "./capability";
|
|
19
|
+
import type { DriverComponent } from "./driver";
|
|
20
|
+
|
|
21
|
+
describe("listComponents", () => {
|
|
22
|
+
let testDir: string;
|
|
23
|
+
|
|
24
|
+
beforeEach(async () => {
|
|
25
|
+
testDir = join(tmpdir(), `chant-list-components-test-${Date.now()}-${Math.random()}`);
|
|
26
|
+
await mkdir(testDir, { recursive: true });
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
afterEach(async () => {
|
|
30
|
+
await rm(testDir, { recursive: true, force: true });
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("returns an empty, successful result for a directory with no components", async () => {
|
|
34
|
+
const result = await listComponents(testDir);
|
|
35
|
+
expect(result.success).toBe(true);
|
|
36
|
+
expect(result.components).toEqual([]);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("lists a discovered component with archetype inferred and phases in order", async () => {
|
|
40
|
+
await writeFile(
|
|
41
|
+
join(testDir, "orders.component.ts"),
|
|
42
|
+
`
|
|
43
|
+
export const ordersTable = {
|
|
44
|
+
name: "orders-table",
|
|
45
|
+
dependsOn: [],
|
|
46
|
+
deploy: [
|
|
47
|
+
{ phase: "Apply", steps: [{ kind: "cfn-deploy" }] },
|
|
48
|
+
{ phase: "Verify", steps: [{ kind: "wait-for-stack" }] },
|
|
49
|
+
],
|
|
50
|
+
};
|
|
51
|
+
`,
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const result = await listComponents(testDir);
|
|
55
|
+
|
|
56
|
+
expect(result.success).toBe(true);
|
|
57
|
+
expect(result.components).toHaveLength(1);
|
|
58
|
+
expect(result.components[0]).toMatchObject({
|
|
59
|
+
name: "orders-table",
|
|
60
|
+
archetype: "infra",
|
|
61
|
+
dependsOn: [],
|
|
62
|
+
hasBuild: false,
|
|
63
|
+
phases: ["Apply", "Verify"],
|
|
64
|
+
});
|
|
65
|
+
expect(result.components[0]!.filePath).toMatch(/orders\.component\.ts$/);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("sorts components by name", async () => {
|
|
69
|
+
await writeFile(
|
|
70
|
+
join(testDir, "z.component.ts"),
|
|
71
|
+
`export const zeta = { name: "zeta", dependsOn: [], deploy: [{ phase: "Apply", steps: [{ kind: "shell" }] }] };`,
|
|
72
|
+
);
|
|
73
|
+
await writeFile(
|
|
74
|
+
join(testDir, "a.component.ts"),
|
|
75
|
+
`export const alpha = { name: "alpha", dependsOn: [], deploy: [{ phase: "Apply", steps: [{ kind: "shell" }] }] };`,
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const result = await listComponents(testDir);
|
|
79
|
+
expect(result.components.map((c) => c.name)).toEqual(["alpha", "zeta"]);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("reports hasBuild: true and the service archetype for a build + apply component", async () => {
|
|
83
|
+
await writeFile(
|
|
84
|
+
join(testDir, "svc.component.ts"),
|
|
85
|
+
`
|
|
86
|
+
export const svc = {
|
|
87
|
+
name: "svc",
|
|
88
|
+
dependsOn: [],
|
|
89
|
+
build: { kind: "docker-build" },
|
|
90
|
+
deploy: [
|
|
91
|
+
{ phase: "Publish", steps: [{ kind: "publish-image" }] },
|
|
92
|
+
{ phase: "Apply", steps: [{ kind: "ecs-update-service" }] },
|
|
93
|
+
],
|
|
94
|
+
};
|
|
95
|
+
`,
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
const result = await listComponents(testDir);
|
|
99
|
+
expect(result.components[0]).toMatchObject({ hasBuild: true, archetype: "service" });
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("fails with discovery errors surfaced when a component file has a syntax error", async () => {
|
|
103
|
+
await writeFile(join(testDir, "bad.component.ts"), `export const bad = { name: "bad"`);
|
|
104
|
+
|
|
105
|
+
const result = await listComponents(testDir);
|
|
106
|
+
expect(result.success).toBe(false);
|
|
107
|
+
expect(result.errors.length).toBeGreaterThan(0);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe("describeComponent", () => {
|
|
112
|
+
let testDir: string;
|
|
113
|
+
|
|
114
|
+
beforeEach(async () => {
|
|
115
|
+
testDir = join(tmpdir(), `chant-describe-components-test-${Date.now()}-${Math.random()}`);
|
|
116
|
+
await mkdir(testDir, { recursive: true });
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
afterEach(async () => {
|
|
120
|
+
await rm(testDir, { recursive: true, force: true });
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("describes a component by its schema name, not its export name", async () => {
|
|
124
|
+
await writeFile(
|
|
125
|
+
join(testDir, "svc.component.ts"),
|
|
126
|
+
`
|
|
127
|
+
export const exportedAsThis = {
|
|
128
|
+
name: "search-service",
|
|
129
|
+
dependsOn: ["shared-alb"],
|
|
130
|
+
build: { kind: "docker-build" },
|
|
131
|
+
deploy: [{ phase: "Apply", steps: [{ kind: "cfn-deploy" }] }],
|
|
132
|
+
};
|
|
133
|
+
`,
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
const result = await describeComponent(testDir, "search-service");
|
|
137
|
+
|
|
138
|
+
expect(result.success).toBe(true);
|
|
139
|
+
expect(result.described?.name).toBe("search-service");
|
|
140
|
+
expect(result.described?.filePath).toMatch(/svc\.component\.ts$/);
|
|
141
|
+
expect(result.described?.json).toMatchObject({
|
|
142
|
+
name: "search-service",
|
|
143
|
+
dependsOn: ["shared-alb"],
|
|
144
|
+
archetype: "service",
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("fails with a helpful message (listing known components) when the name is not found", async () => {
|
|
149
|
+
await writeFile(
|
|
150
|
+
join(testDir, "svc.component.ts"),
|
|
151
|
+
`export const svc = { name: "known-one", dependsOn: [], deploy: [{ phase: "Apply", steps: [{ kind: "shell" }] }] };`,
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
const result = await describeComponent(testDir, "nonexistent");
|
|
155
|
+
|
|
156
|
+
expect(result.success).toBe(false);
|
|
157
|
+
expect(result.described).toBeUndefined();
|
|
158
|
+
expect(result.output).toContain("nonexistent");
|
|
159
|
+
expect(result.output).toContain("known-one");
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test("the JSON projection validates against component.schema.json", async () => {
|
|
163
|
+
await writeFile(
|
|
164
|
+
join(testDir, "svc.component.ts"),
|
|
165
|
+
`
|
|
166
|
+
export const svc = {
|
|
167
|
+
name: "svc",
|
|
168
|
+
dependsOn: [],
|
|
169
|
+
deploy: [{ phase: "Apply", steps: [{ kind: "cfn-deploy", template: "t.json" }] }],
|
|
170
|
+
};
|
|
171
|
+
`,
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
const result = await describeComponent(testDir, "svc");
|
|
175
|
+
expect(result.success).toBe(true);
|
|
176
|
+
|
|
177
|
+
const ajv = new Ajv2020({ strict: true, allErrors: true });
|
|
178
|
+
const validate = ajv.compile(componentSchema);
|
|
179
|
+
expect(validate(result.described!.json)).toBe(true);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
describe("computeComponentGraph", () => {
|
|
184
|
+
let testDir: string;
|
|
185
|
+
|
|
186
|
+
beforeEach(async () => {
|
|
187
|
+
testDir = join(tmpdir(), `chant-graph-components-test-${Date.now()}-${Math.random()}`);
|
|
188
|
+
await mkdir(testDir, { recursive: true });
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
afterEach(async () => {
|
|
192
|
+
await rm(testDir, { recursive: true, force: true });
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test("returns empty order/waves/edges for no components", async () => {
|
|
196
|
+
const result = await computeComponentGraph(testDir);
|
|
197
|
+
expect(result.success).toBe(true);
|
|
198
|
+
expect(result.order).toEqual([]);
|
|
199
|
+
expect(result.waves).toEqual([]);
|
|
200
|
+
expect(result.edges).toEqual([]);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test("orders a consumer after its producer, with a consumer → producer edge", async () => {
|
|
204
|
+
await writeFile(
|
|
205
|
+
join(testDir, "alb.component.ts"),
|
|
206
|
+
`export const alb = { name: "shared-alb", dependsOn: [], deploy: [{ phase: "Apply", steps: [{ kind: "cfn-deploy" }] }] };`,
|
|
207
|
+
);
|
|
208
|
+
await writeFile(
|
|
209
|
+
join(testDir, "svc.component.ts"),
|
|
210
|
+
`export const svc = { name: "search-service", dependsOn: ["shared-alb"], deploy: [{ phase: "Apply", steps: [{ kind: "cfn-deploy" }] }] };`,
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
const result = await computeComponentGraph(testDir);
|
|
214
|
+
|
|
215
|
+
expect(result.success).toBe(true);
|
|
216
|
+
expect(result.order.indexOf("shared-alb")).toBeLessThan(result.order.indexOf("search-service"));
|
|
217
|
+
expect(result.edges).toContainEqual({ from: "search-service", to: "shared-alb" });
|
|
218
|
+
// Independent components share a wave; a dependent is in a strictly later wave.
|
|
219
|
+
const albWave = result.waves.findIndex((w) => w.includes("shared-alb"));
|
|
220
|
+
const svcWave = result.waves.findIndex((w) => w.includes("search-service"));
|
|
221
|
+
expect(svcWave).toBeGreaterThan(albWave);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test("fails with a clear error on a dependency cycle", async () => {
|
|
225
|
+
await writeFile(
|
|
226
|
+
join(testDir, "a.component.ts"),
|
|
227
|
+
`export const a = { name: "a", dependsOn: ["b"], deploy: [{ phase: "Apply", steps: [{ kind: "shell" }] }] };`,
|
|
228
|
+
);
|
|
229
|
+
await writeFile(
|
|
230
|
+
join(testDir, "b.component.ts"),
|
|
231
|
+
`export const b = { name: "b", dependsOn: ["a"], deploy: [{ phase: "Apply", steps: [{ kind: "shell" }] }] };`,
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
const result = await computeComponentGraph(testDir);
|
|
235
|
+
expect(result.success).toBe(false);
|
|
236
|
+
expect(result.error).toMatch(/cycle/i);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
test("fails with a clear error when dependsOn names an undiscovered component", async () => {
|
|
240
|
+
await writeFile(
|
|
241
|
+
join(testDir, "a.component.ts"),
|
|
242
|
+
`export const a = { name: "a", dependsOn: ["ghost"], deploy: [{ phase: "Apply", steps: [{ kind: "shell" }] }] };`,
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
const result = await computeComponentGraph(testDir);
|
|
246
|
+
expect(result.success).toBe(false);
|
|
247
|
+
expect(result.error).toMatch(/unknown component|ghost/i);
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// ── runComponents (#585) ─────────────────────────────────────────────────────
|
|
252
|
+
|
|
253
|
+
/** A fake capability that records every call and returns a canned output — mirrors ../driver.test.ts's `fakeCapability`. */
|
|
254
|
+
function fakeCapability(kind: string, opts?: { failRun?: boolean; output?: unknown }) {
|
|
255
|
+
const calls: { ctx: DeployContext; input: unknown }[] = [];
|
|
256
|
+
return {
|
|
257
|
+
kind,
|
|
258
|
+
async run(ctx: DeployContext, input: unknown) {
|
|
259
|
+
calls.push({ ctx, input });
|
|
260
|
+
if (opts?.failRun) throw new Error(`${kind} failed`);
|
|
261
|
+
return opts?.output ?? { ok: true };
|
|
262
|
+
},
|
|
263
|
+
calls,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/** A registry with one fake "shell"-like capability, `deploy-thing`, standing in for a real verb so tests don't hit the still-stubbed starter set. */
|
|
268
|
+
function fakeRegistry(opts?: { failRun?: boolean }): CapabilityRegistry {
|
|
269
|
+
const registry = new CapabilityRegistry();
|
|
270
|
+
registry.register(fakeCapability("deploy-thing", opts));
|
|
271
|
+
return registry;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
describe("findComponentGate", () => {
|
|
275
|
+
test("returns undefined for a component with no gate", () => {
|
|
276
|
+
const component: DriverComponent = {
|
|
277
|
+
name: "svc",
|
|
278
|
+
dependsOn: [],
|
|
279
|
+
deploy: [{ phase: "Apply", steps: [{ kind: "deploy-thing" }] }],
|
|
280
|
+
};
|
|
281
|
+
expect(findComponentGate(component)).toBeUndefined();
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
test("finds a gate in a top-level deploy phase", () => {
|
|
285
|
+
const component: DriverComponent = {
|
|
286
|
+
name: "svc",
|
|
287
|
+
dependsOn: [],
|
|
288
|
+
deploy: [{ phase: "Approve", steps: [{ kind: "gate", signalName: "release-approval" }] }],
|
|
289
|
+
};
|
|
290
|
+
expect(findComponentGate(component)).toEqual({ kind: "gate", signalName: "release-approval" });
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
test("finds a gate nested inside a fan-out phase", () => {
|
|
294
|
+
const component: DriverComponent = {
|
|
295
|
+
name: "fleet",
|
|
296
|
+
dependsOn: [],
|
|
297
|
+
deploy: [
|
|
298
|
+
{
|
|
299
|
+
phase: "Rollout",
|
|
300
|
+
steps: [
|
|
301
|
+
{
|
|
302
|
+
phase: "instance-2",
|
|
303
|
+
steps: [{ kind: "gate", signalName: "instance-2-approval" }],
|
|
304
|
+
},
|
|
305
|
+
],
|
|
306
|
+
},
|
|
307
|
+
],
|
|
308
|
+
};
|
|
309
|
+
expect(findComponentGate(component)?.signalName).toBe("instance-2-approval");
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
test("finds a gate in a component's rollback phases", () => {
|
|
313
|
+
const component: DriverComponent = {
|
|
314
|
+
name: "svc",
|
|
315
|
+
dependsOn: [],
|
|
316
|
+
deploy: [{ phase: "Apply", steps: [{ kind: "deploy-thing" }] }],
|
|
317
|
+
rollback: [{ phase: "Rollback", steps: [{ kind: "gate", signalName: "rollback-approval" }] }],
|
|
318
|
+
};
|
|
319
|
+
expect(findComponentGate(component)?.signalName).toBe("rollback-approval");
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
describe("runComponents", () => {
|
|
324
|
+
let testDir: string;
|
|
325
|
+
|
|
326
|
+
beforeEach(async () => {
|
|
327
|
+
testDir = join(tmpdir(), `chant-run-components-test-${Date.now()}-${Math.random()}`);
|
|
328
|
+
await mkdir(testDir, { recursive: true });
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
afterEach(async () => {
|
|
332
|
+
await rm(testDir, { recursive: true, force: true });
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
test("runs a single named component through the driver", async () => {
|
|
336
|
+
await writeFile(
|
|
337
|
+
join(testDir, "svc.component.ts"),
|
|
338
|
+
`export const svc = { name: "svc", dependsOn: [], deploy: [{ phase: "Apply", steps: [{ kind: "deploy-thing" }] }] };`,
|
|
339
|
+
);
|
|
340
|
+
|
|
341
|
+
const registry = fakeRegistry();
|
|
342
|
+
const result = await runComponents(testDir, "svc", { env: "staging", registry });
|
|
343
|
+
|
|
344
|
+
expect(result.success).toBe(true);
|
|
345
|
+
expect(result.selected).toEqual(["svc"]);
|
|
346
|
+
expect(result.run?.ok).toBe(true);
|
|
347
|
+
expect(result.run?.results).toHaveLength(1);
|
|
348
|
+
expect(result.run?.results[0]).toMatchObject({ component: "svc", ok: true });
|
|
349
|
+
expect(result.run?.results[0].records[0]).toMatchObject({ component: "svc", kind: "deploy-thing", status: "ok" });
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
test("threads --env into DeployContext", async () => {
|
|
353
|
+
await writeFile(
|
|
354
|
+
join(testDir, "svc.component.ts"),
|
|
355
|
+
`export const svc = { name: "svc", dependsOn: [], deploy: [{ phase: "Apply", steps: [{ kind: "deploy-thing" }] }] };`,
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
const capability = fakeCapability("deploy-thing");
|
|
359
|
+
const registry = new CapabilityRegistry();
|
|
360
|
+
registry.register(capability);
|
|
361
|
+
|
|
362
|
+
await runComponents(testDir, "svc", { env: "prod", registry });
|
|
363
|
+
|
|
364
|
+
expect(capability.calls).toHaveLength(1);
|
|
365
|
+
expect(capability.calls[0].ctx.env).toBe("prod");
|
|
366
|
+
expect(capability.calls[0].ctx.component).toBe("svc");
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
test("defaults --env to \"local\" when omitted", async () => {
|
|
370
|
+
await writeFile(
|
|
371
|
+
join(testDir, "svc.component.ts"),
|
|
372
|
+
`export const svc = { name: "svc", dependsOn: [], deploy: [{ phase: "Apply", steps: [{ kind: "deploy-thing" }] }] };`,
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
const capability = fakeCapability("deploy-thing");
|
|
376
|
+
const registry = new CapabilityRegistry();
|
|
377
|
+
registry.register(capability);
|
|
378
|
+
|
|
379
|
+
await runComponents(testDir, "svc", { registry });
|
|
380
|
+
|
|
381
|
+
expect(capability.calls[0].ctx.env).toBe("local");
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
test("runs a component whose dependsOn names infra outside the discovered set (no UnknownDependencyError)", async () => {
|
|
385
|
+
await writeFile(
|
|
386
|
+
join(testDir, "svc.component.ts"),
|
|
387
|
+
`export const svc = { name: "search-service", dependsOn: ["shared-alb"], deploy: [{ phase: "Apply", steps: [{ kind: "deploy-thing" }] }] };`,
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
const registry = fakeRegistry();
|
|
391
|
+
const result = await runComponents(testDir, "search-service", { registry });
|
|
392
|
+
|
|
393
|
+
expect(result.success).toBe(true);
|
|
394
|
+
expect(result.run?.ok).toBe(true);
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
test("all: dispatches every discovered component in wave order", async () => {
|
|
398
|
+
await writeFile(
|
|
399
|
+
join(testDir, "alb.component.ts"),
|
|
400
|
+
`export const alb = { name: "shared-alb", dependsOn: [], deploy: [{ phase: "Apply", steps: [{ kind: "deploy-thing" }] }] };`,
|
|
401
|
+
);
|
|
402
|
+
await writeFile(
|
|
403
|
+
join(testDir, "svc.component.ts"),
|
|
404
|
+
`export const svc = { name: "search-service", dependsOn: ["shared-alb"], deploy: [{ phase: "Apply", steps: [{ kind: "deploy-thing" }] }] };`,
|
|
405
|
+
);
|
|
406
|
+
|
|
407
|
+
const registry = fakeRegistry();
|
|
408
|
+
const result = await runComponents(testDir, "all", { registry });
|
|
409
|
+
|
|
410
|
+
expect(result.success).toBe(true);
|
|
411
|
+
expect(result.selected.sort()).toEqual(["search-service", "shared-alb"]);
|
|
412
|
+
expect(result.run?.waves).toEqual([["shared-alb"], ["search-service"]]);
|
|
413
|
+
expect(result.run?.order).toEqual(["shared-alb", "search-service"]);
|
|
414
|
+
expect(result.run?.results.every((r) => r.ok)).toBe(true);
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
test("all: fails clearly on a dependency cycle (no exception thrown)", async () => {
|
|
418
|
+
await writeFile(
|
|
419
|
+
join(testDir, "a.component.ts"),
|
|
420
|
+
`export const a = { name: "a", dependsOn: ["b"], deploy: [{ phase: "Apply", steps: [{ kind: "deploy-thing" }] }] };`,
|
|
421
|
+
);
|
|
422
|
+
await writeFile(
|
|
423
|
+
join(testDir, "b.component.ts"),
|
|
424
|
+
`export const b = { name: "b", dependsOn: ["a"], deploy: [{ phase: "Apply", steps: [{ kind: "deploy-thing" }] }] };`,
|
|
425
|
+
);
|
|
426
|
+
|
|
427
|
+
const registry = fakeRegistry();
|
|
428
|
+
const result = await runComponents(testDir, "all", { registry });
|
|
429
|
+
|
|
430
|
+
expect(result.success).toBe(false);
|
|
431
|
+
expect(result.error).toMatch(/cycle/i);
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
test("unknown component name → clear error listing known components", async () => {
|
|
435
|
+
await writeFile(
|
|
436
|
+
join(testDir, "svc.component.ts"),
|
|
437
|
+
`export const svc = { name: "svc", dependsOn: [], deploy: [{ phase: "Apply", steps: [{ kind: "deploy-thing" }] }] };`,
|
|
438
|
+
);
|
|
439
|
+
|
|
440
|
+
const registry = fakeRegistry();
|
|
441
|
+
const result = await runComponents(testDir, "missing", { registry });
|
|
442
|
+
|
|
443
|
+
expect(result.success).toBe(false);
|
|
444
|
+
expect(result.error).toContain('Component "missing" not found');
|
|
445
|
+
expect(result.error).toContain("svc");
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
test("a gate in the selected component is rejected before any step runs", async () => {
|
|
449
|
+
await writeFile(
|
|
450
|
+
join(testDir, "svc.component.ts"),
|
|
451
|
+
`export const svc = { name: "svc", dependsOn: [], deploy: [
|
|
452
|
+
{ phase: "Approve", steps: [{ kind: "gate", signalName: "release-approval" }] },
|
|
453
|
+
{ phase: "Apply", steps: [{ kind: "deploy-thing" }] },
|
|
454
|
+
] };`,
|
|
455
|
+
);
|
|
456
|
+
|
|
457
|
+
const capability = fakeCapability("deploy-thing");
|
|
458
|
+
const registry = new CapabilityRegistry();
|
|
459
|
+
registry.register(capability);
|
|
460
|
+
|
|
461
|
+
const result = await runComponents(testDir, "svc", { registry });
|
|
462
|
+
|
|
463
|
+
expect(result.success).toBe(false);
|
|
464
|
+
expect(result.gateUnsupported).toEqual({ component: "svc", signalName: "release-approval" });
|
|
465
|
+
// Pre-flighted: the deploy-thing step after the gate never ran.
|
|
466
|
+
expect(capability.calls).toHaveLength(0);
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
test("a failing step surfaces a failed component result, not a thrown exception", async () => {
|
|
470
|
+
await writeFile(
|
|
471
|
+
join(testDir, "svc.component.ts"),
|
|
472
|
+
`export const svc = { name: "svc", dependsOn: [], deploy: [{ phase: "Apply", steps: [{ kind: "deploy-thing" }] }] };`,
|
|
473
|
+
);
|
|
474
|
+
|
|
475
|
+
const registry = fakeRegistry({ failRun: true });
|
|
476
|
+
const result = await runComponents(testDir, "svc", { registry });
|
|
477
|
+
|
|
478
|
+
expect(result.success).toBe(false);
|
|
479
|
+
expect(result.run?.ok).toBe(false);
|
|
480
|
+
expect(result.run?.failedComponent).toBe("svc");
|
|
481
|
+
expect(result.run?.results[0].records[0]).toMatchObject({ status: "fail" });
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
test("discovery error surfaces as a failure, not an exception", async () => {
|
|
485
|
+
await writeFile(join(testDir, "broken.component.ts"), `export const broken = notDefined;`);
|
|
486
|
+
|
|
487
|
+
const result = await runComponents(testDir, "all");
|
|
488
|
+
|
|
489
|
+
expect(result.success).toBe(false);
|
|
490
|
+
expect(result.error).toBeDefined();
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
// ── config-defaults wiring (#629) ──────────────────────────────────────────
|
|
494
|
+
|
|
495
|
+
test("threads chant.config.ts's signing defaults into a verify step's policy before dispatch", async () => {
|
|
496
|
+
await writeFile(
|
|
497
|
+
join(testDir, "svc.component.ts"),
|
|
498
|
+
`export const svc = { name: "svc", dependsOn: [], deploy: [{ phase: "Verify", steps: [{ kind: "verify", imageRef: "repo@sha256:abc", policy: {} }] }] };`,
|
|
499
|
+
);
|
|
500
|
+
|
|
501
|
+
const capability = fakeCapability("verify");
|
|
502
|
+
const registry = new CapabilityRegistry();
|
|
503
|
+
registry.register(capability);
|
|
504
|
+
|
|
505
|
+
const result = await runComponents(testDir, "svc", {
|
|
506
|
+
registry,
|
|
507
|
+
config: {
|
|
508
|
+
signing: {
|
|
509
|
+
oidcIssuer: "https://token.actions.githubusercontent.com",
|
|
510
|
+
identity: "https://github.com/acme/repo/.github/workflows/release.yml@refs/heads/main",
|
|
511
|
+
},
|
|
512
|
+
},
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
expect(result.success).toBe(true);
|
|
516
|
+
expect(capability.calls[0]?.input).toMatchObject({
|
|
517
|
+
policy: {
|
|
518
|
+
expectedIssuer: "https://token.actions.githubusercontent.com",
|
|
519
|
+
expectedIdentity: "https://github.com/acme/repo/.github/workflows/release.yml@refs/heads/main",
|
|
520
|
+
},
|
|
521
|
+
});
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
test("a per-step verify policy value overrides the configured default", async () => {
|
|
525
|
+
await writeFile(
|
|
526
|
+
join(testDir, "svc.component.ts"),
|
|
527
|
+
`export const svc = { name: "svc", dependsOn: [], deploy: [{ phase: "Verify", steps: [{ kind: "verify", imageRef: "repo@sha256:abc", policy: { expectedIssuer: "https://per-step-issuer.example.com" } }] }] };`,
|
|
528
|
+
);
|
|
529
|
+
|
|
530
|
+
const capability = fakeCapability("verify");
|
|
531
|
+
const registry = new CapabilityRegistry();
|
|
532
|
+
registry.register(capability);
|
|
533
|
+
|
|
534
|
+
await runComponents(testDir, "svc", {
|
|
535
|
+
registry,
|
|
536
|
+
config: { signing: { oidcIssuer: "https://token.actions.githubusercontent.com" } },
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
expect(capability.calls[0]?.input).toMatchObject({
|
|
540
|
+
policy: { expectedIssuer: "https://per-step-issuer.example.com" },
|
|
541
|
+
});
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
test("a vulnPolicy in config changes a vuln-gate step's effective policy before dispatch", async () => {
|
|
545
|
+
await writeFile(
|
|
546
|
+
join(testDir, "svc.component.ts"),
|
|
547
|
+
`export const svc = { name: "svc", dependsOn: [], deploy: [{ phase: "Gate", steps: [{ kind: "vuln-gate", sbom: { bytes: "", mediaType: "application/json", packageCount: 0, generator: "x", format: "spdx" } }] }] };`,
|
|
548
|
+
);
|
|
549
|
+
|
|
550
|
+
const capability = fakeCapability("vuln-gate", { output: { passed: true, warnings: [], suppressed: [], licenseFindings: [] } });
|
|
551
|
+
const registry = new CapabilityRegistry();
|
|
552
|
+
registry.register(capability);
|
|
553
|
+
|
|
554
|
+
await runComponents(testDir, "svc", {
|
|
555
|
+
registry,
|
|
556
|
+
config: { vulnPolicy: { failSeverity: "high" } },
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
expect(capability.calls[0]?.input).toMatchObject({ policy: { failSeverity: "high" } });
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
test("generate-sbom picks up sbom.format from config", async () => {
|
|
563
|
+
await writeFile(
|
|
564
|
+
join(testDir, "svc.component.ts"),
|
|
565
|
+
`export const svc = { name: "svc", dependsOn: [], deploy: [{ phase: "Build", steps: [{ kind: "generate-sbom", artifactType: "image", path: "img" }] }] };`,
|
|
566
|
+
);
|
|
567
|
+
|
|
568
|
+
const capability = fakeCapability("generate-sbom");
|
|
569
|
+
const registry = new CapabilityRegistry();
|
|
570
|
+
registry.register(capability);
|
|
571
|
+
|
|
572
|
+
await runComponents(testDir, "svc", {
|
|
573
|
+
registry,
|
|
574
|
+
config: { sbom: { format: "cyclonedx" } },
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
expect(capability.calls[0]?.input).toMatchObject({ format: "cyclonedx" });
|
|
578
|
+
});
|
|
579
|
+
});
|