@intentius/chant 0.91.0 → 0.94.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/graph.d.ts.map +1 -1
- package/dist/cli/handlers/operator.d.ts +6 -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-plugins.d.ts +6 -6
- package/dist/cli/mcp/workspace-tools.d.ts +2 -1
- 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 +12 -2
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/codegen/pinned-upgrade.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/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/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 +201 -0
- 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/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 +11 -4
- package/dist/op/index.d.ts.map +1 -1
- package/dist/op/local-executor.d.ts +29 -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 +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/operator.d.ts +97 -1
- package/dist/op/operator.d.ts.map +1 -1
- package/dist/op/runtime.d.ts +37 -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/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 +22 -1
- package/dist/op/steward.d.ts.map +1 -1
- package/dist/op/types.d.ts +7 -0
- package/dist/op/types.d.ts.map +1 -1
- package/dist/op/work-lease-run.d.ts +1 -1
- package/dist/op/work-lease-run.d.ts.map +1 -1
- 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-intent.d.ts +85 -0
- package/dist/workspace/box-intent.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 +104 -0
- package/dist/workspace/chant-migrations.d.ts.map +1 -0
- package/dist/workspace/checks/boxes.d.ts +47 -1
- package/dist/workspace/checks/boxes.d.ts.map +1 -1
- 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 +7 -0
- package/dist/workspace/checks/records.d.ts.map +1 -1
- package/dist/workspace/checks.d.ts +7 -0
- 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 +49 -4
- package/dist/workspace/decide.d.ts.map +1 -1
- package/dist/workspace/decision-points.schema.json +3 -3
- package/dist/workspace/declaration.d.ts +58 -0
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.schema.json +119 -1
- package/dist/workspace/declared-kinds.d.ts +20 -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-joins.d.ts +5 -5
- 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 +2 -0
- package/dist/workspace/points-cli.d.ts.map +1 -1
- package/dist/workspace/points.d.ts +4 -6
- package/dist/workspace/points.d.ts.map +1 -1
- package/dist/workspace/reason-codes.d.ts +16 -2
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/records-cli.d.ts +11 -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 +34 -4
- 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 +65 -1
- package/dist/workspace/status-stewards.d.ts.map +1 -1
- package/dist/workspace/status.d.ts +24 -0
- package/dist/workspace/status.d.ts.map +1 -1
- package/dist/workspace/work-cli.d.ts +113 -2
- package/dist/workspace/work-cli.d.ts.map +1 -1
- 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/graph.test.ts +20 -0
- package/src/cli/handlers/graph.ts +16 -2
- package/src/cli/handlers/operator.ts +34 -2
- package/src/cli/handlers/run.test.ts +154 -2
- package/src/cli/handlers/run.ts +121 -2
- package/src/cli/main.test.ts +10 -0
- package/src/cli/main.ts +23 -3
- package/src/cli/mcp/workspace-plugins.ts +6 -6
- package/src/cli/mcp/workspace-tools.test.ts +35 -19
- package/src/cli/mcp/workspace-tools.ts +62 -4
- package/src/cli/plugins.ts +11 -3
- package/src/cli/registry.ts +12 -2
- package/src/cli/serve-mcp-workspace.test.ts +6 -6
- 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/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/run-ledger.ts +6 -0
- package/src/lifecycle/symptoms.ts +95 -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 +308 -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 +119 -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/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 +18 -3
- package/src/op/local-executor.ts +151 -15
- package/src/op/local-output.test.ts +33 -0
- package/src/op/local-output.ts +20 -2
- package/src/op/op-ir.test.ts +10 -0
- package/src/op/op-ir.ts +4 -0
- package/src/op/op-verb-class.ts +1 -0
- package/src/op/operator.ts +290 -11
- package/src/op/runtime.ts +31 -8
- package/src/op/runtimes/local.ts +3 -1
- package/src/op/source-archive.ts +161 -0
- package/src/op/steward-points.test.ts +364 -0
- package/src/op/steward-points.ts +194 -0
- package/src/op/steward-turn.ts +79 -0
- package/src/op/steward.test.ts +87 -3
- package/src/op/steward.ts +25 -1
- package/src/op/types.ts +7 -0
- package/src/op/work-lease-run.test.ts +20 -1
- package/src/op/work-lease-run.ts +2 -2
- 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-intent.test.ts +205 -0
- package/src/workspace/box-intent.ts +159 -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 +169 -0
- package/src/workspace/check-contract.test.ts +4 -2
- package/src/workspace/check.schema.json +19 -5
- package/src/workspace/checks/boxes.test.ts +82 -2
- package/src/workspace/checks/boxes.ts +202 -1
- package/src/workspace/checks/diagrams.test.ts +134 -0
- package/src/workspace/checks/diagrams.ts +108 -0
- package/src/workspace/checks/records.ts +47 -0
- package/src/workspace/checks.ts +20 -2
- 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 +1 -1
- package/src/workspace/decide.ts +156 -23
- package/src/workspace/decision-points.schema.json +3 -3
- package/src/workspace/declaration.schema.json +119 -1
- package/src/workspace/declaration.test.ts +58 -0
- package/src/workspace/declaration.ts +112 -0
- package/src/workspace/declared-kinds.test.ts +31 -5
- package/src/workspace/declared-kinds.ts +46 -1
- 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-joins.test.ts +5 -5
- package/src/workspace/intent-joins.ts +5 -5
- package/src/workspace/intent.schema.json +8 -2
- 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/points-cli.ts +17 -3
- package/src/workspace/points-write.schema.json +11 -1
- package/src/workspace/points.schema.json +51 -0
- package/src/workspace/points.test.ts +16 -15
- package/src/workspace/points.ts +5 -17
- package/src/workspace/read-contract.test.ts +9 -1
- package/src/workspace/reason-codes.test.ts +18 -8
- package/src/workspace/reason-codes.ts +21 -2
- package/src/workspace/records-amend.schema.json +2 -0
- package/src/workspace/records-cli.ts +27 -4
- package/src/workspace/records-close.schema.json +2 -0
- package/src/workspace/records-formats.test.ts +15 -15
- 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-since.test.ts +8 -7
- 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 +36 -2
- package/src/workspace/records.test.ts +49 -4
- package/src/workspace/records.ts +109 -3
- package/src/workspace/status-contract.test.ts +14 -8
- package/src/workspace/status-gates.ts +9 -4
- package/src/workspace/status-stewards.ts +155 -5
- package/src/workspace/status.schema.json +129 -8
- package/src/workspace/status.ts +46 -1
- package/src/workspace/work-acceptance.test.ts +274 -0
- package/src/workspace/{work-cli.test.ts → work-cli.e2e.test.ts} +1 -1
- package/src/workspace/work-cli.ts +193 -10
- 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-readiness-chud.test.ts → work-readiness.test.ts} +30 -32
- package/src/workspace/work.test.ts +20 -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,882 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `chud-lexicon-exit` (#2737, ws-056): take a repo made from chud's template,
|
|
3
|
+
* or the studio kit's copy of it, off `@intentius/chant-lexicon-chud` and
|
|
4
|
+
* `@intentius/chud-runtime`.
|
|
5
|
+
*
|
|
6
|
+
* ws-056 split the chud lexicon by the boundary. What is specification goes to
|
|
7
|
+
* chant, and this migration moves it:
|
|
8
|
+
*
|
|
9
|
+
* - the decision points (`delivery/decisions/points.yaml`) become chant's
|
|
10
|
+
* points file, `decisions/points.json` at the workspace root, with each
|
|
11
|
+
* input named for the read-contract output it comes from, and an answer
|
|
12
|
+
* kind in `answers/` (ws-058, #2757, #2777);
|
|
13
|
+
* - the release Op asks `ship-skip` through chant's `decide` activity
|
|
14
|
+
* (#2769, core's since #2828, so the repo installs no package for it), and
|
|
15
|
+
* its ship gate takes the point's quorum and the Cedar policy as before;
|
|
16
|
+
* - the app component runs chant's own supply-chain verbs (SBOM, scan,
|
|
17
|
+
* vuln-gate) on the app member;
|
|
18
|
+
* - the Fly site's resources stay on the fly lexicon, without chud's
|
|
19
|
+
* `FlySite` wrapper (#2736);
|
|
20
|
+
* - the chud-runtime `upgrade` Op is replaced by `chant workspace upgrade`;
|
|
21
|
+
* - CI no longer fetches the private runtime package.
|
|
22
|
+
*
|
|
23
|
+
* What is runtime goes to the studio kit (arugula-salad/studio, `template/`),
|
|
24
|
+
* and is deleted here with a pointer: the dispatch Op (ws-057, studio#47), the
|
|
25
|
+
* local site (the box's service), the write-scope policy and the
|
|
26
|
+
* contract-sizing rules, and `chud dev` / `chud design` (hud on the box).
|
|
27
|
+
*
|
|
28
|
+
* chud's steps after the ship gate move to chant's (#2782): the release Op
|
|
29
|
+
* archives the app member (`sourceArchive`), plans the release
|
|
30
|
+
* (`releasePlan`), gates on the plan's digest, ships the approved tree to the
|
|
31
|
+
* Fly Machine with the fly lexicon's `flyRelease` (each migration once per
|
|
32
|
+
* environment, under a receipt), and records it in the release ledger with
|
|
33
|
+
* its plan (`releaseRecord`). Signing the archive (#2515) has no chant home
|
|
34
|
+
* yet. chud's rollback Op is deleted here, and the migration planned after
|
|
35
|
+
* this one, `chud-lexicon-exit-rollback` (#2800), writes chant's in its place.
|
|
36
|
+
*
|
|
37
|
+
* Files are matched by what they hold, not by the template version the scope
|
|
38
|
+
* is pinned at, so a repo from any chud template commit or the studio kit's
|
|
39
|
+
* copy is planned alike. A file this migration replaces or deletes that was
|
|
40
|
+
* edited since the template wrote it is still replaced or deleted, since it
|
|
41
|
+
* could not build without the chud packages, and the plan marks it edited:
|
|
42
|
+
* the edit is in git history. A file it only edits (chant.config.ts, ci.ts,
|
|
43
|
+
* fly.ts, the Cedar policy, ci.yml) keeps the project's edits; an edit whose
|
|
44
|
+
* anchor is gone is a conflict.
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
48
|
+
import { join, posix } from "node:path";
|
|
49
|
+
import yaml from "js-yaml";
|
|
50
|
+
import answerSchema from "../point-answer.schema.json";
|
|
51
|
+
import { fileHash } from "../lineage-lock";
|
|
52
|
+
import { DECISION_POINTS_SCHEMA_ID, POINT_INPUT_OUTPUT_NAMES, parsePoints, PointsError } from "../points";
|
|
53
|
+
import type { ChantMigration, ChantMigrationContext, ChantMigrationPlan, NotMoved, PlanConflict, PlannedChange } from "../chant-migrations";
|
|
54
|
+
|
|
55
|
+
export const CHUD_LEXICON_EXIT = "chud-lexicon-exit";
|
|
56
|
+
|
|
57
|
+
/** The two packages this migration takes a repo off. */
|
|
58
|
+
export const CHUD_PACKAGES = ["@intentius/chant-lexicon-chud", "@intentius/chud-runtime"] as const;
|
|
59
|
+
|
|
60
|
+
/** An import, export-from, require or dynamic import of either package, at any subpath. */
|
|
61
|
+
const CHUD_IMPORT = /(?:\bfrom\s*|\bimport\s*\(?\s*|\brequire\s*\(\s*)["']@intentius\/(?:chant-lexicon-chud|chud-runtime)(?:\/[^"']*)?["']/;
|
|
62
|
+
|
|
63
|
+
/** The studio kit's template, where the runtime half of chud's template lives. */
|
|
64
|
+
const KIT = "the studio kit (arugula-salad/studio, template/)";
|
|
65
|
+
/** Signing a source archive, and checking the signature before the Machine runs it. */
|
|
66
|
+
const SIGN_ISSUE = "INTENTIUS/chant#2515";
|
|
67
|
+
/** Rolling a Fly site shipped by the release Op back to its previous release. */
|
|
68
|
+
const ROLLBACK_ISSUE = "INTENTIUS/chant#2800";
|
|
69
|
+
|
|
70
|
+
/** Where each chud-only file of the delivery project went, relative to the delivery member. */
|
|
71
|
+
const DELETED: Array<{ path: string; why: string; notMoved?: NotMoved }> = [
|
|
72
|
+
{ path: "ops/dispatch.op.ts", why: "the dispatch Op is the kit's runtime (ws-057)", notMoved: { what: "the dispatch Op (ops/dispatch.op.ts) and `chud factory run`", where: `${KIT}: the runner and its dispatch Op (arugula-salad/studio#47); the dispatcher is a Steward (ws-057), and work leases are chant's (INTENTIUS/chant#2762)` } },
|
|
73
|
+
{ path: "ops/rollback.op.ts", why: "chud's site rollback runs on the chud packages", notMoved: { what: "chud's rollback Op (ops/rollback.op.ts)", where: `chant's rollback Op, which the migration chud-lexicon-exit-rollback writes in its place (${ROLLBACK_ISSUE})` } },
|
|
74
|
+
{ path: "ops/upgrade.op.ts", why: "replaced by chant workspace upgrade" },
|
|
75
|
+
{ path: "ops/upgrade.mjs", why: "replaced by chant workspace upgrade" },
|
|
76
|
+
{ path: "ops/site.mjs", why: "npm run check runs the app's tests itself", notMoved: { what: "the approved contracts' checks in `npm run check` and the release's Check phase, and the evidence they record", where: `${KIT}: the development model's checks and evidence records` } },
|
|
77
|
+
{ path: "deploy/site.ts", why: "the local site is the box's service", notMoved: { what: "the local site (deploy/site.ts, ChudLocalSite under .chud/site)", where: `${KIT}: the box's service runs the app (ws-056)` } },
|
|
78
|
+
{ path: ".chant/policies/write-scope.ts", why: "the design app's write scope is the kit's", notMoved: { what: "the write-scope policy (CHUD-WRITE-SCOPE, the Chud-Author trailer)", where: `${KIT}: the design app's write scope; chud.protectedPaths stays in chant.config.ts for it` } },
|
|
79
|
+
{ path: ".chant/rules/contract-sizing.ts", why: "sizing work items is the kit's", notMoved: { what: "the contract-sizing lint rules (CHUD001, CHUD002, CHUD003)", where: `${KIT}: it sizes work items and supplies the slice-tier point's inputs` } },
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
/** How each of chud's points names its inputs in chant (the reference workspace's restatement, #2777). */
|
|
83
|
+
const POINT_OUTPUTS: Record<string, { output: string; rename?: Record<string, string> }> = {
|
|
84
|
+
"slice-tier": { output: "work-item" },
|
|
85
|
+
"ship-skip": { output: "release", rename: { contracts_changed: "work_changed" } },
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const ANSWER_KIND = `// The answers to the workspace's decision points (ws-058), data only: no
|
|
89
|
+
// imports, no code.
|
|
90
|
+
//
|
|
91
|
+
// chant workspace points --open --json
|
|
92
|
+
// chant workspace points answer <id> --answer <answer> --by <name>
|
|
93
|
+
//
|
|
94
|
+
// The points are declared in ../decisions/points.json. Each answer is one
|
|
95
|
+
// record here, named for the point and its inputs' hash, so the same question
|
|
96
|
+
// is answered once. answer.schema.json is a copy of the one @intentius/chant
|
|
97
|
+
// ships as workspace/point-answer.schema.json.
|
|
98
|
+
//
|
|
99
|
+
// Written by \`chant workspace upgrade\` (its migration ${CHUD_LEXICON_EXIT}),
|
|
100
|
+
// when chud's delivery/decisions/points.yaml moved to chant's points file.
|
|
101
|
+
export const recordKind = {
|
|
102
|
+
name: "answer",
|
|
103
|
+
location: { dir: ".", match: "^[a-z][a-z0-9-]*-[0-9a-f]{12}\\\\.md$" },
|
|
104
|
+
format: "markdown-front-matter",
|
|
105
|
+
schema: { id: "urn:intentius:chant:point-answer:1", path: "answer.schema.json" },
|
|
106
|
+
idField: "id",
|
|
107
|
+
stateField: "state",
|
|
108
|
+
states: ["escalated", "proposed", "answered"],
|
|
109
|
+
closedStates: ["answered"],
|
|
110
|
+
constrains: { field: "constrains" },
|
|
111
|
+
source: { field: "source" },
|
|
112
|
+
answers: { points: "../decisions/points.json" },
|
|
113
|
+
};
|
|
114
|
+
`;
|
|
115
|
+
|
|
116
|
+
function releaseOp(pointsRel: string, appRel: string): string {
|
|
117
|
+
return `/**
|
|
118
|
+
* Release: check what is committed on HEAD, plan it, stop at the ship gate,
|
|
119
|
+
* and once the plan is approved, ship it to the Fly site and record it.
|
|
120
|
+
*
|
|
121
|
+
* \`chant workspace upgrade\` wrote this from chud's release Op, with chant's
|
|
122
|
+
* own parts in place of the chud lexicon (its migration ${CHUD_LEXICON_EXIT}):
|
|
123
|
+
*
|
|
124
|
+
* - Check runs the app's own tests (\`appTest\` in chant.config.ts's
|
|
125
|
+
* buildParams) in the app member.
|
|
126
|
+
* - Build archives the app member as committed on HEAD (\`sourceArchive\`,
|
|
127
|
+
* \`git archive\`: the same commit is always the same bytes, named by their
|
|
128
|
+
* sha256), and builds the Fly app's requests (\`npm run build:fly\`, from
|
|
129
|
+
* deploy/fly.ts and deploy/fly-machine.ts, into dist/fly.json).
|
|
130
|
+
* - Plan asks the ship-skip point, declared in decisions/points.json at the
|
|
131
|
+
* workspace root, through chant's \`decide\` activity, and
|
|
132
|
+
* writes the release plan: the archive's digest, the commit, and the
|
|
133
|
+
* point's answer, named by the sha256 of its own content. Its table says no
|
|
134
|
+
* to every release until someone adds a row, and each answer is a record in
|
|
135
|
+
* answers/. An open question stops the run \`waiting\` until a person answers
|
|
136
|
+
* it (\`chant workspace points --open\`).
|
|
137
|
+
* - The \`ship\` gate approves that plan's digest: \`chant approve release ship
|
|
138
|
+
* --plan <digest>\`. Its approver count is the point's quorum, and every
|
|
139
|
+
* approval is put to the Cedar policy in decisions/ship-skip.cedar.ts, with
|
|
140
|
+
* the point's answer and the decider that gave it. The policy runs
|
|
141
|
+
* \`log-only\`: its decision is recorded on each approval, and only people
|
|
142
|
+
* pass the gate. A run whose plan differs (another commit, another answer)
|
|
143
|
+
* is refused by name.
|
|
144
|
+
* - Ship runs the fly lexicon's \`fly-release\` steps for the \`fly\`
|
|
145
|
+
* environment: the archive is read only if its bytes still hash to the
|
|
146
|
+
* planned digest, and its files go onto the declared Machine under
|
|
147
|
+
* /srv/app with \`appStart\` as its command; each migration in the app's
|
|
148
|
+
* migrations folder runs once per environment inside the Machine, under a
|
|
149
|
+
* receipt in chant's lifecycle receipt store; then the Machine must be
|
|
150
|
+
* started with this release (and healthy at \`chud.sites.fly.url\` when set).
|
|
151
|
+
* A failure after the Machine changed puts back what it served before.
|
|
152
|
+
* - Record appends the release to the \`fly\` release ledger with its plan
|
|
153
|
+
* (ws-055), and the gate's approver.
|
|
154
|
+
*
|
|
155
|
+
* A retry is safe: the same commit plans the same digest, so its approval
|
|
156
|
+
* holds; the Machine already serving it is left as it is, every migration's
|
|
157
|
+
* receipt matches, and the ledger is written once.
|
|
158
|
+
*
|
|
159
|
+
* Not yet chant's: signing the archive and checking the signature before the
|
|
160
|
+
* Machine runs it (${SIGN_ISSUE}), and rolling the site back to the previous
|
|
161
|
+
* release (${ROLLBACK_ISSUE}). The local site is the studio kit's box service
|
|
162
|
+
* (arugula-salad/studio, template/), and so are the approved contracts'
|
|
163
|
+
* checks and their evidence.
|
|
164
|
+
*/
|
|
165
|
+
import { execFileSync } from "node:child_process";
|
|
166
|
+
import { createHash } from "node:crypto";
|
|
167
|
+
import { readFileSync } from "node:fs";
|
|
168
|
+
import { fileURLToPath } from "node:url";
|
|
169
|
+
import { Op, phase, gate, shell, build, sourceArchive, releasePlan, releaseRecord, decide } from "@intentius/chant/op";
|
|
170
|
+
import { parsePoints, quorumOf } from "@intentius/chant/workspace/points";
|
|
171
|
+
import { flyRelease } from "@intentius/chant-lexicon-fly";
|
|
172
|
+
import project from "../chant.config.ts";
|
|
173
|
+
import { shipSkipPolicy } from "../decisions/ship-skip.cedar.ts";
|
|
174
|
+
|
|
175
|
+
/** The environment this Op ships to: the Fly app deploy/fly.ts declares. */
|
|
176
|
+
const ENV = "fly";
|
|
177
|
+
/** Where the release's files go on the Machine. */
|
|
178
|
+
const INTO = "/srv/app";
|
|
179
|
+
const params = project.buildParams;
|
|
180
|
+
/** The Fly site's public URL, when chant.config.ts gives one: the release is verified at its health endpoint too. */
|
|
181
|
+
const siteUrl = (project as { chud?: { sites?: { fly?: { url?: string } } } }).chud?.sites?.fly?.url;
|
|
182
|
+
|
|
183
|
+
const pointsFile = fileURLToPath(new URL(${JSON.stringify(pointsRel)}, import.meta.url));
|
|
184
|
+
const shipSkipPoint = parsePoints(readFileSync(pointsFile, "utf-8"), pointsFile)["ship-skip"];
|
|
185
|
+
|
|
186
|
+
/** The migrations committed on HEAD, in order, each with the sha256 of its content. */
|
|
187
|
+
function migrations() {
|
|
188
|
+
const appDir = fileURLToPath(new URL(${JSON.stringify(`../${appRel}/`)}, import.meta.url));
|
|
189
|
+
const git = (...args: string[]) => execFileSync("git", args, { cwd: appDir, encoding: "utf-8" });
|
|
190
|
+
const dir = params.appMigrations.default;
|
|
191
|
+
return git("ls-tree", "--name-only", "HEAD", \`\${dir}/\`)
|
|
192
|
+
.split("\\n")
|
|
193
|
+
.filter((path) => path.endsWith(".sql"))
|
|
194
|
+
.sort()
|
|
195
|
+
.map((path) => {
|
|
196
|
+
const name = path.slice(dir.length + 1);
|
|
197
|
+
return {
|
|
198
|
+
name,
|
|
199
|
+
command: \`cd \${INTO} && \${params.appMigrate.default} \${name}\`,
|
|
200
|
+
sha: \`sha256:\${createHash("sha256").update(git("show", \`HEAD:./\${path}\`)).digest("hex")}\`,
|
|
201
|
+
};
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const shipSkip = decide("ship-skip", { id: "shipSkip" });
|
|
206
|
+
const archive = sourceArchive(${JSON.stringify(appRel)}, { id: "archive" });
|
|
207
|
+
const plan = releasePlan({
|
|
208
|
+
id: "plan",
|
|
209
|
+
component: "app",
|
|
210
|
+
env: ENV,
|
|
211
|
+
gitSha: archive.out.commit,
|
|
212
|
+
content: {
|
|
213
|
+
artifact: { kind: "source-tree", digest: archive.out.digest, dir: archive.out.dir },
|
|
214
|
+
shipSkip: { answer: shipSkip.out.answer, decider: shipSkip.out.decider },
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
export default Op({
|
|
219
|
+
name: "release",
|
|
220
|
+
overview: \`Check HEAD of \${params.name.default}, plan it, and ship it to Fly once the plan is approved.\`,
|
|
221
|
+
phases: [
|
|
222
|
+
phase("Check", [shell(params.appTest.default, { cwd: ${JSON.stringify(appRel)} })]),
|
|
223
|
+
phase("Build", [archive, build(".", { script: "build:fly" })]),
|
|
224
|
+
phase("Plan", [shipSkip, plan]),
|
|
225
|
+
phase("Gate", [
|
|
226
|
+
gate("ship", {
|
|
227
|
+
plan: plan.out.digest,
|
|
228
|
+
description: \`Ship \${params.name.default} to \${ENV}\`,
|
|
229
|
+
approval: {
|
|
230
|
+
quorum: quorumOf(shipSkipPoint),
|
|
231
|
+
policy: shipSkipPolicy,
|
|
232
|
+
mode: "log-only",
|
|
233
|
+
context: { shipSkip: shipSkip.out.answer, shipSkipBy: shipSkip.out.decider },
|
|
234
|
+
},
|
|
235
|
+
}),
|
|
236
|
+
]),
|
|
237
|
+
phase("Ship", [
|
|
238
|
+
flyRelease({
|
|
239
|
+
environment: ENV,
|
|
240
|
+
component: "app",
|
|
241
|
+
plan: "dist/fly.json",
|
|
242
|
+
digest: plan.out.digest,
|
|
243
|
+
gitSha: archive.out.commit,
|
|
244
|
+
source: { archive: archive.out.archive, digest: archive.out.digest, dir: archive.out.dir, into: INTO, start: params.appStart.default },
|
|
245
|
+
migrations: migrations(),
|
|
246
|
+
verify: { url: siteUrl, healthPath: params.appHealth.default },
|
|
247
|
+
}),
|
|
248
|
+
]),
|
|
249
|
+
phase("Record", [releaseRecord({ plan: plan.out.file, digest: plan.out.digest, approval: { op: "release", gate: "ship" } })]),
|
|
250
|
+
],
|
|
251
|
+
});
|
|
252
|
+
`;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function appComponent(appRel: string): string {
|
|
256
|
+
return `/**
|
|
257
|
+
* The app, as a chant component. \`chant list --components\` and \`chant
|
|
258
|
+
* describe app --components\` show it, and \`chant run --components app\` runs
|
|
259
|
+
* its supply chain: the app member's SBOM, its scan, and chant's vuln-gate,
|
|
260
|
+
* which fails the run, listing the blocking findings, when the SBOM violates
|
|
261
|
+
* \`vulnPolicy\` in chant.config.ts.
|
|
262
|
+
*
|
|
263
|
+
* \`chant workspace upgrade\` wrote this from chud's app component (its
|
|
264
|
+
* migration ${CHUD_LEXICON_EXIT}). chud built the app into an archive, signed
|
|
265
|
+
* it and shipped it to a site with the chud lexicon's steps. The release Op
|
|
266
|
+
* (ops/release.op.ts) does the shipping now: it archives the app member,
|
|
267
|
+
* plans and gates the release, and puts it on the Fly Machine with the fly
|
|
268
|
+
* lexicon's \`fly-release\` steps, so this component publishes nothing and
|
|
269
|
+
* records no release. The local site is the studio kit's box service
|
|
270
|
+
* (arugula-salad/studio, template/), and the Fly site's resources stay in
|
|
271
|
+
* fly.ts and fly-machine.ts.
|
|
272
|
+
*/
|
|
273
|
+
import { phase, type Component } from "@intentius/chant/components/component";
|
|
274
|
+
import { generateSbom, scanVulnerabilities, vulnGate } from "@intentius/chant/components/builders";
|
|
275
|
+
import { resolveSbomFormat, resolveVulnPolicy } from "@intentius/chant/config";
|
|
276
|
+
import project from "../chant.config.ts";
|
|
277
|
+
|
|
278
|
+
export const app: Component = {
|
|
279
|
+
name: "app",
|
|
280
|
+
archetype: "service",
|
|
281
|
+
dependsOn: [],
|
|
282
|
+
deploy: [
|
|
283
|
+
phase("SBOM", [generateSbom({ artifactType: "dir", path: ${JSON.stringify(appRel)}, format: resolveSbomFormat(project) })]),
|
|
284
|
+
phase("Scan", [
|
|
285
|
+
scanVulnerabilities({ sbom: "@SBOM.sbom" } as never),
|
|
286
|
+
// chant's gate: throws, listing the blocking findings, when the SBOM violates the policy.
|
|
287
|
+
vulnGate({ sbom: "@SBOM.sbom", findings: "@Scan.findings", policy: resolveVulnPolicy(project) } as never),
|
|
288
|
+
]),
|
|
289
|
+
],
|
|
290
|
+
};
|
|
291
|
+
`;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/** A scope file as text, or undefined. */
|
|
295
|
+
export function readText(dir: string, path: string): string | undefined {
|
|
296
|
+
const abs = join(dir, path);
|
|
297
|
+
return existsSync(abs) ? readFileSync(abs, "utf-8") : undefined;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// ── Text edits ───────────────────────────────────────────────────────────────
|
|
301
|
+
|
|
302
|
+
export interface Edit {
|
|
303
|
+
find: string | RegExp;
|
|
304
|
+
replace: string;
|
|
305
|
+
/** A required edit whose anchor is missing, while `unless` does not match, is a conflict. */
|
|
306
|
+
required?: string;
|
|
307
|
+
/** The edit is not needed when the text matches this (already done, or never there). */
|
|
308
|
+
unless?: RegExp;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export function applyEdits(text: string, edits: Edit[]): { text: string; missing: string[] } {
|
|
312
|
+
const missing: string[] = [];
|
|
313
|
+
for (const e of edits) {
|
|
314
|
+
if (e.unless?.test(text)) continue;
|
|
315
|
+
const hit = typeof e.find === "string" ? text.includes(e.find) : e.find.test(text);
|
|
316
|
+
if (!hit) {
|
|
317
|
+
if (e.required) missing.push(e.required);
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
text = typeof e.find === "string" ? text.split(e.find).join(e.replace) : text.replace(e.find, e.replace);
|
|
321
|
+
}
|
|
322
|
+
return { text, missing };
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const CONFIG_HEADER_OLD = /\/\/ The workspace's chant project \(the `delivery` member[\s\S]*?\/\/ no limit\. The design app reads the same options, so it warns alike\.\n/;
|
|
326
|
+
const CONFIG_HEADER_NEW = `// The workspace's chant project (the \`delivery\` member of chant.workspace.json;
|
|
327
|
+
// the app is the \`app\` member, and the development model's records are the
|
|
328
|
+
// \`design\` member). The fountain lexicon for the agents in agents/, the Ops in
|
|
329
|
+
// ops/ (found by their *.op.ts names), the fly lexicon for the Fly app
|
|
330
|
+
// (deploy/fly.ts), the cedar lexicon for the ship gate's approval policy
|
|
331
|
+
// (decisions/ship-skip.cedar.ts). The release Op's \`decide\` step, chant's
|
|
332
|
+
// own, asks the ship-skip decision point in decisions/points.json at the
|
|
333
|
+
// workspace root. The github lexicon is for this
|
|
334
|
+
// repo's CI (ci/ci.ts), which \`npm run ci:build\` writes to
|
|
335
|
+
// .github/workflows/ci.yml. The app is a chant component
|
|
336
|
+
// (deploy/app.component.ts); \`sourceDir\` is where chant finds it, so
|
|
337
|
+
// \`chant list --components\` and \`chant components status --live\` read
|
|
338
|
+
// deploy/.
|
|
339
|
+
//
|
|
340
|
+
// \`chant workspace upgrade\` took this project off the chud lexicon (its
|
|
341
|
+
// migration ${CHUD_LEXICON_EXIT}). The \`chud\` block below is data the studio
|
|
342
|
+
// kit reads (arugula-salad/studio, template/): the design app's protected
|
|
343
|
+
// paths, the sites, and the issue its decision records cite.
|
|
344
|
+
`;
|
|
345
|
+
|
|
346
|
+
function configEdits(): Edit[] {
|
|
347
|
+
return [
|
|
348
|
+
{ find: "lexicons:", replace: "lexicons:", required: "the `lexicons` list" },
|
|
349
|
+
{
|
|
350
|
+
find: /\n lint: \{\n rules: \{\n CHUD001: \["warning", sizing\],\n CHUD002: \["warning", sizing\],\n \},\n \/\/ CHUD-WRITE-SCOPE: the release plan runs it \(through `chant build`\) over\n \/\/ the commits it would ship\. See the file\.\n policies: \["\.chant\/policies\/write-scope\.ts"\],\n \},/,
|
|
351
|
+
replace: "",
|
|
352
|
+
required: "the `lint` block naming CHUD001, CHUD002 and the write-scope policy",
|
|
353
|
+
unless: /^(?![\s\S]*(?:CHUD00[123]|write-scope\.ts))/,
|
|
354
|
+
},
|
|
355
|
+
{ find: CONFIG_HEADER_OLD, replace: CONFIG_HEADER_NEW },
|
|
356
|
+
{
|
|
357
|
+
find: "// migrate and test the app. chud (and @intentius/chud-runtime) reads them from\n// here.",
|
|
358
|
+
replace: "// migrate and test the app. The release Op and the studio kit's box read them\n// from here.",
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
find: " // Where releases ship: prod, the local site under .chud/site, and fly, a Fly\n // app (deploy/fly.ts). `npm run release` ships to prod; `npm run release --\n // --env fly` to fly, with FLY_API_TOKEN set (FLY_FLAPS_BASE_URL points it at\n // mudflaps, the Machines API emulator, instead of Fly).\n",
|
|
362
|
+
replace: " // The environments: prod, the local site (the studio kit's box), and fly, the\n // Fly app (deploy/fly.ts). `npm run release` ships to fly once its plan is\n // approved (ops/release.op.ts), with FLY_API_TOKEN set (FLY_FLAPS_BASE_URL\n // points it at mudflaps, the Machines API emulator, instead of Fly).\n",
|
|
363
|
+
},
|
|
364
|
+
];
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/** The `lexicons` list without "chud", and no `sizing` const once nothing reads it. */
|
|
368
|
+
function fixConfig(text: string): string {
|
|
369
|
+
text = text.replace(/lexicons:\s*\[([^\]]*)\]/, (_all, inner: string) => {
|
|
370
|
+
const names = [...inner.matchAll(/["']([^"']+)["']/g)].map((m) => m[1]).filter((n) => n !== "chud");
|
|
371
|
+
return `lexicons: [${names.map((n) => JSON.stringify(n)).join(", ")}]`;
|
|
372
|
+
});
|
|
373
|
+
const sizing = /const sizing = \{\n small: \{[^\n]*\},\n medium: \{[^\n]*\},\n\};\n\n/;
|
|
374
|
+
const without = text.replace(sizing, "");
|
|
375
|
+
return without !== text && !/\bsizing\b/.test(without) ? without : text;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const FLY_EDITS: Edit[] = [
|
|
379
|
+
{
|
|
380
|
+
find: /import \{ FlySite \} from "@intentius\/chud-runtime\/lexicon";\n/,
|
|
381
|
+
replace: "",
|
|
382
|
+
required: "the FlySite import from @intentius/chud-runtime/lexicon",
|
|
383
|
+
unless: /^(?![\s\S]*FlySite)/,
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
find: /\n(?:\/\*\*[^\n]*\*\/\n)?export const flySite = new FlySite\([^\n]*\);\n/,
|
|
387
|
+
replace: "\n",
|
|
388
|
+
required: "the flySite declaration",
|
|
389
|
+
unless: /^(?![\s\S]*new FlySite\()/,
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
find: /\(@intentius\/chud-runtime's fly-site\.mjs\)/g,
|
|
393
|
+
replace: "(the release Op's Ship phase, ops/release.op.ts)",
|
|
394
|
+
},
|
|
395
|
+
];
|
|
396
|
+
|
|
397
|
+
const FLY_MACHINE_EDITS: Edit[] = [
|
|
398
|
+
{
|
|
399
|
+
find: /\(@intentius\/chud-runtime's fly-site\.mjs\)/g,
|
|
400
|
+
replace: "(the release Op's Ship phase, ops/release.op.ts)",
|
|
401
|
+
},
|
|
402
|
+
];
|
|
403
|
+
|
|
404
|
+
const CEDAR_EDITS: Edit[] = [
|
|
405
|
+
{
|
|
406
|
+
find: `const tableSaysYes = 'context has shipSkip && context.shipSkip == "yes by table"';`,
|
|
407
|
+
replace: `const tableSaysYes = 'context has shipSkip && context has shipSkipBy && context.shipSkip == true && context.shipSkipBy == "table"';`,
|
|
408
|
+
required: "the tableSaysYes condition over chud's `said` string",
|
|
409
|
+
unless: /^(?![\s\S]*"yes by table")/,
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
find: " * `PassGate` request: the principal is `Chant::Human::\"<approver>\"` or, with\n * `--agent` (or over MCP/ACP), `Chant::Agent::\"<name>\"`. Its context is the\n * plan digest and `shipSkip`, the answer to the ship-skip decision point in\n * decisions/points.yaml as the release plan recorded it: `no by table`,\n * `yes by table`, `yes by model`, `escalated to quorum`, ...",
|
|
413
|
+
replace: " * `PassGate` request: the principal is `Chant::Human::\"<approver>\"` or, with\n * `--agent` (or over MCP/ACP), `Chant::Agent::\"<name>\"`. Its context holds\n * `shipSkip`, the answer to the ship-skip decision point in the workspace's\n * decisions/points.json (true or false), and `shipSkipBy`, the decider that\n * gave it: `table`, `model` or `quorum`.",
|
|
414
|
+
},
|
|
415
|
+
];
|
|
416
|
+
|
|
417
|
+
const CI_TS_EDITS: Edit[] = [
|
|
418
|
+
{
|
|
419
|
+
find: /\n\/\/ @intentius\/chud-runtime is installed from a private GitHub repo[\s\S]*?\/\/ With neither set, `npm ci` installs only what it can reach without them\.\n/,
|
|
420
|
+
replace: "\n",
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
find: /\/\/ npm installs a github: dependency over ssh[\s\S]*?\]\.join\("\\n"\);\n\n/,
|
|
424
|
+
replace: "",
|
|
425
|
+
required: "the runtimeAccess script",
|
|
426
|
+
unless: /^(?![\s\S]*const runtimeAccess)/,
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
find: / new Step\(\{\n name: "Read access to @intentius\/chud-runtime",\n[\s\S]*?\n run: runtimeAccess,\n \}\),\n/,
|
|
430
|
+
replace: "",
|
|
431
|
+
required: "the \"Read access to @intentius/chud-runtime\" step",
|
|
432
|
+
unless: /^(?![\s\S]*Read access to @intentius\/chud-runtime)/,
|
|
433
|
+
},
|
|
434
|
+
{ find: "import { Workflow, Job, Step, Checkout, SetupNode, secrets } from", replace: "import { Workflow, Job, Step, Checkout, SetupNode } from", unless: /secrets\(/ },
|
|
435
|
+
{
|
|
436
|
+
find: "then `npm run check`: the\n// app's own tests and every approved contract's check, run by\n// @intentius/chud-runtime the way a release's Check phase runs them (records\n// valid, approved contracts unchanged since approval), without recording\n// evidence.",
|
|
437
|
+
replace: "then `npm run check`: the\n// app's own tests. The approved contracts' checks are the studio kit's.\n//",
|
|
438
|
+
},
|
|
439
|
+
{ find: "/** The delivery member, where chant, its lexicons and the runtime are installed. */", replace: "/** The delivery member, where chant and its lexicons are installed. */" },
|
|
440
|
+
{ find: `name: "App tests and approved contract checks"`, replace: `name: "App tests"` },
|
|
441
|
+
];
|
|
442
|
+
|
|
443
|
+
const CI_YML_EDITS: Edit[] = [
|
|
444
|
+
{
|
|
445
|
+
find: / - name: Read access to @intentius\/chud-runtime\n(?: {8,}[^\n]*\n)+/,
|
|
446
|
+
replace: "",
|
|
447
|
+
required: "the \"Read access to @intentius/chud-runtime\" step",
|
|
448
|
+
unless: /^(?![\s\S]*Read access to @intentius\/chud-runtime)/,
|
|
449
|
+
},
|
|
450
|
+
{ find: " - name: App tests and approved contract checks\n", replace: " - name: App tests\n" },
|
|
451
|
+
];
|
|
452
|
+
|
|
453
|
+
// ── Root prose (#2805): README.md, CLAUDE.md and design/CLAUDE.md still
|
|
454
|
+
// describe what this migration deletes. Each edit's `unless` marker is text
|
|
455
|
+
// the replacement drops, so a second upgrade (or a template that never had
|
|
456
|
+
// the passage) finds nothing to do.
|
|
457
|
+
|
|
458
|
+
const README_EDITS: Edit[] = [
|
|
459
|
+
{
|
|
460
|
+
find: "| delivery | [`delivery/`](delivery) | `chant` | the chant project, which still runs on chud's runtime package (`@intentius/chud-runtime`) and the chud lexicon until they are split out of chud (INTENTIUS/chant#2713): the release, rollback, upgrade and dispatch Ops, the sites releases ship to, the agents that build the app, the decision points, the write-scope policy and this repo's CI |",
|
|
461
|
+
replace:
|
|
462
|
+
"| delivery | [`delivery/`](delivery) | `chant` | the chant project, off chud (INTENTIUS/chant#2737, ws-056): the release Op (Check, the ship-skip decision point, then the ship gate), the app component's supply chain, the Fly site, the agents that build the app, and this repo's CI. The dispatch Op, the local site and the write-scope policy are the kit's |",
|
|
463
|
+
required: "the delivery row naming chud's runtime package and the release, rollback, upgrade and dispatch Ops",
|
|
464
|
+
unless: /^(?![\s\S]*chud's runtime package)/,
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
find: "npm run release # chant run release --on chud: check, plan, stop at the ship gate\nchant approve release ship --plan <digest>\nnpm run release # ship it\nnpm run rollback\nnpm run upgrade # move to a newer @intentius/chud-runtime, through its gate\nnpm run check # the app's tests and every approved contract's check (CI runs this)\nnpm run lint # chant lint agents, with the contract-sizing rules",
|
|
468
|
+
replace:
|
|
469
|
+
"npm run release # chant run release: check, ask ship-skip, stop at the ship gate\nchant approve release ship --plan <digest>\nnpm run release # ship it\nnpm run upgrade # cd .. && chant workspace upgrade\nnpm run check # the app's own tests (CI runs this)\nnpm run lint # chant lint agents",
|
|
470
|
+
required: "the everyday commands naming --on chud, npm run rollback and the contract-sizing rules",
|
|
471
|
+
unless: /^(?![\s\S]*--on chud: check, plan)/,
|
|
472
|
+
},
|
|
473
|
+
];
|
|
474
|
+
|
|
475
|
+
const CLAUDE_EDITS: Edit[] = [
|
|
476
|
+
{
|
|
477
|
+
find: "| delivery | `delivery/` | The chant project: `chant.config.ts` (this repo's settings as `buildParams`: ports and how to run the app), `ops/` (the release, rollback, upgrade and dispatch Ops), `deploy/` (the app as a chant component and the sites it ships to), `agents/team.ts` (the agents that build the app, as fountain specs), `decisions/` (the decision points and the ship gate's Cedar policy), `ci/ci.ts` (this repo's CI), `.chant/` (the write-scope policy and the contract-sizing lint rules), and `node_modules/`. |",
|
|
478
|
+
replace:
|
|
479
|
+
"| delivery | `delivery/` | The chant project: `chant.config.ts` (this repo's settings as `buildParams`: ports and how to run the app), `ops/` (the release Op), `deploy/` (the app as a chant component and the Fly site's resources), `agents/team.ts` (the agents that build the app, as fountain specs; its factory Schedule still calls the kit's dispatch Op), `decisions/ship-skip.cedar.ts` (the ship gate's policy; the decision points are the workspace's `decisions/points.json` at the root), `ci/ci.ts` (this repo's CI), and `node_modules/`. |",
|
|
480
|
+
required: "the delivery row naming the release, rollback, upgrade and dispatch Ops and .chant/",
|
|
481
|
+
unless: /^(?![\s\S]*the release, rollback, upgrade and dispatch Ops)/,
|
|
482
|
+
},
|
|
483
|
+
{
|
|
484
|
+
find: "- Records keep their core fields in YAML front matter (or JSON), validated\n against the schemas in\n `delivery/node_modules/@intentius/chud-runtime/schemas/`. Extra fields must\n start with `x-`.\n",
|
|
485
|
+
replace:
|
|
486
|
+
"- Records keep their core fields in YAML front matter (or JSON), validated\n against the schema each kind declares beside it (`decision.schema.json`,\n `evidence.schema.json`, `session.schema.json`,\n `design/schemas/defs.schema.json`). Extra fields must start with `x-`.\n",
|
|
487
|
+
required: "the records bullet pointing at delivery/node_modules/@intentius/chud-runtime/schemas/",
|
|
488
|
+
unless: /^(?![\s\S]*chud-runtime\/schemas\/)/,
|
|
489
|
+
},
|
|
490
|
+
{
|
|
491
|
+
find: "- `delivery/node_modules/@intentius/chud-runtime` is the machinery (the design\n app, the Op steps). It is a versioned dependency: never edit it. Upgrade it\n with the upgrade Op (`npm run upgrade` in `delivery/`), which stops at a\n gate for a person.\n",
|
|
492
|
+
replace:
|
|
493
|
+
"- The design app and its Op steps are the studio kit's now\n (arugula-salad/studio, template/), not a `delivery/` dependency\n (INTENTIUS/chant#2737, ws-056). Upgrade `delivery/`'s chant packages with\n `npm run upgrade` (`chant workspace upgrade` at the root), which stops at a\n gate for a person.\n",
|
|
494
|
+
required: "the bullet naming delivery/node_modules/@intentius/chud-runtime as the machinery",
|
|
495
|
+
unless: /^(?![\s\S]*is the machinery)/,
|
|
496
|
+
},
|
|
497
|
+
];
|
|
498
|
+
|
|
499
|
+
const DESIGN_CLAUDE_EDITS: Edit[] = [
|
|
500
|
+
{
|
|
501
|
+
// The template's own name parameter ({{chant:name}}) is already
|
|
502
|
+
// substituted by the time this migration runs, so the anchor captures
|
|
503
|
+
// whatever it became rather than matching the placeholder literally.
|
|
504
|
+
find: /You are inside the design app \(`chud design`\), and your working directory is\nthis one\. The team uses the app to write contracts, record decisions and ship\nreleases of ([^\n.]+)\. Your job is to change this app when the team\nasks: new views, different workflows, extra checks, whatever helps them build\n\1\. You do not build the app itself here; that happens in `chud\ndev` \(see `\.\.\/app\/CLAUDE\.md`\)\./,
|
|
505
|
+
replace:
|
|
506
|
+
"You are inside the design app. Its own CLI command is gone now that\n`@intentius/chud-runtime` is gone (INTENTIUS/chant#2737, ws-056); the design\napp is hud's chant views now (alecraso/hud#627). The team still uses it to\nwrite contracts, record decisions and ship releases of $1, and\nyour job stays the same: change this app when the team asks: new views,\ndifferent workflows, extra checks, whatever helps them build\n$1. You do not build the app itself here; that happens with\n`npm run dev` in `app/` (see `../app/CLAUDE.md`).",
|
|
507
|
+
required: "the paragraph naming the design app's `chud design`/`chud dev` commands",
|
|
508
|
+
unless: /^(?![\s\S]*chud design)/,
|
|
509
|
+
},
|
|
510
|
+
{
|
|
511
|
+
find: "The design app ships in the `@intentius/chud-runtime` package, the version\n`../delivery/package-lock.json` pins, at\n`../delivery/node_modules/@intentius/chud-runtime/design/`. `chud design` runs\nits `server.js` directly. Read it there, but never edit it: an `npm install`\nor an upgrade replaces it. This directory holds only this repo's overrides,\nand they win:",
|
|
512
|
+
replace:
|
|
513
|
+
"The design app used to ship in the `@intentius/chud-runtime` package, at\n`../delivery/node_modules/@intentius/chud-runtime/design/`. That package is\ngone (INTENTIUS/chant#2737, ws-056): the design app is hud's chant views now\n(alecraso/hud#627), not a `delivery/` dependency. This directory still holds\nonly this repo's overrides, and they win once hud reads them from here:",
|
|
514
|
+
required: "the paragraph naming @intentius/chud-runtime's design/ path",
|
|
515
|
+
unless: /^(?![\s\S]*chud design)/,
|
|
516
|
+
},
|
|
517
|
+
{
|
|
518
|
+
find: "- `design/lib/ops.js`: runs the chant Ops in `../delivery/ops/` (`chant run\n <op> --on chud --json`, in `../delivery/`), reads the gate ledger on the\n `chant/lifecycle` branch, and approves gates with `chant approve`.\n",
|
|
519
|
+
replace:
|
|
520
|
+
"- `design/lib/ops.js`: runs the chant Ops in `../delivery/ops/` (`chant run\n <op> --json`, in `../delivery/`), reads the gate ledger on the\n `chant/lifecycle` branch, and approves gates with `chant approve`.\n",
|
|
521
|
+
required: "the ops.js bullet's --on chud flag",
|
|
522
|
+
unless: /^(?![\s\S]*--on chud --json)/,
|
|
523
|
+
},
|
|
524
|
+
];
|
|
525
|
+
|
|
526
|
+
// ── package.json ─────────────────────────────────────────────────────────────
|
|
527
|
+
|
|
528
|
+
const SCRIPT_MOVES: Record<string, { from: RegExp; to: string | null; why: string; notMoved?: NotMoved }> = {
|
|
529
|
+
dev: { from: /^chud dev$/, to: null, why: "`chud dev` is hud on the box", notMoved: { what: "`npm run dev` (`chud dev`)", where: `hud, serving the app on the box: ${KIT}, alecraso/hud#627` } },
|
|
530
|
+
design: { from: /^chud design$/, to: null, why: "`chud design` is hud's chant views", notMoved: { what: "`npm run design` (`chud design`)", where: "hud's chant views (jhgaylor/chud#79, alecraso/hud#627)" } },
|
|
531
|
+
release: { from: /^chant run release --on chud$/, to: "chant run release", why: "chant's local executor runs the Op" },
|
|
532
|
+
rollback: { from: /^chant run rollback --on chud$/, to: null, why: "the rollback Op is deleted" },
|
|
533
|
+
upgrade: { from: /^chant run upgrade --on chud$/, to: "cd .. && chant workspace upgrade", why: "the lineage upgrade replaces the chud-runtime bump" },
|
|
534
|
+
dispatch: { from: /^chant run dispatch --on chud$/, to: null, why: "the dispatch Op is the kit's" },
|
|
535
|
+
};
|
|
536
|
+
|
|
537
|
+
function rangeFloor(range: string): number[] | null {
|
|
538
|
+
const m = /^(?:\^|~|>=)?\s*(\d+)\.(\d+)\.(\d+)$/.exec(range.trim());
|
|
539
|
+
return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function older(a: number[], b: number[]): boolean {
|
|
543
|
+
for (let i = 0; i < 3; i++) if (a[i] !== b[i]) return a[i] < b[i];
|
|
544
|
+
return false;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/** The delivery project's package.json without the chud packages, on this chant. */
|
|
548
|
+
function fixPackageJson(text: string, chantVersion: string, appRel: string): { text: string; notes: string[]; notMoved: NotMoved[] } {
|
|
549
|
+
const pkg = JSON.parse(text) as Record<string, unknown>;
|
|
550
|
+
const notes: string[] = [];
|
|
551
|
+
const notMoved: NotMoved[] = [];
|
|
552
|
+
const floor = rangeFloor(chantVersion);
|
|
553
|
+
for (const field of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
|
|
554
|
+
const deps = pkg[field] as Record<string, string> | undefined;
|
|
555
|
+
if (!deps) continue;
|
|
556
|
+
for (const name of CHUD_PACKAGES) {
|
|
557
|
+
if (name in deps) {
|
|
558
|
+
delete deps[name];
|
|
559
|
+
notes.push(`drops ${name}`);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
if (floor) {
|
|
563
|
+
for (const [name, range] of Object.entries(deps)) {
|
|
564
|
+
if (!/^@intentius\/chant(?:-lexicon-[a-z0-9-]+)?$/.test(name)) continue;
|
|
565
|
+
const at = rangeFloor(range);
|
|
566
|
+
if (at && older(at, floor)) deps[name] = `^${chantVersion}`;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
pkg[field] = Object.fromEntries(Object.entries(deps).sort(([a], [b]) => a.localeCompare(b)));
|
|
570
|
+
}
|
|
571
|
+
const scripts = pkg.scripts as Record<string, string> | undefined;
|
|
572
|
+
if (scripts) {
|
|
573
|
+
for (const [name, move] of Object.entries(SCRIPT_MOVES)) {
|
|
574
|
+
if (typeof scripts[name] !== "string" || !move.from.test(scripts[name])) continue;
|
|
575
|
+
if (move.to === null) delete scripts[name];
|
|
576
|
+
else scripts[name] = move.to;
|
|
577
|
+
if (move.notMoved) notMoved.push(move.notMoved);
|
|
578
|
+
}
|
|
579
|
+
if (scripts.check === "node ops/site.mjs ci") scripts.check = `npm --prefix ${appRel} test --silent`;
|
|
580
|
+
// The release Op's Build phase writes the Fly app's requests with it.
|
|
581
|
+
if (scripts.release === "chant run release" && !("build:fly" in scripts)) scripts["build:fly"] = "chant build deploy --lexicon fly -o dist/fly.json";
|
|
582
|
+
for (const [name, cmd] of Object.entries(scripts)) {
|
|
583
|
+
if (/--on chud\b/.test(cmd)) scripts[name] = cmd.replace(/\s*--on chud\b/g, "");
|
|
584
|
+
else if (/(^|[;&|]\s*)chud\s/.test(cmd)) notMoved.push({ what: `\`npm run ${name}\` (${cmd})`, where: `${KIT}: the chud CLI is not installed once the runtime is gone` });
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
return { text: JSON.stringify(pkg, null, 2) + "\n", notes, notMoved };
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// ── Points ───────────────────────────────────────────────────────────────────
|
|
591
|
+
|
|
592
|
+
const KNOWN_OUTPUT = new RegExp(`^(?:${POINT_INPUT_OUTPUT_NAMES.map((n) => n.replace(/[-]/g, "\\-")).join("|")})(?:\\.|$)`);
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* chud's `slice-tier` point tells its decider where the sizing limits are:
|
|
596
|
+
* "(lint.rules in chant.config.ts)". This migration removes that `lint`
|
|
597
|
+
* block (`configEdits`), so the converted point would point at nothing
|
|
598
|
+
* (#2805). Its sizing rules are the kit's now, per the plan's own `DELETED`
|
|
599
|
+
* entry for `.chant/rules/contract-sizing.ts`.
|
|
600
|
+
*/
|
|
601
|
+
function fixSizingReference(instructions: string): string {
|
|
602
|
+
return instructions.replace("(lint.rules in chant.config.ts)", "(the studio kit's contract-sizing rules, arugula-salad/studio, template/)");
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/** chud's points.yaml as chant's points file, or the reason it cannot be. */
|
|
606
|
+
export function convertPoints(text: string, file: string): { json: string } | { error: string } {
|
|
607
|
+
let doc: unknown;
|
|
608
|
+
try {
|
|
609
|
+
doc = yaml.load(text);
|
|
610
|
+
} catch (err) {
|
|
611
|
+
return { error: `is not YAML: ${(err as Error).message}` };
|
|
612
|
+
}
|
|
613
|
+
const src = (doc as { points?: Record<string, Record<string, unknown>> } | null)?.points;
|
|
614
|
+
if (!src || typeof src !== "object") return { error: "declares no `points`" };
|
|
615
|
+
const points: Record<string, unknown> = {};
|
|
616
|
+
const unknown: string[] = [];
|
|
617
|
+
for (const [name, point] of Object.entries(src)) {
|
|
618
|
+
const map = POINT_OUTPUTS[name];
|
|
619
|
+
const rename = (input: string): string | null => {
|
|
620
|
+
if (KNOWN_OUTPUT.test(input)) return input;
|
|
621
|
+
if (!map) return null;
|
|
622
|
+
return `${map.output}.${map.rename?.[input] ?? input}`;
|
|
623
|
+
};
|
|
624
|
+
const inputs: Record<string, string> = {};
|
|
625
|
+
let ok = true;
|
|
626
|
+
for (const [k, v] of Object.entries((point.inputs as Record<string, string> | undefined) ?? {})) {
|
|
627
|
+
const n = rename(k);
|
|
628
|
+
if (n === null) ok = false;
|
|
629
|
+
else inputs[n] = v;
|
|
630
|
+
}
|
|
631
|
+
if (!ok) {
|
|
632
|
+
unknown.push(name);
|
|
633
|
+
continue;
|
|
634
|
+
}
|
|
635
|
+
const question = { ...(point.question as Record<string, unknown>) };
|
|
636
|
+
if (question.type === "boolean") question.type = "noul";
|
|
637
|
+
if (typeof question.instructions === "string") question.instructions = fixSizingReference(question.instructions);
|
|
638
|
+
const deciders = ((point.deciders as Array<Record<string, unknown>> | undefined) ?? []).map((d) => {
|
|
639
|
+
if (d.kind !== "table") return d;
|
|
640
|
+
const rows = (d.rows as Array<Record<string, unknown>>).map((r) => ({
|
|
641
|
+
...r,
|
|
642
|
+
when: Object.fromEntries(Object.entries((r.when as Record<string, unknown>) ?? {}).map(([k, v]) => [rename(k) ?? k, v])),
|
|
643
|
+
}));
|
|
644
|
+
return { ...d, rows };
|
|
645
|
+
});
|
|
646
|
+
points[name] = { ...point, question, inputs, deciders };
|
|
647
|
+
}
|
|
648
|
+
if (unknown.length > 0) {
|
|
649
|
+
return {
|
|
650
|
+
error: `point(s) ${unknown.join(", ")} name inputs with no read-contract output, and only chud's slice-tier and ship-skip have a known one. Write them into decisions/points.json by hand, each input prefixed by its output (work-item., release., finding., ...), then run the upgrade again`,
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
const json = JSON.stringify({ $schema: DECISION_POINTS_SCHEMA_ID, points }, null, 2) + "\n";
|
|
654
|
+
try {
|
|
655
|
+
parsePoints(json, file);
|
|
656
|
+
} catch (err) {
|
|
657
|
+
if (err instanceof PointsError) return { error: `does not validate once converted: ${err.message}` };
|
|
658
|
+
throw err;
|
|
659
|
+
}
|
|
660
|
+
return { json };
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// ── The plan ─────────────────────────────────────────────────────────────────
|
|
664
|
+
|
|
665
|
+
interface Declaration {
|
|
666
|
+
members?: Array<{ name?: string; dir?: string; kind?: string }>;
|
|
667
|
+
records?: Array<{ kind?: string }>;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
function readDeclaration(dir: string): Declaration | null {
|
|
671
|
+
const text = readText(dir, "chant.workspace.json");
|
|
672
|
+
if (text === undefined) return null;
|
|
673
|
+
try {
|
|
674
|
+
return JSON.parse(text) as Declaration;
|
|
675
|
+
} catch {
|
|
676
|
+
return null;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
function dependsOnChud(dir: string, member: string): boolean {
|
|
681
|
+
const text = readText(dir, posix.join(member, "package.json"));
|
|
682
|
+
if (text === undefined) return false;
|
|
683
|
+
try {
|
|
684
|
+
const pkg = JSON.parse(text) as Record<string, Record<string, string> | undefined>;
|
|
685
|
+
return ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"].some((f) => CHUD_PACKAGES.some((p) => pkg[f]?.[p] !== undefined));
|
|
686
|
+
} catch {
|
|
687
|
+
return false;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
const SKIP_DIRS = new Set(["node_modules", ".git", "dist", ".chud", ".data", ".hud"]);
|
|
692
|
+
|
|
693
|
+
/** Every file in the scope that chud's packages could be imported from. */
|
|
694
|
+
function sourceFiles(dir: string, rel = ""): string[] {
|
|
695
|
+
const out: string[] = [];
|
|
696
|
+
for (const name of readdirSync(join(dir, rel))) {
|
|
697
|
+
if (SKIP_DIRS.has(name) || (rel === "" && name === ".chant")) continue;
|
|
698
|
+
const path = rel ? `${rel}/${name}` : name;
|
|
699
|
+
const st = statSync(join(dir, path));
|
|
700
|
+
if (st.isDirectory()) out.push(...sourceFiles(dir, path));
|
|
701
|
+
else if (/\.(?:[cm]?[jt]s|tsx|jsx)$/.test(name) && st.size < 2_000_000) out.push(path);
|
|
702
|
+
}
|
|
703
|
+
return out;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
function planExit(ctx: ChantMigrationContext): ChantMigrationPlan | null {
|
|
707
|
+
const { dir, lineage, chantVersion } = ctx;
|
|
708
|
+
const decl = readDeclaration(dir);
|
|
709
|
+
const members = decl?.members ?? [];
|
|
710
|
+
const delivery = members.filter((m) => m.kind === "chant" && m.dir && dependsOnChud(dir, m.dir)).map((m) => posix.normalize(m.dir!));
|
|
711
|
+
if (delivery.length === 0 && dependsOnChud(dir, "delivery")) delivery.push("delivery");
|
|
712
|
+
|
|
713
|
+
const changes: PlannedChange[] = [];
|
|
714
|
+
const notMoved: NotMoved[] = [];
|
|
715
|
+
const conflicts: PlanConflict[] = [];
|
|
716
|
+
const edited = (path: string, text: string): boolean => {
|
|
717
|
+
const entry = lineage.files[path];
|
|
718
|
+
return entry !== undefined && entry.sha256 !== fileHash(text);
|
|
719
|
+
};
|
|
720
|
+
const write = (path: string, text: string, why: string): void => {
|
|
721
|
+
const current = readText(dir, path);
|
|
722
|
+
if (current === text) return;
|
|
723
|
+
changes.push({ path, action: "write", why, data: Buffer.from(text), ...(current !== undefined && edited(path, current) ? { edited: true } : {}) });
|
|
724
|
+
};
|
|
725
|
+
const remove = (path: string, why: string): void => {
|
|
726
|
+
const current = readText(dir, path);
|
|
727
|
+
if (current === undefined) return;
|
|
728
|
+
changes.push({ path, action: "delete", why, ...(edited(path, current) ? { edited: true } : {}) });
|
|
729
|
+
};
|
|
730
|
+
const edit = (path: string, edits: Edit[], why: string, after?: (t: string) => string): void => {
|
|
731
|
+
const current = readText(dir, path);
|
|
732
|
+
if (current === undefined) return;
|
|
733
|
+
const { text, missing } = applyEdits(current, edits);
|
|
734
|
+
if (missing.length > 0) {
|
|
735
|
+
conflicts.push({ path, reason: `cannot find ${missing.join("; ")}. Take it off the chud packages by hand, then run the upgrade again` });
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
write(path, after ? after(text) : text, why);
|
|
739
|
+
};
|
|
740
|
+
|
|
741
|
+
const appDir = members.find((m) => m.name === "app")?.dir ?? "app";
|
|
742
|
+
const designDir = members.find((m) => m.name === "design")?.dir ?? "design";
|
|
743
|
+
|
|
744
|
+
for (const d of delivery) {
|
|
745
|
+
const at = (p: string) => posix.join(d, p);
|
|
746
|
+
const appRel = posix.relative(d, appDir) || ".";
|
|
747
|
+
|
|
748
|
+
// The chud lexicon's shim and its runtime.
|
|
749
|
+
const shim = at("ops/chant-lexicon-chud");
|
|
750
|
+
if (existsSync(join(dir, shim))) {
|
|
751
|
+
for (const f of sourceFilesUnder(dir, shim)) remove(f, "the chud lexicon's entry, re-exporting @intentius/chud-runtime");
|
|
752
|
+
}
|
|
753
|
+
const npmrc = readText(dir, at(".npmrc"));
|
|
754
|
+
if (npmrc !== undefined && npmrc.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#")).join("\n") === "install-links=true") {
|
|
755
|
+
remove(at(".npmrc"), "it only installed the chud lexicon's file: dependency");
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
// package.json.
|
|
759
|
+
const pkgPath = at("package.json");
|
|
760
|
+
const pkgText = readText(dir, pkgPath)!;
|
|
761
|
+
const pkg = fixPackageJson(pkgText, chantVersion, appRel);
|
|
762
|
+
write(pkgPath, pkg.text, `${pkg.notes.join(", ") || "updates the chant ranges"}; scripts run chant alone`);
|
|
763
|
+
notMoved.push(...pkg.notMoved);
|
|
764
|
+
|
|
765
|
+
// Files that were chud's only.
|
|
766
|
+
for (const del of DELETED) {
|
|
767
|
+
if (readText(dir, at(del.path)) === undefined) continue;
|
|
768
|
+
remove(at(del.path), del.why);
|
|
769
|
+
if (del.notMoved) notMoved.push(del.notMoved);
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
// The decision points.
|
|
773
|
+
const yamlPath = at("decisions/points.yaml");
|
|
774
|
+
const yamlText = readText(dir, yamlPath);
|
|
775
|
+
if (yamlText !== undefined) {
|
|
776
|
+
const converted = convertPoints(yamlText, "decisions/points.json");
|
|
777
|
+
if ("error" in converted) {
|
|
778
|
+
conflicts.push({ path: yamlPath, reason: converted.error });
|
|
779
|
+
} else {
|
|
780
|
+
const existing = readText(dir, "decisions/points.json");
|
|
781
|
+
if (existing !== undefined && existing !== converted.json) {
|
|
782
|
+
conflicts.push({ path: "decisions/points.json", reason: `exists, and ${yamlPath} would replace it. Merge the two by hand, delete ${yamlPath}, then run the upgrade again` });
|
|
783
|
+
} else {
|
|
784
|
+
write("decisions/points.json", converted.json, `chud's ${yamlPath}, as chant's points file (inputs named for their read-contract outputs)`);
|
|
785
|
+
remove(yamlPath, "moved to decisions/points.json");
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
const pointsRel = posix.relative(at("ops"), "decisions/points.json");
|
|
790
|
+
|
|
791
|
+
// The answer kind, declared.
|
|
792
|
+
if (!existsSync(join(dir, "answers/answer.kind.mjs"))) {
|
|
793
|
+
write("answers/answer.kind.mjs", ANSWER_KIND, "the answer kind for the decision points (ws-058)");
|
|
794
|
+
write("answers/answer.schema.json", JSON.stringify(answerSchema, null, 2) + "\n", "a copy of chant's point-answer schema");
|
|
795
|
+
}
|
|
796
|
+
const declText = readText(dir, "chant.workspace.json");
|
|
797
|
+
if (declText !== undefined && decl) {
|
|
798
|
+
const records = decl.records ?? [];
|
|
799
|
+
if (!records.some((r) => r.kind === "answers/answer.kind.mjs")) {
|
|
800
|
+
const next = { ...decl, records: [...records, { kind: "answers/answer.kind.mjs" }] } as Record<string, unknown>;
|
|
801
|
+
write("chant.workspace.json", JSON.stringify(next, null, 2) + "\n", "declares the answer kind");
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// The release Op and the app component.
|
|
806
|
+
if (readText(dir, at("ops/release.op.ts")) !== undefined) {
|
|
807
|
+
write(at("ops/release.op.ts"), releaseOp(pointsRel, appRel), "chant's release Op: Check, Build, Plan with the ship-skip point through decide, the ship gate on the plan's digest, Ship to Fly, and Record");
|
|
808
|
+
notMoved.push({ what: "signing the release archive, and checking the signature before the Machine runs it (chud's release-sign and release-verify)", where: `${SIGN_ISSUE}: blob signing for a source archive` });
|
|
809
|
+
}
|
|
810
|
+
if (readText(dir, at("deploy/app.component.ts")) !== undefined) {
|
|
811
|
+
write(at("deploy/app.component.ts"), appComponent(appRel), "the app component on chant's supply-chain verbs (SBOM, scan, vuln-gate)");
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
// Files the project keeps, edited.
|
|
815
|
+
edit(at("chant.config.ts"), configEdits(), "lexicons without chud; no chud lint rules", fixConfig);
|
|
816
|
+
edit(at("deploy/fly.ts"), FLY_EDITS, "the Fly app on the fly lexicon alone, without chud's FlySite");
|
|
817
|
+
edit(at("deploy/fly-machine.ts"), FLY_MACHINE_EDITS, "a comment that named chud-runtime's fly-site.mjs");
|
|
818
|
+
edit(at("decisions/ship-skip.cedar.ts"), CEDAR_EDITS, "the policy reads the decide step's answer and decider");
|
|
819
|
+
edit(at("ci/ci.ts"), CI_TS_EDITS, "CI no longer fetches the private chud runtime");
|
|
820
|
+
|
|
821
|
+
const team = readText(dir, at("agents/team.ts"));
|
|
822
|
+
if (team?.includes('"chant run dispatch"')) {
|
|
823
|
+
notMoved.push({ what: `the factory's Schedule in ${at("agents/team.ts")} runs \`chant run dispatch\``, where: `${KIT}: its dispatch Op (arugula-salad/studio#47); the Schedule fails until that Op is here` });
|
|
824
|
+
}
|
|
825
|
+
const config = readText(dir, at("chant.config.ts"));
|
|
826
|
+
if (config && /\bvulnDb\b/.test(config)) {
|
|
827
|
+
notMoved.push({ what: "buildParams.vulnDb (CHUD_VULN_DB), the pinned advisory database", where: "INTENTIUS/chant#2515: chant's scan reads it once the config fallback lands; until then grype or trivy" });
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
edit(".github/workflows/ci.yml", CI_YML_EDITS, "built from ci/ci.ts again (`npm run ci:build`)");
|
|
831
|
+
|
|
832
|
+
// The repo's prose (#2805): README.md, CLAUDE.md and design/CLAUDE.md
|
|
833
|
+
// describe chud's runtime, `--on chud` and the Ops and paths this migration
|
|
834
|
+
// deletes. Rewrite the passages this migration knows about; an edit whose
|
|
835
|
+
// anchor is gone (a different template shape) is a conflict, same as the
|
|
836
|
+
// code files above.
|
|
837
|
+
edit("README.md", README_EDITS, "the delivery row and everyday commands, off chud");
|
|
838
|
+
edit("CLAUDE.md", CLAUDE_EDITS, "the delivery row, the schema path and the upgrade Op, off chud");
|
|
839
|
+
edit(posix.join(designDir, "CLAUDE.md"), DESIGN_CLAUDE_EDITS, "the design app is the kit's now; no --on chud");
|
|
840
|
+
|
|
841
|
+
// Whatever still imports the chud packages once the plan ran is a conflict.
|
|
842
|
+
const planned = new Map(changes.map((c) => [c.path, c]));
|
|
843
|
+
for (const f of sourceFiles(dir)) {
|
|
844
|
+
const c = planned.get(f);
|
|
845
|
+
if (c?.action === "delete") continue;
|
|
846
|
+
const text = c?.data ? c.data.toString("utf-8") : readText(dir, f)!;
|
|
847
|
+
if (CHUD_IMPORT.test(text)) {
|
|
848
|
+
conflicts.push({ path: f, reason: "imports the chud packages, and this migration does not know the file. Move it off them by hand (see ws-056 for where each part went), then run the upgrade again" });
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
if (delivery.length === 0 && conflicts.length === 0) return null;
|
|
853
|
+
if (delivery.length > 0) notMoved.push({ what: "decision-point answers already on chud's `chud/decisions` branch", where: "they stay chud's; new answers are chant records in answers/" });
|
|
854
|
+
return {
|
|
855
|
+
id: CHUD_LEXICON_EXIT,
|
|
856
|
+
description: "take the repo off @intentius/chant-lexicon-chud and @intentius/chud-runtime (ws-056)",
|
|
857
|
+
changes,
|
|
858
|
+
notMoved: dedupe(notMoved),
|
|
859
|
+
conflicts,
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
function sourceFilesUnder(dir: string, rel: string): string[] {
|
|
864
|
+
const out: string[] = [];
|
|
865
|
+
for (const name of readdirSync(join(dir, rel))) {
|
|
866
|
+
const path = `${rel}/${name}`;
|
|
867
|
+
if (statSync(join(dir, path)).isDirectory()) out.push(...sourceFilesUnder(dir, path));
|
|
868
|
+
else out.push(path);
|
|
869
|
+
}
|
|
870
|
+
return out;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
function dedupe(items: NotMoved[]): NotMoved[] {
|
|
874
|
+
const seen = new Set<string>();
|
|
875
|
+
return items.filter((i) => (seen.has(i.what) ? false : (seen.add(i.what), true)));
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
export const chudLexiconExit: ChantMigration = {
|
|
879
|
+
id: CHUD_LEXICON_EXIT,
|
|
880
|
+
description: "take the repo off @intentius/chant-lexicon-chud and @intentius/chud-runtime (ws-056)",
|
|
881
|
+
plan: planExit,
|
|
882
|
+
};
|