@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
|
@@ -41,12 +41,16 @@
|
|
|
41
41
|
* pipelines run as chant Ops, such as one on GitLab or Forgejo, gets green
|
|
42
42
|
* and revoked tags with no forge check runs. A phase's patterns then match
|
|
43
43
|
* Op names, a run counts for the commit its record names as `gitSha`, and
|
|
44
|
-
* the latest run of each Op decides, by its end time.
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
44
|
+
* the latest run of each Op decides, by its end time. Signing is opt-in
|
|
45
|
+
* (#4156): an unsigned run counts, with no signer, and a run whose statement
|
|
46
|
+
* a runner key holding the `runner` role at base signed counts with that
|
|
47
|
+
* signer (`./trust/op-run-statement.ts`). A run whose statement is there and
|
|
48
|
+
* does not verify is ignored and reported, so it neither greens nor revokes
|
|
49
|
+
* a commit. With `ci.green.signedSince`, an unsigned run counts only on a
|
|
50
|
+
* strict first-parent ancestor of that commit, as an unsigned tag does. A
|
|
51
|
+
* tick given a runner key signs the tags it makes, and each run in the
|
|
52
|
+
* tag's record names the Op run's id and carries its statement when it has
|
|
53
|
+
* one.
|
|
50
54
|
*/
|
|
51
55
|
|
|
52
56
|
import { judgeCommits, mergeCommits, readWorkIndex, type CommitLink } from "./commit-links";
|
|
@@ -55,7 +59,10 @@ import { readDeclaration, readerVersion, WORKSPACE_ERROR_CODES, WorkspaceReadErr
|
|
|
55
59
|
import type { CheckRun, CiForge, OpRunRef } from "./ci-green-forge";
|
|
56
60
|
import type { OpRunRecord } from "../op/runtime";
|
|
57
61
|
import { readRunLedger } from "../lifecycle/run-ledger";
|
|
58
|
-
import { fetchLifecycleStatus, listFilesInDir, listLedgerTrees } from "../lifecycle/git";
|
|
62
|
+
import { fetchLifecycleStatus, listFilesInDir, listLedgerTrees, pushLifecycleStatus, writeLedgerFiles } from "../lifecycle/git";
|
|
63
|
+
import { ledgerRecordingOff } from "../lifecycle/no-record";
|
|
64
|
+
import { agedOut, buildRequestAnnotation, CI_BUILD_TAG_PREFIX, planBuilds, readBuildRequests, readCiRecords, recordOf, recordPath, requestedBuild, sameRecord, type BuildRequest, type CiRecord } from "./ci-builds";
|
|
65
|
+
import { dispatchableWorkflows, readWorkflowSources } from "./ci-green-workflow";
|
|
59
66
|
import { signOpRunRecord, verifyOpRunStatement } from "./trust/op-run-statement";
|
|
60
67
|
import type { ReasonCode } from "./reason-codes";
|
|
61
68
|
import { gitTop } from "./tree";
|
|
@@ -90,7 +97,15 @@ export class CiGreenError extends Error {
|
|
|
90
97
|
|
|
91
98
|
// ── Judging check runs ───────────────────────────────────────────────────────
|
|
92
99
|
|
|
93
|
-
|
|
100
|
+
/**
|
|
101
|
+
* `unbuilt` (#4169): the phase's runs were cancelled, or, for a phase that
|
|
102
|
+
* builds only the newest commit, a newer commit's run superseded this one's.
|
|
103
|
+
* Neither is a failure, and neither is a pass.
|
|
104
|
+
*/
|
|
105
|
+
export type Verdict = "pass" | "fail" | "pending" | "unbuilt";
|
|
106
|
+
|
|
107
|
+
/** Conclusions that say a run was stopped, or never started, rather than that it failed (#4169). */
|
|
108
|
+
const UNBUILT_CONCLUSIONS = new Set(["cancelled", "stale"]);
|
|
94
109
|
|
|
95
110
|
/** Whether a check run's name matches a pattern: the whole name, `*` standing for any run of characters. */
|
|
96
111
|
export function matchesCheckRun(pattern: string, name: string): boolean {
|
|
@@ -128,12 +143,27 @@ export interface PhaseVerdict {
|
|
|
128
143
|
runs: RunVerdict[];
|
|
129
144
|
/** Patterns no check run matches yet, which leave the phase undecided. */
|
|
130
145
|
unmatched: string[];
|
|
146
|
+
/**
|
|
147
|
+
* Why an `unbuilt` or held `pending` phase is so (#4169): its runs were
|
|
148
|
+
* `cancelled`, a newer commit's run `superseded` it, its retry or a
|
|
149
|
+
* requested build is still to come (`retrying`, `requested`), or a
|
|
150
|
+
* request no runner served (`request-unserved`).
|
|
151
|
+
*/
|
|
152
|
+
reason?: PhaseReason;
|
|
153
|
+
/** For a superseded phase, the newer commit whose run covers it (#4169). */
|
|
154
|
+
coveredBy?: string;
|
|
155
|
+
/** The commit a bisect found broke this phase, on that commit and the commits after it (#4169). */
|
|
156
|
+
culprit?: string;
|
|
131
157
|
}
|
|
132
158
|
|
|
159
|
+
/** Why a phase is `unbuilt`, or held `pending` (#4169). */
|
|
160
|
+
export type PhaseReason = "cancelled" | "superseded" | "retrying" | "requested" | "request-unserved";
|
|
161
|
+
|
|
133
162
|
function runVerdict(phase: CiPhase, r: CheckRun): Verdict {
|
|
134
163
|
if (r.status !== "completed") return "pending";
|
|
135
164
|
if (r.conclusion === "success") return "pass";
|
|
136
165
|
if (r.conclusion === "skipped") return phase.skipped === "pass" ? "pass" : "fail";
|
|
166
|
+
if (r.conclusion !== null && UNBUILT_CONCLUSIONS.has(r.conclusion)) return "unbuilt";
|
|
137
167
|
return "fail";
|
|
138
168
|
}
|
|
139
169
|
|
|
@@ -149,12 +179,15 @@ export function evaluatePhase(phase: CiPhase, latest: readonly CheckRun[]): Phas
|
|
|
149
179
|
runs.push({ name: r.name, id: r.id, status: r.status, conclusion: r.conclusion, url: r.url, verdict: runVerdict(phase, r), ...(r.opRun ? { opRun: r.opRun } : {}), ...(r.failureCode ? { failureCode: r.failureCode } : {}) });
|
|
150
180
|
}
|
|
151
181
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
182
|
+
return { phase: phase.name, ...phaseVerdictOf(runs, unmatched), runs, unmatched };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** A phase's verdict from its runs: a failure wins, then a run still going or a pattern no run matches, then a cancelled run. */
|
|
186
|
+
export function phaseVerdictOf(runs: readonly RunVerdict[], unmatched: readonly string[]): { verdict: Verdict; reason?: PhaseReason } {
|
|
187
|
+
if (runs.some((r) => r.verdict === "fail")) return { verdict: "fail" };
|
|
188
|
+
if (runs.some((r) => r.verdict === "pending") || unmatched.length > 0) return { verdict: "pending" };
|
|
189
|
+
if (runs.some((r) => r.verdict === "unbuilt")) return { verdict: "unbuilt", reason: "cancelled" };
|
|
190
|
+
return { verdict: "pass" };
|
|
158
191
|
}
|
|
159
192
|
|
|
160
193
|
export interface CommitVerdict {
|
|
@@ -168,10 +201,19 @@ export interface CommitVerdict {
|
|
|
168
201
|
/** Judge a commit: it fails when a required phase fails, and passes when every one passes. */
|
|
169
202
|
export function evaluateCommit(green: CiGreen, runs: readonly CheckRun[]): CommitVerdict {
|
|
170
203
|
const latest = latestAttempts(runs);
|
|
171
|
-
|
|
204
|
+
return commitVerdictOf(green.require.map((name) => evaluatePhase(green.phases.find((p) => p.name === name)!, latest)));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* A commit's verdict from its required phases: it fails when one fails, is
|
|
209
|
+
* pending while one is, is unbuilt when one is unbuilt (#4169), and passes
|
|
210
|
+
* when every one passes.
|
|
211
|
+
*/
|
|
212
|
+
export function commitVerdictOf(phases: PhaseVerdict[]): CommitVerdict {
|
|
172
213
|
const failed = phases.find((p) => p.verdict === "fail");
|
|
173
214
|
if (failed) return { verdict: "fail", phases, failure: { phase: failed.phase, run: failed.runs.find((r) => r.verdict === "fail")! } };
|
|
174
|
-
|
|
215
|
+
const verdict: Verdict = phases.some((p) => p.verdict === "pending") ? "pending" : phases.some((p) => p.verdict === "unbuilt") ? "unbuilt" : "pass";
|
|
216
|
+
return { verdict, phases, failure: null };
|
|
175
217
|
}
|
|
176
218
|
|
|
177
219
|
// ── The tick ─────────────────────────────────────────────────────────────────
|
|
@@ -237,7 +279,7 @@ export function windowMs(window: string): number {
|
|
|
237
279
|
*/
|
|
238
280
|
const runRecord = (r: RunVerdict) =>
|
|
239
281
|
r.opRun
|
|
240
|
-
? { name: r.name, id: null, conclusion: r.conclusion, url: r.url, opRun: { id: r.opRun.id, env: r.opRun.env, signer: r.opRun.signer, statement: r.opRun.statement } }
|
|
282
|
+
? { name: r.name, id: null, conclusion: r.conclusion, url: r.url, opRun: { id: r.opRun.id, env: r.opRun.env, signer: r.opRun.signer, ...(r.opRun.statement ? { statement: r.opRun.statement } : {}) } }
|
|
241
283
|
: { name: r.name, id: r.id, conclusion: r.conclusion, url: r.url };
|
|
242
284
|
|
|
243
285
|
/** `source: "runs"` on a record made from chant runs (#4044); nothing for check runs, so their records read as before. */
|
|
@@ -443,6 +485,21 @@ export function readCiTags(top: string, green: CiGreen | null = null, base: stri
|
|
|
443
485
|
return out;
|
|
444
486
|
}
|
|
445
487
|
|
|
488
|
+
/**
|
|
489
|
+
* Whether the workspace at `cwd` requires signed runs (#4156): its
|
|
490
|
+
* `ci.green.signedSince`, or null when it sets none, declares no
|
|
491
|
+
* `ci.green`, or has no declaration chant can read. Signing is opt-in, so
|
|
492
|
+
* only this makes `ci serve` refuse to start without a trusted runner key
|
|
493
|
+
* and `ci run` and `chant run` warn about a run they leave unsigned.
|
|
494
|
+
*/
|
|
495
|
+
export function signedRunsRequired(cwd: string): string | null {
|
|
496
|
+
try {
|
|
497
|
+
return readDeclaration(locateWorkspace(cwd).tree).ci?.green?.signedSince ?? null;
|
|
498
|
+
} catch {
|
|
499
|
+
return null;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
446
503
|
/** The declaration's `ci.green`, read where `cwd` is, and the git top. */
|
|
447
504
|
function readGreen(cwd: string): { green: CiGreen; top: string; workspace: { name: string; root: string; file: string } } {
|
|
448
505
|
const located = locateWorkspace(cwd);
|
|
@@ -486,6 +543,14 @@ export interface TickResult {
|
|
|
486
543
|
commits: TickCommit[];
|
|
487
544
|
/** The tags made and pushed, or that would be under `dryRun`. */
|
|
488
545
|
made: string[];
|
|
546
|
+
/** The builds asked for (#4169), as `ci/build/<sha>` tags made and pushed, or that would be under `dryRun`. */
|
|
547
|
+
requested: BuildRequest[];
|
|
548
|
+
/** The failed runs asked to run again through the forge (#4169). */
|
|
549
|
+
retried: { sha: string; run: string }[];
|
|
550
|
+
/** The `_ci/<sha>.json` records written (#4169). Empty under `dryRun`, or when recording is off. */
|
|
551
|
+
recorded: string[];
|
|
552
|
+
/** What the tick could not do and carried on without: a retry, a request, a record. */
|
|
553
|
+
warnings: string[];
|
|
489
554
|
/** The runner the tags were signed as (#4031), or null when they carry no statement. */
|
|
490
555
|
signer: string | null;
|
|
491
556
|
/** Where the phase results came from (#4044). */
|
|
@@ -553,28 +618,32 @@ export async function ciTick(req: TickRequest): Promise<TickResult> {
|
|
|
553
618
|
`ci.green.signedSince is ${green.signedSince}, so every tag the tick makes needs a statement signed by a runner key, and none was given: pass --key <runner key.pem> or set CHANT_RUNNER_KEY`,
|
|
554
619
|
);
|
|
555
620
|
}
|
|
556
|
-
if (green.source === "runs" && req.runnerKey === undefined && !req.dryRun) {
|
|
557
|
-
throw new Error("the tick reads chant runs (source runs), and every tag it makes from them is signed by a runner key, and none was given: pass --key <runner key.pem> or set CHANT_RUNNER_KEY");
|
|
558
|
-
}
|
|
559
621
|
const signer = req.runnerKey !== undefined ? ciTagSigner(top, base, req.runnerKey) : undefined;
|
|
560
622
|
const links = await commitLinks(req.cwd, top, tracking, commits.map((c) => c.sha));
|
|
561
623
|
const tags = readCiTags(top, green, base);
|
|
624
|
+
const warnings: string[] = [];
|
|
562
625
|
let runs: OpRunSource | undefined;
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
626
|
+
// The runs and the records (#4169) live on chant/lifecycle, which a CI checkout may not have yet.
|
|
627
|
+
const fetched = await fetchLifecycleStatus({ cwd: top });
|
|
628
|
+
const ledgerOk = fetched.status !== "diverged" && fetched.status !== "unreachable";
|
|
629
|
+
if (!ledgerOk) {
|
|
630
|
+
const why = `could not fetch chant/lifecycle from ${fetched.remote}: ${fetched.status === "diverged" ? "the local branch and the remote's have diverged" : fetched.stderr}`;
|
|
631
|
+
if (green.source === "runs") throw new Error(why.replace("chant/lifecycle", "chant/lifecycle, where the Op runs are recorded,"));
|
|
632
|
+
warnings.push(`${why}; no _ci records were read or written`);
|
|
570
633
|
}
|
|
634
|
+
if (green.source === "runs") runs = await readOpRunSource(req.cwd, top, base, green.signedSince);
|
|
635
|
+
const runsBy = new Map<string, CheckRun[]>();
|
|
571
636
|
const planned = await planTick({
|
|
572
637
|
green,
|
|
573
638
|
commits,
|
|
574
639
|
greenTags: new Set(tags.green.keys()),
|
|
575
640
|
revokedTags: new Set(tags.revoked.keys()),
|
|
576
641
|
ignored: tags.ignored,
|
|
577
|
-
checkRuns: async (sha) =>
|
|
642
|
+
checkRuns: async (sha) => {
|
|
643
|
+
const got = runs ? runs.checkRuns(sha) : await forge!.checkRuns(sha);
|
|
644
|
+
runsBy.set(sha, got);
|
|
645
|
+
return got;
|
|
646
|
+
},
|
|
578
647
|
now,
|
|
579
648
|
signer,
|
|
580
649
|
});
|
|
@@ -585,6 +654,10 @@ export async function ciTick(req: TickRequest): Promise<TickResult> {
|
|
|
585
654
|
if (ignored && ignored.length > 0) c.ignoredRuns = ignored;
|
|
586
655
|
}
|
|
587
656
|
}
|
|
657
|
+
const records = ledgerOk ? readCiRecords(top) : new Map<string, CiRecord>();
|
|
658
|
+
const requests = readBuildRequests(top);
|
|
659
|
+
const retryVia = green.source === "runs" ? "request" : forge?.rerun ? "forge" : null;
|
|
660
|
+
const builds = planBuilds({ green, commits: planned, runs: runsBy, requests, records, now, retryVia });
|
|
588
661
|
const due = planned.filter((c) => c.made !== null);
|
|
589
662
|
if (!req.dryRun && due.length > 0) {
|
|
590
663
|
const ident = taggerConfig(top);
|
|
@@ -598,14 +671,234 @@ export async function ciTick(req: TickRequest): Promise<TickResult> {
|
|
|
598
671
|
throw new Error(`could not push ${due.map((c) => c.made!.tag).join(", ")} to ${remote}: ${cause ? `${cause}\n` : ""}${stderr}`);
|
|
599
672
|
}
|
|
600
673
|
}
|
|
601
|
-
|
|
674
|
+
const retried = builds.retries.map((r) => ({ sha: r.sha, run: r.run.name }));
|
|
675
|
+
let recorded: string[] = [];
|
|
676
|
+
if (!req.dryRun) {
|
|
677
|
+
const asked = await askForBuilds({ top, remote, green, forge, builds, requests, records, planned, warnings });
|
|
678
|
+
recorded = ledgerOk && !ledgerRecordingOff() ? await writeRecords({ top, green, planned, records, builds, requests: asked.live, dispatched: asked.dispatched, now, warnings }) : [];
|
|
679
|
+
}
|
|
680
|
+
return {
|
|
681
|
+
branch: green.branch,
|
|
682
|
+
window: green.window,
|
|
683
|
+
ref: tracking,
|
|
684
|
+
commits: planned,
|
|
685
|
+
made: due.map((c) => c.made!.tag),
|
|
686
|
+
requested: builds.requests,
|
|
687
|
+
retried,
|
|
688
|
+
recorded,
|
|
689
|
+
warnings,
|
|
690
|
+
signer: signer?.principal ?? null,
|
|
691
|
+
source: green.source,
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/**
|
|
696
|
+
* Make what the plan asks for (#4169): push each new request tag and start
|
|
697
|
+
* the workflows holding its phases at it on GitHub, start any older request
|
|
698
|
+
* no tick has started yet, run the failed jobs again, and delete the tags of
|
|
699
|
+
* requests that are done. Each step that fails is a warning, never the
|
|
700
|
+
* tick's failure: the green tags are already pushed.
|
|
701
|
+
*/
|
|
702
|
+
async function askForBuilds(input: {
|
|
703
|
+
top: string;
|
|
704
|
+
remote: string;
|
|
705
|
+
green: CiGreen;
|
|
706
|
+
forge: CiForge | undefined;
|
|
707
|
+
builds: ReturnType<typeof planBuilds>;
|
|
708
|
+
requests: Map<string, BuildRequest>;
|
|
709
|
+
records: Map<string, CiRecord>;
|
|
710
|
+
planned: TickCommit[];
|
|
711
|
+
warnings: string[];
|
|
712
|
+
}): Promise<{ live: Map<string, BuildRequest>; dispatched: Set<string> }> {
|
|
713
|
+
const { top, remote, green, forge, builds, warnings } = input;
|
|
714
|
+
const live = new Map(input.requests);
|
|
715
|
+
for (const tag of builds.resolved) live.delete(tag.slice(CI_BUILD_TAG_PREFIX.length));
|
|
716
|
+
if (builds.requests.length > 0) {
|
|
717
|
+
const ident = taggerConfig(top);
|
|
718
|
+
const made: BuildRequest[] = [];
|
|
719
|
+
for (const r of builds.requests) {
|
|
720
|
+
if (tryGit(top, [...ident, "tag", "-a", "-m", buildRequestAnnotation(r), r.tag, r.commit]) === null) warnings.push(`could not make ${r.tag}`);
|
|
721
|
+
else made.push(r);
|
|
722
|
+
}
|
|
723
|
+
if (made.length > 0) {
|
|
724
|
+
try {
|
|
725
|
+
git(top, ["push", "--quiet", remote, ...made.map((r) => `refs/tags/${r.tag}`)]);
|
|
726
|
+
for (const r of made) live.set(r.commit, r);
|
|
727
|
+
} catch (err) {
|
|
728
|
+
for (const r of made) tryGit(top, ["tag", "-d", r.tag]);
|
|
729
|
+
const stderr = stderrOf(err);
|
|
730
|
+
warnings.push(`could not push ${made.map((r) => r.tag).join(", ")} to ${remote}: ${pushRefusalCause(stderr) ?? stderr}`);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
const dispatched = new Set<string>();
|
|
735
|
+
if (green.source === "checks" && forge?.dispatch) {
|
|
736
|
+
const sources = readWorkflowSources(top, "");
|
|
737
|
+
for (const r of live.values()) {
|
|
738
|
+
if (input.records.get(r.commit)?.request?.dispatched && input.records.get(r.commit)?.request?.tag === r.tag) continue;
|
|
739
|
+
// A run already going at the commit was started for it, by the tick before or by `ci run --at`.
|
|
740
|
+
const c = input.planned.find((x) => x.sha === r.commit);
|
|
741
|
+
if (c?.verdict?.phases.some((p) => r.phases.includes(p.phase) && p.runs.some((x) => x.status !== "completed"))) {
|
|
742
|
+
dispatched.add(r.commit);
|
|
743
|
+
continue;
|
|
744
|
+
}
|
|
745
|
+
const { dispatchable, without } = dispatchableWorkflows(green, r.phases, sources);
|
|
746
|
+
for (const w of without) warnings.push(`${w} holds a run of ${r.phases.join(", ")} and has no workflow_dispatch trigger, so ${r.tag} can't start it; regenerate it with chant ci workflow`);
|
|
747
|
+
let ok = dispatchable.length > 0;
|
|
748
|
+
for (const w of dispatchable) {
|
|
749
|
+
try {
|
|
750
|
+
await forge.dispatch(w, r.tag);
|
|
751
|
+
} catch (err) {
|
|
752
|
+
ok = false;
|
|
753
|
+
warnings.push(`could not start ${w} at ${r.tag}: ${err instanceof Error ? err.message : String(err)}`);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
if (ok) dispatched.add(r.commit);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
if (forge?.rerun) {
|
|
760
|
+
for (const r of builds.retries) {
|
|
761
|
+
try {
|
|
762
|
+
await forge.rerun(r.run);
|
|
763
|
+
} catch (err) {
|
|
764
|
+
warnings.push(`could not run ${r.run.name} at ${r.sha.slice(0, 12)} again, so its failure counts once the retry's hour is up: ${err instanceof Error ? err.message : String(err)}`);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
if (builds.resolved.length > 0) {
|
|
769
|
+
for (const tag of builds.resolved) tryGit(top, ["tag", "-d", tag]);
|
|
770
|
+
if (tryGit(top, ["push", "--quiet", remote, ...builds.resolved.map((t) => `:refs/tags/${t}`)]) === null) {
|
|
771
|
+
warnings.push(`could not delete ${builds.resolved.join(", ")} on ${remote}; a done request asks for nothing, so they can stay`);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
return { live, dispatched };
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
/**
|
|
778
|
+
* Write each judged commit's record, and rewrite a record whose commit left
|
|
779
|
+
* the window still pending as unbuilt (#4169), in one commit on
|
|
780
|
+
* `chant/lifecycle`, and push it. Only records whose content changed are
|
|
781
|
+
* written. A push another writer beat is tried once more on the new tip.
|
|
782
|
+
*/
|
|
783
|
+
async function writeRecords(input: {
|
|
784
|
+
top: string;
|
|
785
|
+
green: CiGreen;
|
|
786
|
+
planned: TickCommit[];
|
|
787
|
+
records: Map<string, CiRecord>;
|
|
788
|
+
builds: ReturnType<typeof planBuilds>;
|
|
789
|
+
requests: Map<string, BuildRequest>;
|
|
790
|
+
dispatched: Set<string>;
|
|
791
|
+
now: Date;
|
|
792
|
+
warnings: string[];
|
|
793
|
+
}): Promise<string[]> {
|
|
794
|
+
const { top, green, planned, records, builds, requests, now, warnings } = input;
|
|
795
|
+
const writes: { path: string; content: string }[] = [];
|
|
796
|
+
const inWindow = new Set(planned.map((c) => c.sha));
|
|
797
|
+
for (const c of planned) {
|
|
798
|
+
const previous = records.get(c.sha);
|
|
799
|
+
let record = recordOf({ green, commit: c, previous, retries: builds.retries, requests, now });
|
|
800
|
+
if (record.request) {
|
|
801
|
+
const started = input.dispatched.has(c.sha) || (previous?.request?.tag === record.request.tag && previous.request.dispatched === true);
|
|
802
|
+
if (started) record = { ...record, request: { ...record.request, dispatched: true } };
|
|
803
|
+
}
|
|
804
|
+
if (!sameRecord(previous, record)) writes.push({ path: recordPath(c.sha), content: `${JSON.stringify(record, null, 2)}\n` });
|
|
805
|
+
}
|
|
806
|
+
for (const [sha, r] of records) {
|
|
807
|
+
if (inWindow.has(sha) || r.state !== "pending") continue;
|
|
808
|
+
writes.push({ path: recordPath(sha), content: `${JSON.stringify(agedOut(r, now), null, 2)}\n` });
|
|
809
|
+
}
|
|
810
|
+
if (writes.length === 0) return [];
|
|
811
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
812
|
+
try {
|
|
813
|
+
await writeLedgerFiles(writes, `ci: ${writes.length} commit record${writes.length === 1 ? "" : "s"}`, { cwd: top });
|
|
814
|
+
} catch (err) {
|
|
815
|
+
warnings.push(`could not write the _ci records: ${err instanceof Error ? err.message : String(err)}`);
|
|
816
|
+
return [];
|
|
817
|
+
}
|
|
818
|
+
const pushed = await pushLifecycleStatus({ cwd: top });
|
|
819
|
+
if (pushed.status === "pushed" || pushed.status === "no-remote" || pushed.status === "not-recorded") return writes.map((w) => w.path);
|
|
820
|
+
if (pushed.status !== "stale" || attempt === 1) {
|
|
821
|
+
warnings.push(`could not push the _ci records: ${pushed.stderr}`);
|
|
822
|
+
return [];
|
|
823
|
+
}
|
|
824
|
+
// Another writer pushed first: drop this commit, follow the remote, and write again on its tip.
|
|
825
|
+
tryGit(top, ["update-ref", "refs/heads/chant/lifecycle", "refs/heads/chant/lifecycle^"]);
|
|
826
|
+
const again = await fetchLifecycleStatus({ cwd: top });
|
|
827
|
+
if (again.status === "diverged" || again.status === "unreachable") {
|
|
828
|
+
warnings.push(`could not push the _ci records: chant/lifecycle moved on the remote and could not be fetched again`);
|
|
829
|
+
return [];
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
return [];
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
// ── A build on demand (#4169) ────────────────────────────────────────────────
|
|
836
|
+
|
|
837
|
+
export interface RequestBuildResult {
|
|
838
|
+
request: BuildRequest;
|
|
839
|
+
/** Whether a request tag for the commit was already there, in which case nothing was made. */
|
|
840
|
+
existed: boolean;
|
|
841
|
+
/** The workflows started at the tag now, on GitHub. Empty when the next tick or `ci serve` starts it. */
|
|
842
|
+
dispatched: string[];
|
|
843
|
+
warnings: string[];
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
/**
|
|
847
|
+
* `chant ci run --at <sha>`: ask for every required phase to be built at a
|
|
848
|
+
* commit of the branch, by pushing `ci/build/<sha>`. Given a forge that can
|
|
849
|
+
* start workflows, and with the source check runs, it starts the workflows
|
|
850
|
+
* holding the phases at the tag now; otherwise the next tick does, or `ci
|
|
851
|
+
* serve --build-command` for chant runs. Once the phases pass, the tick tags
|
|
852
|
+
* the commit green like any other, so it can be cut.
|
|
853
|
+
*/
|
|
854
|
+
export async function requestBuild(req: { cwd: string; at: string; remote?: string; forge?: CiForge; now?: Date }): Promise<RequestBuildResult> {
|
|
855
|
+
const read = readGreen(req.cwd);
|
|
856
|
+
const { green, top } = read;
|
|
857
|
+
const remote = req.remote ?? "origin";
|
|
858
|
+
const tracking = `refs/remotes/${remote}/${green.branch}`;
|
|
859
|
+
try {
|
|
860
|
+
git(top, ["fetch", "--quiet", "--no-tags", remote, `+refs/heads/${green.branch}:${tracking}`, `+refs/tags/${CI_BUILD_TAG_PREFIX}*:refs/tags/${CI_BUILD_TAG_PREFIX}*`]);
|
|
861
|
+
} catch (err) {
|
|
862
|
+
throw new Error(`could not fetch ${green.branch} and the build requests from ${remote}: ${stderrOf(err)}`);
|
|
863
|
+
}
|
|
864
|
+
if (req.at.startsWith("-") || tryGit(top, ["rev-parse", "--verify", "--quiet", `${req.at}^{commit}`]) === null) throw new WorkspaceReadError("revision-unknown", `--at ${req.at} names no commit in this repository`);
|
|
865
|
+
const sha = git(top, ["rev-parse", `${req.at}^{commit}`]);
|
|
866
|
+
const onBranch = git(top, ["rev-list", "--first-parent", tracking]).split("\n").includes(sha);
|
|
867
|
+
if (!onBranch) throw new Error(`${sha.slice(0, 12)} is not a first-parent commit of ${remote}/${green.branch}, and only those are judged green`);
|
|
868
|
+
const existing = readBuildRequests(top).get(sha);
|
|
869
|
+
const request = existing ?? requestedBuild(green, sha, green.require, req.now ?? new Date());
|
|
870
|
+
const warnings: string[] = [];
|
|
871
|
+
if (!existing) {
|
|
872
|
+
git(top, [...taggerConfig(top), "tag", "-a", "-m", buildRequestAnnotation(request), request.tag, sha]);
|
|
873
|
+
try {
|
|
874
|
+
git(top, ["push", "--quiet", remote, `refs/tags/${request.tag}`]);
|
|
875
|
+
} catch (err) {
|
|
876
|
+
tryGit(top, ["tag", "-d", request.tag]);
|
|
877
|
+
const stderr = stderrOf(err);
|
|
878
|
+
throw new Error(`could not push ${request.tag} to ${remote}: ${pushRefusalCause(stderr) ?? stderr}`);
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
const dispatched: string[] = [];
|
|
882
|
+
if (green.source === "checks" && req.forge?.dispatch) {
|
|
883
|
+
const { dispatchable, without } = dispatchableWorkflows(green, request.phases, readWorkflowSources(top, ""));
|
|
884
|
+
for (const w of without) warnings.push(`${w} holds a run of a required phase and has no workflow_dispatch trigger, so it can't be started at ${request.tag}`);
|
|
885
|
+
for (const w of dispatchable) {
|
|
886
|
+
try {
|
|
887
|
+
await req.forge.dispatch(w, request.tag);
|
|
888
|
+
dispatched.push(w);
|
|
889
|
+
} catch (err) {
|
|
890
|
+
warnings.push(`could not start ${w} at ${request.tag}: ${err instanceof Error ? err.message : String(err)}; the next tick tries again`);
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
return { request, existed: existing !== undefined, dispatched, warnings };
|
|
602
895
|
}
|
|
603
896
|
|
|
604
897
|
// ── Chant runs as the source (#4044) ─────────────────────────────────────────
|
|
605
898
|
|
|
606
899
|
/** The Op runs on the ledger, judged, as check runs by commit. */
|
|
607
900
|
export interface OpRunSource {
|
|
608
|
-
/** The
|
|
901
|
+
/** The runs on `sha` that count, each as a check run named for its Op. */
|
|
609
902
|
checkRuns(sha: string): CheckRun[];
|
|
610
903
|
/** The runs on each commit that do not count, by commit. */
|
|
611
904
|
ignored: Map<string, IgnoredOpRun[]>;
|
|
@@ -619,7 +912,7 @@ const RUN_LEDGER_FILE = /^runs__(.+)\.jsonl$/;
|
|
|
619
912
|
* going. The id is the run's end time in milliseconds, so the latest run of
|
|
620
913
|
* an Op is its latest attempt.
|
|
621
914
|
*/
|
|
622
|
-
export function opRunCheckRun(record: OpRunRecord, signer: string): CheckRun {
|
|
915
|
+
export function opRunCheckRun(record: OpRunRecord, signer: string | null): CheckRun {
|
|
623
916
|
const settled = record.status === "ok" || record.status === "fail";
|
|
624
917
|
const ended = Date.parse(record.ended);
|
|
625
918
|
return {
|
|
@@ -629,7 +922,7 @@ export function opRunCheckRun(record: OpRunRecord, signer: string): CheckRun {
|
|
|
629
922
|
conclusion: settled ? (record.status === "ok" ? "success" : "failure") : null,
|
|
630
923
|
completedAt: settled ? record.ended : null,
|
|
631
924
|
url: record.runOrigin?.url ?? null,
|
|
632
|
-
opRun: { id: record.id, env: record.env, signer, statement: record.statement
|
|
925
|
+
opRun: { id: record.id, env: record.env, signer, ...(signer !== null && record.statement ? { statement: record.statement } : {}) },
|
|
633
926
|
...(record.status === "fail" && record.failure ? { failureCode: record.failure.code } : {}),
|
|
634
927
|
};
|
|
635
928
|
}
|
|
@@ -637,11 +930,16 @@ export function opRunCheckRun(record: OpRunRecord, signer: string): CheckRun {
|
|
|
637
930
|
/**
|
|
638
931
|
* Judge every Op run record that names a commit against `.chant/trust.json`
|
|
639
932
|
* at `base` (#4044), from the run ledger the project at `cwd` writes to. A
|
|
640
|
-
* record with a valid statement becomes a check run on its `gitSha
|
|
641
|
-
*
|
|
933
|
+
* record with a valid statement becomes a check run on its `gitSha` with
|
|
934
|
+
* its signer, and an unsigned one becomes a check run with signer null
|
|
935
|
+
* (#4156). A record whose statement does not verify is kept as an ignored
|
|
936
|
+
* run. With `signedSince` (`ci.green.signedSince`) an unsigned record counts
|
|
937
|
+
* only on a strict first-parent ancestor of that commit, and is ignored
|
|
938
|
+
* elsewhere. Reads local refs only.
|
|
642
939
|
*/
|
|
643
|
-
export async function readOpRunSource(cwd: string, top: string, base: string): Promise<OpRunSource> {
|
|
940
|
+
export async function readOpRunSource(cwd: string, top: string, base: string, signedSince: string | null = null): Promise<OpRunSource> {
|
|
644
941
|
const policy = readTrustPolicy(gitRevisionSource(top, base), base);
|
|
942
|
+
const before = signedSince ? unsignedHistory(top, signedSince) : null;
|
|
645
943
|
const byCommit = new Map<string, CheckRun[]>();
|
|
646
944
|
const ignored = new Map<string, IgnoredOpRun[]>();
|
|
647
945
|
for (const env of await listLedgerTrees({ cwd })) {
|
|
@@ -651,9 +949,15 @@ export async function readOpRunSource(cwd: string, top: string, base: string): P
|
|
|
651
949
|
const { records } = await readRunLedger(env, op, { cwd });
|
|
652
950
|
for (const record of records) {
|
|
653
951
|
if (!record.gitSha) continue;
|
|
952
|
+
// #4161: a job `ci run --local` ran stays on the machine that ran it, and is never CI's verdict.
|
|
953
|
+
if (record.runOrigin?.forge === "local") continue;
|
|
654
954
|
const v = verifyOpRunStatement(record, policy.runners, policy.roles);
|
|
655
|
-
|
|
656
|
-
|
|
955
|
+
const counts = v.status === "signed" || (v.status === "unsigned" && (before === null || before.has(record.gitSha)));
|
|
956
|
+
if (counts) byCommit.set(record.gitSha, [...(byCommit.get(record.gitSha) ?? []), opRunCheckRun(record, v.status === "signed" ? v.signer : null)]);
|
|
957
|
+
else {
|
|
958
|
+
const reason = v.status === "unsigned" ? `${v.reason}; ci.green.signedSince is ${signedSince}, so a run on a commit that is not a strict first-parent ancestor of it needs one` : v.reason;
|
|
959
|
+
ignored.set(record.gitSha, [...(ignored.get(record.gitSha) ?? []), { run: record.id, op: record.op, env: record.env, status: v.status, reason }]);
|
|
960
|
+
}
|
|
657
961
|
}
|
|
658
962
|
}
|
|
659
963
|
}
|
|
@@ -739,8 +1043,18 @@ export function formatTick(r: TickResult, dryRun = false): string {
|
|
|
739
1043
|
lines.push(` ${short} revoked`);
|
|
740
1044
|
} else {
|
|
741
1045
|
const v = c.verdict!;
|
|
742
|
-
const
|
|
743
|
-
const
|
|
1046
|
+
const why = (p: PhaseVerdict) => `${p.phase}${p.reason ? ` (${p.reason}${p.coveredBy ? `, covered by ${p.coveredBy.slice(0, 12)}` : ""})` : ""}`;
|
|
1047
|
+
const waiting = v.phases.filter((p) => p.verdict === "pending").map(why);
|
|
1048
|
+
const unbuilt = v.phases.filter((p) => p.verdict === "unbuilt").map(why);
|
|
1049
|
+
const culprit = v.phases.find((p) => p.culprit)?.culprit;
|
|
1050
|
+
const what =
|
|
1051
|
+
c.before === "green"
|
|
1052
|
+
? "green"
|
|
1053
|
+
: v.verdict === "fail"
|
|
1054
|
+
? `failed: phase ${v.failure!.phase}, ${v.failure!.run.name} ${v.failure!.run.conclusion}${culprit ? `; culprit ${culprit.slice(0, 12)}` : ""}`
|
|
1055
|
+
: v.verdict === "unbuilt"
|
|
1056
|
+
? `unbuilt: ${unbuilt.join(", ")}`
|
|
1057
|
+
: `undecided: ${waiting.join(", ")}`;
|
|
744
1058
|
lines.push(` ${short} ${what}`);
|
|
745
1059
|
}
|
|
746
1060
|
const why = whyWorkUnknown(c.work);
|
|
@@ -750,6 +1064,10 @@ export function formatTick(r: TickResult, dryRun = false): string {
|
|
|
750
1064
|
if (c.blocked) lines.push(` did not make ${c.blocked}: an ignored tag holds the name. Delete it to let the tick tag the commit: git push origin :refs/tags/${c.blocked}`);
|
|
751
1065
|
}
|
|
752
1066
|
if (r.signer && r.made.length > 0) lines.push(`signed as ${r.signer}`);
|
|
1067
|
+
for (const q of r.requested) lines.push(`${dryRun ? "would ask" : "asked"} for ${q.phases.join(", ")} at ${q.commit.slice(0, 12)} (${q.reason}): ${q.tag}`);
|
|
1068
|
+
for (const x of r.retried) lines.push(`${dryRun ? "would run" : "ran"} ${x.run} at ${x.sha.slice(0, 12)} again`);
|
|
1069
|
+
if (r.recorded.length > 0) lines.push(`recorded ${r.recorded.length} commit${r.recorded.length === 1 ? "" : "s"} on chant/lifecycle`);
|
|
1070
|
+
for (const w of r.warnings) lines.push(`warning: ${w}`);
|
|
753
1071
|
return lines.join("\n");
|
|
754
1072
|
}
|
|
755
1073
|
|
|
@@ -775,9 +1093,14 @@ export type LastGreenDocument =
|
|
|
775
1093
|
commit: { sha: string; tag: string; tagged: string | null; committed: string } | null;
|
|
776
1094
|
/** Tags on the commits walked, down to the one found, that were ignored for want of a valid statement (#4031). Newest first. */
|
|
777
1095
|
ignored: IgnoredCiTag[];
|
|
1096
|
+
/** The commits walked past above the one found, newest first, each with its record (#4169). At most {@link LAST_GREEN_NEWER}. */
|
|
1097
|
+
newer: { sha: string; record: CiRecordSummary | null }[];
|
|
778
1098
|
}
|
|
779
1099
|
| { $schema: string; contract: number; chant: string; error: { code: CiGreenErrorCode; message: string } };
|
|
780
1100
|
|
|
1101
|
+
/** How many commits above the green one `ci last-green` reports with their records. */
|
|
1102
|
+
export const LAST_GREEN_NEWER = 50;
|
|
1103
|
+
|
|
781
1104
|
/**
|
|
782
1105
|
* `chant ci last-green`: the newest first-parent commit of the branch with a
|
|
783
1106
|
* green tag and no revoked tag. It reads local refs and tags and never
|
|
@@ -799,15 +1122,20 @@ export function lastGreen(req: { cwd: string; remote?: string; from?: string }):
|
|
|
799
1122
|
const tags = readCiTags(top, green, git(top, ["rev-parse", `${branchRef}^{commit}`]));
|
|
800
1123
|
let commit: Extract<LastGreenDocument, { branch: string }>["commit"] = null;
|
|
801
1124
|
const ignored: IgnoredCiTag[] = [];
|
|
802
|
-
|
|
1125
|
+
const newer: { sha: string; record: CiRecordSummary | null }[] = [];
|
|
1126
|
+
const records = readCiRecords(top);
|
|
1127
|
+
if (tags.green.size > 0 || tags.ignored.size > 0 || records.size > 0) {
|
|
803
1128
|
for (const sha of git(top, ["rev-list", "--first-parent", ref]).split("\n")) {
|
|
804
1129
|
ignored.push(...(tags.ignored.get(sha) ?? []));
|
|
805
|
-
if (!tags.green.has(sha) || tags.revoked.has(sha))
|
|
1130
|
+
if (!tags.green.has(sha) || tags.revoked.has(sha)) {
|
|
1131
|
+
if (newer.length < LAST_GREEN_NEWER) newer.push({ sha, record: recordSummary(green, records.get(sha)) });
|
|
1132
|
+
continue;
|
|
1133
|
+
}
|
|
806
1134
|
commit = { sha, tag: `${CI_GREEN_TAG_PREFIX}${sha}`, tagged: tags.green.get(sha)!.tagged, committed: git(top, ["show", "-s", "--format=%cI", sha]) };
|
|
807
1135
|
break;
|
|
808
1136
|
}
|
|
809
1137
|
}
|
|
810
|
-
return { ...head, workspace, branch: green.branch, ref, commit, ignored };
|
|
1138
|
+
return { ...head, workspace, branch: green.branch, ref, commit, ignored, newer: commit ? newer : [] };
|
|
811
1139
|
} catch (err) {
|
|
812
1140
|
if (err instanceof CiGreenError || err instanceof WorkspaceReadError) return { ...head, error: { code: err.code, message: err instanceof WorkspaceReadError ? err.describe() : err.message } };
|
|
813
1141
|
throw err;
|
|
@@ -825,10 +1153,11 @@ export type CiCommitState = "green" | "revoked" | "undecided";
|
|
|
825
1153
|
/**
|
|
826
1154
|
* One required phase's result on a commit. `pass` and `fail` are what a tag
|
|
827
1155
|
* recorded or a live read found, `pending` is a live read with runs still
|
|
828
|
-
* going or not yet reported,
|
|
829
|
-
* and the
|
|
1156
|
+
* going or not yet reported, `unbuilt` (#4169) is a live read whose runs
|
|
1157
|
+
* were cancelled, and `unrecorded` means no tag holds the result and the
|
|
1158
|
+
* forge was not asked.
|
|
830
1159
|
*/
|
|
831
|
-
export type CiPhaseResult = "pass" | "fail" | "pending" | "unrecorded";
|
|
1160
|
+
export type CiPhaseResult = "pass" | "fail" | "pending" | "unbuilt" | "unrecorded";
|
|
832
1161
|
|
|
833
1162
|
export interface CiListRun {
|
|
834
1163
|
name: string;
|
|
@@ -836,7 +1165,7 @@ export interface CiListRun {
|
|
|
836
1165
|
conclusion: string | null;
|
|
837
1166
|
url: string | null;
|
|
838
1167
|
/** The Op run, when the run is a chant run (#4044): its id, env and the runner whose statement it carries. Absent for a check run. */
|
|
839
|
-
opRun?: { id: string; env: string; signer: string };
|
|
1168
|
+
opRun?: { id: string; env: string; signer: string | null };
|
|
840
1169
|
}
|
|
841
1170
|
|
|
842
1171
|
export interface CiListPhase {
|
|
@@ -869,6 +1198,27 @@ export interface CiListCommit {
|
|
|
869
1198
|
work: CommitLink | null;
|
|
870
1199
|
/** The Op runs on the commit that do not count, each for want of a valid statement (#4044). Present only when the source is chant runs. */
|
|
871
1200
|
ignoredRuns?: IgnoredOpRun[];
|
|
1201
|
+
/** What the commit's `_ci/<sha>.json` record says (#4169), or null when it has none. */
|
|
1202
|
+
record: CiRecordSummary | null;
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
/** A commit's record (#4169), as `ci list` and `ci last-green` show it: its state, and the first phase's reason, covering commit and culprit. */
|
|
1206
|
+
export interface CiRecordSummary {
|
|
1207
|
+
state: CiRecord["state"];
|
|
1208
|
+
reason?: string;
|
|
1209
|
+
coveredBy?: string;
|
|
1210
|
+
culprit?: string;
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
/** The summary of a record, from its required phases in `require` order. */
|
|
1214
|
+
export function recordSummary(green: CiGreen, r: CiRecord | undefined): CiRecordSummary | null {
|
|
1215
|
+
if (!r) return null;
|
|
1216
|
+
const phases = green.require.map((name) => r.phases[name]).filter((p) => p !== undefined);
|
|
1217
|
+
const first = <K extends "reason" | "coveredBy" | "culprit">(k: K) => phases.find((p) => p[k] !== undefined)?.[k];
|
|
1218
|
+
const reason = first("reason");
|
|
1219
|
+
const coveredBy = first("coveredBy");
|
|
1220
|
+
const culprit = first("culprit");
|
|
1221
|
+
return { state: r.state, ...(reason ? { reason } : {}), ...(coveredBy ? { coveredBy } : {}), ...(culprit ? { culprit } : {}) };
|
|
872
1222
|
}
|
|
873
1223
|
|
|
874
1224
|
export type CiListDocument =
|
|
@@ -895,7 +1245,8 @@ const asRun = (r: unknown): CiListRun | null => {
|
|
|
895
1245
|
const o = r as Record<string, unknown>;
|
|
896
1246
|
if (typeof o.name !== "string") return null;
|
|
897
1247
|
const op = o.opRun as Record<string, unknown> | undefined;
|
|
898
|
-
const
|
|
1248
|
+
const signer = op && typeof op === "object" ? (typeof op.signer === "string" ? op.signer : op.signer === null ? null : undefined) : undefined;
|
|
1249
|
+
const opRun = op && typeof op === "object" && typeof op.id === "string" && typeof op.env === "string" && signer !== undefined ? { opRun: { id: op.id, env: op.env, signer } } : {};
|
|
899
1250
|
return { name: o.name, id: typeof o.id === "number" ? o.id : null, conclusion: typeof o.conclusion === "string" ? o.conclusion : null, url: typeof o.url === "string" ? o.url : null, ...opRun };
|
|
900
1251
|
};
|
|
901
1252
|
|
|
@@ -973,7 +1324,8 @@ export async function ciList(req: ListRequest): Promise<CiListDocument> {
|
|
|
973
1324
|
const tags = readCiTags(top, green, base);
|
|
974
1325
|
const source = req.source ?? green.source;
|
|
975
1326
|
if (source === "runs" && req.forge) throw new Error("--forge reads check runs, and the source is chant runs (ci.green.source or --source runs); drop one");
|
|
976
|
-
const runs = source === "runs" ? await readOpRunSource(req.cwd, top, base) : undefined;
|
|
1327
|
+
const runs = source === "runs" ? await readOpRunSource(req.cwd, top, base, green.signedSince) : undefined;
|
|
1328
|
+
const records = readCiRecords(top);
|
|
977
1329
|
const out: CiListCommit[] = [];
|
|
978
1330
|
for (const c of commits) {
|
|
979
1331
|
const g = tags.green.get(c.sha);
|
|
@@ -998,6 +1350,7 @@ export async function ciList(req: ListRequest): Promise<CiListDocument> {
|
|
|
998
1350
|
work: links?.get(c.sha) ?? null,
|
|
999
1351
|
ignored: tags.ignored.get(c.sha) ?? [],
|
|
1000
1352
|
...(runs ? { ignoredRuns: runs.ignored.get(c.sha) ?? [] } : {}),
|
|
1353
|
+
record: recordSummary(green, records.get(c.sha)),
|
|
1001
1354
|
});
|
|
1002
1355
|
}
|
|
1003
1356
|
return { ...head, workspace, branch: green.branch, ref, window: req.since === undefined ? green.window : null, since: req.since ?? null, require: [...green.require], commits: out };
|
|
@@ -1007,6 +1360,12 @@ export async function ciList(req: ListRequest): Promise<CiListDocument> {
|
|
|
1007
1360
|
}
|
|
1008
1361
|
}
|
|
1009
1362
|
|
|
1363
|
+
/** A record summary in words, for `ci list` and the tick. */
|
|
1364
|
+
export function describeRecord(r: CiRecordSummary): string {
|
|
1365
|
+
const parts = [r.state, ...(r.reason ? [r.reason] : []), ...(r.coveredBy ? [`covered by ${r.coveredBy.slice(0, 12)}`] : []), ...(r.culprit ? [`culprit ${r.culprit.slice(0, 12)}`] : [])];
|
|
1366
|
+
return parts.join(", ");
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1010
1369
|
/** The text `chant ci list` prints: one line per commit. */
|
|
1011
1370
|
export function formatList(doc: Extract<CiListDocument, { branch: string }>): string {
|
|
1012
1371
|
const lines = [`${doc.branch}, ${doc.since ? `commits after ${doc.since}` : `commits within ${doc.window}`}: ${doc.commits.length}`];
|
|
@@ -1014,6 +1373,7 @@ export function formatList(doc: Extract<CiListDocument, { branch: string }>): st
|
|
|
1014
1373
|
const phases = c.phases.map((p) => `${p.phase}=${p.result}`).join(" ");
|
|
1015
1374
|
const why = whyWorkUnknown(c.work);
|
|
1016
1375
|
lines.push(` ${c.sha.slice(0, 12)} ${c.state.padEnd(9)} ${phases}${why ? `; why unknown: ${why}` : ""}`);
|
|
1376
|
+
if (c.record && c.state === "undecided") lines.push(` recorded ${describeRecord(c.record)}`);
|
|
1017
1377
|
for (const t of c.ignored) lines.push(` ignored ${t.tag} (${t.status}): ${t.reason}`);
|
|
1018
1378
|
for (const r of c.ignoredRuns ?? []) lines.push(` ignored run ${r.run} of ${r.op} in ${r.env} (${r.status}): ${r.reason}`);
|
|
1019
1379
|
}
|