@intentius/chant 0.90.0 → 0.93.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/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/lint.d.ts.map +1 -1
- package/dist/cli/handlers/components.d.ts +8 -0
- package/dist/cli/handlers/components.d.ts.map +1 -1
- package/dist/cli/handlers/graph.d.ts.map +1 -1
- package/dist/cli/handlers/operator.d.ts +20 -0
- package/dist/cli/handlers/operator.d.ts.map +1 -1
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/mcp/workspace-tools.d.ts +7 -4
- package/dist/cli/mcp/workspace-tools.d.ts.map +1 -1
- package/dist/cli/plugins.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +42 -1
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/codegen/pinned-upgrade.d.ts.map +1 -1
- package/dist/components/verbs/vuln-scan.d.ts +72 -0
- package/dist/components/verbs/vuln-scan.d.ts.map +1 -1
- package/dist/config.d.ts +22 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/lexicon-module.d.ts +22 -0
- package/dist/lexicon-module.d.ts.map +1 -1
- package/dist/lifecycle/converge-ledger.d.ts +19 -0
- package/dist/lifecycle/converge-ledger.d.ts.map +1 -1
- package/dist/lifecycle/git.d.ts +40 -5
- package/dist/lifecycle/git.d.ts.map +1 -1
- package/dist/lifecycle/lease.d.ts +72 -16
- package/dist/lifecycle/lease.d.ts.map +1 -1
- package/dist/lifecycle/member-ledger.d.ts +3 -2
- package/dist/lifecycle/member-ledger.d.ts.map +1 -1
- package/dist/lifecycle/plan-ledger.d.ts +114 -0
- package/dist/lifecycle/plan-ledger.d.ts.map +1 -0
- package/dist/lifecycle/run-ledger.d.ts +2 -0
- package/dist/lifecycle/run-ledger.d.ts.map +1 -1
- package/dist/lifecycle/symptoms.d.ts +51 -0
- package/dist/lifecycle/symptoms.d.ts.map +1 -1
- package/dist/lifecycle/work-lease.d.ts +140 -0
- package/dist/lifecycle/work-lease.d.ts.map +1 -0
- package/dist/lint/rules/index.d.ts +4 -2
- package/dist/lint/rules/index.d.ts.map +1 -1
- package/dist/lint/rules/op/index.d.ts +14 -3
- package/dist/lint/rules/op/index.d.ts.map +1 -1
- package/dist/lint/rules/op/ops014-converge-rule-refusals.d.ts.map +1 -1
- package/dist/lint/rules/op/sys010-key-over-plain-http.d.ts +29 -0
- package/dist/lint/rules/op/sys010-key-over-plain-http.d.ts.map +1 -0
- package/dist/lint/rules/sys001-no-literal-key.d.ts +3 -0
- package/dist/lint/rules/sys001-no-literal-key.d.ts.map +1 -0
- package/dist/op/__fixtures__/decide-stub-backend.d.ts +53 -0
- package/dist/op/__fixtures__/decide-stub-backend.d.ts.map +1 -0
- package/dist/op/activities/activity-contracts.d.ts +203 -2
- package/dist/op/activities/activity-contracts.d.ts.map +1 -1
- package/dist/op/activities/apply.d.ts.map +1 -1
- package/dist/op/activities/change-coverage.d.ts +44 -0
- package/dist/op/activities/change-coverage.d.ts.map +1 -0
- package/dist/op/activities/converge.d.ts +24 -1
- package/dist/op/activities/converge.d.ts.map +1 -1
- package/dist/op/activities/decide.d.ts +107 -0
- package/dist/op/activities/decide.d.ts.map +1 -0
- package/dist/op/activities/index.d.ts +10 -0
- package/dist/op/activities/index.d.ts.map +1 -1
- package/dist/op/activities/shell.d.ts +32 -0
- package/dist/op/activities/shell.d.ts.map +1 -1
- package/dist/op/activities/source-release.d.ts +86 -0
- package/dist/op/activities/source-release.d.ts.map +1 -0
- package/dist/op/activities/source-rollback.d.ts +100 -0
- package/dist/op/activities/source-rollback.d.ts.map +1 -0
- package/dist/op/activities/work-evidence.d.ts +42 -0
- package/dist/op/activities/work-evidence.d.ts.map +1 -0
- package/dist/op/activity-contract.d.ts +1 -0
- package/dist/op/activity-contract.d.ts.map +1 -1
- package/dist/op/activity-profiles.d.ts +16 -0
- package/dist/op/activity-profiles.d.ts.map +1 -1
- package/dist/op/builders.d.ts +84 -2
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/composites/converge-op.d.ts +31 -4
- package/dist/op/composites/converge-op.d.ts.map +1 -1
- package/dist/op/decide-backend.d.ts +116 -0
- package/dist/op/decide-backend.d.ts.map +1 -0
- package/dist/op/decide-config.d.ts +120 -0
- package/dist/op/decide-config.d.ts.map +1 -0
- package/dist/op/decide-read-inputs.d.ts +40 -0
- package/dist/op/decide-read-inputs.d.ts.map +1 -0
- package/dist/op/discover.d.ts +25 -0
- package/dist/op/discover.d.ts.map +1 -1
- package/dist/op/gate-wait.d.ts +43 -0
- package/dist/op/gate-wait.d.ts.map +1 -0
- package/dist/op/gate.d.ts +5 -2
- package/dist/op/gate.d.ts.map +1 -1
- package/dist/op/index.d.ts +18 -5
- package/dist/op/index.d.ts.map +1 -1
- package/dist/op/lifecycle-receipt-store.d.ts +34 -0
- package/dist/op/lifecycle-receipt-store.d.ts.map +1 -0
- package/dist/op/local-executor.d.ts +48 -4
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/local-output.d.ts +2 -0
- package/dist/op/local-output.d.ts.map +1 -1
- package/dist/op/op-ir.d.ts +12 -1
- package/dist/op/op-ir.d.ts.map +1 -1
- package/dist/op/op-verb-class.d.ts.map +1 -1
- package/dist/op/operator.d.ts +126 -1
- package/dist/op/operator.d.ts.map +1 -1
- package/dist/op/runtime.d.ts +46 -5
- package/dist/op/runtime.d.ts.map +1 -1
- package/dist/op/runtimes/local.d.ts.map +1 -1
- package/dist/op/source-archive.d.ts +63 -0
- package/dist/op/source-archive.d.ts.map +1 -0
- package/dist/op/step-output-ref.d.ts +2 -2
- package/dist/op/step-output-ref.d.ts.map +1 -1
- package/dist/op/steward-points.d.ts +136 -0
- package/dist/op/steward-points.d.ts.map +1 -0
- package/dist/op/steward-turn.d.ts +53 -0
- package/dist/op/steward-turn.d.ts.map +1 -0
- package/dist/op/steward.d.ts +161 -0
- package/dist/op/steward.d.ts.map +1 -0
- package/dist/op/types.d.ts +58 -0
- package/dist/op/types.d.ts.map +1 -1
- package/dist/op/work-lease-decl.d.ts +18 -0
- package/dist/op/work-lease-decl.d.ts.map +1 -0
- package/dist/op/work-lease-run.d.ts +173 -0
- package/dist/op/work-lease-run.d.ts.map +1 -0
- package/dist/workspace/__fixtures__/decide-workspace.d.ts +100 -0
- package/dist/workspace/__fixtures__/decide-workspace.d.ts.map +1 -0
- package/dist/workspace/answers-ledger.d.ts +76 -0
- package/dist/workspace/answers-ledger.d.ts.map +1 -0
- package/dist/workspace/box-isolation.d.ts +99 -0
- package/dist/workspace/box-isolation.d.ts.map +1 -0
- package/dist/workspace/changes-cli.d.ts +16 -0
- package/dist/workspace/changes-cli.d.ts.map +1 -0
- package/dist/workspace/changes.d.ts +200 -0
- package/dist/workspace/changes.d.ts.map +1 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-fly-site.d.ts +59 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-fly-site.d.ts.map +1 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-live-names.d.ts +51 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-live-names.d.ts.map +1 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-rollback.d.ts +26 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-rollback.d.ts.map +1 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-inputs.d.ts +59 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-inputs.d.ts.map +1 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-policy.d.ts +22 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-policy.d.ts.map +1 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit.d.ts +71 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit.d.ts.map +1 -0
- package/dist/workspace/chant-migrations.d.ts +99 -0
- package/dist/workspace/chant-migrations.d.ts.map +1 -0
- package/dist/workspace/checks/box-isolation.d.ts +18 -0
- package/dist/workspace/checks/box-isolation.d.ts.map +1 -0
- package/dist/workspace/checks/boxes.d.ts +104 -0
- package/dist/workspace/checks/boxes.d.ts.map +1 -0
- package/dist/workspace/checks/diagrams.d.ts +31 -0
- package/dist/workspace/checks/diagrams.d.ts.map +1 -0
- package/dist/workspace/checks/records.d.ts +8 -0
- package/dist/workspace/checks/records.d.ts.map +1 -1
- package/dist/workspace/checks.d.ts +13 -1
- package/dist/workspace/checks.d.ts.map +1 -1
- package/dist/workspace/compose-graph.d.ts +2 -0
- package/dist/workspace/compose-graph.d.ts.map +1 -1
- package/dist/workspace/conformance/index.d.ts.map +1 -1
- package/dist/workspace/decide.d.ts +229 -0
- package/dist/workspace/decide.d.ts.map +1 -0
- package/dist/workspace/decision-points.schema.json +137 -0
- package/dist/workspace/declaration.d.ts +103 -0
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.schema.json +285 -0
- package/dist/workspace/declared-kinds.d.ts +32 -0
- package/dist/workspace/declared-kinds.d.ts.map +1 -1
- package/dist/workspace/generated-files.d.ts +7 -0
- package/dist/workspace/generated-files.d.ts.map +1 -1
- package/dist/workspace/intent.d.ts.map +1 -1
- package/dist/workspace/lineage-check.d.ts +3 -0
- package/dist/workspace/lineage-check.d.ts.map +1 -1
- package/dist/workspace/lineage-upgrade.d.ts +6 -1
- package/dist/workspace/lineage-upgrade.d.ts.map +1 -1
- package/dist/workspace/ls.d.ts +39 -1
- package/dist/workspace/ls.d.ts.map +1 -1
- package/dist/workspace/point-answer.schema.json +95 -0
- package/dist/workspace/points-cli.d.ts +113 -0
- package/dist/workspace/points-cli.d.ts.map +1 -0
- package/dist/workspace/points.d.ts +320 -0
- package/dist/workspace/points.d.ts.map +1 -0
- package/dist/workspace/reason-codes.d.ts +38 -0
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/record-assets.d.ts.map +1 -1
- package/dist/workspace/records-cli.d.ts +23 -1
- package/dist/workspace/records-cli.d.ts.map +1 -1
- package/dist/workspace/records-close.d.ts +1 -1
- package/dist/workspace/records-close.d.ts.map +1 -1
- package/dist/workspace/records-write.d.ts +11 -5
- package/dist/workspace/records-write.d.ts.map +1 -1
- package/dist/workspace/records.d.ts +42 -6
- package/dist/workspace/records.d.ts.map +1 -1
- package/dist/workspace/status-gates.d.ts +7 -2
- package/dist/workspace/status-gates.d.ts.map +1 -1
- package/dist/workspace/status-stewards.d.ts +179 -0
- package/dist/workspace/status-stewards.d.ts.map +1 -0
- package/dist/workspace/status.d.ts +68 -1
- package/dist/workspace/status.d.ts.map +1 -1
- package/dist/workspace/work-cli.d.ts +189 -0
- package/dist/workspace/work-cli.d.ts.map +1 -0
- package/dist/workspace/work-evidence.d.ts +74 -0
- package/dist/workspace/work-evidence.d.ts.map +1 -0
- package/dist/workspace/work.d.ts +43 -1
- package/dist/workspace/work.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/cli/commands/audit.test.ts +9 -1
- package/src/cli/commands/build.ts +3 -2
- package/src/cli/commands/lint.ts +7 -1
- package/src/cli/handlers/components.test.ts +93 -0
- package/src/cli/handlers/components.ts +44 -3
- package/src/cli/handlers/graph.test.ts +20 -0
- package/src/cli/handlers/graph.ts +16 -2
- package/src/cli/handlers/operator.ts +141 -4
- package/src/cli/handlers/run.test.ts +173 -2
- package/src/cli/handlers/run.ts +174 -3
- package/src/cli/main.test.ts +50 -0
- package/src/cli/main.ts +97 -2
- package/src/cli/mcp/workspace-tools.test.ts +48 -19
- package/src/cli/mcp/workspace-tools.ts +110 -8
- package/src/cli/plugins.ts +11 -3
- package/src/cli/registry.ts +42 -1
- package/src/codegen/pinned-upgrade.ts +2 -1
- package/src/codegen/release-wiring.test.ts +49 -2
- package/src/components/capability-plugin-loader.ts +3 -3
- package/src/components/cli-support.ts +2 -2
- package/src/components/verbs/vuln-scan.test.ts +124 -1
- package/src/components/verbs/vuln-scan.ts +142 -1
- package/src/config.ts +11 -0
- package/src/lexicon-module.ts +56 -1
- package/src/lexicon-package.test.ts +96 -0
- package/src/lifecycle/converge-ledger.ts +20 -0
- package/src/lifecycle/git.ts +65 -15
- package/src/lifecycle/lease.test.ts +22 -0
- package/src/lifecycle/lease.ts +133 -29
- package/src/lifecycle/member-ledger.ts +3 -2
- package/src/lifecycle/plan-ledger.test.ts +148 -0
- package/src/lifecycle/plan-ledger.ts +158 -0
- package/src/lifecycle/run-ledger.ts +6 -0
- package/src/lifecycle/symptoms.ts +95 -0
- package/src/lifecycle/work-lease.test.ts +236 -0
- package/src/lifecycle/work-lease.ts +426 -0
- package/src/lint/rules/index.ts +6 -2
- package/src/lint/rules/op/index.ts +17 -4
- package/src/lint/rules/op/ops014-converge-rule-refusals.test.ts +23 -1
- package/src/lint/rules/op/ops014-converge-rule-refusals.ts +10 -6
- package/src/lint/rules/op/sys010-key-over-plain-http.test.ts +52 -0
- package/src/lint/rules/op/sys010-key-over-plain-http.ts +100 -0
- package/src/lint/rules/sys001-no-literal-key.test.ts +31 -0
- package/src/lint/rules/sys001-no-literal-key.ts +67 -0
- package/src/op/__fixtures__/decide-stub-backend.ts +143 -0
- package/src/op/activities/activity-contracts.ts +190 -1
- package/src/op/activities/apply.ts +7 -6
- package/src/op/activities/change-coverage.ts +60 -0
- package/src/op/activities/converge.test.ts +55 -1
- package/src/op/activities/converge.ts +208 -29
- package/src/op/activities/decide.live.test.ts +78 -0
- package/src/op/activities/decide.test.ts +300 -0
- package/src/op/activities/decide.ts +267 -0
- package/src/op/activities/index.ts +21 -0
- package/src/op/activities/shell.test.ts +91 -1
- package/src/op/activities/shell.ts +91 -2
- package/src/op/activities/source-release.test.ts +118 -0
- package/src/op/activities/source-release.ts +168 -0
- package/src/op/activities/source-rollback.test.ts +131 -0
- package/src/op/activities/source-rollback.ts +239 -0
- package/src/op/activities/work-evidence.ts +62 -0
- package/src/op/activity-contract-registry.test.ts +2 -0
- package/src/op/activity-contract-registry.ts +2 -2
- package/src/op/activity-contract.test.ts +12 -0
- package/src/op/activity-contract.ts +6 -1
- package/src/op/activity-profiles.ts +29 -0
- package/src/op/activity-registry.test.ts +1 -1
- package/src/op/activity-registry.ts +2 -2
- package/src/op/builders.ts +124 -5
- package/src/op/command-gate.test.ts +192 -0
- package/src/op/composites/converge-op.test.ts +27 -1
- package/src/op/composites/converge-op.ts +69 -15
- package/src/op/decide-backend.test.ts +44 -0
- package/src/op/decide-backend.ts +212 -0
- package/src/op/decide-config.test.ts +52 -0
- package/src/op/decide-config.ts +80 -0
- package/src/op/decide-read-inputs.ts +117 -0
- package/src/op/discover.ts +71 -0
- package/src/op/gate-approval.ts +2 -2
- package/src/op/gate-wait.ts +57 -0
- package/src/op/gate.ts +8 -3
- package/src/op/generate-pipeline.ts +2 -2
- package/src/op/index.ts +34 -6
- package/src/op/lifecycle-receipt-store.test.ts +60 -0
- package/src/op/lifecycle-receipt-store.ts +61 -0
- package/src/op/local-executor.ts +367 -33
- package/src/op/local-output.test.ts +33 -0
- package/src/op/local-output.ts +33 -2
- package/src/op/op-ir.test.ts +10 -0
- package/src/op/op-ir.ts +18 -0
- package/src/op/op-verb-class.ts +1 -0
- package/src/op/operator.ts +360 -10
- package/src/op/runtime.ts +37 -8
- package/src/op/runtimes/local.ts +6 -1
- package/src/op/source-archive.ts +161 -0
- package/src/op/step-output-ref.ts +6 -2
- package/src/op/steward-points.test.ts +313 -0
- package/src/op/steward-points.ts +194 -0
- package/src/op/steward-turn.ts +79 -0
- package/src/op/steward.test.ts +296 -0
- package/src/op/steward.ts +277 -0
- package/src/op/types.ts +60 -0
- package/src/op/work-lease-decl.ts +80 -0
- package/src/op/work-lease-run.test.ts +345 -0
- package/src/op/work-lease-run.ts +395 -0
- package/src/workspace/__fixtures__/decide-workspace.ts +129 -0
- package/src/workspace/__fixtures__/studio-template-84c8b21.json +67 -0
- package/src/workspace/answers-ledger.ts +118 -0
- package/src/workspace/box-isolation.test.ts +261 -0
- package/src/workspace/box-isolation.ts +205 -0
- package/src/workspace/changes-cli.ts +56 -0
- package/src/workspace/changes.schema.json +472 -0
- package/src/workspace/changes.test.ts +315 -0
- package/src/workspace/changes.ts +512 -0
- package/src/workspace/chant-migrations/chud-lexicon-exit-fly-site.ts +234 -0
- package/src/workspace/chant-migrations/chud-lexicon-exit-live-names.ts +164 -0
- package/src/workspace/chant-migrations/chud-lexicon-exit-rollback.ts +223 -0
- package/src/workspace/chant-migrations/chud-lexicon-exit-ship-inputs.ts +195 -0
- package/src/workspace/chant-migrations/chud-lexicon-exit-ship-policy.ts +108 -0
- package/src/workspace/chant-migrations/chud-lexicon-exit.ts +882 -0
- package/src/workspace/chant-migrations.e2e.test.ts +1089 -0
- package/src/workspace/chant-migrations.ts +164 -0
- package/src/workspace/check-contract.test.ts +5 -1
- package/src/workspace/check.schema.json +25 -7
- package/src/workspace/checks/box-isolation.ts +68 -0
- package/src/workspace/checks/boxes.test.ts +276 -0
- package/src/workspace/checks/boxes.ts +442 -0
- package/src/workspace/checks/diagrams.test.ts +134 -0
- package/src/workspace/checks/diagrams.ts +108 -0
- package/src/workspace/checks/records.ts +72 -0
- package/src/workspace/checks.test.ts +7 -0
- package/src/workspace/checks.ts +27 -3
- package/src/workspace/compose-graph.ts +2 -0
- package/src/workspace/conformance/__fixture__/decisions/decision.kind.mjs +10 -0
- package/src/workspace/conformance/__fixture__/decisions/decision.schema.json +26 -0
- package/src/workspace/conformance/index.ts +5 -0
- package/src/workspace/decide.test.ts +224 -0
- package/src/workspace/decide.ts +709 -0
- package/src/workspace/decision-points.schema.json +137 -0
- package/src/workspace/declaration.schema.json +285 -0
- package/src/workspace/declaration.test.ts +58 -0
- package/src/workspace/declaration.ts +241 -0
- package/src/workspace/declared-kinds.test.ts +5 -5
- package/src/workspace/declared-kinds.ts +71 -3
- package/src/workspace/generated-files.ts +14 -0
- package/src/workspace/graph-cli.ts +1 -1
- package/src/workspace/graph.schema.json +9 -1
- package/src/workspace/intent.schema.json +12 -3
- package/src/workspace/intent.ts +3 -10
- package/src/workspace/lineage-check.ts +16 -1
- package/src/workspace/lineage-upgrade-cli.ts +1 -1
- package/src/workspace/lineage-upgrade.test.ts +70 -0
- package/src/workspace/lineage-upgrade.ts +81 -10
- package/src/workspace/ls-contract.test.ts +57 -0
- package/src/workspace/ls.schema.json +46 -1
- package/src/workspace/ls.ts +62 -3
- package/src/workspace/point-answer.schema.json +95 -0
- package/src/workspace/points-cli.ts +284 -0
- package/src/workspace/points-write.schema.json +499 -0
- package/src/workspace/points.schema.json +743 -0
- package/src/workspace/points.test.ts +264 -0
- package/src/workspace/points.ts +564 -0
- package/src/workspace/read-contract.test.ts +18 -1
- package/src/workspace/reason-codes.test.ts +24 -1
- package/src/workspace/reason-codes.ts +49 -0
- package/src/workspace/record-assets.test.ts +3 -2
- package/src/workspace/record-assets.ts +4 -1
- package/src/workspace/records-amend.schema.json +2 -0
- package/src/workspace/records-cli.ts +28 -5
- package/src/workspace/records-close.schema.json +2 -0
- package/src/workspace/records-contract.test.ts +3 -2
- package/src/workspace/records-new.schema.json +2 -0
- package/src/workspace/records-review.schema.json +2 -0
- package/src/workspace/records-sessions-write.e2e.test.ts +77 -0
- package/src/workspace/records-sessions-write.test.ts +2 -34
- package/src/workspace/records-write.e2e.test.ts +108 -0
- package/src/workspace/records-write.test.ts +32 -41
- package/src/workspace/records-write.ts +33 -15
- package/src/workspace/records.schema.json +53 -2
- package/src/workspace/records.test.ts +48 -3
- package/src/workspace/records.ts +142 -6
- package/src/workspace/status-contract.test.ts +189 -6
- package/src/workspace/status-gates.ts +9 -4
- package/src/workspace/status-stewards.ts +329 -0
- package/src/workspace/status.schema.json +335 -5
- package/src/workspace/status.ts +142 -6
- package/src/workspace/work-acceptance.test.ts +274 -0
- package/src/workspace/work-cli.e2e.test.ts +180 -0
- package/src/workspace/work-cli.ts +429 -0
- package/src/workspace/work-evidence.schema.json +212 -0
- package/src/workspace/work-evidence.ts +169 -0
- package/src/workspace/work-history.schema.json +349 -0
- package/src/workspace/work-history.test.ts +156 -0
- package/src/workspace/work-lease.schema.json +233 -0
- package/src/workspace/work-readiness-chud.test.ts +145 -0
- package/src/workspace/work.test.ts +3 -2
- package/src/workspace/work.ts +100 -1
- /package/src/cli/{discovery-skip.test.ts → discovery-skip.e2e.test.ts} +0 -0
- /package/src/workspace/conformance/{conformance.test.ts → conformance.e2e.test.ts} +0 -0
- /package/src/workspace/{which-chant.test.ts → which-chant.e2e.test.ts} +0 -0
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `chud-lexicon-exit-fly-site` (#2809): declare a migrated repo's Fly site
|
|
3
|
+
* with the fly lexicon's `FlySite` composite.
|
|
4
|
+
*
|
|
5
|
+
* `chud-lexicon-exit` (0.92.0) deleted deploy/site.ts, chud's `ChudLocalSite`
|
|
6
|
+
* composite, and removed chud's `FlySite` marker from deploy/fly.ts, so the
|
|
7
|
+
* repo declared no composite instance: `chant workspace graph --composites`
|
|
8
|
+
* listed none, and nothing told a reader the app component deploys the site.
|
|
9
|
+
* Per ws-056 the Fly resources stay on the fly lexicon. This migration:
|
|
10
|
+
*
|
|
11
|
+
* - rewrites deploy/fly.ts as one `FlySite` instance (`flySite`) with the
|
|
12
|
+
* values deploy/fly.ts and deploy/fly-machine.ts declared, and deletes
|
|
13
|
+
* deploy/fly-machine.ts, when the two declare exactly the template's
|
|
14
|
+
* resources (comments aside). The build's plan has the same App, Volume,
|
|
15
|
+
* IP, Secret and one Machine, which the release Op's `flyRelease` finds;
|
|
16
|
+
* - names `FlySite` in the app component's `composites`, so the listing joins
|
|
17
|
+
* the instance to the component.
|
|
18
|
+
*
|
|
19
|
+
* A site the project changed keeps its files, and the plan lists the
|
|
20
|
+
* composite as not moved. The plan is empty unless the app component is the
|
|
21
|
+
* one `chud-lexicon-exit` wrote and names no composites yet.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
25
|
+
import { join, posix } from "node:path";
|
|
26
|
+
import type { ChantMigration, ChantMigrationContext, ChantMigrationPlan, NotMoved, PlannedChange } from "../chant-migrations";
|
|
27
|
+
|
|
28
|
+
export const CHUD_LEXICON_EXIT_FLY_SITE = "chud-lexicon-exit-fly-site";
|
|
29
|
+
|
|
30
|
+
const DESCRIPTION = "declare the Fly site with the fly lexicon's FlySite composite, the instance the app component deploys (#2809)";
|
|
31
|
+
|
|
32
|
+
function readText(dir: string, path: string): string | undefined {
|
|
33
|
+
const abs = join(dir, path);
|
|
34
|
+
return existsSync(abs) ? readFileSync(abs, "utf-8") : undefined;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** The template's Fly site, as deploy/fly.ts and deploy/fly-machine.ts declare it. */
|
|
38
|
+
export interface FlySiteValues {
|
|
39
|
+
/** The import that gives the app's name, kept as it is: `import { appSlug } from "../app-name.ts";`. */
|
|
40
|
+
nameImport: string | null;
|
|
41
|
+
/** The expression the App's name is, as written. */
|
|
42
|
+
app: string;
|
|
43
|
+
region: string;
|
|
44
|
+
machine: string;
|
|
45
|
+
image: string;
|
|
46
|
+
port: string;
|
|
47
|
+
env: string;
|
|
48
|
+
cpuKind: string;
|
|
49
|
+
cpus: string;
|
|
50
|
+
memoryMb: string;
|
|
51
|
+
volume: { name: string; sizeGb: string; path: string };
|
|
52
|
+
ip: string;
|
|
53
|
+
secret: { name: string; value: string };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** A file's code without its comments and blank lines. */
|
|
57
|
+
function codeOf(text: string): string {
|
|
58
|
+
return text
|
|
59
|
+
.replace(/\/\*[\s\S]*?\*\//g, "")
|
|
60
|
+
.split("\n")
|
|
61
|
+
.map((l) => l.replace(/\s+$/, ""))
|
|
62
|
+
.filter((l) => l !== "" && !/^\s*\/\//.test(l))
|
|
63
|
+
.join("\n");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const STR = `("[^"\\n]*")`;
|
|
67
|
+
const FLY_CODE = new RegExp(
|
|
68
|
+
"^" +
|
|
69
|
+
`import \\{ App, Fly, IPAddress, Secret, Volume \\} from "@intentius/chant-lexicon-fly";\\n` +
|
|
70
|
+
`(?:import \\{ FlySite \\} from "@intentius/chud-runtime/lexicon";\\n)?` +
|
|
71
|
+
`(?:(import \\{ \\w+ \\} from "\\.\\./app-name\\.ts";)\\n)?` +
|
|
72
|
+
`const name = ([^;\\n]+);\\n` +
|
|
73
|
+
`const region = ${STR};\\n` +
|
|
74
|
+
`export const flyApp = new App\\(\\{ name, org_slug: Fly\\.OrgSlug \\}\\);\\n` +
|
|
75
|
+
`export const data = new Volume\\(\\{ name: ${STR}, region, size_gb: (\\d+) \\}\\);\\n` +
|
|
76
|
+
`export const publicIp = new IPAddress\\(\\{ type: ${STR} \\}\\);\\n` +
|
|
77
|
+
`export const appSecret = new Secret\\(\\{ name: ${STR}, value: ([^\\n]+) \\}\\);` +
|
|
78
|
+
`(?:\\nexport const flySite = new FlySite\\([^\\n]*\\);)?$`,
|
|
79
|
+
);
|
|
80
|
+
const MACHINE_CODE = new RegExp(
|
|
81
|
+
[
|
|
82
|
+
`^import \\{ Machine, MachineConfig, MachineGuest, MachineMount, MachinePort, MachineService \\} from "@intentius/chant-lexicon-fly";`,
|
|
83
|
+
`export const guest = new MachineGuest\\(\\{ cpu_kind: ${STR}, cpus: (\\d+), memory_mb: (\\d+) \\}\\);`,
|
|
84
|
+
`export const dataMount = new MachineMount\\(\\{ volume: ${STR}, path: ${STR} \\}\\);`,
|
|
85
|
+
`export const https = new MachinePort\\(\\{ port: 443, handlers: \\["tls", "http"\\] \\}\\);`,
|
|
86
|
+
`export const http = new MachinePort\\(\\{ port: 80, handlers: \\["http"\\] \\}\\);`,
|
|
87
|
+
`export const web = new MachineService\\(\\{ protocol: "tcp", internal_port: (\\d+), ports: \\[https, http\\] \\}\\);`,
|
|
88
|
+
`export const server = new Machine\\(\\{`,
|
|
89
|
+
` {2}name: ${STR},`,
|
|
90
|
+
` {2}region: ${STR},`,
|
|
91
|
+
` {2}config: new MachineConfig\\(\\{`,
|
|
92
|
+
` {4}image: ${STR},`,
|
|
93
|
+
` {4}guest,`,
|
|
94
|
+
` {4}mounts: \\[dataMount\\],`,
|
|
95
|
+
` {4}services: \\[web\\],`,
|
|
96
|
+
` {4}env: (\\{[^\\n]*\\}),`,
|
|
97
|
+
` {2}\\}\\),`,
|
|
98
|
+
`\\}\\);$`,
|
|
99
|
+
].join("\\n"),
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* The Fly site's values when deploy/fly.ts and deploy/fly-machine.ts declare
|
|
104
|
+
* exactly the template's resources (their comments aside), or null. A site
|
|
105
|
+
* the project changed is left in its files, and the plan says so.
|
|
106
|
+
*/
|
|
107
|
+
export function readFlySite(fly: string, machine: string): FlySiteValues | null {
|
|
108
|
+
const f = FLY_CODE.exec(codeOf(fly));
|
|
109
|
+
const m = MACHINE_CODE.exec(codeOf(machine));
|
|
110
|
+
if (!f || !m) return null;
|
|
111
|
+
const [, nameImport, app, region, volumeName, sizeGb, ip, secretName, secretValue] = f;
|
|
112
|
+
const [, cpuKind, cpus, memoryMb, mountVolume, mountPath, port, machineName, machineRegion, image, env] = m;
|
|
113
|
+
// One region and one Volume: what the composite declares.
|
|
114
|
+
if (machineRegion !== region || mountVolume !== volumeName) return null;
|
|
115
|
+
return {
|
|
116
|
+
nameImport: nameImport ?? null,
|
|
117
|
+
app,
|
|
118
|
+
region,
|
|
119
|
+
machine: machineName,
|
|
120
|
+
image,
|
|
121
|
+
port,
|
|
122
|
+
env,
|
|
123
|
+
cpuKind,
|
|
124
|
+
cpus,
|
|
125
|
+
memoryMb,
|
|
126
|
+
volume: { name: volumeName, sizeGb, path: mountPath },
|
|
127
|
+
ip,
|
|
128
|
+
secret: { name: secretName, value: secretValue },
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** deploy/fly.ts declaring the site with the fly lexicon's FlySite composite. */
|
|
133
|
+
export function flySiteFile(v: FlySiteValues): string {
|
|
134
|
+
return `/**
|
|
135
|
+
* The Fly site, \`fly\` in chant.config.ts's chud.sites, declared with the fly
|
|
136
|
+
* lexicon's FlySite composite: the App, the Machine that serves the app (Fly's
|
|
137
|
+
* proxy sends 443 and 80 to its port ${v.port}), the Volume the app's data
|
|
138
|
+
* lives on, mounted at ${JSON.parse(v.volume.path)}, a public IP and the ${JSON.parse(v.secret.name)}
|
|
139
|
+
* Secret. \`chant build deploy --lexicon fly\` serializes it to Machines API
|
|
140
|
+
* requests, with the one Machine a release ships to. \`chant workspace graph
|
|
141
|
+
* --composites\` lists it as the instance \`flySite\`, which the app component
|
|
142
|
+
* (app.component.ts) deploys: it names FlySite in its \`composites\`.
|
|
143
|
+
*
|
|
144
|
+
* The same declarations apply to a real Fly org (FLY_API_TOKEN, and FLY_ORG
|
|
145
|
+
* for the org) or to mudflaps, the Machines API emulator, when
|
|
146
|
+
* FLY_FLAPS_BASE_URL points at it. Fly app names are global; change the name
|
|
147
|
+
* before the first release to a real org if it is taken.
|
|
148
|
+
*
|
|
149
|
+
* No secret value is in the repo. ${JSON.parse(v.secret.name)}'s value comes from the
|
|
150
|
+
* environment at release time; without it, the release checks the app
|
|
151
|
+
* already has it (\`fly secrets set ${JSON.parse(v.secret.name)}=...\`) and refuses otherwise.
|
|
152
|
+
*
|
|
153
|
+
* \`chant workspace upgrade\` wrote this from deploy/fly.ts and
|
|
154
|
+
* deploy/fly-machine.ts (its migration ${CHUD_LEXICON_EXIT_FLY_SITE}), with the
|
|
155
|
+
* same resources, in place of chud's FlySite marker and ChudLocalSite
|
|
156
|
+
* composite (ws-056). The local site is the studio kit's box service
|
|
157
|
+
* (arugula-salad/studio, template/).
|
|
158
|
+
*/
|
|
159
|
+
import { Fly, FlySite } from "@intentius/chant-lexicon-fly";
|
|
160
|
+
${v.nameImport ? `${v.nameImport}\n` : ""}
|
|
161
|
+
export const flySite = FlySite({
|
|
162
|
+
app: ${v.app},
|
|
163
|
+
org: Fly.OrgSlug,
|
|
164
|
+
region: ${v.region},
|
|
165
|
+
machine: ${v.machine},
|
|
166
|
+
image: ${v.image},
|
|
167
|
+
port: ${v.port},
|
|
168
|
+
env: ${v.env},
|
|
169
|
+
cpuKind: ${v.cpuKind},
|
|
170
|
+
cpus: ${v.cpus},
|
|
171
|
+
memoryMb: ${v.memoryMb},
|
|
172
|
+
volume: { name: ${v.volume.name}, sizeGb: ${v.volume.sizeGb}, path: ${v.volume.path} },
|
|
173
|
+
ip: ${v.ip},
|
|
174
|
+
secrets: { ${/^[A-Za-z_$][\w$]*$/.test(JSON.parse(v.secret.name)) ? JSON.parse(v.secret.name) : v.secret.name}: ${v.secret.value} },
|
|
175
|
+
});
|
|
176
|
+
`;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
const COMPONENT_BARE = ' archetype: "service",\n dependsOn: [],\n';
|
|
181
|
+
const COMPONENT_NAMED = ' archetype: "service",\n composites: ["FlySite"],\n dependsOn: [],\n';
|
|
182
|
+
const COMPONENT_SAID = " * (arugula-salad/studio, template/), and the Fly site's resources stay in\n * fly.ts and fly-machine.ts.\n */";
|
|
183
|
+
const COMPONENT_SAYS =
|
|
184
|
+
" * (arugula-salad/studio, template/).\n *\n * It deploys the Fly site, the fly lexicon's FlySite composite in fly.ts, and\n * says so in `composites`, so `chant workspace graph --composites` lists the\n * site's instance with this component.\n */";
|
|
185
|
+
/** The release Op's comment on where the Fly requests come from, as chud-lexicon-exit wrote it, and once the site is a FlySite. */
|
|
186
|
+
const RELEASE_SAID = " * deploy/fly.ts and deploy/fly-machine.ts, into dist/fly.json).";
|
|
187
|
+
const RELEASE_SAYS = " * the FlySite composite in deploy/fly.ts, into dist/fly.json).";
|
|
188
|
+
|
|
189
|
+
interface Declaration {
|
|
190
|
+
members?: Array<{ dir?: string; kind?: string }>;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function planFlySite(ctx: ChantMigrationContext): ChantMigrationPlan | null {
|
|
194
|
+
const { dir } = ctx;
|
|
195
|
+
let decl: Declaration | null = null;
|
|
196
|
+
try {
|
|
197
|
+
decl = JSON.parse(readText(dir, "chant.workspace.json") ?? "null") as Declaration | null;
|
|
198
|
+
} catch {
|
|
199
|
+
decl = null;
|
|
200
|
+
}
|
|
201
|
+
const candidates = (decl?.members ?? []).filter((m) => m.kind === "chant" && m.dir).map((m) => posix.normalize(m.dir!));
|
|
202
|
+
if (!candidates.includes("delivery")) candidates.push("delivery");
|
|
203
|
+
|
|
204
|
+
const changes: PlannedChange[] = [];
|
|
205
|
+
const notMoved: NotMoved[] = [];
|
|
206
|
+
for (const d of candidates) {
|
|
207
|
+
const at = (p: string) => posix.join(d, p);
|
|
208
|
+
const component = readText(dir, at("deploy/app.component.ts"));
|
|
209
|
+
if (component === undefined || !/its\s+\*?\s*migration chud-lexicon-exit\)/.test(component) || !component.includes(COMPONENT_BARE)) continue;
|
|
210
|
+
|
|
211
|
+
const fly = readText(dir, at("deploy/fly.ts"));
|
|
212
|
+
const machine = readText(dir, at("deploy/fly-machine.ts"));
|
|
213
|
+
const site = fly !== undefined && machine !== undefined ? readFlySite(fly, machine) : null;
|
|
214
|
+
if (site) {
|
|
215
|
+
changes.push({ path: at("deploy/fly.ts"), action: "write", why: "the Fly site as the fly lexicon's FlySite composite, the instance the app component deploys", data: Buffer.from(flySiteFile(site)) });
|
|
216
|
+
changes.push({ path: at("deploy/fly-machine.ts"), action: "delete", why: "its Machine is the FlySite composite's, in deploy/fly.ts" });
|
|
217
|
+
const release = readText(dir, at("ops/release.op.ts"));
|
|
218
|
+
if (release?.includes(RELEASE_SAID)) {
|
|
219
|
+
changes.push({ path: at("ops/release.op.ts"), action: "write", why: "its comment names the FlySite composite", data: Buffer.from(release.replace(RELEASE_SAID, RELEASE_SAYS)) });
|
|
220
|
+
}
|
|
221
|
+
} else if (fly !== undefined) {
|
|
222
|
+
notMoved.push({ what: `the Fly site as a composite instance (${at("deploy/fly.ts")} is not the template's, so its resources are kept as they are)`, where: "the fly lexicon's FlySite composite: declare the site with it, and the app component, which names FlySite in `composites`, deploys it" });
|
|
223
|
+
}
|
|
224
|
+
changes.push({ path: at("deploy/app.component.ts"), action: "write", why: "names FlySite in `composites`", data: Buffer.from(component.replace(COMPONENT_BARE, COMPONENT_NAMED).replace(COMPONENT_SAID, COMPONENT_SAYS)) });
|
|
225
|
+
}
|
|
226
|
+
if (changes.length === 0) return null;
|
|
227
|
+
return { id: CHUD_LEXICON_EXIT_FLY_SITE, description: DESCRIPTION, changes, notMoved, conflicts: [] };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export const chudLexiconExitFlySite: ChantMigration = {
|
|
231
|
+
id: CHUD_LEXICON_EXIT_FLY_SITE,
|
|
232
|
+
description: DESCRIPTION,
|
|
233
|
+
plan: planFlySite,
|
|
234
|
+
};
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `chud-lexicon-exit-live-names` (#2833): join the migrated `app` component
|
|
3
|
+
* to the Machine its release Op ships to, so `chant components status --live`
|
|
4
|
+
* reads it reconciled instead of stale.
|
|
5
|
+
*
|
|
6
|
+
* `chud-lexicon-exit` (0.92.0) rewrote `deploy/app.component.ts` off chud's
|
|
7
|
+
* app component, and wrote no `liveNames` on it. `components status --live`
|
|
8
|
+
* joins a component to live entities by name (`Component.liveNames`, falling
|
|
9
|
+
* back to the component's own name), and the component is `app` while the
|
|
10
|
+
* Machine the release Op ships to is some other entity. With no `liveNames`,
|
|
11
|
+
* the identity fallback looks for an entity named `app`, finds none, and the
|
|
12
|
+
* status reads `stale` even right after a release.
|
|
13
|
+
*
|
|
14
|
+
* This migration adds `liveNames: [<name>]` to the app component chant
|
|
15
|
+
* wrote, deriving `<name>` from what the member's Fly resources actually
|
|
16
|
+
* declare (#2839) rather than assuming one: while `deploy/fly-machine.ts`
|
|
17
|
+
* still declares the Machine directly, it is the entity `server`, its own
|
|
18
|
+
* top-level export. Once chant#2809's `FlySite` migration has taken over
|
|
19
|
+
* (deploy/fly.ts declares the site with the fly lexicon's `FlySite`
|
|
20
|
+
* composite and `fly-machine.ts` is gone), the composite expands its
|
|
21
|
+
* `machine` member under the site's own instance name, so the Machine is
|
|
22
|
+
* `<instance>Machine` — `flySiteMachine` for the instance name the FlySite
|
|
23
|
+
* migration writes (`flySite`), but read from the file rather than assumed,
|
|
24
|
+
* since a person may rename the export. The two migrations run in registry
|
|
25
|
+
* order (`FlySite` first), so a fresh upgrade that takes both always sees the
|
|
26
|
+
* post-`FlySite` shape by the time this one plans.
|
|
27
|
+
*
|
|
28
|
+
* It looks for the component `chud-lexicon-exit` left (by its own comment and
|
|
29
|
+
* shape) and inserts the field after `dependsOn: [],` rather than next to
|
|
30
|
+
* `archetype` — chant#2809's `FlySite` migration recognises the component by
|
|
31
|
+
* the exact, contiguous text `archetype: "service",\n dependsOn: [],\n`, so
|
|
32
|
+
* anything inserted between those two lines (by either migration, in either
|
|
33
|
+
* order) would stop it from finding a component whose `composites` a person
|
|
34
|
+
* later removes. Anchoring after `dependsOn` instead keeps that pair intact
|
|
35
|
+
* whichever of the two migrations plans first.
|
|
36
|
+
*
|
|
37
|
+
* The plan is empty once `liveNames` already names the Machine this would
|
|
38
|
+
* derive. A component whose `liveNames` is exactly `["server"]` — this
|
|
39
|
+
* migration's own hardcoded output before #2839 — is corrected once the
|
|
40
|
+
* Machine it names is gone (`FlySite` has since taken over); any other
|
|
41
|
+
* hand-set value is left alone, on the same "by any hand" rule the empty
|
|
42
|
+
* case follows. It is a conflict when the component still reads as
|
|
43
|
+
* chud-lexicon-exit's but the `dependsOn: [],` line the edit anchors on is
|
|
44
|
+
* gone, or neither `deploy/fly.ts` nor `deploy/fly-machine.ts` says what the
|
|
45
|
+
* Machine's live name is — either way a person needs to add the mapping by
|
|
46
|
+
* hand.
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
import { posix } from "node:path";
|
|
50
|
+
import type { ChantMigration, ChantMigrationContext, ChantMigrationPlan, PlanConflict, PlannedChange } from "../chant-migrations";
|
|
51
|
+
import { applyEdits, readText, CHUD_LEXICON_EXIT, type Edit } from "./chud-lexicon-exit";
|
|
52
|
+
|
|
53
|
+
export const CHUD_LEXICON_EXIT_LIVE_NAMES = "chud-lexicon-exit-live-names";
|
|
54
|
+
|
|
55
|
+
const DESCRIPTION = "join the app component to the Machine its release Op ships to, so components status --live reads it (#2833)";
|
|
56
|
+
|
|
57
|
+
/** The app component `chud-lexicon-exit` writes, recognised by its own comment and shape (composites or not, ws-056's FlySite migration may have added one). */
|
|
58
|
+
function isExitAppComponent(text: string): boolean {
|
|
59
|
+
return text.includes("wrote this from chud's app component") && text.includes(`migration ${CHUD_LEXICON_EXIT}`) && /name:\s*"app"/.test(text);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** This migration's own hardcoded output before #2839, worth correcting once the Machine it names no longer exists. */
|
|
63
|
+
const BAD_DEFAULT = 'liveNames: ["server"]';
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The live name of the Machine the member's release Op ships to, derived
|
|
67
|
+
* from what `deploy/fly.ts` and `deploy/fly-machine.ts` actually declare
|
|
68
|
+
* (#2839): `<instance>Machine`, from the export name a `FlySite` composite
|
|
69
|
+
* instance is declared under, once chant#2809's migration has taken over; the
|
|
70
|
+
* entity `server`, `fly-machine.ts`'s own top-level export, while that file
|
|
71
|
+
* still declares the Machine directly. Neither shape: null, so the caller
|
|
72
|
+
* leaves it to a person rather than guessing.
|
|
73
|
+
*/
|
|
74
|
+
function machineLiveName(dir: string, memberDir: string): string | null {
|
|
75
|
+
const fly = readText(dir, posix.join(memberDir, "deploy/fly.ts"));
|
|
76
|
+
const instance = fly !== undefined ? /^export const (\w+) = FlySite\(/m.exec(fly)?.[1] : undefined;
|
|
77
|
+
if (instance) return `${instance}Machine`;
|
|
78
|
+
if (readText(dir, posix.join(memberDir, "deploy/fly-machine.ts")) !== undefined) return "server";
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function liveNamesEdits(name: string): Edit[] {
|
|
83
|
+
return [
|
|
84
|
+
{
|
|
85
|
+
find: /dependsOn: \[\],\n/,
|
|
86
|
+
replace: `dependsOn: [],\n liveNames: ["${name}"],\n`,
|
|
87
|
+
required: "the dependsOn: [] line",
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
// Best-effort: names the Machine in the component's own doc comment, when
|
|
91
|
+
// it still reads as chud-lexicon-exit left it. Not required — an edited
|
|
92
|
+
// comment is left alone. The caller only reaches this when the component
|
|
93
|
+
// has no liveNames yet, so this never double-fires.
|
|
94
|
+
find: " * records no release. The local site is the studio kit's box service\n * (arugula-salad/studio, template/), and the Fly site's resources stay in\n * fly.ts and fly-machine.ts.\n",
|
|
95
|
+
replace:
|
|
96
|
+
" * records no release. The local site is the studio kit's box service\n" +
|
|
97
|
+
" * (arugula-salad/studio, template/), and the Fly site's resources stay in\n" +
|
|
98
|
+
" * fly.ts and fly-machine.ts. Its live name is the Machine the release Op\n" +
|
|
99
|
+
` * ships to, the entity ${name} (chant#2833), which\n` +
|
|
100
|
+
" * `chant components status --live` observes for it.\n",
|
|
101
|
+
},
|
|
102
|
+
];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
interface Declaration {
|
|
106
|
+
members?: Array<{ dir?: string; kind?: string }>;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function planLiveNames(ctx: ChantMigrationContext): ChantMigrationPlan | null {
|
|
110
|
+
const { dir } = ctx;
|
|
111
|
+
let decl: Declaration | null = null;
|
|
112
|
+
try {
|
|
113
|
+
decl = JSON.parse(readText(dir, "chant.workspace.json") ?? "null") as Declaration | null;
|
|
114
|
+
} catch {
|
|
115
|
+
decl = null;
|
|
116
|
+
}
|
|
117
|
+
const candidates = (decl?.members ?? []).filter((m) => m.kind === "chant" && m.dir).map((m) => posix.normalize(m.dir!));
|
|
118
|
+
if (!candidates.includes("delivery")) candidates.push("delivery");
|
|
119
|
+
|
|
120
|
+
const changes: PlannedChange[] = [];
|
|
121
|
+
const conflicts: PlanConflict[] = [];
|
|
122
|
+
for (const d of candidates) {
|
|
123
|
+
const path = posix.join(d, "deploy/app.component.ts");
|
|
124
|
+
const text = readText(dir, path);
|
|
125
|
+
if (text === undefined || !isExitAppComponent(text)) continue;
|
|
126
|
+
|
|
127
|
+
if (/liveNames:/.test(text)) {
|
|
128
|
+
// Already there, by any hand: nothing to add. Correct it only when it
|
|
129
|
+
// is exactly this migration's own bad default and the Machine it names
|
|
130
|
+
// is gone — any other value, including the right one, is left alone.
|
|
131
|
+
if (!text.includes(BAD_DEFAULT)) continue;
|
|
132
|
+
const name = machineLiveName(dir, d);
|
|
133
|
+
if (name === null || name === "server") continue;
|
|
134
|
+
changes.push({
|
|
135
|
+
path,
|
|
136
|
+
action: "write",
|
|
137
|
+
why: `liveNames: ["${name}"]: the app component joins the Machine the FlySite composite gives it now, in place of the entity this migration wrongly assumed (#2839)`,
|
|
138
|
+
data: Buffer.from(text.replace(BAD_DEFAULT, `liveNames: ["${name}"]`)),
|
|
139
|
+
});
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const name = machineLiveName(dir, d);
|
|
144
|
+
if (name === null) {
|
|
145
|
+
conflicts.push({ path, reason: "cannot tell the Machine's live name from deploy/fly.ts or deploy/fly-machine.ts. Add liveNames to the app component by hand, then run the upgrade again" });
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
const edited = applyEdits(text, liveNamesEdits(name));
|
|
149
|
+
if (edited.missing.length > 0) {
|
|
150
|
+
conflicts.push({ path, reason: `cannot find ${edited.missing.join("; ")}. Add liveNames: ["${name}"] to the app component by hand, then run the upgrade again` });
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (edited.text === text) continue;
|
|
154
|
+
changes.push({ path, action: "write", why: `liveNames: ["${name}"]: the app component joins the Machine its release Op ships to`, data: Buffer.from(edited.text) });
|
|
155
|
+
}
|
|
156
|
+
if (changes.length === 0 && conflicts.length === 0) return null;
|
|
157
|
+
return { id: CHUD_LEXICON_EXIT_LIVE_NAMES, description: DESCRIPTION, changes, notMoved: [], conflicts };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export const chudLexiconExitLiveNames: ChantMigration = {
|
|
161
|
+
id: CHUD_LEXICON_EXIT_LIVE_NAMES,
|
|
162
|
+
description: DESCRIPTION,
|
|
163
|
+
plan: planLiveNames,
|
|
164
|
+
};
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `chud-lexicon-exit-rollback` (#2800): give a repo `chud-lexicon-exit`
|
|
3
|
+
* migrated the rollback Op chud had, on chant's own steps.
|
|
4
|
+
*
|
|
5
|
+
* `chud-lexicon-exit` shipped in 0.92.0 with a release Op that ships the app
|
|
6
|
+
* member to Fly as a source tree (#2782), and it deleted chud's rollback Op
|
|
7
|
+
* because nothing in chant could take that site back yet. This migration
|
|
8
|
+
* writes `ops/rollback.op.ts` beside the release Op, and a `rollback` script.
|
|
9
|
+
* A repo `chud-lexicon-exit` migrates from now on gets both, since this
|
|
10
|
+
* migration is planned after it in the same upgrade.
|
|
11
|
+
*
|
|
12
|
+
* The rollback is an Op rather than `chant components rollback` because the
|
|
13
|
+
* release is one: the release Op archives, plans, gates, ships and records,
|
|
14
|
+
* and the app component only runs the supply chain. Rolling back through the
|
|
15
|
+
* component would put publish and rollback in two places, which the boundary
|
|
16
|
+
* between Ops and components says not to do (components/backends-reference).
|
|
17
|
+
*
|
|
18
|
+
* The plan is empty unless the member's release Op is the one
|
|
19
|
+
* `chud-lexicon-exit` wrote, and once the rollback Op is there. A file at
|
|
20
|
+
* `ops/rollback.op.ts` that is not this one is a conflict.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
24
|
+
import { join, posix } from "node:path";
|
|
25
|
+
import type { ChantMigration, ChantMigrationContext, ChantMigrationPlan, PlanConflict, PlannedChange } from "../chant-migrations";
|
|
26
|
+
import { CHUD_LEXICON_EXIT } from "./chud-lexicon-exit";
|
|
27
|
+
|
|
28
|
+
export const CHUD_LEXICON_EXIT_ROLLBACK = "chud-lexicon-exit-rollback";
|
|
29
|
+
|
|
30
|
+
const DESCRIPTION = "add the rollback Op to a repo chud-lexicon-exit migrated: the previous source release back on the Fly site (#2800)";
|
|
31
|
+
|
|
32
|
+
/** The release Op `chud-lexicon-exit` writes, recognised by what it holds. */
|
|
33
|
+
function isExitReleaseOp(text: string): boolean {
|
|
34
|
+
return text.includes(`its migration ${CHUD_LEXICON_EXIT}`) && text.includes("flyRelease(") && text.includes("releasePlan(");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function rollbackOp(pointsRel: string): string {
|
|
38
|
+
return `/**
|
|
39
|
+
* Rollback: put the release the Fly site served before the latest one back
|
|
40
|
+
* on it, once the rollback plan is approved, and record it.
|
|
41
|
+
*
|
|
42
|
+
* \`chant workspace upgrade\` wrote this (its migration ${CHUD_LEXICON_EXIT_ROLLBACK})
|
|
43
|
+
* in place of chud's rollback Op, beside the release Op (ops/release.op.ts):
|
|
44
|
+
*
|
|
45
|
+
* - Plan reads the \`fly\` release ledger. The release it goes back to is the
|
|
46
|
+
* one the site served before the latest release the release Op shipped
|
|
47
|
+
* (\`to: "sha256:..."\` on the plan step picks another). Its plan is read
|
|
48
|
+
* back from chant/lifecycle, its commit's app member is archived again, and
|
|
49
|
+
* the run stops unless the archive hashes to the digest that plan
|
|
50
|
+
* recorded. The rollback plan names both releases and the archive, and is
|
|
51
|
+
* named by its own sha256. Build writes the Fly app's requests.
|
|
52
|
+
* - The \`rollback\` gate approves that plan's digest: \`chant approve rollback
|
|
53
|
+
* rollback --plan <digest>\`. It takes the ship gate's approver count and
|
|
54
|
+
* Cedar policy (decisions/ship-skip.cedar.ts, \`log-only\`): a rollback has
|
|
55
|
+
* no ship-skip answer, so only people pass it.
|
|
56
|
+
* - Roll back reads the archive again, refused unless it still hashes to the
|
|
57
|
+
* planned digest, before any call to Fly. The Machine config the release
|
|
58
|
+
* Op recorded for that release must carry exactly that tree under
|
|
59
|
+
* /srv/app, or nothing changes. Then that config goes back on the Machine,
|
|
60
|
+
* files and start command included, and the Machine must be started with
|
|
61
|
+
* that release (and healthy at \`chud.sites.fly.url\` when set). Migrations
|
|
62
|
+
* are not undone: the data stays where the later release left it.
|
|
63
|
+
* - Record appends the restored release to the \`fly\` release ledger, with
|
|
64
|
+
* \`restores\` naming it, the actor who ran the rollback and the approver.
|
|
65
|
+
*
|
|
66
|
+
* Running it again changes nothing: the same ledger plans the same rollback,
|
|
67
|
+
* so its approval holds, the Machine already serves the release, and the
|
|
68
|
+
* ledger is written once. A release shipped after the rollback is the latest
|
|
69
|
+
* one again, so the next rollback goes back from it.
|
|
70
|
+
*/
|
|
71
|
+
import { readFileSync } from "node:fs";
|
|
72
|
+
import { fileURLToPath } from "node:url";
|
|
73
|
+
import { Op, phase, gate, build, releaseRollbackPlan, releaseRollbackRecord } from "@intentius/chant/op";
|
|
74
|
+
import { parsePoints, quorumOf } from "@intentius/chant/workspace/points";
|
|
75
|
+
import { flyRollback } from "@intentius/chant-lexicon-fly";
|
|
76
|
+
import project from "../chant.config.ts";
|
|
77
|
+
import { shipSkipPolicy } from "../decisions/ship-skip.cedar.ts";
|
|
78
|
+
|
|
79
|
+
/** The environment the release Op ships to. */
|
|
80
|
+
const ENV = "fly";
|
|
81
|
+
/** Where the release Op put the files on the Machine. */
|
|
82
|
+
const INTO = "/srv/app";
|
|
83
|
+
const params = project.buildParams;
|
|
84
|
+
/** The Fly site's public URL, when chant.config.ts gives one: the restored release is verified at its health endpoint too. */
|
|
85
|
+
const siteUrl = (project as { chud?: { sites?: { fly?: { url?: string } } } }).chud?.sites?.fly?.url;
|
|
86
|
+
|
|
87
|
+
const pointsFile = fileURLToPath(new URL(${JSON.stringify(pointsRel)}, import.meta.url));
|
|
88
|
+
const shipSkipPoint = parsePoints(readFileSync(pointsFile, "utf-8"), pointsFile)["ship-skip"];
|
|
89
|
+
|
|
90
|
+
const plan = releaseRollbackPlan({ id: "plan", component: "app", env: ENV });
|
|
91
|
+
|
|
92
|
+
export default Op({
|
|
93
|
+
name: "rollback",
|
|
94
|
+
overview: \`Put the previous release of \${params.name.default} back on Fly once the rollback plan is approved.\`,
|
|
95
|
+
phases: [
|
|
96
|
+
phase("Plan", [plan, build(".", { script: "build:fly" })]),
|
|
97
|
+
phase("Gate", [
|
|
98
|
+
gate("rollback", {
|
|
99
|
+
plan: plan.out.digest,
|
|
100
|
+
description: \`Roll \${params.name.default} on \${ENV} back to its previous release\`,
|
|
101
|
+
approval: { quorum: quorumOf(shipSkipPoint), policy: shipSkipPolicy, mode: "log-only" },
|
|
102
|
+
}),
|
|
103
|
+
]),
|
|
104
|
+
phase("Roll back", [
|
|
105
|
+
flyRollback({
|
|
106
|
+
environment: ENV,
|
|
107
|
+
component: "app",
|
|
108
|
+
plan: "dist/fly.json",
|
|
109
|
+
to: plan.out.to,
|
|
110
|
+
source: { archive: plan.out.archive, digest: plan.out.archiveDigest, dir: plan.out.dir, into: INTO },
|
|
111
|
+
verify: { url: siteUrl, healthPath: params.appHealth.default },
|
|
112
|
+
}),
|
|
113
|
+
]),
|
|
114
|
+
phase("Record", [releaseRollbackRecord({ plan: plan.out.file, digest: plan.out.digest, approval: { op: "rollback", gate: "rollback" } })]),
|
|
115
|
+
],
|
|
116
|
+
});
|
|
117
|
+
`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** The release Op's sentence about the rollback, as chud-lexicon-exit wrote it, and what it says once the rollback Op is there. */
|
|
121
|
+
const RELEASE_SAID = /\), and rolling the site back to the previous\n \* release \(INTENTIUS\/chant#2800\)\. /;
|
|
122
|
+
const RELEASE_SAYS = "). ops/rollback.op.ts rolls the site back to the\n * previous release. ";
|
|
123
|
+
|
|
124
|
+
/** The root docs' lines `chud-lexicon-exit` wrote without a rollback (#2805), and what they say once it is there. Each is left alone when not found. */
|
|
125
|
+
const DOC_EDITS: Array<{ path: string; find: string; replace: string }> = [
|
|
126
|
+
{
|
|
127
|
+
path: "README.md",
|
|
128
|
+
find: "the release Op (Check, the ship-skip decision point, then the ship gate), the app component's supply chain,",
|
|
129
|
+
replace: "the release Op (Check, the ship-skip decision point, then the ship gate) and the rollback Op, the app component's supply chain,",
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
path: "README.md",
|
|
133
|
+
find: "npm run release # ship it\n",
|
|
134
|
+
replace: "npm run release # ship it\nnpm run rollback # chant run rollback: the previous release back, through its gate\n",
|
|
135
|
+
},
|
|
136
|
+
{ path: "CLAUDE.md", find: "`ops/` (the release Op),", replace: "`ops/` (the release and rollback Ops)," },
|
|
137
|
+
];
|
|
138
|
+
|
|
139
|
+
interface Declaration {
|
|
140
|
+
members?: Array<{ name?: string; dir?: string; kind?: string }>;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function readText(dir: string, path: string): string | undefined {
|
|
144
|
+
const abs = join(dir, path);
|
|
145
|
+
return existsSync(abs) ? readFileSync(abs, "utf-8") : undefined;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function planRollback(ctx: ChantMigrationContext): ChantMigrationPlan | null {
|
|
149
|
+
const { dir } = ctx;
|
|
150
|
+
let decl: Declaration | null = null;
|
|
151
|
+
try {
|
|
152
|
+
decl = JSON.parse(readText(dir, "chant.workspace.json") ?? "null") as Declaration | null;
|
|
153
|
+
} catch {
|
|
154
|
+
decl = null;
|
|
155
|
+
}
|
|
156
|
+
const candidates = (decl?.members ?? []).filter((m) => m.kind === "chant" && m.dir).map((m) => posix.normalize(m.dir!));
|
|
157
|
+
if (!candidates.includes("delivery")) candidates.push("delivery");
|
|
158
|
+
const members = candidates.filter((d) => {
|
|
159
|
+
const release = readText(dir, posix.join(d, "ops/release.op.ts"));
|
|
160
|
+
return release !== undefined && isExitReleaseOp(release);
|
|
161
|
+
});
|
|
162
|
+
if (members.length === 0) return null;
|
|
163
|
+
|
|
164
|
+
const changes: PlannedChange[] = [];
|
|
165
|
+
const conflicts: PlanConflict[] = [];
|
|
166
|
+
for (const d of members) {
|
|
167
|
+
const at = (p: string) => posix.join(d, p);
|
|
168
|
+
const opPath = at("ops/rollback.op.ts");
|
|
169
|
+
const text = rollbackOp(posix.relative(at("ops"), "decisions/points.json"));
|
|
170
|
+
const current = readText(dir, opPath);
|
|
171
|
+
if (current === undefined) {
|
|
172
|
+
changes.push({ path: opPath, action: "write", why: "chant's rollback Op: Plan from the release ledger with the archive checked again, the rollback gate, Roll back on Fly, and Record", data: Buffer.from(text) });
|
|
173
|
+
} else if (current !== text && !current.includes(`its migration ${CHUD_LEXICON_EXIT_ROLLBACK}`)) {
|
|
174
|
+
conflicts.push({ path: opPath, reason: "exists and is not the rollback Op this migration writes. Move it aside or delete it, then run the upgrade again" });
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// The release Op's comment named the rollback as not chant's yet.
|
|
179
|
+
const releasePath = at("ops/release.op.ts");
|
|
180
|
+
const release = readText(dir, releasePath)!;
|
|
181
|
+
const said = RELEASE_SAID.exec(release);
|
|
182
|
+
if (said) {
|
|
183
|
+
changes.push({ path: releasePath, action: "write", why: "its comment names ops/rollback.op.ts", data: Buffer.from(release.replace(RELEASE_SAID, RELEASE_SAYS)) });
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const pkgPath = at("package.json");
|
|
187
|
+
const pkgText = readText(dir, pkgPath);
|
|
188
|
+
if (pkgText !== undefined) {
|
|
189
|
+
try {
|
|
190
|
+
const pkg = JSON.parse(pkgText) as { scripts?: Record<string, string> };
|
|
191
|
+
if (pkg.scripts?.release === "chant run release" && pkg.scripts.rollback === undefined) {
|
|
192
|
+
const scripts: Record<string, string> = {};
|
|
193
|
+
for (const [k, v] of Object.entries(pkg.scripts)) {
|
|
194
|
+
scripts[k] = v;
|
|
195
|
+
if (k === "release") scripts.rollback = "chant run rollback";
|
|
196
|
+
}
|
|
197
|
+
const next = JSON.stringify({ ...pkg, scripts }, null, 2) + "\n";
|
|
198
|
+
changes.push({ path: pkgPath, action: "write", why: "adds `rollback`: chant run rollback", data: Buffer.from(next) });
|
|
199
|
+
}
|
|
200
|
+
} catch {
|
|
201
|
+
conflicts.push({ path: pkgPath, reason: "is not JSON" });
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (conflicts.length === 0) {
|
|
206
|
+
for (const path of [...new Set(DOC_EDITS.map((e) => e.path))]) {
|
|
207
|
+
const text = readText(dir, path);
|
|
208
|
+
if (text === undefined) continue;
|
|
209
|
+
let next = text;
|
|
210
|
+
for (const e of DOC_EDITS.filter((x) => x.path === path)) {
|
|
211
|
+
if (next.includes(e.find) && !next.includes(e.replace)) next = next.replace(e.find, e.replace);
|
|
212
|
+
}
|
|
213
|
+
if (next !== text) changes.push({ path, action: "write", why: "names the rollback Op", data: Buffer.from(next) });
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return { id: CHUD_LEXICON_EXIT_ROLLBACK, description: DESCRIPTION, changes, notMoved: [], conflicts };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export const chudLexiconExitRollback: ChantMigration = {
|
|
220
|
+
id: CHUD_LEXICON_EXIT_ROLLBACK,
|
|
221
|
+
description: DESCRIPTION,
|
|
222
|
+
plan: planRollback,
|
|
223
|
+
};
|