@intentius/chant 0.102.0 → 0.104.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/audit/core.d.ts +2 -0
- package/dist/audit/core.d.ts.map +1 -1
- package/dist/audit/discover.d.ts.map +1 -1
- package/dist/change-set.d.ts +232 -0
- package/dist/change-set.d.ts.map +1 -0
- package/dist/cli/commands/audit.d.ts +7 -0
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/change-set.d.ts +11 -0
- package/dist/cli/handlers/change-set.d.ts.map +1 -0
- package/dist/cli/handlers/fan-out.d.ts +33 -0
- package/dist/cli/handlers/fan-out.d.ts.map +1 -1
- package/dist/cli/handlers/operator.d.ts +7 -0
- package/dist/cli/handlers/operator.d.ts.map +1 -1
- package/dist/cli/handlers/pr.d.ts +31 -0
- package/dist/cli/handlers/pr.d.ts.map +1 -0
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/mcp/op-tools.d.ts.map +1 -1
- package/dist/cli/mcp/workspace-tools.d.ts +3 -2
- package/dist/cli/mcp/workspace-tools.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +63 -1
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/components/auto-release.d.ts +16 -0
- package/dist/components/auto-release.d.ts.map +1 -1
- package/dist/components/capability.d.ts +53 -0
- package/dist/components/capability.d.ts.map +1 -1
- package/dist/components/changed-files.d.ts +31 -0
- package/dist/components/changed-files.d.ts.map +1 -0
- package/dist/components/cli-support.d.ts +9 -1
- package/dist/components/cli-support.d.ts.map +1 -1
- package/dist/components/component.d.ts +24 -0
- package/dist/components/component.d.ts.map +1 -1
- package/dist/components/deploy-units.d.ts.map +1 -1
- package/dist/components/driver.d.ts +13 -0
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/fan-out-output.d.ts +6 -0
- package/dist/components/fan-out-output.d.ts.map +1 -1
- package/dist/components/fan-out-record.d.ts +41 -0
- package/dist/components/fan-out-record.d.ts.map +1 -0
- package/dist/components/fan-out-run.d.ts +65 -0
- package/dist/components/fan-out-run.d.ts.map +1 -1
- package/dist/components/fan-out-support.d.ts +2 -0
- package/dist/components/fan-out-support.d.ts.map +1 -1
- package/dist/components/fan-out.d.ts +6 -0
- package/dist/components/fan-out.d.ts.map +1 -1
- package/dist/components/gated-wave-pipeline.d.ts +50 -0
- package/dist/components/gated-wave-pipeline.d.ts.map +1 -0
- package/dist/components/index.d.ts +1 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/layers.d.ts +25 -0
- package/dist/components/layers.d.ts.map +1 -0
- package/dist/components/pr-pipeline.d.ts +61 -0
- package/dist/components/pr-pipeline.d.ts.map +1 -0
- package/dist/components/pr-run.d.ts +119 -0
- package/dist/components/pr-run.d.ts.map +1 -0
- package/dist/components/promote.d.ts.map +1 -1
- package/dist/components/verbs/shell.d.ts.map +1 -1
- package/dist/components/wave-plan.d.ts +49 -0
- package/dist/components/wave-plan.d.ts.map +1 -0
- package/dist/gated-waves.d.ts +147 -0
- package/dist/gated-waves.d.ts.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/lexicon.d.ts +48 -0
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/gate-ledger.d.ts +7 -0
- package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
- package/dist/lifecycle/git.d.ts +46 -1
- package/dist/lifecycle/git.d.ts.map +1 -1
- package/dist/lint/post-synth.d.ts +18 -0
- package/dist/lint/post-synth.d.ts.map +1 -1
- package/dist/op/activities/activity-contracts.d.ts +126 -0
- package/dist/op/activities/activity-contracts.d.ts.map +1 -1
- package/dist/op/activities/change-set.d.ts +62 -0
- package/dist/op/activities/change-set.d.ts.map +1 -0
- package/dist/op/activities/decide.d.ts +2 -0
- package/dist/op/activities/decide.d.ts.map +1 -1
- package/dist/op/activities/factory.d.ts +137 -0
- package/dist/op/activities/factory.d.ts.map +1 -0
- package/dist/op/activities/index.d.ts +4 -0
- package/dist/op/activities/index.d.ts.map +1 -1
- package/dist/op/activity-contract.d.ts.map +1 -1
- package/dist/op/builders.d.ts +11 -0
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/decide-backend.d.ts +5 -1
- package/dist/op/decide-backend.d.ts.map +1 -1
- package/dist/op/decide-read-inputs.d.ts +4 -2
- package/dist/op/decide-read-inputs.d.ts.map +1 -1
- package/dist/op/factory.d.ts +83 -0
- package/dist/op/factory.d.ts.map +1 -0
- package/dist/op/index.d.ts +4 -1
- package/dist/op/index.d.ts.map +1 -1
- package/dist/op/op-ir.d.ts +2 -0
- package/dist/op/op-ir.d.ts.map +1 -1
- package/dist/op/op-verb-class.d.ts.map +1 -1
- package/dist/op/types.d.ts +8 -0
- package/dist/op/types.d.ts.map +1 -1
- package/dist/op/work-lease-run.d.ts.map +1 -1
- package/dist/plan-summary.d.ts +207 -0
- package/dist/plan-summary.d.ts.map +1 -0
- package/dist/pr-forge.d.ts +105 -0
- package/dist/pr-forge.d.ts.map +1 -0
- package/dist/pr-loop.d.ts +178 -0
- package/dist/pr-loop.d.ts.map +1 -0
- package/dist/telemetry-attribution.d.ts +31 -0
- package/dist/telemetry-attribution.d.ts.map +1 -1
- package/dist/terraform/hcl2json.d.ts +45 -0
- package/dist/terraform/hcl2json.d.ts.map +1 -0
- package/dist/terraform/parse.d.ts +4 -40
- package/dist/terraform/parse.d.ts.map +1 -1
- package/dist/workspace/box-cli.d.ts +12 -0
- package/dist/workspace/box-cli.d.ts.map +1 -0
- package/dist/workspace/box-factory.d.ts +27 -3
- package/dist/workspace/box-factory.d.ts.map +1 -1
- package/dist/workspace/box-listing.d.ts +85 -0
- package/dist/workspace/box-listing.d.ts.map +1 -0
- package/dist/workspace/box-publish.d.ts +147 -0
- package/dist/workspace/box-publish.d.ts.map +1 -0
- package/dist/workspace/broker-protocol.d.ts +190 -0
- package/dist/workspace/broker-protocol.d.ts.map +1 -0
- package/dist/workspace/checks.d.ts.map +1 -1
- package/dist/workspace/composites.d.ts +2 -2
- package/dist/workspace/composites.d.ts.map +1 -1
- package/dist/workspace/conformance/__fixtures__/reference-broker.d.ts +17 -0
- package/dist/workspace/conformance/__fixtures__/reference-broker.d.ts.map +1 -0
- package/dist/workspace/conformance/broker.d.ts +202 -0
- package/dist/workspace/conformance/broker.d.ts.map +1 -0
- package/dist/workspace/conformance/index.d.ts +2 -1
- package/dist/workspace/conformance/index.d.ts.map +1 -1
- package/dist/workspace/conformance/vitest.d.ts +9 -2
- package/dist/workspace/conformance/vitest.d.ts.map +1 -1
- package/dist/workspace/conformance/writer.d.ts +108 -14
- package/dist/workspace/conformance/writer.d.ts.map +1 -1
- package/dist/workspace/decide.d.ts +126 -12
- package/dist/workspace/decide.d.ts.map +1 -1
- package/dist/workspace/decision-points.schema.json +11 -4
- package/dist/workspace/declaration.d.ts +65 -0
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.schema.json +153 -4
- package/dist/workspace/environments.d.ts +36 -10
- package/dist/workspace/environments.d.ts.map +1 -1
- package/dist/workspace/factory-rules.d.ts +180 -0
- package/dist/workspace/factory-rules.d.ts.map +1 -0
- package/dist/workspace/init.d.ts +21 -0
- package/dist/workspace/init.d.ts.map +1 -1
- package/dist/workspace/intent-cli.d.ts +1 -1
- package/dist/workspace/intent-cli.d.ts.map +1 -1
- package/dist/workspace/intent-record.d.ts +28 -1
- package/dist/workspace/intent-record.d.ts.map +1 -1
- package/dist/workspace/intent.d.ts +102 -5
- package/dist/workspace/intent.d.ts.map +1 -1
- package/dist/workspace/json-edit.d.ts +24 -0
- package/dist/workspace/json-edit.d.ts.map +1 -0
- package/dist/workspace/jsonc.d.ts +11 -0
- package/dist/workspace/jsonc.d.ts.map +1 -1
- package/dist/workspace/kinds.d.ts +48 -0
- package/dist/workspace/kinds.d.ts.map +1 -1
- package/dist/workspace/lexicon-symbols.d.ts +29 -0
- package/dist/workspace/lexicon-symbols.d.ts.map +1 -0
- package/dist/workspace/lineage-upgrade.d.ts +31 -2
- package/dist/workspace/lineage-upgrade.d.ts.map +1 -1
- package/dist/workspace/point-answer.schema.json +42 -2
- package/dist/workspace/points-cli.d.ts +10 -3
- package/dist/workspace/points-cli.d.ts.map +1 -1
- package/dist/workspace/points.d.ts +66 -5
- package/dist/workspace/points.d.ts.map +1 -1
- package/dist/workspace/reason-codes.d.ts +33 -4
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/record-sessions.d.ts +5 -7
- package/dist/workspace/record-sessions.d.ts.map +1 -1
- package/dist/workspace/records-cli.d.ts +7 -0
- package/dist/workspace/records-cli.d.ts.map +1 -1
- package/dist/workspace/records-close.d.ts +7 -3
- package/dist/workspace/records-close.d.ts.map +1 -1
- package/dist/workspace/records-write.d.ts +66 -6
- package/dist/workspace/records-write.d.ts.map +1 -1
- package/dist/workspace/records.d.ts +28 -9
- package/dist/workspace/records.d.ts.map +1 -1
- package/dist/workspace/runs-cli.d.ts +138 -6
- package/dist/workspace/runs-cli.d.ts.map +1 -1
- package/dist/workspace/runs.d.ts +132 -11
- package/dist/workspace/runs.d.ts.map +1 -1
- package/dist/workspace/session-kinds.d.ts +1 -1
- package/dist/workspace/session-kinds.d.ts.map +1 -1
- package/dist/workspace/squash.d.ts +80 -0
- package/dist/workspace/squash.d.ts.map +1 -0
- package/dist/workspace/status-gates.d.ts +2 -0
- package/dist/workspace/status-gates.d.ts.map +1 -1
- package/dist/workspace/status.d.ts +54 -0
- package/dist/workspace/status.d.ts.map +1 -1
- package/dist/workspace/symbols.d.ts +16 -5
- package/dist/workspace/symbols.d.ts.map +1 -1
- package/dist/workspace/trust/run-commits.d.ts +46 -0
- package/dist/workspace/trust/run-commits.d.ts.map +1 -0
- package/dist/workspace/trust/run-statement.d.ts +128 -0
- package/dist/workspace/trust/run-statement.d.ts.map +1 -0
- package/dist/workspace/trust/seal.d.ts +6 -1
- package/dist/workspace/trust/seal.d.ts.map +1 -1
- package/dist/workspace/trust/verify-cli.d.ts +1 -1
- package/dist/workspace/trust/verify.d.ts +16 -2
- package/dist/workspace/trust/verify.d.ts.map +1 -1
- package/dist/workspace/wip-cli.d.ts +21 -0
- package/dist/workspace/wip-cli.d.ts.map +1 -0
- package/dist/workspace/wip.d.ts +268 -0
- package/dist/workspace/wip.d.ts.map +1 -0
- package/dist/workspace/work-cli.d.ts.map +1 -1
- package/dist/workspace/work-evidence.d.ts +1 -1
- package/dist/workspace/work-evidence.d.ts.map +1 -1
- package/dist/workspace/work-size.d.ts +55 -0
- package/dist/workspace/work-size.d.ts.map +1 -0
- package/dist/workspace/workspace-kinds.schema.json +122 -0
- package/dist/workspace/write-lock-cli.d.ts +60 -0
- package/dist/workspace/write-lock-cli.d.ts.map +1 -0
- package/dist/workspace/write-lock.d.ts +152 -0
- package/dist/workspace/write-lock.d.ts.map +1 -0
- package/dist/workspace/write-scope.d.ts +6 -2
- package/dist/workspace/write-scope.d.ts.map +1 -1
- package/package.json +6 -1
- package/src/__fixtures__/change-set/lifecycle-plan.floci.json +59 -0
- package/src/__fixtures__/change-set/lifecycle-v1.ts.txt +4 -0
- package/src/__fixtures__/change-set/lifecycle-v2.ts.txt +4 -0
- package/src/__fixtures__/change-set/lifecycle.change-set.golden.json +95 -0
- package/src/__fixtures__/change-set/warden.change-set.golden.json +106 -0
- package/src/__fixtures__/change-set/warden.plan.json +44 -0
- package/src/__fixtures__/plan-summary/normalization-vectors.json +1593 -0
- package/src/__fixtures__/pr-loop/pr-apply-applied.note.golden.md +31 -0
- package/src/__fixtures__/pr-loop/pr-apply-refused.note.golden.md +31 -0
- package/src/__fixtures__/pr-loop/pr-plan.golden.json +127 -0
- package/src/__fixtures__/pr-loop/pr-plan.note.golden.md +37 -0
- package/src/audit/core.test.ts +15 -0
- package/src/audit/core.ts +8 -2
- package/src/audit/discover.test.ts +11 -0
- package/src/audit/discover.ts +7 -2
- package/src/change-set-bundle.test.ts +57 -0
- package/src/change-set.test.ts +215 -0
- package/src/change-set.ts +445 -0
- package/src/cli/commands/audit-opt-in.test.ts +54 -0
- package/src/cli/commands/audit.ts +29 -2
- package/src/cli/commands/build.test.ts +19 -0
- package/src/cli/commands/build.ts +3 -2
- package/src/cli/handlers/build.ts +13 -1
- package/src/cli/handlers/change-set.test.ts +56 -0
- package/src/cli/handlers/change-set.ts +44 -0
- package/src/cli/handlers/fan-out.test.ts +221 -2
- package/src/cli/handlers/fan-out.ts +165 -75
- package/src/cli/handlers/graph.ts +3 -0
- package/src/cli/handlers/operator.test.ts +21 -0
- package/src/cli/handlers/operator.ts +34 -3
- package/src/cli/handlers/pr.ts +373 -0
- package/src/cli/handlers/run.ts +1 -0
- package/src/cli/main.test.ts +20 -0
- package/src/cli/main.ts +185 -23
- package/src/cli/mcp/never-over-mcp.test.ts +101 -0
- package/src/cli/mcp/op-tools.ts +24 -2
- package/src/cli/mcp/workspace-tools.test.ts +1 -1
- package/src/cli/mcp/workspace-tools.ts +34 -6
- package/src/cli/registry.ts +63 -1
- package/src/components/auto-release.ts +1 -1
- package/src/components/capability.ts +55 -0
- package/src/components/changed-files.ts +66 -0
- package/src/components/cli-support.ts +28 -5
- package/src/components/component.schema.json +17 -0
- package/src/components/component.ts +25 -0
- package/src/components/deploy-units.ts +3 -0
- package/src/components/driver.test.ts +52 -0
- package/src/components/driver.ts +31 -13
- package/src/components/fan-out-output.ts +42 -1
- package/src/components/fan-out-record.ts +69 -0
- package/src/components/fan-out-run.test.ts +17 -0
- package/src/components/fan-out-run.ts +304 -1
- package/src/components/fan-out-support.ts +3 -0
- package/src/components/fan-out-waves.test.ts +375 -0
- package/src/components/fan-out.ts +18 -17
- package/src/components/gated-wave-pipeline.ts +82 -0
- package/src/components/index.ts +1 -0
- package/src/components/layers.ts +30 -0
- package/src/components/pr-pipeline.ts +91 -0
- package/src/components/pr-run.test.ts +205 -0
- package/src/components/pr-run.ts +280 -0
- package/src/components/promote.ts +5 -0
- package/src/components/verbs/shell.test.ts +11 -0
- package/src/components/verbs/shell.ts +7 -3
- package/src/components/wave-plan.ts +124 -0
- package/src/discovery/sandbox/bundle.ts +1 -1
- package/src/gated-waves-bundle.test.ts +62 -0
- package/src/gated-waves.test.ts +118 -0
- package/src/gated-waves.ts +240 -0
- package/src/index.ts +1 -0
- package/src/lexicon.ts +51 -0
- package/src/lifecycle/gate-ledger.ts +7 -0
- package/src/lifecycle/git.ts +102 -16
- package/src/lint/post-synth.test.ts +19 -0
- package/src/lint/post-synth.ts +19 -1
- package/src/meta/declared-imports.test.ts +1 -1
- package/src/op/activities/activity-contracts.ts +84 -0
- package/src/op/activities/change-set.ts +114 -0
- package/src/op/activities/decide.ts +3 -0
- package/src/op/activities/factory.ts +675 -0
- package/src/op/activities/index.ts +6 -0
- package/src/op/activity-contract.test.ts +21 -1
- package/src/op/activity-contract.ts +19 -9
- package/src/op/builders.ts +23 -0
- package/src/op/decide-backend.ts +5 -1
- package/src/op/decide-read-inputs.ts +33 -5
- package/src/op/factory.e2e.test.ts +232 -0
- package/src/op/factory.ts +118 -0
- package/src/op/index.ts +9 -1
- package/src/op/op-ir.test.ts +9 -0
- package/src/op/op-ir.ts +4 -0
- package/src/op/op-verb-class.ts +3 -0
- package/src/op/types.ts +8 -0
- package/src/op/work-lease-run.ts +3 -0
- package/src/plan-summary.test.ts +263 -0
- package/src/plan-summary.ts +845 -0
- package/src/pr-forge.test.ts +237 -0
- package/src/pr-forge.ts +332 -0
- package/src/pr-loop-bundle.test.ts +46 -0
- package/src/pr-loop.test.ts +171 -0
- package/src/pr-loop.ts +324 -0
- package/src/telemetry-attribution.test.ts +23 -1
- package/src/telemetry-attribution.ts +51 -0
- package/src/terraform/hcl2json.test.ts +21 -0
- package/src/terraform/hcl2json.ts +81 -0
- package/src/terraform/parse.ts +11 -74
- package/src/workspace/__fixtures__/studio-template-84c8b21.json +1 -1
- package/src/workspace/app-kind.test.ts +196 -0
- package/src/workspace/box-cli.ts +113 -0
- package/src/workspace/box-factory.test.ts +4 -1
- package/src/workspace/box-factory.ts +37 -4
- package/src/workspace/box-listing-write.schema.json +204 -0
- package/src/workspace/box-listing.test.ts +234 -0
- package/src/workspace/box-listing.ts +324 -0
- package/src/workspace/box-publish.schema.json +498 -0
- package/src/workspace/box-publish.test.ts +204 -0
- package/src/workspace/box-publish.ts +420 -0
- package/src/workspace/box-ship.test.ts +96 -0
- package/src/workspace/broker-protocol.schema.json +186 -0
- package/src/workspace/broker-protocol.test.ts +122 -0
- package/src/workspace/broker-protocol.ts +317 -0
- package/src/workspace/builder-tiers.test.ts +146 -0
- package/src/workspace/change-set.schema.json +460 -0
- package/src/workspace/chant-migrations.e2e.test.ts +17 -5
- package/src/workspace/checks/boxes.test.ts +3 -0
- package/src/workspace/checks.ts +10 -3
- package/src/workspace/component-environments.test.ts +105 -0
- package/src/workspace/composites.schema.json +23 -4
- package/src/workspace/composites.test.ts +8 -8
- package/src/workspace/composites.ts +13 -2
- package/src/workspace/conformance/__fixture__/decisions/decision.schema.json +2 -2
- package/src/workspace/conformance/__fixtures__/reference-broker.ts +207 -0
- package/src/workspace/conformance/__writer_fixture__/answers/answer.kind.mjs +7 -2
- package/src/workspace/conformance/__writer_fixture__/answers/answer.schema.json +42 -2
- package/src/workspace/conformance/__writer_fixture__/contracts/contract.kind.mjs +37 -0
- package/src/workspace/conformance/__writer_fixture__/contracts/contract.schema.json +213 -0
- package/src/workspace/conformance/__writer_fixture__/decisions/points.json +96 -0
- package/src/workspace/conformance/__writer_fixture__/drivers/driver.kind.mjs +15 -0
- package/src/workspace/conformance/__writer_fixture__/drivers/driver.schema.json +38 -0
- package/src/workspace/conformance/__writer_fixture__/sessions/session.kind.mjs +14 -8
- package/src/workspace/conformance/__writer_fixture__/sessions/session.schema.json +187 -6
- package/src/workspace/conformance/__writer_fixture__/work/work.schema.json +25 -0
- package/src/workspace/conformance/broker.test.ts +122 -0
- package/src/workspace/conformance/broker.ts +792 -0
- package/src/workspace/conformance/conformance.e2e.test.ts +2 -1
- package/src/workspace/conformance/index.mjs +22 -1
- package/src/workspace/conformance/index.ts +5 -1
- package/src/workspace/conformance/vitest.ts +33 -1
- package/src/workspace/conformance/writer-conformance.e2e.test.ts +49 -3
- package/src/workspace/conformance/writer.test.ts +32 -2
- package/src/workspace/conformance/writer.ts +339 -28
- package/src/workspace/decide.test.ts +208 -4
- package/src/workspace/decide.ts +427 -73
- package/src/workspace/decision-points.schema.json +11 -4
- package/src/workspace/declaration.schema.json +153 -4
- package/src/workspace/declaration.ts +141 -0
- package/src/workspace/environments.ts +66 -14
- package/src/workspace/factory-rules.test.ts +62 -0
- package/src/workspace/factory-rules.ts +218 -0
- package/src/workspace/graph-cli.ts +2 -2
- package/src/workspace/graph.schema.json +67 -0
- package/src/workspace/identity.test.ts +23 -0
- package/src/workspace/init.ts +82 -2
- package/src/workspace/intent-cli.ts +26 -9
- package/src/workspace/intent-patch-id.test.ts +162 -0
- package/src/workspace/intent-record.schema.json +130 -3
- package/src/workspace/intent-record.ts +74 -28
- package/src/workspace/intent-reviews.test.ts +76 -0
- package/src/workspace/intent-squash.test.ts +213 -0
- package/src/workspace/intent-why.test.ts +5 -5
- package/src/workspace/intent.schema.json +234 -4
- package/src/workspace/intent.ts +260 -66
- package/src/workspace/json-edit.test.ts +121 -0
- package/src/workspace/json-edit.ts +142 -0
- package/src/workspace/jsonc.ts +20 -0
- package/src/workspace/kinds.ts +157 -2
- package/src/workspace/lexicon-symbols.test.ts +148 -0
- package/src/workspace/lexicon-symbols.ts +125 -0
- package/src/workspace/lineage-upgrade.test.ts +63 -0
- package/src/workspace/lineage-upgrade.ts +146 -5
- package/src/workspace/plan-summary.schema.json +155 -0
- package/src/workspace/point-answer.schema.json +42 -2
- package/src/workspace/points-cli.ts +42 -16
- package/src/workspace/points-write.schema.json +162 -4
- package/src/workspace/points.schema.json +157 -2
- package/src/workspace/points.test.ts +13 -1
- package/src/workspace/points.ts +118 -12
- package/src/workspace/pr-report.schema.json +95 -0
- package/src/workspace/profiles.test.ts +159 -0
- package/src/workspace/read-contract.test.ts +2 -2
- package/src/workspace/reason-codes.test.ts +13 -1
- package/src/workspace/reason-codes.ts +38 -4
- package/src/workspace/record-sessions.ts +9 -8
- package/src/workspace/records-amend.schema.json +78 -0
- package/src/workspace/records-cli.ts +20 -6
- package/src/workspace/records-close.schema.json +78 -0
- package/src/workspace/records-close.ts +31 -7
- package/src/workspace/records-concurrent.e2e.test.ts +135 -0
- package/src/workspace/records-concurrent.test.ts +145 -0
- package/src/workspace/records-formats.test.ts +46 -10
- package/src/workspace/records-new.schema.json +7 -0
- package/src/workspace/records-review.schema.json +78 -0
- package/src/workspace/records-sessions-write.test.ts +69 -0
- package/src/workspace/records-sessions.test.ts +22 -0
- package/src/workspace/records-write.ts +192 -25
- package/src/workspace/records.schema.json +16 -0
- package/src/workspace/records.ts +50 -9
- package/src/workspace/reference-kinds/app/package.json +10 -0
- package/src/workspace/reference-kinds/app/workspace-kinds.json +45 -0
- package/src/workspace/run-statement.schema.json +622 -0
- package/src/workspace/runs-cli.ts +343 -14
- package/src/workspace/runs-statement.test.ts +239 -0
- package/src/workspace/runs-write.schema.json +416 -5
- package/src/workspace/runs.schema.json +451 -4
- package/src/workspace/runs.test.ts +39 -0
- package/src/workspace/runs.ts +296 -35
- package/src/workspace/session-kinds.ts +3 -3
- package/src/workspace/squash.ts +198 -0
- package/src/workspace/status-contract.test.ts +5 -2
- package/src/workspace/status-gates.ts +3 -1
- package/src/workspace/status.schema.json +126 -3
- package/src/workspace/status.ts +90 -1
- package/src/workspace/symbols.ts +20 -9
- package/src/workspace/trust/run-commits.ts +115 -0
- package/src/workspace/trust/run-statement.ts +226 -0
- package/src/workspace/trust/seal.ts +8 -2
- package/src/workspace/trust/verify-cli.ts +6 -5
- package/src/workspace/trust/verify.ts +39 -6
- package/src/workspace/wip-cli.ts +97 -0
- package/src/workspace/wip-write.schema.json +504 -0
- package/src/workspace/wip.schema.json +334 -0
- package/src/workspace/wip.test.ts +294 -0
- package/src/workspace/wip.ts +753 -0
- package/src/workspace/work-cli.ts +3 -0
- package/src/workspace/work-evidence.schema.json +3 -0
- package/src/workspace/work-size.test.ts +77 -0
- package/src/workspace/work-size.ts +93 -0
- package/src/workspace/workspace-kinds.schema.json +122 -0
- package/src/workspace/write-lock-cli.ts +131 -0
- package/src/workspace/write-lock.schema.json +172 -0
- package/src/workspace/write-lock.test.ts +189 -0
- package/src/workspace/write-lock.ts +474 -0
- package/src/workspace/write-scope.test.ts +16 -1
- package/src/workspace/write-scope.ts +33 -11
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<!-- chant-pr:prod -->
|
|
2
|
+
### chant apply for `prod`
|
|
3
|
+
|
|
4
|
+
Applied 2 members. Head `222222222222`, measured from `111111111111`.
|
|
5
|
+
|
|
6
|
+
| Member | Component | Status | Create | Update | Replace | Delete |
|
|
7
|
+
|---|---|---|---:|---:|---:|---:|
|
|
8
|
+
| `a` | a | applied | 0 | 1 | 0 | 0 |
|
|
9
|
+
| `app` | app | applied, inputs from a moved | 0 | 0 | 0 | 0 |
|
|
10
|
+
|
|
11
|
+
A member whose inputs moved applied the plan it was approved with. The next run plans it against the new values.
|
|
12
|
+
|
|
13
|
+
Plan digest: `jcs1-sha256:46bff52773a158dffa5c895b98f8760d601a2f9dc6af7dc235dbf65496f7bf65`
|
|
14
|
+
|
|
15
|
+
Approved by `github:alice` for this digest.
|
|
16
|
+
|
|
17
|
+
### Plan summary
|
|
18
|
+
|
|
19
|
+
2 members: 2 groups, 0 destroys or replacements.
|
|
20
|
+
|
|
21
|
+
#### Group e84467713bf6: 1 member (outlier), change
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
~ terraform_data.subnet: input
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Members: `a`
|
|
28
|
+
|
|
29
|
+
#### Group 49648f9475c7: 1 member (outlier), no changes
|
|
30
|
+
|
|
31
|
+
Members: `app`
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<!-- chant-pr:prod -->
|
|
2
|
+
### chant apply for `prod`
|
|
3
|
+
|
|
4
|
+
The apply refused. Head `222222222222`, measured from `111111111111`.
|
|
5
|
+
|
|
6
|
+
> The plan changed after review, so nothing was applied. approved: jcs1-sha256:0000000000000000000000000000000000000000000000000000000000000063; planned now: jcs1-sha256:46bff52773a158dffa5c895b98f8760d601a2f9dc6af7dc235dbf65496f7bf65. Read the new plan, and if it is right, approve it: chant approve pr-12 pr-apply --plan jcs1-sha256:46bff52773a158dffa5c895b98f8760d601a2f9dc6af7dc235dbf65496f7bf65 --approver <you> --sign; then run the apply again.
|
|
7
|
+
|
|
8
|
+
| Member | Component | Status | Create | Update | Replace | Delete |
|
|
9
|
+
|---|---|---|---:|---:|---:|---:|
|
|
10
|
+
| `a` | a | planned | 0 | 1 | 0 | 0 |
|
|
11
|
+
| `app` | app | planned | 0 | 0 | 0 | 0 |
|
|
12
|
+
|
|
13
|
+
Plan digest: `jcs1-sha256:46bff52773a158dffa5c895b98f8760d601a2f9dc6af7dc235dbf65496f7bf65`
|
|
14
|
+
|
|
15
|
+
An approval stands for `jcs1-sha256:0000000000000000000000000000000000000000000000000000000000000063`, not for this digest: the plan changed since it was approved.
|
|
16
|
+
|
|
17
|
+
### Plan summary
|
|
18
|
+
|
|
19
|
+
2 members: 2 groups, 0 destroys or replacements.
|
|
20
|
+
|
|
21
|
+
#### Group e84467713bf6: 1 member (outlier), change
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
~ terraform_data.subnet: input
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Members: `a`
|
|
28
|
+
|
|
29
|
+
#### Group 49648f9475c7: 1 member (outlier), no changes
|
|
30
|
+
|
|
31
|
+
Members: `app`
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://intentius.io/chant/schemas/workspace/pr-report/v1/pr-report.schema.json",
|
|
3
|
+
"contract": 1,
|
|
4
|
+
"stage": "plan",
|
|
5
|
+
"pr": 12,
|
|
6
|
+
"env": "prod",
|
|
7
|
+
"base": "1111111111111111111111111111111111111111",
|
|
8
|
+
"head": "2222222222222222222222222222222222222222",
|
|
9
|
+
"op": "pr-12",
|
|
10
|
+
"gate": "pr-apply",
|
|
11
|
+
"digest": "jcs1-sha256:46bff52773a158dffa5c895b98f8760d601a2f9dc6af7dc235dbf65496f7bf65",
|
|
12
|
+
"status": "planned",
|
|
13
|
+
"approval": {
|
|
14
|
+
"status": "pending"
|
|
15
|
+
},
|
|
16
|
+
"selection": {
|
|
17
|
+
"changed": [
|
|
18
|
+
"a"
|
|
19
|
+
],
|
|
20
|
+
"dependents": [
|
|
21
|
+
"app"
|
|
22
|
+
],
|
|
23
|
+
"unclaimed": [],
|
|
24
|
+
"indeterminate": [],
|
|
25
|
+
"waves": [
|
|
26
|
+
[
|
|
27
|
+
"a"
|
|
28
|
+
],
|
|
29
|
+
[
|
|
30
|
+
"app"
|
|
31
|
+
]
|
|
32
|
+
]
|
|
33
|
+
},
|
|
34
|
+
"members": [
|
|
35
|
+
{
|
|
36
|
+
"member": "a",
|
|
37
|
+
"component": "a",
|
|
38
|
+
"planDigest": "jcs1-sha256:0000000000000000000000000000000000000000000000000000000000000001",
|
|
39
|
+
"status": "planned",
|
|
40
|
+
"counts": {
|
|
41
|
+
"create": 0,
|
|
42
|
+
"update": 1,
|
|
43
|
+
"replace": 0,
|
|
44
|
+
"delete": 0
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"member": "app",
|
|
49
|
+
"component": "app",
|
|
50
|
+
"planDigest": "jcs1-sha256:0000000000000000000000000000000000000000000000000000000000000002",
|
|
51
|
+
"status": "planned",
|
|
52
|
+
"counts": {
|
|
53
|
+
"create": 0,
|
|
54
|
+
"update": 0,
|
|
55
|
+
"replace": 0,
|
|
56
|
+
"delete": 0
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
],
|
|
60
|
+
"changeSet": {
|
|
61
|
+
"$schema": "https://intentius.io/chant/schemas/workspace/change-set/v1/change-set.schema.json",
|
|
62
|
+
"contract": 1,
|
|
63
|
+
"digest": "jcs1-sha256:46bff52773a158dffa5c895b98f8760d601a2f9dc6af7dc235dbf65496f7bf65",
|
|
64
|
+
"members": [
|
|
65
|
+
{
|
|
66
|
+
"member": "a",
|
|
67
|
+
"lexicon": "terraform",
|
|
68
|
+
"planner": "tofu",
|
|
69
|
+
"status": "planned",
|
|
70
|
+
"planDigest": "jcs1-sha256:0000000000000000000000000000000000000000000000000000000000000001",
|
|
71
|
+
"holes": []
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"member": "app",
|
|
75
|
+
"lexicon": "terraform",
|
|
76
|
+
"planner": "tofu",
|
|
77
|
+
"status": "planned",
|
|
78
|
+
"planDigest": "jcs1-sha256:0000000000000000000000000000000000000000000000000000000000000002",
|
|
79
|
+
"holes": []
|
|
80
|
+
}
|
|
81
|
+
],
|
|
82
|
+
"entries": [
|
|
83
|
+
{
|
|
84
|
+
"member": "a",
|
|
85
|
+
"lexicon": "terraform",
|
|
86
|
+
"planner": "tofu",
|
|
87
|
+
"address": "terraform_data.subnet",
|
|
88
|
+
"type": "terraform_data",
|
|
89
|
+
"action": "update",
|
|
90
|
+
"attributes": [
|
|
91
|
+
{
|
|
92
|
+
"path": "input",
|
|
93
|
+
"before": "v1",
|
|
94
|
+
"after": "v2"
|
|
95
|
+
}
|
|
96
|
+
]
|
|
97
|
+
}
|
|
98
|
+
],
|
|
99
|
+
"summary": {
|
|
100
|
+
"members": 2,
|
|
101
|
+
"entries": 1,
|
|
102
|
+
"actions": {
|
|
103
|
+
"create": 0,
|
|
104
|
+
"update": 1,
|
|
105
|
+
"replace": 0,
|
|
106
|
+
"delete": 0,
|
|
107
|
+
"read": 0,
|
|
108
|
+
"no-op": 0,
|
|
109
|
+
"forget": 0
|
|
110
|
+
},
|
|
111
|
+
"types": {
|
|
112
|
+
"terraform_data": {
|
|
113
|
+
"update": 1
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
"byMember": {
|
|
117
|
+
"a": {
|
|
118
|
+
"update": 1
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
"deletes": [],
|
|
122
|
+
"replacements": [],
|
|
123
|
+
"failed": [],
|
|
124
|
+
"holes": 0
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<!-- chant-pr:prod -->
|
|
2
|
+
### chant plan for `prod`
|
|
3
|
+
|
|
4
|
+
Planned 2 members. Head `222222222222`, measured from `111111111111`.
|
|
5
|
+
|
|
6
|
+
| Member | Component | Status | Create | Update | Replace | Delete |
|
|
7
|
+
|---|---|---|---:|---:|---:|---:|
|
|
8
|
+
| `a` | a | planned | 0 | 1 | 0 | 0 |
|
|
9
|
+
| `app` | app | planned | 0 | 0 | 0 | 0 |
|
|
10
|
+
|
|
11
|
+
Plan digest: `jcs1-sha256:46bff52773a158dffa5c895b98f8760d601a2f9dc6af7dc235dbf65496f7bf65`
|
|
12
|
+
|
|
13
|
+
Not approved yet.
|
|
14
|
+
|
|
15
|
+
A reviewer who approved this pull request approves the plan with:
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
chant approve pr-12 pr-apply --plan jcs1-sha256:46bff52773a158dffa5c895b98f8760d601a2f9dc6af7dc235dbf65496f7bf65 --approver github:<your-login> --sign
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
It applies on merge if the plan is still this one. If it moved, the apply refuses and names both digests.
|
|
22
|
+
|
|
23
|
+
### Plan summary
|
|
24
|
+
|
|
25
|
+
2 members: 2 groups, 0 destroys or replacements.
|
|
26
|
+
|
|
27
|
+
#### Group e84467713bf6: 1 member (outlier), change
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
~ terraform_data.subnet: input
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Members: `a`
|
|
34
|
+
|
|
35
|
+
#### Group 49648f9475c7: 1 member (outlier), no changes
|
|
36
|
+
|
|
37
|
+
Members: `app`
|
package/src/audit/core.test.ts
CHANGED
|
@@ -128,6 +128,21 @@ describe("auditFiles", () => {
|
|
|
128
128
|
expect(findings.map((f) => f.file).sort()).toEqual(["a.yml", "b.yml"]);
|
|
129
129
|
});
|
|
130
130
|
|
|
131
|
+
test("skips an auditOptIn check unless enabledRules names it (#3190)", async () => {
|
|
132
|
+
const optIn: PostSynthCheck = {
|
|
133
|
+
id: "OPT001",
|
|
134
|
+
description: "opt-in",
|
|
135
|
+
auditOptIn: true,
|
|
136
|
+
check: () => [{ checkId: "OPT001", severity: "warning", message: "hit" }],
|
|
137
|
+
};
|
|
138
|
+
const input = [{ path: "a.yml", content: "x", lexicon: "github" }];
|
|
139
|
+
const run = (enabledRules?: ReadonlySet<string>) =>
|
|
140
|
+
auditFiles(input, { checksProvider: async () => [optIn], enabledRules });
|
|
141
|
+
expect(await run()).toEqual([]);
|
|
142
|
+
expect(await run(new Set(["OTHER"]))).toEqual([]);
|
|
143
|
+
expect((await run(new Set(["OPT001"]))).map((f) => f.checkId)).toEqual(["OPT001"]);
|
|
144
|
+
});
|
|
145
|
+
|
|
131
146
|
test("accepts a lexicon name outside the built-in set (open AuditLexicon)", async () => {
|
|
132
147
|
const fake: PostSynthCheck = {
|
|
133
148
|
id: "CUSTOM001",
|
package/src/audit/core.ts
CHANGED
|
@@ -231,7 +231,13 @@ export interface SuppressionStats {
|
|
|
231
231
|
*/
|
|
232
232
|
export async function auditFiles(
|
|
233
233
|
inputs: AuditInput[],
|
|
234
|
-
opts: {
|
|
234
|
+
opts: {
|
|
235
|
+
checksProvider?: ChecksProvider;
|
|
236
|
+
entitiesProvider?: EntitiesProvider;
|
|
237
|
+
suppressionStats?: SuppressionStats;
|
|
238
|
+
/** Check ids the project enabled with `lint.rules`; a check marked `auditOptIn` runs only when listed here (#3190). */
|
|
239
|
+
enabledRules?: ReadonlySet<string>;
|
|
240
|
+
} = {},
|
|
235
241
|
): Promise<AuditFinding[]> {
|
|
236
242
|
const provider = opts.checksProvider ?? defaultChecksProvider;
|
|
237
243
|
const entitiesProvider = opts.entitiesProvider ?? defaultEntitiesProvider;
|
|
@@ -246,7 +252,7 @@ export async function auditFiles(
|
|
|
246
252
|
}
|
|
247
253
|
|
|
248
254
|
for (const [lexicon, files] of byLexicon) {
|
|
249
|
-
const checks = await provider(lexicon);
|
|
255
|
+
const checks = (await provider(lexicon)).filter((c) => !c.auditOptIn || opts.enabledRules?.has(c.id));
|
|
250
256
|
if (checks.length === 0) continue;
|
|
251
257
|
const parseEntities = await entitiesProvider(lexicon);
|
|
252
258
|
findings.push(...(await auditLexicon(lexicon, files, checks, parseEntities, opts.suppressionStats)));
|
|
@@ -126,6 +126,17 @@ describe("discoverByDetection (unified, detectTemplate-driven)", () => {
|
|
|
126
126
|
expect(found.some((i) => i.path.includes(".terraform"))).toBe(false);
|
|
127
127
|
});
|
|
128
128
|
|
|
129
|
+
test("terragrunt.hcl and root.hcl join the terraform bundle, and a unit's source path does not make a module of its target (#3417)", () => {
|
|
130
|
+
const files = [
|
|
131
|
+
{ path: "live/app/terragrunt.hcl", content: 'terraform {\n source = "../../modules/app"\n}\n' },
|
|
132
|
+
{ path: "root.hcl", content: "remote_state {\n}\n" },
|
|
133
|
+
{ path: "modules/app/main.tf", content: 'resource "x" "y" {}\n' },
|
|
134
|
+
];
|
|
135
|
+
const found = classifyFiles(files, [terraform], { baseDir: "/repo" });
|
|
136
|
+
expect(targets(found)).toEqual(["terraform:.", "terraform:live/app", "terraform:modules/app"]);
|
|
137
|
+
expect(found.find((i) => i.path === "live/app")!.content).toContain("# file: terragrunt.hcl");
|
|
138
|
+
});
|
|
139
|
+
|
|
129
140
|
test("without the terraform plugin, .tf files are classified into nothing (skipped, same as an unloaded helm)", () => {
|
|
130
141
|
const found = discoverByDetection(fixture("audit-terraform"), []);
|
|
131
142
|
expect(found).toEqual([]);
|
package/src/audit/discover.ts
CHANGED
|
@@ -151,7 +151,9 @@ function isDockerfileName(name: string): boolean {
|
|
|
151
151
|
}
|
|
152
152
|
|
|
153
153
|
function isTerraformFileName(name: string): boolean {
|
|
154
|
-
|
|
154
|
+
// `terragrunt.hcl` and `root.hcl` ride in the terraform bundle (#3417): the
|
|
155
|
+
// terraform lexicon reads them into Terragrunt entities, apart from modules.
|
|
156
|
+
return /\.tf$/i.test(name) || name === "terragrunt.hcl" || name === "root.hcl";
|
|
155
157
|
}
|
|
156
158
|
|
|
157
159
|
/**
|
|
@@ -470,7 +472,10 @@ function calledAsLocalModule(byDir: Map<string, Record<string, string>>): Set<st
|
|
|
470
472
|
const called = new Set<string>();
|
|
471
473
|
for (const [dir, bundle] of byDir) {
|
|
472
474
|
const targets = new Set<string>();
|
|
473
|
-
for (const source of Object.
|
|
475
|
+
for (const [name, source] of Object.entries(bundle)) {
|
|
476
|
+
// A Terragrunt unit's `terraform { source = "../modules/x" }` is not a
|
|
477
|
+
// `module` call: the directory it names stays a root of its own (#3417).
|
|
478
|
+
if (name === "terragrunt.hcl" || name === "root.hcl") continue;
|
|
474
479
|
for (const match of source.matchAll(LOCAL_MODULE_SOURCE)) {
|
|
475
480
|
const target = resolveRepoDir(dir, match[1]);
|
|
476
481
|
if (target === undefined || target === dir || !byDir.has(target)) continue;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The change-set modules bundle small (#3181, ruling #3421).
|
|
3
|
+
*
|
|
4
|
+
* terragucci runs its stages in customer CI as one bundled file with no
|
|
5
|
+
* TypeScript toolchain. So `@intentius/chant/change-set`, the grouped plan
|
|
6
|
+
* summary at `@intentius/chant/plan-summary` (#3188) and the terraform
|
|
7
|
+
* lexicon's `change-set` subpath must not reach `typescript`, esbuild, zod or
|
|
8
|
+
* chant's fold, lint and codegen modules. This bundles the three entry points
|
|
9
|
+
* the way terragucci would and reads esbuild's own list of what went in.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { build } from "esbuild";
|
|
14
|
+
import { describe, expect, test } from "vitest";
|
|
15
|
+
import { REPO } from "./workspace/__fixtures__/contract-repo";
|
|
16
|
+
|
|
17
|
+
/** What the three bundles may hold together at most. They are about 40 KB today (9 KB change-set, 21 KB plan-summary, 10 KB terraform). */
|
|
18
|
+
const BUDGET_BYTES = 64 * 1024;
|
|
19
|
+
|
|
20
|
+
const FORBIDDEN = [/node_modules\/typescript\//, /node_modules\/esbuild\//, /node_modules\/zod\//, /\/src\/fold\//, /\/src\/lint\//, /\/src\/codegen\//];
|
|
21
|
+
|
|
22
|
+
describe("the change-set bundle", () => {
|
|
23
|
+
test("holds the document, the adapters, the summary and the digest, and nothing heavier", async () => {
|
|
24
|
+
const result = await build({
|
|
25
|
+
entryPoints: [
|
|
26
|
+
join(REPO, "packages", "core", "src", "change-set.ts"),
|
|
27
|
+
join(REPO, "packages", "core", "src", "plan-summary.ts"),
|
|
28
|
+
join(REPO, "lexicons", "terraform", "src", "change-set.ts"),
|
|
29
|
+
],
|
|
30
|
+
bundle: true,
|
|
31
|
+
platform: "node",
|
|
32
|
+
format: "esm",
|
|
33
|
+
write: false,
|
|
34
|
+
metafile: true,
|
|
35
|
+
outdir: "out",
|
|
36
|
+
logLevel: "silent",
|
|
37
|
+
});
|
|
38
|
+
const inputs = Object.keys(result.metafile.inputs);
|
|
39
|
+
for (const pattern of FORBIDDEN) {
|
|
40
|
+
expect(inputs.filter((i) => pattern.test(i)), String(pattern)).toEqual([]);
|
|
41
|
+
}
|
|
42
|
+
// The whole graph: the three entry points, the terraform plan digest, and core's digest code.
|
|
43
|
+
expect(inputs.map((i) => i.replace(/^.*?(packages|lexicons)\//, "$1/")).sort()).toEqual([
|
|
44
|
+
"lexicons/terraform/src/change-set.ts",
|
|
45
|
+
"lexicons/terraform/src/plan-digest.ts",
|
|
46
|
+
"packages/core/src/change-set.ts",
|
|
47
|
+
"packages/core/src/content-digest.ts",
|
|
48
|
+
"packages/core/src/declarable.ts",
|
|
49
|
+
"packages/core/src/effect-receipt.ts",
|
|
50
|
+
"packages/core/src/intrinsic.ts",
|
|
51
|
+
"packages/core/src/lifecycle/plan-digest.ts",
|
|
52
|
+
"packages/core/src/plan-summary.ts",
|
|
53
|
+
]);
|
|
54
|
+
const bytes = result.outputFiles.reduce((n, f) => n + f.contents.byteLength, 0);
|
|
55
|
+
expect(bytes).toBeLessThan(BUDGET_BYTES);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The change-set document (#3181): its schema, the lifecycle and reconcile
|
|
3
|
+
* adapters, and the digest.
|
|
4
|
+
*
|
|
5
|
+
* `__fixtures__/change-set/lifecycle-plan.floci.json` is real `chant
|
|
6
|
+
* lifecycle plan dev --owned --json` output: `lifecycle-v1.ts.txt` deployed as
|
|
7
|
+
* CloudFormation stack `dev` to floci 1.5.34 and snapshotted, then
|
|
8
|
+
* `lifecycle-v2.ts.txt` planned. It has a create and no-ops; the aws lexicon
|
|
9
|
+
* reads stack resources, so out-of-band drift does not reach it as an
|
|
10
|
+
* update. The update, delete, effect and hole mappings are pinned on typed
|
|
11
|
+
* entries below.
|
|
12
|
+
*
|
|
13
|
+
* `warden.plan.json` is the reconcile change set chant's own
|
|
14
|
+
* `diffCollection` produces for a warden's teams cycle: one create, one
|
|
15
|
+
* update and one owned delete. github-warden prints its plan as text today,
|
|
16
|
+
* so this is the shape it would write.
|
|
17
|
+
*
|
|
18
|
+
* Goldens regenerate with `UPDATE_GOLDEN=1`.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
22
|
+
import { join } from "node:path";
|
|
23
|
+
import { describe, expect, test } from "vitest";
|
|
24
|
+
import {
|
|
25
|
+
CHANGE_SET_SCHEMA_ID,
|
|
26
|
+
changeSetDigest,
|
|
27
|
+
composeChangeSet,
|
|
28
|
+
lifecyclePlanPart,
|
|
29
|
+
reconcilePlanPart,
|
|
30
|
+
verifyChangeSetDigest,
|
|
31
|
+
type ChangeSetPart,
|
|
32
|
+
} from "./change-set";
|
|
33
|
+
import { computePlanDigest } from "./lifecycle/plan-digest";
|
|
34
|
+
import type { ChangeSet as LifecycleChangeSet } from "./lifecycle/change-set";
|
|
35
|
+
import { diffCollection, diffFields, type ChangeSet as ReconcileChangeSet, type ChangeSetEntry as ReconcileEntry } from "./reconcile";
|
|
36
|
+
import { contract, REPO, validSchema } from "./workspace/__fixtures__/contract-repo";
|
|
37
|
+
import schema from "./workspace/change-set.schema.json";
|
|
38
|
+
|
|
39
|
+
const DIR = join(import.meta.dirname, "__fixtures__", "change-set");
|
|
40
|
+
const TF_DIR = join(REPO, "lexicons", "terraform", "src", "__fixtures__", "change-set");
|
|
41
|
+
const read = (dir: string, name: string): unknown => JSON.parse(readFileSync(join(dir, name), "utf-8"));
|
|
42
|
+
|
|
43
|
+
function golden(name: string, value: unknown): void {
|
|
44
|
+
const path = join(DIR, name);
|
|
45
|
+
const text = JSON.stringify(value, null, 2) + "\n";
|
|
46
|
+
if (process.env.UPDATE_GOLDEN) writeFileSync(path, text);
|
|
47
|
+
expect(text).toBe(readFileSync(path, "utf-8"));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const { expectValid, validate } = contract(schema);
|
|
51
|
+
|
|
52
|
+
/** A warden's teams cycle, planned by the reconcile primitives it is built on. */
|
|
53
|
+
function wardenPlan(): ReconcileChangeSet {
|
|
54
|
+
type Team = { privacy: string; description: string };
|
|
55
|
+
const desired = new Map<string, Team>([
|
|
56
|
+
["platform", { privacy: "closed", description: "Platform team" }],
|
|
57
|
+
["release", { privacy: "secret", description: "Release managers" }],
|
|
58
|
+
]);
|
|
59
|
+
const live = new Map<string, Team>([
|
|
60
|
+
["platform", { privacy: "secret", description: "Platform team" }],
|
|
61
|
+
["contractors", { privacy: "closed", description: "Old contractors" }],
|
|
62
|
+
]);
|
|
63
|
+
const entries: ReconcileEntry[] = [];
|
|
64
|
+
diffCollection({
|
|
65
|
+
resourceType: "team",
|
|
66
|
+
desired,
|
|
67
|
+
live,
|
|
68
|
+
compareFields: (d, l) => diffFields(d, l),
|
|
69
|
+
opts: { isOwned: () => true },
|
|
70
|
+
out: entries,
|
|
71
|
+
});
|
|
72
|
+
return { org: "acme", entries, managedCount: live.size };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
describe("the change-set schema", () => {
|
|
76
|
+
test("is a valid draft 2020-12 document at the read contract's version", () => {
|
|
77
|
+
expect(validSchema(schema)).toBe(true);
|
|
78
|
+
expect(schema.$id).toBe(CHANGE_SET_SCHEMA_ID);
|
|
79
|
+
expect(schema.properties.contract).toEqual({ const: 1 });
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("every golden document in both fixture directories validates", () => {
|
|
83
|
+
const docs = [
|
|
84
|
+
...readdirSync(DIR).filter((f) => f.endsWith(".change-set.golden.json")).map((f) => read(DIR, f)),
|
|
85
|
+
...readdirSync(TF_DIR).filter((f) => f.endsWith(".change-set.golden.json")).map((f) => read(TF_DIR, f)),
|
|
86
|
+
...readdirSync(TF_DIR).filter((f) => f.endsWith(".part.golden.json")).map((f) => composeChangeSet([read(TF_DIR, f) as ChangeSetPart])),
|
|
87
|
+
];
|
|
88
|
+
expect(docs.length).toBeGreaterThanOrEqual(5);
|
|
89
|
+
for (const doc of docs) expectValid(doc);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("refuses an unknown action, a missing digest and a malformed plan digest", () => {
|
|
93
|
+
const doc = composeChangeSet([lifecyclePlanPart({ member: "delivery", plan: read(DIR, "lifecycle-plan.floci.json") as LifecycleChangeSet })]);
|
|
94
|
+
expect(validate({ ...doc, entries: [{ ...doc.entries[0], action: "teleport" }] })).toBe(false);
|
|
95
|
+
const { digest: _d, ...noDigest } = doc;
|
|
96
|
+
expect(validate(noDigest)).toBe(false);
|
|
97
|
+
expect(validate({ ...doc, members: [{ ...doc.members[0], planDigest: "sha256:abc" }] })).toBe(false);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe("lifecyclePlanPart", () => {
|
|
102
|
+
test("a real lifecycle plan against floci matches its golden", () => {
|
|
103
|
+
const plan = read(DIR, "lifecycle-plan.floci.json") as LifecycleChangeSet;
|
|
104
|
+
const part = lifecyclePlanPart({ member: "delivery", plan });
|
|
105
|
+
expect(part.member.planDigest).toBe(computePlanDigest("lifecycle-plan", plan));
|
|
106
|
+
expect(part.entries.map((e) => [e.address, e.action])).toEqual([
|
|
107
|
+
["alerts", "no-op"], ["dlq", "create"], ["events", "no-op"], ["jobs", "no-op"],
|
|
108
|
+
]);
|
|
109
|
+
golden("lifecycle.change-set.golden.json", composeChangeSet([part]));
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("update, replace, delete, effect, holes, and what is left out", () => {
|
|
113
|
+
const plan: LifecycleChangeSet = {
|
|
114
|
+
env: "prod",
|
|
115
|
+
entries: [
|
|
116
|
+
{ name: "web", type: "Deployment", lexicon: "k8s", action: "update", evidence: { declared: true, inSnapshot: true, live: true, observed: true }, ownership: "owned", deltas: [{ path: "replicas", oldValue: 2, newValue: 3 }], disruption: "rolling" },
|
|
117
|
+
{ name: "db", type: "AWS::RDS::DBInstance", lexicon: "aws", action: "update", evidence: { declared: true, inSnapshot: true, live: true, observed: true }, ownership: "owned", deltas: [{ path: "Engine", oldValue: "mysql", newValue: "postgres" }], disruption: "destroy", disruptionBecause: ["Engine"] },
|
|
118
|
+
{ name: "old", type: "AWS::SQS::Queue", lexicon: "aws", action: "delete", evidence: { declared: false, inSnapshot: true, live: true, observed: true }, ownership: "owned" },
|
|
119
|
+
{ name: "notify-receipt", type: "Receipt", lexicon: "aws", action: "effect", effect: "notify", effectReason: "receipt-absent", evidence: { declared: true, inSnapshot: false, live: false, observed: true }, ownership: "owned" },
|
|
120
|
+
{ name: "cache", type: "AWS::ElastiCache::CacheCluster", lexicon: "aws", action: "unobserved", unobservedReason: "read-failed", evidence: { declared: true, inSnapshot: false, live: false, observed: false }, ownership: "unknown" },
|
|
121
|
+
{ name: "stray", lexicon: "aws", action: "adopt", evidence: { declared: false, inSnapshot: false, live: true, observed: true }, ownership: "unknown" },
|
|
122
|
+
{ name: "web-abc12", lexicon: "k8s", action: "runtime", runtimeOwner: "web", evidence: { declared: false, inSnapshot: false, live: true, observed: true }, ownership: "unknown" },
|
|
123
|
+
],
|
|
124
|
+
};
|
|
125
|
+
const { member, entries } = lifecyclePlanPart({ member: "delivery", plan });
|
|
126
|
+
expect(entries.map((e) => [e.address, e.action, e.disruption ?? null])).toEqual([
|
|
127
|
+
["web", "update", "rolling"],
|
|
128
|
+
["db", "replace", "destroy"],
|
|
129
|
+
["old", "delete", "destroy"],
|
|
130
|
+
["notify-receipt", "create", null],
|
|
131
|
+
]);
|
|
132
|
+
expect(entries[1].attributes).toEqual([{ path: "Engine", before: "mysql", after: "postgres", forcesReplacement: true }]);
|
|
133
|
+
expect(member.holes).toEqual([{ address: "cache", type: "AWS::ElastiCache::CacheCluster", reason: "read-failed" }]);
|
|
134
|
+
expect(member.lexicon).toBe("chant");
|
|
135
|
+
const doc = composeChangeSet([{ member, entries }]);
|
|
136
|
+
expectValid(doc);
|
|
137
|
+
expect(doc.summary.holes).toBe(1);
|
|
138
|
+
expect(doc.summary.replacements).toEqual([{ member: "delivery", address: "db", type: "AWS::RDS::DBInstance", disruption: "destroy" }]);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
describe("reconcilePlanPart", () => {
|
|
143
|
+
test("a warden's teams cycle, as chant's reconcile primitives plan it", () => {
|
|
144
|
+
const plan = wardenPlan();
|
|
145
|
+
golden("warden.plan.json", plan);
|
|
146
|
+
const part = reconcilePlanPart({ member: "warden", plan: read(DIR, "warden.plan.json") as ReconcileChangeSet });
|
|
147
|
+
expect(part.entries.map((e) => [e.address, e.action, e.disruption ?? null])).toEqual([
|
|
148
|
+
["team.contractors", "delete", "destroy"],
|
|
149
|
+
["team.platform", "update", "unknown"],
|
|
150
|
+
["team.release", "create", null],
|
|
151
|
+
]);
|
|
152
|
+
expect(part.entries[1].attributes).toEqual([{ path: "privacy", before: "secret", after: "closed" }]);
|
|
153
|
+
golden("warden.change-set.golden.json", composeChangeSet([part]));
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test("the plan digest ignores the managed counts and the order of change sets", () => {
|
|
157
|
+
const a = wardenPlan();
|
|
158
|
+
const b: ReconcileChangeSet = { org: "beta", entries: [] };
|
|
159
|
+
const one = reconcilePlanPart({ member: "w", plan: [a, b] }).member.planDigest;
|
|
160
|
+
expect(reconcilePlanPart({ member: "w", plan: [b, { ...a, managedCount: 99 }] }).member.planDigest).toBe(one);
|
|
161
|
+
expect(reconcilePlanPart({ member: "w", plan: [b, { ...a, entries: a.entries.slice(1) }] }).member.planDigest).not.toBe(one);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
describe("the digest", () => {
|
|
166
|
+
const lifecycle = () => lifecyclePlanPart({ member: "delivery", plan: read(DIR, "lifecycle-plan.floci.json") as LifecycleChangeSet });
|
|
167
|
+
const warden = () => reconcilePlanPart({ member: "warden", plan: wardenPlan() });
|
|
168
|
+
const tofu = () => read(TF_DIR, "tofu.part.golden.json") as ChangeSetPart;
|
|
169
|
+
|
|
170
|
+
test("is the set digest over { member, planDigest }, whatever order the parts come in", () => {
|
|
171
|
+
const doc = composeChangeSet([lifecycle(), warden(), tofu()]);
|
|
172
|
+
expect(composeChangeSet([tofu(), warden(), lifecycle()])).toEqual(doc);
|
|
173
|
+
const pairs = doc.members.map((m) => ({ member: m.member, planDigest: m.planDigest }));
|
|
174
|
+
expect(doc.digest).toBe(computePlanDigest("change-set", pairs));
|
|
175
|
+
expect(doc.digest).toMatch(/^jcs1-sha256:[0-9a-f]{64}$/);
|
|
176
|
+
expect(verifyChangeSetDigest(doc)).toBe(true);
|
|
177
|
+
expect(verifyChangeSetDigest({ ...doc, digest: changeSetDigest(doc.members.slice(1)) })).toBe(false);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test("moves when one member's plan moves, and only then", () => {
|
|
181
|
+
const base = composeChangeSet([lifecycle(), warden()]).digest;
|
|
182
|
+
expect(composeChangeSet([lifecycle(), warden()], { chant: "9.9.9" }).digest).toBe(base);
|
|
183
|
+
const moved = wardenPlan();
|
|
184
|
+
moved.entries[0] = { ...moved.entries[0], after: { privacy: "closed", description: "Release engineers" } };
|
|
185
|
+
expect(composeChangeSet([lifecycle(), reconcilePlanPart({ member: "warden", plan: moved })]).digest).not.toBe(base);
|
|
186
|
+
// A wave is a subset of members: its digest is the same function over them.
|
|
187
|
+
expect(changeSetDigest([lifecycle().member])).toBe(composeChangeSet([lifecycle()]).digest);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test("leaves provisional members out, so approving the document never covers them", () => {
|
|
191
|
+
const provisional = tofu();
|
|
192
|
+
provisional.member = { ...provisional.member, provisional: true };
|
|
193
|
+
const doc = composeChangeSet([lifecycle(), warden(), provisional]);
|
|
194
|
+
expect(doc.members.find((m) => m.provisional)?.member).toBe(provisional.member.member);
|
|
195
|
+
expect(doc.digest).toBe(composeChangeSet([lifecycle(), warden()]).digest);
|
|
196
|
+
expect(verifyChangeSetDigest(doc)).toBe(true);
|
|
197
|
+
expectValid(doc);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test("refuses two parts for one member, and an entry naming another member", () => {
|
|
201
|
+
expect(() => composeChangeSet([warden(), warden()])).toThrow(/names member warden twice/);
|
|
202
|
+
const stray = warden();
|
|
203
|
+
stray.entries[0] = { ...stray.entries[0], member: "other" };
|
|
204
|
+
expect(() => composeChangeSet([stray])).toThrow(/names member other, inside member warden/);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test("the summary counts per action, type and member, and names every delete and replacement", () => {
|
|
208
|
+
const doc = composeChangeSet([lifecycle(), warden(), tofu()]);
|
|
209
|
+
expect(doc.summary.actions).toEqual({ create: 4, update: 3, replace: 1, delete: 2, read: 0, "no-op": 4, forget: 0 });
|
|
210
|
+
expect(doc.summary.byMember.warden).toEqual({ create: 1, update: 1, delete: 1 });
|
|
211
|
+
expect(doc.summary.types.terraform_data).toEqual({ create: 2, update: 2, replace: 1, delete: 1, "no-op": 1 });
|
|
212
|
+
expect(doc.summary.deletes.map((d) => `${d.member}:${d.address}`)).toEqual(["estate:terraform_data.old", "warden:team.contractors"]);
|
|
213
|
+
expect(doc.summary.replacements.map((d) => `${d.member}:${d.address}`)).toEqual(["estate:terraform_data.worker"]);
|
|
214
|
+
});
|
|
215
|
+
});
|