@intentius/chant 0.124.31 → 0.124.33
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/bin/chant +7 -2
- package/dist/ci-jobs.d.ts +4 -0
- package/dist/ci-jobs.d.ts.map +1 -1
- package/dist/ci-jobs.js +9 -2
- package/dist/cli/commands/check-lexicon-plugin.d.ts +7 -4
- package/dist/cli/commands/check-lexicon-plugin.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon-plugin.js +10 -7
- package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon.js +18 -12
- package/dist/cli/commands/import.d.ts +10 -0
- package/dist/cli/commands/import.d.ts.map +1 -1
- package/dist/cli/commands/import.js +37 -0
- package/dist/cli/commands/init-lexicon/templates/project.d.ts.map +1 -1
- package/dist/cli/commands/init-lexicon/templates/project.js +4 -3
- package/dist/cli/commands/onboard.d.ts.map +1 -1
- package/dist/cli/commands/onboard.js +8 -4
- package/dist/cli/commands/update.d.ts.map +1 -1
- package/dist/cli/commands/update.js +2 -1
- package/dist/cli/dependencies.d.ts +18 -0
- package/dist/cli/dependencies.d.ts.map +1 -0
- package/dist/cli/dependencies.js +120 -0
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/build.js +38 -4
- package/dist/cli/handlers/fan-out.d.ts.map +1 -1
- package/dist/cli/handlers/fan-out.js +12 -4
- package/dist/cli/handlers/graph.d.ts.map +1 -1
- package/dist/cli/handlers/graph.js +16 -6
- package/dist/cli/handlers/lifecycle-health.d.ts +16 -0
- package/dist/cli/handlers/lifecycle-health.d.ts.map +1 -0
- package/dist/cli/handlers/lifecycle-health.js +150 -0
- package/dist/cli/handlers/lifecycle.d.ts +26 -0
- package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
- package/dist/cli/handlers/lifecycle.js +1 -1
- package/dist/cli/handlers/lint.d.ts.map +1 -1
- package/dist/cli/handlers/lint.js +6 -0
- package/dist/cli/handlers/operator.d.ts +2 -2
- package/dist/cli/handlers/operator.js +3 -3
- package/dist/cli/handlers/pr.d.ts +16 -0
- package/dist/cli/handlers/pr.d.ts.map +1 -1
- package/dist/cli/handlers/pr.js +610 -32
- package/dist/cli/handlers/promote.d.ts.map +1 -1
- package/dist/cli/handlers/promote.js +19 -2
- package/dist/cli/handlers/release.d.ts +16 -1
- package/dist/cli/handlers/release.d.ts.map +1 -1
- package/dist/cli/handlers/release.js +83 -11
- package/dist/cli/handlers/run.d.ts +13 -1
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/handlers/run.js +38 -15
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +121 -33
- package/dist/cli/parse-args.d.ts.map +1 -1
- package/dist/cli/parse-args.js +37 -0
- package/dist/cli/registry.d.ts +29 -6
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/cli/registry.js +6 -2
- package/dist/codegen/generate-post-synth-barrel.js +2 -2
- package/dist/codegen/lexicon-regen.d.ts +4 -0
- package/dist/codegen/lexicon-regen.d.ts.map +1 -1
- package/dist/codegen/lexicon-regen.js +1 -1
- package/dist/codegen/pinned-upgrade.js +2 -2
- package/dist/components/capability.d.ts +44 -3
- package/dist/components/capability.d.ts.map +1 -1
- package/dist/components/capability.js +21 -0
- package/dist/components/changed-files.d.ts +7 -1
- package/dist/components/changed-files.d.ts.map +1 -1
- package/dist/components/changed-files.js +9 -1
- package/dist/components/deploy-inputs.d.ts +91 -0
- package/dist/components/deploy-inputs.d.ts.map +1 -0
- package/dist/components/deploy-inputs.js +184 -0
- package/dist/components/driver.d.ts +38 -0
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/driver.js +56 -6
- package/dist/components/fan-out-record.d.ts +6 -1
- package/dist/components/fan-out-record.d.ts.map +1 -1
- package/dist/components/fan-out-record.js +24 -0
- package/dist/components/fan-out-run.d.ts +20 -0
- package/dist/components/fan-out-run.d.ts.map +1 -1
- package/dist/components/fan-out-run.js +21 -1
- package/dist/components/gate-plan.d.ts +8 -0
- package/dist/components/gate-plan.d.ts.map +1 -1
- package/dist/components/gate-plan.js +6 -0
- package/dist/components/pr-pipeline.d.ts +98 -12
- package/dist/components/pr-pipeline.d.ts.map +1 -1
- package/dist/components/pr-pipeline.js +146 -23
- package/dist/components/pr-run.d.ts +128 -30
- package/dist/components/pr-run.d.ts.map +1 -1
- package/dist/components/pr-run.js +290 -70
- package/dist/components/release-compile.d.ts +37 -2
- package/dist/components/release-compile.d.ts.map +1 -1
- package/dist/components/release-compile.js +64 -8
- package/dist/components/release-promote.d.ts +17 -3
- package/dist/components/release-promote.d.ts.map +1 -1
- package/dist/components/release-promote.js +79 -18
- package/dist/components/verbs/bom-writer.d.ts +1 -1
- package/dist/components/verbs/bom-writer.d.ts.map +1 -1
- package/dist/components/verbs/lockfile-sbom-generator.d.ts +20 -0
- package/dist/components/verbs/lockfile-sbom-generator.d.ts.map +1 -1
- package/dist/components/verbs/lockfile-sbom-generator.js +41 -4
- package/dist/components/wave-plan.d.ts +15 -2
- package/dist/components/wave-plan.d.ts.map +1 -1
- package/dist/components/wave-plan.js +44 -5
- package/dist/discovery/fold-import.d.ts.map +1 -1
- package/dist/discovery/fold-import.js +16 -6
- package/dist/discovery/sandbox/bundle.d.ts.map +1 -1
- package/dist/discovery/sandbox/bundle.js +6 -0
- package/dist/discovery/specifier-resolve.d.ts.map +1 -1
- package/dist/discovery/specifier-resolve.js +8 -2
- package/dist/fold/fold.d.ts.map +1 -1
- package/dist/fold/fold.js +68 -1
- package/dist/fold/subset.d.ts +37 -0
- package/dist/fold/subset.d.ts.map +1 -1
- package/dist/fold/subset.js +88 -0
- package/dist/graph-detail.d.ts.map +1 -1
- package/dist/graph-detail.js +32 -7
- package/dist/health.d.ts +110 -0
- package/dist/health.d.ts.map +1 -0
- package/dist/health.js +105 -0
- package/dist/health.schema.json +191 -0
- package/dist/lexicon-module.d.ts +10 -0
- package/dist/lexicon-module.d.ts.map +1 -1
- package/dist/lexicon-module.js +53 -8
- package/dist/lexicon-plugin-helpers.d.ts.map +1 -1
- package/dist/lexicon-plugin-helpers.js +6 -4
- package/dist/lexicon.d.ts +29 -0
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/affected.d.ts.map +1 -1
- package/dist/lifecycle/affected.js +7 -1
- package/dist/lifecycle/merge-record.d.ts +104 -0
- package/dist/lifecycle/merge-record.d.ts.map +1 -0
- package/dist/lifecycle/merge-record.js +102 -0
- package/dist/lifecycle/publish-attachments.d.ts +8 -5
- package/dist/lifecycle/publish-attachments.d.ts.map +1 -1
- package/dist/lifecycle/publish-attachments.js +27 -13
- package/dist/lifecycle/publish-npm.d.ts +3 -2
- package/dist/lifecycle/publish-npm.d.ts.map +1 -1
- package/dist/lifecycle/publish-npm.js +4 -3
- package/dist/lifecycle/release-at.d.ts +40 -0
- package/dist/lifecycle/release-at.d.ts.map +1 -0
- package/dist/lifecycle/release-at.js +83 -0
- package/dist/lifecycle/release-cut.d.ts.map +1 -1
- package/dist/lifecycle/release-cut.js +23 -12
- package/dist/lifecycle/release-ledger.d.ts +21 -0
- package/dist/lifecycle/release-ledger.d.ts.map +1 -1
- package/dist/lifecycle/release-ledger.js +24 -0
- package/dist/lifecycle/release-publish.d.ts +28 -12
- package/dist/lifecycle/release-publish.d.ts.map +1 -1
- package/dist/lifecycle/release-publish.js +29 -17
- package/dist/lifecycle/release-store.d.ts +25 -2
- package/dist/lifecycle/release-store.d.ts.map +1 -1
- package/dist/lifecycle/release-store.js +40 -13
- package/dist/lifecycle/run-ledger.d.ts.map +1 -1
- package/dist/lifecycle/run-ledger.js +1 -0
- package/dist/lifecycle/work-dir.d.ts +45 -0
- package/dist/lifecycle/work-dir.d.ts.map +1 -0
- package/dist/lifecycle/work-dir.js +78 -0
- package/dist/lint/discover.d.ts +17 -9
- package/dist/lint/discover.d.ts.map +1 -1
- package/dist/lint/discover.js +62 -54
- package/dist/lint/rules/op/index.d.ts +2 -1
- package/dist/lint/rules/op/index.d.ts.map +1 -1
- package/dist/lint/rules/op/index.js +4 -2
- package/dist/lint/rules/op/ops018-shell-image-digest.d.ts +10 -0
- package/dist/lint/rules/op/ops018-shell-image-digest.d.ts.map +1 -0
- package/dist/lint/rules/op/ops018-shell-image-digest.js +38 -0
- package/dist/module-source.d.ts +57 -0
- package/dist/module-source.d.ts.map +1 -0
- package/dist/module-source.js +104 -0
- package/dist/op/activities/activity-contracts.d.ts +5 -0
- package/dist/op/activities/activity-contracts.d.ts.map +1 -1
- package/dist/op/activities/activity-contracts.js +3 -0
- package/dist/op/activities/factory.js +1 -1
- package/dist/op/activities/shell.d.ts +38 -0
- package/dist/op/activities/shell.d.ts.map +1 -1
- package/dist/op/activities/shell.js +89 -3
- package/dist/op/builders.d.ts +7 -0
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/builders.js +7 -0
- package/dist/op/discover.d.ts.map +1 -1
- package/dist/op/discover.js +13 -3
- package/dist/op/gate.d.ts.map +1 -1
- package/dist/op/gate.js +3 -0
- package/dist/op/op-waves-run.d.ts.map +1 -1
- package/dist/op/op-waves-run.js +20 -5
- package/dist/op/operator.d.ts +2 -2
- package/dist/op/operator.js +2 -2
- package/dist/op/runtime.d.ts +2 -0
- package/dist/op/runtime.d.ts.map +1 -1
- package/dist/op/steward.d.ts +6 -4
- package/dist/op/steward.d.ts.map +1 -1
- package/dist/op/steward.js +5 -3
- package/dist/op/work-lease-run.d.ts +20 -7
- package/dist/op/work-lease-run.d.ts.map +1 -1
- package/dist/op/work-lease-run.js +56 -12
- package/dist/pr-forge.d.ts +10 -0
- package/dist/pr-forge.d.ts.map +1 -1
- package/dist/pr-forge.js +39 -0
- package/dist/pr-loop.d.ts +155 -7
- package/dist/pr-loop.d.ts.map +1 -1
- package/dist/pr-loop.js +137 -11
- package/dist/pr-report-url.d.ts +4 -4
- package/dist/pr-report-url.d.ts.map +1 -1
- package/dist/pr-report-url.js +5 -5
- package/dist/runner-start.d.ts +179 -0
- package/dist/runner-start.d.ts.map +1 -0
- package/dist/runner-start.js +277 -0
- package/dist/ts-loader.d.ts +38 -4
- package/dist/ts-loader.d.ts.map +1 -1
- package/dist/ts-loader.js +190 -16
- package/dist/wave-runtime.mjs +238 -231
- package/dist/workspace/chant-migrations/fountain-retired-kinds.d.ts +34 -0
- package/dist/workspace/chant-migrations/fountain-retired-kinds.d.ts.map +1 -0
- package/dist/workspace/chant-migrations/fountain-retired-kinds.js +176 -0
- package/dist/workspace/chant-migrations.d.ts.map +1 -1
- package/dist/workspace/chant-migrations.js +2 -0
- package/dist/workspace/check.schema.json +70 -2
- package/dist/workspace/checks/boxes.d.ts +6 -6
- package/dist/workspace/checks/boxes.js +9 -9
- package/dist/workspace/checks/security.d.ts +35 -0
- package/dist/workspace/checks/security.d.ts.map +1 -0
- package/dist/workspace/checks/security.js +305 -0
- package/dist/workspace/checks.d.ts +2 -0
- package/dist/workspace/checks.d.ts.map +1 -1
- package/dist/workspace/checks.js +5 -0
- package/dist/workspace/ci-builds.d.ts +163 -0
- package/dist/workspace/ci-builds.d.ts.map +1 -0
- package/dist/workspace/ci-builds.js +315 -0
- package/dist/workspace/ci-green-cli.d.ts +2 -1
- package/dist/workspace/ci-green-cli.d.ts.map +1 -1
- package/dist/workspace/ci-green-cli.js +25 -5
- package/dist/workspace/ci-green-forge.d.ts +18 -4
- package/dist/workspace/ci-green-forge.d.ts.map +1 -1
- package/dist/workspace/ci-green-forge.js +12 -0
- package/dist/workspace/ci-green-workflow.d.ts +55 -1
- package/dist/workspace/ci-green-workflow.d.ts.map +1 -1
- package/dist/workspace/ci-green-workflow.js +128 -1
- package/dist/workspace/ci-green.d.ts +117 -16
- package/dist/workspace/ci-green.d.ts.map +1 -1
- package/dist/workspace/ci-green.js +358 -43
- package/dist/workspace/ci-last-green.schema.json +49 -0
- package/dist/workspace/ci-list.schema.json +47 -7
- package/dist/workspace/ci-report-url.d.ts +18 -0
- package/dist/workspace/ci-report-url.d.ts.map +1 -0
- package/dist/workspace/ci-report-url.js +33 -0
- package/dist/workspace/ci-run-index.d.ts +13 -1
- package/dist/workspace/ci-run-index.d.ts.map +1 -1
- package/dist/workspace/ci-run-index.js +6 -4
- package/dist/workspace/ci-run-index.schema.json +6 -4
- package/dist/workspace/ci-run.d.ts +85 -6
- package/dist/workspace/ci-run.d.ts.map +1 -1
- package/dist/workspace/ci-run.js +262 -64
- package/dist/workspace/ci-runner-key.d.ts +16 -3
- package/dist/workspace/ci-runner-key.d.ts.map +1 -1
- package/dist/workspace/ci-runner-key.js +30 -10
- package/dist/workspace/ci-runner-start.d.ts +77 -0
- package/dist/workspace/ci-runner-start.d.ts.map +1 -0
- package/dist/workspace/ci-runner-start.js +185 -0
- package/dist/workspace/ci-serve.d.ts +130 -11
- package/dist/workspace/ci-serve.d.ts.map +1 -1
- package/dist/workspace/ci-serve.js +323 -40
- package/dist/workspace/declaration.d.ts +25 -0
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.js +14 -2
- package/dist/workspace/declaration.schema.json +30 -2
- package/dist/workspace/export.d.ts +1 -1
- package/dist/workspace/export.d.ts.map +1 -1
- package/dist/workspace/export.js +7 -3
- package/dist/workspace/ls.d.ts +8 -1
- package/dist/workspace/ls.d.ts.map +1 -1
- package/dist/workspace/ls.js +2 -1
- package/dist/workspace/ls.schema.json +9 -2
- package/dist/workspace/member-pipeline.d.ts +1 -0
- package/dist/workspace/member-pipeline.d.ts.map +1 -1
- package/dist/workspace/member-pipeline.js +2 -0
- package/dist/workspace/merge-record.schema.json +49 -0
- package/dist/workspace/pin-integrity.d.ts.map +1 -1
- package/dist/workspace/pin-integrity.js +3 -2
- package/dist/workspace/pr-report.schema.json +75 -8
- package/dist/workspace/reason-codes.d.ts +18 -1
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/reason-codes.js +20 -2
- package/dist/workspace/release-candidates.d.ts +129 -0
- package/dist/workspace/release-candidates.d.ts.map +1 -0
- package/dist/workspace/release-candidates.js +233 -0
- package/dist/workspace/release-candidates.schema.json +353 -0
- package/dist/workspace/run-event.schema.json +3 -1
- package/dist/workspace/security.d.ts +40 -0
- package/dist/workspace/security.d.ts.map +1 -0
- package/dist/workspace/security.js +99 -0
- package/dist/workspace/status.schema.json +3 -1
- package/dist/workspace/write-scope.d.ts +9 -0
- package/dist/workspace/write-scope.d.ts.map +1 -1
- package/dist/workspace/write-scope.js +11 -1
- package/package.json +8 -1
- package/src/__fixtures__/pr-loop/pr-apply-wave-waiting.note.golden.md +48 -0
- package/src/__fixtures__/pr-loop/pr-plan.note.golden.md +1 -1
- package/src/ci-jobs.ts +11 -2
- package/src/cli/commands/__fixtures__/init-lexicon-output/package.json +3 -3
- package/src/cli/commands/audit.test.ts +2 -2
- package/src/cli/commands/check-lexicon-plugin.test.ts +9 -0
- package/src/cli/commands/check-lexicon-plugin.ts +11 -8
- package/src/cli/commands/check-lexicon.test.ts +1 -1
- package/src/cli/commands/check-lexicon.ts +16 -11
- package/src/cli/commands/import.ts +40 -0
- package/src/cli/commands/init-lexicon/templates/project.ts +4 -3
- package/src/cli/commands/init-lexicon.test.ts +8 -5
- package/src/cli/commands/onboard.test.ts +45 -0
- package/src/cli/commands/onboard.ts +8 -4
- package/src/cli/commands/update.test.ts +6 -0
- package/src/cli/commands/update.ts +4 -1
- package/src/cli/compiled-cli.e2e.test.ts +76 -11
- package/src/cli/dependencies.e2e.test.ts +101 -0
- package/src/cli/dependencies.test.ts +93 -0
- package/src/cli/dependencies.ts +122 -0
- package/src/cli/handlers/build.test.ts +105 -0
- package/src/cli/handlers/build.ts +35 -4
- package/src/cli/handlers/fan-out.test.ts +73 -0
- package/src/cli/handlers/fan-out.ts +11 -4
- package/src/cli/handlers/graph.test.ts +76 -0
- package/src/cli/handlers/graph.ts +19 -12
- package/src/cli/handlers/lifecycle-health.test.ts +123 -0
- package/src/cli/handlers/lifecycle-health.ts +157 -0
- package/src/cli/handlers/lifecycle.ts +1 -1
- package/src/cli/handlers/lint.test.ts +42 -0
- package/src/cli/handlers/lint.ts +6 -0
- package/src/cli/handlers/operator.ts +3 -3
- package/src/cli/handlers/pr-deploy-inputs.test.ts +209 -0
- package/src/cli/handlers/pr-record.test.ts +201 -0
- package/src/cli/handlers/pr-waves.test.ts +449 -0
- package/src/cli/handlers/pr.test.ts +156 -0
- package/src/cli/handlers/pr.ts +648 -33
- package/src/cli/handlers/promote.test.ts +43 -1
- package/src/cli/handlers/promote.ts +19 -3
- package/src/cli/handlers/release-cut-at.e2e.test.ts +115 -0
- package/src/cli/handlers/release-publish-targets.e2e.test.ts +2 -2
- package/src/cli/handlers/release-verify.e2e.test.ts +12 -10
- package/src/cli/handlers/release.e2e.test.ts +19 -28
- package/src/cli/handlers/release.ts +81 -11
- package/src/cli/handlers/run-components-exit.test.ts +99 -0
- package/src/cli/handlers/run-signer.test.ts +79 -0
- package/src/cli/handlers/run.ts +34 -16
- package/src/cli/main.test.ts +18 -0
- package/src/cli/main.ts +120 -33
- package/src/cli/parse-args.ts +28 -0
- package/src/cli/registry.ts +35 -8
- package/src/codegen/generate-post-synth-barrel.ts +2 -2
- package/src/codegen/lexicon-regen.ts +1 -1
- package/src/codegen/pinned-upgrade.ts +2 -2
- package/src/codegen/post-synth-barrel-guards.test.ts +6 -11
- package/src/components/capability.ts +51 -3
- package/src/components/changed-files.test.ts +16 -1
- package/src/components/changed-files.ts +9 -1
- package/src/components/deploy-inputs.test.ts +108 -0
- package/src/components/deploy-inputs.ts +225 -0
- package/src/components/driver.test.ts +36 -1
- package/src/components/driver.ts +87 -4
- package/src/components/fan-out-record.ts +29 -1
- package/src/components/fan-out-run.test.ts +35 -0
- package/src/components/fan-out-run.ts +40 -1
- package/src/components/gate-plan.ts +9 -0
- package/src/components/pr-pipeline.test.ts +100 -0
- package/src/components/pr-pipeline.ts +184 -28
- package/src/components/pr-run.test.ts +323 -18
- package/src/components/pr-run.ts +355 -76
- package/src/components/release-compile.test.ts +34 -0
- package/src/components/release-compile.ts +89 -8
- package/src/components/release-promote.test.ts +55 -2
- package/src/components/release-promote.ts +93 -23
- package/src/components/verbs/bom-writer.ts +1 -1
- package/src/components/verbs/lockfile-sbom-generator.test.ts +36 -0
- package/src/components/verbs/lockfile-sbom-generator.ts +43 -5
- package/src/components/wave-plan.ts +48 -8
- package/src/discovery/fold-host-method.test.ts +87 -0
- package/src/discovery/fold-import.ts +15 -5
- package/src/discovery/fold-package-composite.test.ts +27 -0
- package/src/discovery/fold-specifiers.test.ts +8 -0
- package/src/discovery/sandbox/bundle.ts +6 -0
- package/src/discovery/specifier-resolve.ts +8 -2
- package/src/fold/fold.test.ts +72 -0
- package/src/fold/fold.ts +71 -0
- package/src/fold/subset.test.ts +31 -0
- package/src/fold/subset.ts +99 -0
- package/src/graph-detail.test.ts +57 -0
- package/src/graph-detail.ts +33 -7
- package/src/health.schema.json +191 -0
- package/src/health.test.ts +62 -0
- package/src/health.ts +178 -0
- package/src/lexicon-dist.e2e.test.ts +254 -0
- package/src/lexicon-module.test.ts +47 -0
- package/src/lexicon-module.ts +49 -7
- package/src/lexicon-plugin-helpers.ts +6 -4
- package/src/lexicon.ts +43 -1
- package/src/lifecycle/affected.test.ts +7 -0
- package/src/lifecycle/affected.ts +6 -1
- package/src/lifecycle/merge-record.test.ts +111 -0
- package/src/lifecycle/merge-record.ts +154 -0
- package/src/lifecycle/publish-attachments.ts +25 -13
- package/src/lifecycle/publish-npm.ts +4 -3
- package/src/lifecycle/publish-targets.test.ts +34 -4
- package/src/lifecycle/release-at.test.ts +48 -0
- package/src/lifecycle/release-at.ts +100 -0
- package/src/lifecycle/release-cut.test.ts +41 -0
- package/src/lifecycle/release-cut.ts +22 -11
- package/src/lifecycle/release-ledger.ts +34 -0
- package/src/lifecycle/release-publish.test.ts +43 -13
- package/src/lifecycle/release-publish.ts +38 -18
- package/src/lifecycle/release-store.ts +57 -13
- package/src/lifecycle/run-ledger.test.ts +13 -0
- package/src/lifecycle/run-ledger.ts +1 -0
- package/src/lifecycle/work-dir.test.ts +77 -0
- package/src/lifecycle/work-dir.ts +82 -0
- package/src/lint/discover.test.ts +46 -2
- package/src/lint/discover.ts +55 -49
- package/src/lint/rules/op/index.ts +4 -2
- package/src/lint/rules/op/ops018-shell-image-digest.test.ts +60 -0
- package/src/lint/rules/op/ops018-shell-image-digest.ts +48 -0
- package/src/module-source.test.ts +85 -0
- package/src/module-source.ts +112 -0
- package/src/op/activities/activity-contracts.ts +3 -0
- package/src/op/activities/factory.ts +1 -1
- package/src/op/activities/shell-container.e2e.test.ts +67 -0
- package/src/op/activities/shell.test.ts +44 -1
- package/src/op/activities/shell.ts +110 -3
- package/src/op/builders.ts +7 -0
- package/src/op/discover-root.test.ts +31 -2
- package/src/op/discover.ts +14 -4
- package/src/op/gate.test.ts +30 -0
- package/src/op/gate.ts +3 -0
- package/src/op/op-waves-run.ts +20 -5
- package/src/op/op-waves.test.ts +49 -0
- package/src/op/operator.ts +2 -2
- package/src/op/runtime.ts +2 -0
- package/src/op/step-console.test.ts +4 -2
- package/src/op/steward.ts +6 -4
- package/src/op/work-lease-run.test.ts +88 -2
- package/src/op/work-lease-run.ts +52 -12
- package/src/pr-forge-bundle.test.ts +2 -2
- package/src/pr-forge.test.ts +42 -0
- package/src/pr-forge.ts +40 -0
- package/src/pr-loop-bundle.test.ts +2 -1
- package/src/pr-loop.test.ts +146 -1
- package/src/pr-loop.ts +278 -17
- package/src/pr-report-url.ts +7 -7
- package/src/runner-start-bundle.test.ts +29 -0
- package/src/runner-start.test.ts +179 -0
- package/src/runner-start.ts +329 -0
- package/src/terraform/__fixtures__/advise.test.ts +21 -2
- package/src/ts-loader.test.ts +89 -1
- package/src/ts-loader.ts +180 -13
- package/src/workspace/changes.test.ts +1 -1
- package/src/workspace/chant-migrations/fountain-retired-kinds.test.ts +33 -0
- package/src/workspace/chant-migrations/fountain-retired-kinds.ts +190 -0
- package/src/workspace/chant-migrations.e2e.test.ts +5 -0
- package/src/workspace/chant-migrations.ts +2 -0
- package/src/workspace/check-contract.test.ts +3 -2
- package/src/workspace/check.schema.json +70 -2
- package/src/workspace/checks/boxes.ts +9 -9
- package/src/workspace/checks/security.test.ts +138 -0
- package/src/workspace/checks/security.ts +305 -0
- package/src/workspace/checks.ts +5 -0
- package/src/workspace/ci-builds.e2e.test.ts +208 -0
- package/src/workspace/ci-builds.test.ts +174 -0
- package/src/workspace/ci-builds.ts +385 -0
- package/src/workspace/ci-green-builds.test.ts +180 -0
- package/src/workspace/ci-green-cli.test.ts +54 -0
- package/src/workspace/ci-green-cli.ts +26 -5
- package/src/workspace/ci-green-forge.ts +30 -4
- package/src/workspace/ci-green-runs.test.ts +53 -11
- package/src/workspace/ci-green-workflow.test.ts +84 -1
- package/src/workspace/ci-green-workflow.ts +139 -2
- package/src/workspace/ci-green.test.ts +6 -3
- package/src/workspace/ci-green.ts +408 -48
- package/src/workspace/ci-last-green.schema.json +49 -0
- package/src/workspace/ci-list.schema.json +47 -7
- package/src/workspace/ci-report-url.test.ts +220 -0
- package/src/workspace/ci-report-url.ts +33 -0
- package/src/workspace/ci-run-index.schema.json +6 -4
- package/src/workspace/ci-run-index.test.ts +42 -3
- package/src/workspace/ci-run-index.ts +19 -5
- package/src/workspace/ci-run.test.ts +203 -8
- package/src/workspace/ci-run.ts +295 -63
- package/src/workspace/ci-runner-key.test.ts +55 -17
- package/src/workspace/ci-runner-key.ts +36 -11
- package/src/workspace/ci-runner-start.test.ts +151 -0
- package/src/workspace/ci-runner-start.ts +193 -0
- package/src/workspace/ci-serve-builds.test.ts +86 -0
- package/src/workspace/ci-serve.test.ts +209 -6
- package/src/workspace/ci-serve.ts +341 -42
- package/src/workspace/declaration.schema.json +30 -2
- package/src/workspace/declaration.test.ts +40 -0
- package/src/workspace/declaration.ts +40 -3
- package/src/workspace/export.ts +6 -3
- package/src/workspace/ls.schema.json +9 -2
- package/src/workspace/ls.ts +10 -4
- package/src/workspace/member-pipeline.ts +2 -1
- package/src/workspace/merge-record.schema.json +49 -0
- package/src/workspace/pin-integrity.test.ts +5 -0
- package/src/workspace/pin-integrity.ts +3 -2
- package/src/workspace/pr-report.schema.json +75 -8
- package/src/workspace/reason-codes.test.ts +2 -0
- package/src/workspace/reason-codes.ts +20 -2
- package/src/workspace/release-candidates.schema.json +353 -0
- package/src/workspace/release-candidates.test.ts +106 -0
- package/src/workspace/release-candidates.ts +315 -0
- package/src/workspace/run-event.schema.json +3 -1
- package/src/workspace/security.test.ts +70 -0
- package/src/workspace/security.ts +118 -0
- package/src/workspace/status.schema.json +3 -1
- package/src/workspace/write-scope.test.ts +29 -1
- package/src/workspace/write-scope.ts +12 -1
package/src/fold/subset.ts
CHANGED
|
@@ -461,6 +461,98 @@ export function unsupportedExpressionMessage(node: ts.Node): string {
|
|
|
461
461
|
return `unsupported expression: ${ts.SyntaxKind[node.kind]}`;
|
|
462
462
|
}
|
|
463
463
|
|
|
464
|
+
/**
|
|
465
|
+
* chant#4011 — S-CallMethod's closed table of HOST methods, by receiver type.
|
|
466
|
+
* PROVISIONAL until typescript-as-data narrows F-Eval-CallMethod to the same
|
|
467
|
+
* table (INTENTIUS/typescript-as-data#264; see the module doc's provisional path).
|
|
468
|
+
*
|
|
469
|
+
* Since #1966 `fold()` called any function-valued property on a real
|
|
470
|
+
* receiver, so `[].constructor.constructor("return Date.now()")` reached
|
|
471
|
+
* `Function` and a `[fold:fold]` file could put the clock, `process.env` or
|
|
472
|
+
* `Math.random()` in its output. A method the JavaScript engine provides
|
|
473
|
+
* (a native function) now folds only when it is listed here for the
|
|
474
|
+
* receiver's type AND is the very function that type's prototype carries, so
|
|
475
|
+
* an own property cannot shadow a listed name. Every entry is deterministic
|
|
476
|
+
* given its receiver and arguments: no callbacks (`map`, `filter`), no
|
|
477
|
+
* mutation (`push`, `sort`), and no `toLocale*` or `localeCompare`, whose
|
|
478
|
+
* answer follows the host's locale and time zone.
|
|
479
|
+
*
|
|
480
|
+
* A method defined by a lexicon package's own code (the github lexicon's
|
|
481
|
+
* `Expression.prototype.toString`) is not a host method and is not looked up
|
|
482
|
+
* here; it is the same class of package code as an eager intrinsic call.
|
|
483
|
+
*/
|
|
484
|
+
export const FOLDABLE_HOST_METHODS: {
|
|
485
|
+
readonly string: ReadonlySet<string>;
|
|
486
|
+
readonly number: ReadonlySet<string>;
|
|
487
|
+
readonly boolean: ReadonlySet<string>;
|
|
488
|
+
readonly array: ReadonlySet<string>;
|
|
489
|
+
readonly object: ReadonlySet<string>;
|
|
490
|
+
} = {
|
|
491
|
+
string: new Set([
|
|
492
|
+
"at",
|
|
493
|
+
"charAt",
|
|
494
|
+
"charCodeAt",
|
|
495
|
+
"codePointAt",
|
|
496
|
+
"concat",
|
|
497
|
+
"endsWith",
|
|
498
|
+
"includes",
|
|
499
|
+
"indexOf",
|
|
500
|
+
"lastIndexOf",
|
|
501
|
+
"padEnd",
|
|
502
|
+
"padStart",
|
|
503
|
+
"repeat",
|
|
504
|
+
"replace",
|
|
505
|
+
"replaceAll",
|
|
506
|
+
"slice",
|
|
507
|
+
"split",
|
|
508
|
+
"startsWith",
|
|
509
|
+
"substring",
|
|
510
|
+
"toLowerCase",
|
|
511
|
+
"toString",
|
|
512
|
+
"toUpperCase",
|
|
513
|
+
"trim",
|
|
514
|
+
"trimEnd",
|
|
515
|
+
"trimStart",
|
|
516
|
+
"valueOf",
|
|
517
|
+
]),
|
|
518
|
+
number: new Set(["toExponential", "toFixed", "toPrecision", "toString", "valueOf"]),
|
|
519
|
+
boolean: new Set(["toString", "valueOf"]),
|
|
520
|
+
array: new Set(["at", "concat", "flat", "includes", "indexOf", "join", "lastIndexOf", "slice", "toString"]),
|
|
521
|
+
object: new Set(["hasOwnProperty", "toString", "valueOf"]),
|
|
522
|
+
};
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* chant#4011 — method names `fold()` refuses on every receiver, lexicon
|
|
526
|
+
* objects included. These are the names that reach `Function` (`constructor`,
|
|
527
|
+
* and `call`/`apply`/`bind` on whatever it returns) or read the host locale.
|
|
528
|
+
* Unlike {@link FOLDABLE_HOST_METHODS}, which needs the receiver's runtime
|
|
529
|
+
* type, a name in this set is decidable from syntax, so EVL001 refuses it too.
|
|
530
|
+
*/
|
|
531
|
+
export const REFUSED_METHOD_NAMES: ReadonlySet<string> = new Set([
|
|
532
|
+
"constructor",
|
|
533
|
+
"call",
|
|
534
|
+
"apply",
|
|
535
|
+
"bind",
|
|
536
|
+
"toLocaleString",
|
|
537
|
+
"toLocaleDateString",
|
|
538
|
+
"toLocaleTimeString",
|
|
539
|
+
"toLocaleUpperCase",
|
|
540
|
+
"toLocaleLowerCase",
|
|
541
|
+
"localeCompare",
|
|
542
|
+
"__defineGetter__",
|
|
543
|
+
"__defineSetter__",
|
|
544
|
+
"__lookupGetter__",
|
|
545
|
+
"__lookupSetter__",
|
|
546
|
+
]);
|
|
547
|
+
|
|
548
|
+
/** The refusal for a method name in {@link REFUSED_METHOD_NAMES}, shared by `fold()` and EVL001. */
|
|
549
|
+
export function refusedMethodMessage(methodName: string): string {
|
|
550
|
+
return (
|
|
551
|
+
`method call \`.${methodName}(...)\` is not foldable: it reaches \`Function\` or the host locale, ` +
|
|
552
|
+
"so its result is not determined by the source (chant#4011) — falls back to run"
|
|
553
|
+
);
|
|
554
|
+
}
|
|
555
|
+
|
|
464
556
|
/**
|
|
465
557
|
* Classify one object-literal member (`{ a: 1 }`'s `a: 1`, `{ ...x }`'s
|
|
466
558
|
* `...x`, or a shorthand `{ a }`). Checks the key's shape before the
|
|
@@ -720,7 +812,14 @@ export function findSubsetViolation(
|
|
|
720
812
|
// a same-file `new` used as a value (#1169). Accepted here regardless of
|
|
721
813
|
// the intrinsics registry, unlike the two cases just above: nothing about
|
|
722
814
|
// whether a method call folds depends on a lexicon's registration.
|
|
815
|
+
//
|
|
816
|
+
// chant#4011 — except a name `fold()` refuses on every receiver, which is
|
|
817
|
+
// decidable here. The per-type host table needs the receiver's value, so
|
|
818
|
+
// it stays on `fold()`'s side, in the direction F-Direction allows.
|
|
723
819
|
if (ts.isPropertyAccessExpression(node.expression)) {
|
|
820
|
+
if (REFUSED_METHOD_NAMES.has(node.expression.name.text)) {
|
|
821
|
+
return violation(node, refusedMethodMessage(node.expression.name.text));
|
|
822
|
+
}
|
|
724
823
|
const v = findSubsetViolation(node.expression.expression, intrinsics);
|
|
725
824
|
if (v) return v;
|
|
726
825
|
for (const arg of node.arguments) {
|
package/src/graph-detail.test.ts
CHANGED
|
@@ -87,6 +87,63 @@ describe("applyDetail", () => {
|
|
|
87
87
|
expect(ir.imports).toEqual([{ name: "pClusterArn", node: "pClusterArn" }]);
|
|
88
88
|
});
|
|
89
89
|
|
|
90
|
+
test("T1 keeps exports, each on the node that now holds its producer (#4178)", () => {
|
|
91
|
+
const withExports: GraphIR = {
|
|
92
|
+
...base,
|
|
93
|
+
version: 2,
|
|
94
|
+
meta: { _behaviour: { engine: "x" } },
|
|
95
|
+
exports: [
|
|
96
|
+
{ name: "DbEndpoint", node: "dbDeployment", attr: "status" },
|
|
97
|
+
{ name: "SubnetLink", node: "subnet", attr: "selfLink" },
|
|
98
|
+
{ name: "Literal" },
|
|
99
|
+
],
|
|
100
|
+
};
|
|
101
|
+
const ir = applyDetail(withExports, DETAIL.COMPOSITES);
|
|
102
|
+
expect(ir.exports).toEqual([
|
|
103
|
+
{ name: "DbEndpoint", node: "db", attr: "status" },
|
|
104
|
+
{ name: "SubnetLink", node: "subnet", attr: "selfLink" },
|
|
105
|
+
{ name: "Literal" },
|
|
106
|
+
]);
|
|
107
|
+
expect(ir.version).toBe(2);
|
|
108
|
+
expect(ir.meta).toEqual({ _behaviour: { engine: "x" } });
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("T1 keeps two same-named composites from different composite types apart (#4178)", () => {
|
|
112
|
+
const twoWebs: GraphIR = {
|
|
113
|
+
nodes: [
|
|
114
|
+
{ id: "webAlb", kind: "AWS::ElasticLoadBalancingV2::LoadBalancer", lexicon: "aws", compositeParent: "FargateAlb", compositeInstance: "web", attrs: {} },
|
|
115
|
+
{ id: "webService", kind: "AWS::ECS::Service", lexicon: "aws", compositeParent: "FargateAlb", compositeInstance: "web", attrs: {} },
|
|
116
|
+
{ id: "webRun", kind: "RunService", lexicon: "gcp", compositeParent: "CloudRunServiceComposite", compositeInstance: "web", attrs: {} },
|
|
117
|
+
{ id: "record", kind: "AWS::Route53::RecordSet", lexicon: "aws", attrs: {} },
|
|
118
|
+
],
|
|
119
|
+
edges: [
|
|
120
|
+
{ from: "record", to: "webAlb", kind: "ref", viaAttr: "AliasTarget" },
|
|
121
|
+
{ from: "record", to: "webRun", kind: "ref", viaAttr: "ResourceRecords" },
|
|
122
|
+
{ from: "webService", to: "webAlb", kind: "ref", viaAttr: "LoadBalancers" },
|
|
123
|
+
],
|
|
124
|
+
groups: {},
|
|
125
|
+
exports: [{ name: "WebUri", node: "webRun", attr: "uri" }],
|
|
126
|
+
};
|
|
127
|
+
const ir = applyDetail(twoWebs, DETAIL.COMPOSITES);
|
|
128
|
+
expect(ir.nodes.map((n) => n.id)).toEqual(["record", "web@CloudRunServiceComposite", "web@FargateAlb"]);
|
|
129
|
+
expect(ir.nodes.find((n) => n.id === "web@FargateAlb")).toMatchObject({ kind: "FargateAlb", lexicon: "aws", attrs: { members: 2 } });
|
|
130
|
+
expect(ir.nodes.find((n) => n.id === "web@CloudRunServiceComposite")).toMatchObject({ kind: "CloudRunServiceComposite", lexicon: "gcp", attrs: { members: 1 } });
|
|
131
|
+
expect(ir.edges).toEqual(expect.arrayContaining([
|
|
132
|
+
{ from: "record", to: "web@FargateAlb", kind: "ref" },
|
|
133
|
+
{ from: "record", to: "web@CloudRunServiceComposite", kind: "ref" },
|
|
134
|
+
]));
|
|
135
|
+
expect(ir.edges).toHaveLength(2);
|
|
136
|
+
expect(ir.exports).toEqual([{ name: "WebUri", node: "web@CloudRunServiceComposite", attr: "uri" }]);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("T1 keeps one composite whose members span lexicons as one node", () => {
|
|
140
|
+
const ir = applyDetail({
|
|
141
|
+
...base,
|
|
142
|
+
nodes: base.nodes.map((n) => (n.id === "dbNamespace" ? { ...n, lexicon: "gcp" } : n)),
|
|
143
|
+
}, DETAIL.COMPOSITES);
|
|
144
|
+
expect(ir.nodes.find((n) => n.id === "db")).toMatchObject({ kind: "DbStack", lexicon: "multi", attrs: { members: 2 } });
|
|
145
|
+
});
|
|
146
|
+
|
|
90
147
|
test("T1 node count is between T0 and T2", () => {
|
|
91
148
|
const t0 = applyDetail(base, DETAIL.STACKS).nodes.length;
|
|
92
149
|
const t1 = applyDetail(base, DETAIL.COMPOSITES).nodes.length;
|
package/src/graph-detail.ts
CHANGED
|
@@ -127,27 +127,46 @@ function toDeclarables(ir: GraphIR): GraphIR {
|
|
|
127
127
|
/** T1 — collapse each composite instance to one node; internal edges disappear.
|
|
128
128
|
* Topology view: surviving plain nodes keep only overlay paint, not properties. */
|
|
129
129
|
function toComposites(ir: GraphIR): GraphIR {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
130
|
+
// A composite instance is its export name plus the composite that made it:
|
|
131
|
+
// an AWS FargateAlb and a GCP CloudRunServiceComposite both exported as
|
|
132
|
+
// `web` are two instances (#4178). One composite whose members span
|
|
133
|
+
// lexicons stays one instance.
|
|
134
|
+
const instanceKey = (n: IRNode) => `${n.compositeInstance}\0${n.compositeParent ?? ""}`;
|
|
133
135
|
const instances = new Map<string, IRNode[]>();
|
|
134
136
|
const plain: IRNode[] = [];
|
|
135
137
|
for (const n of ir.nodes) {
|
|
136
138
|
if (n.compositeInstance) {
|
|
137
|
-
const
|
|
139
|
+
const key = instanceKey(n);
|
|
140
|
+
const arr = instances.get(key) ?? [];
|
|
138
141
|
arr.push(n);
|
|
139
|
-
instances.set(
|
|
142
|
+
instances.set(key, arr);
|
|
140
143
|
} else {
|
|
141
144
|
plain.push({ ...n, attrs: paintAttrs(n.attrs) });
|
|
142
145
|
}
|
|
143
146
|
}
|
|
144
147
|
|
|
148
|
+
// The collapsed id is the instance name, or `<instance>@<composite>` when
|
|
149
|
+
// two composites share the name.
|
|
150
|
+
const sharing = new Map<string, number>();
|
|
151
|
+
for (const members of instances.values()) {
|
|
152
|
+
const inst = members[0].compositeInstance!;
|
|
153
|
+
sharing.set(inst, (sharing.get(inst) ?? 0) + 1);
|
|
154
|
+
}
|
|
155
|
+
const collapsedId = new Map<string, string>();
|
|
156
|
+
for (const [key, members] of instances) {
|
|
157
|
+
const { compositeInstance: inst, compositeParent: parent } = members[0];
|
|
158
|
+
collapsedId.set(key, sharing.get(inst!)! > 1 && parent ? `${inst}@${parent}` : inst!);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const idMap = new Map<string, string>();
|
|
162
|
+
for (const n of ir.nodes) idMap.set(n.id, n.compositeInstance ? collapsedId.get(instanceKey(n))! : n.id);
|
|
163
|
+
|
|
145
164
|
const nodes: IRNode[] = [...plain];
|
|
146
|
-
for (const [
|
|
165
|
+
for (const [key, members] of instances) {
|
|
147
166
|
const types = new Set(members.map((m) => m.compositeParent).filter(Boolean) as string[]);
|
|
148
167
|
const lexicons = new Set(members.map((m) => m.lexicon));
|
|
149
168
|
nodes.push({
|
|
150
|
-
id:
|
|
169
|
+
id: collapsedId.get(key)!,
|
|
151
170
|
kind: types.size === 1 ? [...types][0] : "Composite",
|
|
152
171
|
lexicon: lexicons.size === 1 ? [...lexicons][0] : "multi",
|
|
153
172
|
attrs: { members: members.length },
|
|
@@ -197,11 +216,18 @@ function toComposites(ir: GraphIR): GraphIR {
|
|
|
197
216
|
}
|
|
198
217
|
if (Object.keys(byPipeline).length) groups.byPipeline = byPipeline;
|
|
199
218
|
}
|
|
219
|
+
// Exports (#4178) stay at this tier, each on the node that now holds its
|
|
220
|
+
// producer, so a viewer can still match another member's import to it.
|
|
221
|
+
const exports = ir.exports?.map((x) => (x.node ? { ...x, node: idMap.get(x.node) ?? x.node } : { ...x }));
|
|
200
222
|
return {
|
|
223
|
+
...(ir.version !== undefined ? { version: ir.version } : {}),
|
|
201
224
|
nodes,
|
|
202
225
|
edges: sortEdges([...seen.values()]),
|
|
203
226
|
groups,
|
|
227
|
+
...(ir.meta ? { meta: ir.meta } : {}),
|
|
228
|
+
...(exports && exports.length ? { exports } : {}),
|
|
204
229
|
...(imports && imports.length ? { imports } : {}),
|
|
230
|
+
...(ir.pipeline ? { pipeline: ir.pipeline } : {}),
|
|
205
231
|
...(dangling && dangling.length ? { dangling } : {}),
|
|
206
232
|
};
|
|
207
233
|
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://intentius.io/chant/schemas/health/v1/health.schema.json",
|
|
4
|
+
"title": "chant-health/v1",
|
|
5
|
+
"description": "What `chant lifecycle health <env> --json` prints (#4177): whether each declared resource that has a health read is up, read separately from drift. Every entity a read applies to lands in exactly one of `entities` (read, with a state), `unobserved` (the read failed, never folded into unhealthy) or `absent` (the provider says it is not deployed). Entity names are the graph's node ids. A single-stack project puts `lexicons` at the top level; a multi-stack one nests them under `stacks`. Written by the first chant release after 0.124.32. Readers ignore fields they do not know; a field is only ever added within a version.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": [
|
|
8
|
+
"schema",
|
|
9
|
+
"chantVersion",
|
|
10
|
+
"environment",
|
|
11
|
+
"observedAt",
|
|
12
|
+
"summary"
|
|
13
|
+
],
|
|
14
|
+
"properties": {
|
|
15
|
+
"schema": {
|
|
16
|
+
"const": "chant-health/v1"
|
|
17
|
+
},
|
|
18
|
+
"chantVersion": {
|
|
19
|
+
"type": "string",
|
|
20
|
+
"description": "The chant that ran the read."
|
|
21
|
+
},
|
|
22
|
+
"environment": {
|
|
23
|
+
"type": "string",
|
|
24
|
+
"description": "The environment named on the command line."
|
|
25
|
+
},
|
|
26
|
+
"observedAt": {
|
|
27
|
+
"type": "string",
|
|
28
|
+
"description": "When the command started reading, ISO 8601 UTC."
|
|
29
|
+
},
|
|
30
|
+
"summary": {
|
|
31
|
+
"type": "object",
|
|
32
|
+
"required": [
|
|
33
|
+
"healthy",
|
|
34
|
+
"unhealthy",
|
|
35
|
+
"unknown",
|
|
36
|
+
"unobserved",
|
|
37
|
+
"absent"
|
|
38
|
+
],
|
|
39
|
+
"description": "Counts over every lexicon and stack.",
|
|
40
|
+
"properties": {
|
|
41
|
+
"healthy": {
|
|
42
|
+
"type": "integer",
|
|
43
|
+
"minimum": 0
|
|
44
|
+
},
|
|
45
|
+
"unhealthy": {
|
|
46
|
+
"type": "integer",
|
|
47
|
+
"minimum": 0
|
|
48
|
+
},
|
|
49
|
+
"unknown": {
|
|
50
|
+
"type": "integer",
|
|
51
|
+
"minimum": 0
|
|
52
|
+
},
|
|
53
|
+
"unobserved": {
|
|
54
|
+
"type": "integer",
|
|
55
|
+
"minimum": 0
|
|
56
|
+
},
|
|
57
|
+
"absent": {
|
|
58
|
+
"type": "integer",
|
|
59
|
+
"minimum": 0
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"oneOf": [
|
|
65
|
+
{
|
|
66
|
+
"required": [
|
|
67
|
+
"lexicons"
|
|
68
|
+
],
|
|
69
|
+
"properties": {
|
|
70
|
+
"lexicons": {
|
|
71
|
+
"$ref": "#/$defs/byLexicon"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"required": [
|
|
77
|
+
"stacks"
|
|
78
|
+
],
|
|
79
|
+
"properties": {
|
|
80
|
+
"stacks": {
|
|
81
|
+
"type": "object",
|
|
82
|
+
"description": "Per stack name, the same per-lexicon results.",
|
|
83
|
+
"additionalProperties": {
|
|
84
|
+
"$ref": "#/$defs/byLexicon"
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
],
|
|
90
|
+
"$defs": {
|
|
91
|
+
"byLexicon": {
|
|
92
|
+
"type": "object",
|
|
93
|
+
"description": "Per lexicon name, that lexicon's read. A lexicon with no applicable entity is left out.",
|
|
94
|
+
"additionalProperties": {
|
|
95
|
+
"$ref": "#/$defs/read"
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
"read": {
|
|
99
|
+
"type": "object",
|
|
100
|
+
"required": [
|
|
101
|
+
"entities",
|
|
102
|
+
"unobserved",
|
|
103
|
+
"absent"
|
|
104
|
+
],
|
|
105
|
+
"properties": {
|
|
106
|
+
"entities": {
|
|
107
|
+
"type": "object",
|
|
108
|
+
"additionalProperties": {
|
|
109
|
+
"$ref": "#/$defs/entry"
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
"unobserved": {
|
|
113
|
+
"type": "object",
|
|
114
|
+
"additionalProperties": {
|
|
115
|
+
"$ref": "#/$defs/hole"
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
"absent": {
|
|
119
|
+
"type": "array",
|
|
120
|
+
"items": {
|
|
121
|
+
"type": "string"
|
|
122
|
+
},
|
|
123
|
+
"description": "Entity names the provider says are not deployed."
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
"entry": {
|
|
128
|
+
"type": "object",
|
|
129
|
+
"required": [
|
|
130
|
+
"type",
|
|
131
|
+
"state",
|
|
132
|
+
"detail",
|
|
133
|
+
"observedAt"
|
|
134
|
+
],
|
|
135
|
+
"properties": {
|
|
136
|
+
"type": {
|
|
137
|
+
"type": "string",
|
|
138
|
+
"description": "Entity type, such as AWS::ECS::Service."
|
|
139
|
+
},
|
|
140
|
+
"state": {
|
|
141
|
+
"enum": [
|
|
142
|
+
"healthy",
|
|
143
|
+
"unhealthy",
|
|
144
|
+
"unknown"
|
|
145
|
+
],
|
|
146
|
+
"description": "healthy and unhealthy are what the provider reported. unknown means the read succeeded and the answer decides neither way: a rollout in progress, a service scaled to zero, a condition pending."
|
|
147
|
+
},
|
|
148
|
+
"detail": {
|
|
149
|
+
"type": "string",
|
|
150
|
+
"description": "One short line saying why, in the provider's terms."
|
|
151
|
+
},
|
|
152
|
+
"observedAt": {
|
|
153
|
+
"type": "string",
|
|
154
|
+
"description": "When chant read it, ISO 8601."
|
|
155
|
+
},
|
|
156
|
+
"checkedAt": {
|
|
157
|
+
"type": "string",
|
|
158
|
+
"description": "When the provider last checked or changed the state, when it says, ISO 8601."
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
"hole": {
|
|
163
|
+
"type": "object",
|
|
164
|
+
"required": [
|
|
165
|
+
"reason"
|
|
166
|
+
],
|
|
167
|
+
"description": "A read that failed. Never counted as unhealthy.",
|
|
168
|
+
"properties": {
|
|
169
|
+
"type": {
|
|
170
|
+
"type": "string"
|
|
171
|
+
},
|
|
172
|
+
"reason": {
|
|
173
|
+
"enum": [
|
|
174
|
+
"read-failed",
|
|
175
|
+
"no-credentials",
|
|
176
|
+
"no-binding",
|
|
177
|
+
"unsupported-kind",
|
|
178
|
+
"filtered"
|
|
179
|
+
]
|
|
180
|
+
},
|
|
181
|
+
"detail": {
|
|
182
|
+
"type": "string"
|
|
183
|
+
},
|
|
184
|
+
"queried": {
|
|
185
|
+
"type": "string",
|
|
186
|
+
"description": "The address the read was issued against, when the lexicon reports it."
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { healthApplicable, observeHealthFor, summarizeHealth, totalHealth, type HealthLexicon } from "./health";
|
|
3
|
+
|
|
4
|
+
const entities = new Map([
|
|
5
|
+
["web", { entityType: "AWS::ECS::Service", props: {} }],
|
|
6
|
+
["check", { entityType: "AWS::Route53::HealthCheck", props: {} }],
|
|
7
|
+
]);
|
|
8
|
+
|
|
9
|
+
describe("health reads (#4177)", () => {
|
|
10
|
+
test("healthApplicable keeps only the types the lexicon names", () => {
|
|
11
|
+
const all = new Map([...entities, ["bucket", { entityType: "AWS::S3::Bucket", props: {} }]]);
|
|
12
|
+
const picked = healthApplicable({ name: "aws", healthTypes: ["AWS::ECS::Service"] }, all);
|
|
13
|
+
expect([...picked.keys()]).toEqual(["web"]);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("a thrown reader is read-failed for every entity, never unhealthy", async () => {
|
|
17
|
+
const plugin: HealthLexicon = {
|
|
18
|
+
name: "aws",
|
|
19
|
+
healthTypes: ["AWS::ECS::Service"],
|
|
20
|
+
observeHealth: async () => {
|
|
21
|
+
throw new Error("connect ECONNREFUSED");
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
const r = await observeHealthFor(plugin, { environment: "dev", buildOutput: "", entities });
|
|
25
|
+
expect(r.entities).toEqual({});
|
|
26
|
+
expect(r.unobserved.web).toEqual({ reason: "read-failed", type: "AWS::ECS::Service", detail: "connect ECONNREFUSED" });
|
|
27
|
+
expect(Object.keys(r.unobserved)).toEqual(["web", "check"]);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("a lexicon without a reader reports unsupported-kind", async () => {
|
|
31
|
+
const r = await observeHealthFor({ name: "aws" }, { environment: "dev", buildOutput: "", entities });
|
|
32
|
+
expect(r.unobserved.web?.reason).toBe("unsupported-kind");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("the result is made total, and entities win over absent and unobserved", () => {
|
|
36
|
+
const r = totalHealth(["web", "check", "gone", "forgot"], {
|
|
37
|
+
entities: { web: { type: "AWS::ECS::Service", state: "healthy", detail: "1/1 running", observedAt: "t" } },
|
|
38
|
+
unobserved: { web: { reason: "read-failed" }, check: { reason: "no-credentials" } },
|
|
39
|
+
absent: ["gone", "web"],
|
|
40
|
+
});
|
|
41
|
+
expect(Object.keys(r.entities)).toEqual(["web"]);
|
|
42
|
+
expect(r.absent).toEqual(["gone"]);
|
|
43
|
+
expect(r.unobserved.check?.reason).toBe("no-credentials");
|
|
44
|
+
expect(r.unobserved.forgot?.reason).toBe("read-failed");
|
|
45
|
+
expect(r.unobserved.web).toBeUndefined();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("summarizeHealth counts each place", () => {
|
|
49
|
+
const s = summarizeHealth([
|
|
50
|
+
{
|
|
51
|
+
entities: {
|
|
52
|
+
a: { type: "t", state: "healthy", detail: "", observedAt: "" },
|
|
53
|
+
b: { type: "t", state: "unhealthy", detail: "", observedAt: "" },
|
|
54
|
+
c: { type: "t", state: "unknown", detail: "", observedAt: "" },
|
|
55
|
+
},
|
|
56
|
+
unobserved: { d: { reason: "read-failed" } },
|
|
57
|
+
absent: ["e", "f"],
|
|
58
|
+
},
|
|
59
|
+
]);
|
|
60
|
+
expect(s).toEqual({ healthy: 1, unhealthy: 1, unknown: 1, unobserved: 1, absent: 2 });
|
|
61
|
+
});
|
|
62
|
+
});
|
package/src/health.ts
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime health (#4177): whether a declared thing is up, read separately from
|
|
3
|
+
* drift.
|
|
4
|
+
*
|
|
5
|
+
* The observation family answers what is deployed (`describeResources`), what
|
|
6
|
+
* its properties are (`observeResourcesDeep`) and what it would do
|
|
7
|
+
* (`predictBehaviour`). None of them says whether a service is serving. A
|
|
8
|
+
* lexicon that can read that implements {@link HealthLexicon.observeHealth}
|
|
9
|
+
* for the types it names in `healthTypes`, and `chant lifecycle health` reports
|
|
10
|
+
* the answers in the {@link HEALTH_SCHEMA} shape.
|
|
11
|
+
*
|
|
12
|
+
* The verdicts follow #1089's rule. Every entity the caller asks about lands in
|
|
13
|
+
* exactly one place: `entities` (read, with a state), `unobserved` (the read
|
|
14
|
+
* failed, a hole with a reason) or `absent` (the provider says it is not
|
|
15
|
+
* deployed). A failed read is never `unhealthy`: `unhealthy` is something the
|
|
16
|
+
* provider said.
|
|
17
|
+
*
|
|
18
|
+
* Routing is not modelled. A consumer that wants rotation (a latency record
|
|
19
|
+
* whose health check is unhealthy is out of rotation) derives it from the
|
|
20
|
+
* graph's edges and these states.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import type { UnobservedEntity, UnobservedReason } from "./observation";
|
|
24
|
+
import { unobservedAll } from "./observation";
|
|
25
|
+
|
|
26
|
+
/** The versioned shape `chant lifecycle health --json` prints. The JSON Schema is `./health.schema.json`. */
|
|
27
|
+
export const HEALTH_SCHEMA = "chant-health/v1";
|
|
28
|
+
|
|
29
|
+
/** The `$id` of `./health.schema.json`, which the package exports as `@intentius/chant/health.schema.json`. */
|
|
30
|
+
export const HEALTH_SCHEMA_ID = "https://intentius.io/chant/schemas/health/v1/health.schema.json";
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* `healthy` and `unhealthy` are what the provider reported. `unknown` means the
|
|
34
|
+
* read succeeded and the provider's answer does not decide either way (a
|
|
35
|
+
* deployment still rolling, a service scaled to zero, a condition pending).
|
|
36
|
+
*/
|
|
37
|
+
export type HealthState = "healthy" | "unhealthy" | "unknown";
|
|
38
|
+
|
|
39
|
+
export interface HealthEntry {
|
|
40
|
+
/** Entity type, e.g. `AWS::ECS::Service`. */
|
|
41
|
+
type: string;
|
|
42
|
+
state: HealthState;
|
|
43
|
+
/** One short line saying why, in the provider's terms. */
|
|
44
|
+
detail: string;
|
|
45
|
+
/** When chant read it (ISO 8601). */
|
|
46
|
+
observedAt: string;
|
|
47
|
+
/** When the provider last checked or changed the state, when it says (ISO 8601). */
|
|
48
|
+
checkedAt?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** What one lexicon's health read returns. */
|
|
52
|
+
export interface HealthReadResult {
|
|
53
|
+
/** Read, keyed by chant entity name, which is the graph's node id. */
|
|
54
|
+
entities: Record<string, HealthEntry>;
|
|
55
|
+
/** Could not be read. Never folded into `unhealthy`. */
|
|
56
|
+
unobserved: Record<string, UnobservedEntity>;
|
|
57
|
+
/** The provider says these are not deployed. */
|
|
58
|
+
absent: string[];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** The options core passes a lexicon's health read. They mirror `observeResourcesDeep`'s. */
|
|
62
|
+
export interface HealthReadOptions {
|
|
63
|
+
environment: string;
|
|
64
|
+
buildOutput: string;
|
|
65
|
+
/** Only entities whose type is in the lexicon's `healthTypes`. */
|
|
66
|
+
entities: Map<string, { entityType: string; props: Record<string, unknown> }>;
|
|
67
|
+
stack?: string;
|
|
68
|
+
region?: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** The two plugin members a lexicon implements to answer a health read. */
|
|
72
|
+
export interface HealthLexicon {
|
|
73
|
+
name: string;
|
|
74
|
+
/** Entity types {@link observeHealth} can read. Data, so core picks the entities without calling the reader. */
|
|
75
|
+
healthTypes?: readonly string[];
|
|
76
|
+
observeHealth?(options: HealthReadOptions): Promise<HealthReadResult>;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The entities of one lexicon a health read applies to: those whose type the
|
|
81
|
+
* lexicon names in `healthTypes`.
|
|
82
|
+
*/
|
|
83
|
+
export function healthApplicable(
|
|
84
|
+
plugin: HealthLexicon,
|
|
85
|
+
entities: Map<string, { entityType: string; props: Record<string, unknown> }>,
|
|
86
|
+
): Map<string, { entityType: string; props: Record<string, unknown> }> {
|
|
87
|
+
const types = new Set(plugin.healthTypes ?? []);
|
|
88
|
+
const out = new Map<string, { entityType: string; props: Record<string, unknown> }>();
|
|
89
|
+
for (const [name, entity] of entities) if (types.has(entity.entityType)) out.set(name, entity);
|
|
90
|
+
return out;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Run one lexicon's health read. Never throws: a thrown reader reports every
|
|
95
|
+
* applicable entity `read-failed`. The result is made total, so an entity the
|
|
96
|
+
* reader forgot comes back `unobserved` rather than missing.
|
|
97
|
+
*/
|
|
98
|
+
export async function observeHealthFor(
|
|
99
|
+
plugin: HealthLexicon,
|
|
100
|
+
options: HealthReadOptions,
|
|
101
|
+
): Promise<HealthReadResult> {
|
|
102
|
+
const names = [...options.entities.keys()];
|
|
103
|
+
if (names.length === 0) return { entities: {}, unobserved: {}, absent: [] };
|
|
104
|
+
if (!plugin.observeHealth) {
|
|
105
|
+
return {
|
|
106
|
+
entities: {},
|
|
107
|
+
unobserved: unobservedAll(names, "unsupported-kind", `${plugin.name} has no health read`, options.entities),
|
|
108
|
+
absent: [],
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
let result: HealthReadResult;
|
|
112
|
+
try {
|
|
113
|
+
result = await plugin.observeHealth(options);
|
|
114
|
+
} catch (err) {
|
|
115
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
116
|
+
return { entities: {}, unobserved: unobservedAll(names, "read-failed", message, options.entities), absent: [] };
|
|
117
|
+
}
|
|
118
|
+
return totalHealth(names, result, options.entities);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Put every asked-about name in exactly one of the three places. A name in
|
|
123
|
+
* `entities` wins over the others, then `absent`, then `unobserved`; a name in
|
|
124
|
+
* none of them becomes `read-failed`.
|
|
125
|
+
*/
|
|
126
|
+
export function totalHealth(
|
|
127
|
+
names: string[],
|
|
128
|
+
result: HealthReadResult,
|
|
129
|
+
types?: Map<string, { entityType: string }>,
|
|
130
|
+
): HealthReadResult {
|
|
131
|
+
const entities: Record<string, HealthEntry> = {};
|
|
132
|
+
const unobserved: Record<string, UnobservedEntity> = {};
|
|
133
|
+
const absentSet = new Set(result.absent ?? []);
|
|
134
|
+
const absent: string[] = [];
|
|
135
|
+
for (const name of names) {
|
|
136
|
+
const entry = result.entities?.[name];
|
|
137
|
+
if (entry) entities[name] = entry;
|
|
138
|
+
else if (absentSet.has(name)) absent.push(name);
|
|
139
|
+
else if (result.unobserved?.[name]) unobserved[name] = result.unobserved[name];
|
|
140
|
+
else Object.assign(unobserved, unobservedAll([name], "read-failed", "the health read returned no answer for this entity", types));
|
|
141
|
+
}
|
|
142
|
+
return { entities, unobserved, absent };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Counts across a health read, for the summary line and `--json`. */
|
|
146
|
+
export interface HealthSummary {
|
|
147
|
+
healthy: number;
|
|
148
|
+
unhealthy: number;
|
|
149
|
+
unknown: number;
|
|
150
|
+
unobserved: number;
|
|
151
|
+
absent: number;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function summarizeHealth(results: Iterable<HealthReadResult>): HealthSummary {
|
|
155
|
+
const summary: HealthSummary = { healthy: 0, unhealthy: 0, unknown: 0, unobserved: 0, absent: 0 };
|
|
156
|
+
for (const r of results) {
|
|
157
|
+
for (const entry of Object.values(r.entities)) summary[entry.state]++;
|
|
158
|
+
summary.unobserved += Object.keys(r.unobserved).length;
|
|
159
|
+
summary.absent += r.absent.length;
|
|
160
|
+
}
|
|
161
|
+
return summary;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** A health entry, for a reader building its result. */
|
|
165
|
+
export function healthEntry(
|
|
166
|
+
type: string,
|
|
167
|
+
state: HealthState,
|
|
168
|
+
detail: string,
|
|
169
|
+
observedAt: string,
|
|
170
|
+
checkedAt?: string,
|
|
171
|
+
): HealthEntry {
|
|
172
|
+
return { type, state, detail, observedAt, ...(checkedAt ? { checkedAt } : {}) };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** A hole, for a reader building its result. */
|
|
176
|
+
export function healthHole(type: string, reason: UnobservedReason, detail: string): UnobservedEntity {
|
|
177
|
+
return { type, reason, detail };
|
|
178
|
+
}
|