@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,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Migrations chant ships (#2737).
|
|
3
|
+
*
|
|
4
|
+
* A template's own migrations (./lineage-migrations.ts) move a scope between
|
|
5
|
+
* two versions of that template. Some moves are chant's instead: a part a
|
|
6
|
+
* template used goes away, and chant knows where each piece of it went. The
|
|
7
|
+
* first is `chud-lexicon-exit`, which takes a repo made from chud's template
|
|
8
|
+
* (or the studio kit's copy of it) off `@intentius/chant-lexicon-chud` and
|
|
9
|
+
* `@intentius/chud-runtime` (ws-056).
|
|
10
|
+
*
|
|
11
|
+
* `chant workspace upgrade` asks each one for a plan after the template's own
|
|
12
|
+
* chain has run in the staging worktree, and before the per-file merge. A
|
|
13
|
+
* migration plans from what the scope holds, not from a version range, so it
|
|
14
|
+
* works whatever ref the scope is pinned at. Its plan lists every file it
|
|
15
|
+
* writes or deletes and why, what it cannot move and where that went, and any
|
|
16
|
+
* conflict. A plan with a conflict is not applied at all, and the upgrade's
|
|
17
|
+
* checks fail with it, so the dry run shows the whole plan and the tree is
|
|
18
|
+
* never left half moved.
|
|
19
|
+
*
|
|
20
|
+
* An applied migration's id is added to the lineage's `migrations`, so the
|
|
21
|
+
* lock records it and a second upgrade does not plan it again. A migration's
|
|
22
|
+
* plan is also empty once nothing it moves is left, so running it twice
|
|
23
|
+
* changes nothing either way.
|
|
24
|
+
*
|
|
25
|
+
* Files the migration rewrites keep their lineage entry: the lock's hash is
|
|
26
|
+
* still the template's, so to a later upgrade they are the project's own
|
|
27
|
+
* edits, merged per file like any other (ws-005). A deleted file keeps its
|
|
28
|
+
* entry too, so a template version that still has it leaves it deleted.
|
|
29
|
+
*
|
|
30
|
+
* chant itself carries no chud lexicon or chud-shaped code (#2830, once
|
|
31
|
+
* #2715's proof passed): `chud-lexicon-exit` and its follow-ons are the
|
|
32
|
+
* retirement tool, not a remaining dependency. They stay until no known repo
|
|
33
|
+
* still depends on chud, and are removed only in a major version.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
37
|
+
import { dirname, join } from "node:path";
|
|
38
|
+
import type { Lineage } from "./lineage-lock";
|
|
39
|
+
import { chudLexiconExit } from "./chant-migrations/chud-lexicon-exit";
|
|
40
|
+
import { chudLexiconExitRollback } from "./chant-migrations/chud-lexicon-exit-rollback";
|
|
41
|
+
import { chudLexiconExitShipInputs } from "./chant-migrations/chud-lexicon-exit-ship-inputs";
|
|
42
|
+
import { chudLexiconExitShipPolicy } from "./chant-migrations/chud-lexicon-exit-ship-policy";
|
|
43
|
+
import { chudLexiconExitFlySite } from "./chant-migrations/chud-lexicon-exit-fly-site";
|
|
44
|
+
import { chudLexiconExitLiveNames } from "./chant-migrations/chud-lexicon-exit-live-names";
|
|
45
|
+
|
|
46
|
+
/** One file a plan writes or deletes, relative to the scope. */
|
|
47
|
+
export interface PlannedChange {
|
|
48
|
+
path: string;
|
|
49
|
+
action: "write" | "delete";
|
|
50
|
+
/** Why, in a few words. */
|
|
51
|
+
why: string;
|
|
52
|
+
/** The new content, for a write. */
|
|
53
|
+
data?: Buffer;
|
|
54
|
+
/** True when the file had been edited since the template wrote it; the edit stays in git history. */
|
|
55
|
+
edited?: boolean;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** A part the migration does not move, and where it went instead. */
|
|
59
|
+
export interface NotMoved {
|
|
60
|
+
what: string;
|
|
61
|
+
where: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Why a plan cannot be applied. */
|
|
65
|
+
export interface PlanConflict {
|
|
66
|
+
path: string;
|
|
67
|
+
reason: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface ChantMigrationPlan {
|
|
71
|
+
id: string;
|
|
72
|
+
description: string;
|
|
73
|
+
changes: PlannedChange[];
|
|
74
|
+
notMoved: NotMoved[];
|
|
75
|
+
conflicts: PlanConflict[];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface ChantMigrationContext {
|
|
79
|
+
/** The scope directory (in the staging worktree). */
|
|
80
|
+
dir: string;
|
|
81
|
+
lineage: Lineage;
|
|
82
|
+
/** The chant version doing the upgrade, for dependency ranges. */
|
|
83
|
+
chantVersion: string;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface ChantMigration {
|
|
87
|
+
id: string;
|
|
88
|
+
description: string;
|
|
89
|
+
/** The plan for this scope, or null when there is nothing here for this migration to move. */
|
|
90
|
+
plan(ctx: ChantMigrationContext): ChantMigrationPlan | null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Every migration chant ships, in the order they are planned. */
|
|
94
|
+
export const CHANT_MIGRATIONS: readonly ChantMigration[] = [
|
|
95
|
+
chudLexiconExit,
|
|
96
|
+
chudLexiconExitRollback,
|
|
97
|
+
chudLexiconExitShipInputs,
|
|
98
|
+
chudLexiconExitShipPolicy,
|
|
99
|
+
chudLexiconExitFlySite,
|
|
100
|
+
chudLexiconExitLiveNames,
|
|
101
|
+
];
|
|
102
|
+
|
|
103
|
+
/** The plans for a scope: each migration not yet in the lineage that finds something to move. */
|
|
104
|
+
export function planChantMigrations(ctx: ChantMigrationContext, migrations: readonly ChantMigration[] = CHANT_MIGRATIONS): ChantMigrationPlan[] {
|
|
105
|
+
const applied = new Set(ctx.lineage.migrations);
|
|
106
|
+
const plans: ChantMigrationPlan[] = [];
|
|
107
|
+
for (const m of migrations) {
|
|
108
|
+
if (applied.has(m.id)) continue;
|
|
109
|
+
const plan = m.plan(ctx);
|
|
110
|
+
if (plan && (plan.changes.length > 0 || plan.conflicts.length > 0)) plans.push(plan);
|
|
111
|
+
}
|
|
112
|
+
return plans;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Plan each migration in order and apply each plan with no conflict before
|
|
117
|
+
* the next is planned, so a migration planned after another sees the tree
|
|
118
|
+
* the earlier one left (#2800: `chud-lexicon-exit-rollback` after
|
|
119
|
+
* `chud-lexicon-exit`). A plan with a conflict is not applied, and the ones
|
|
120
|
+
* after it plan from the tree as it is.
|
|
121
|
+
*/
|
|
122
|
+
export function runChantMigrations(ctx: ChantMigrationContext, migrations: readonly ChantMigration[] = CHANT_MIGRATIONS): Array<{ plan: ChantMigrationPlan; applied: boolean }> {
|
|
123
|
+
const out: Array<{ plan: ChantMigrationPlan; applied: boolean }> = [];
|
|
124
|
+
for (const m of migrations) {
|
|
125
|
+
if (ctx.lineage.migrations.includes(m.id)) continue;
|
|
126
|
+
const plan = m.plan(ctx);
|
|
127
|
+
if (!plan || (plan.changes.length === 0 && plan.conflicts.length === 0)) continue;
|
|
128
|
+
const applied = plan.conflicts.length === 0;
|
|
129
|
+
if (applied) applyChantMigration(plan, ctx.dir, ctx.lineage);
|
|
130
|
+
out.push({ plan, applied });
|
|
131
|
+
}
|
|
132
|
+
return out;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Apply a plan with no conflicts, and record its id in the lineage. */
|
|
136
|
+
export function applyChantMigration(plan: ChantMigrationPlan, dir: string, lineage: Lineage): void {
|
|
137
|
+
if (plan.conflicts.length > 0) throw new Error(`${plan.id} has conflicts and cannot be applied`);
|
|
138
|
+
for (const c of plan.changes) {
|
|
139
|
+
const abs = join(dir, c.path);
|
|
140
|
+
if (c.action === "delete") {
|
|
141
|
+
rmSync(abs, { force: true });
|
|
142
|
+
} else {
|
|
143
|
+
mkdirSync(dirname(abs), { recursive: true });
|
|
144
|
+
writeFileSync(abs, c.data!);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (!lineage.migrations.includes(plan.id)) lineage.migrations.push(plan.id);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** A plan as the upgrade reports it: no file contents. */
|
|
151
|
+
export interface ChantMigrationReport {
|
|
152
|
+
id: string;
|
|
153
|
+
description: string;
|
|
154
|
+
applied: boolean;
|
|
155
|
+
changes: Array<Omit<PlannedChange, "data">>;
|
|
156
|
+
notMoved: NotMoved[];
|
|
157
|
+
conflicts: PlanConflict[];
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function reportPlan(plan: ChantMigrationPlan, applied: boolean): ChantMigrationReport {
|
|
161
|
+
return {
|
|
162
|
+
id: plan.id,
|
|
163
|
+
description: plan.description,
|
|
164
|
+
applied,
|
|
165
|
+
changes: plan.changes.map(({ data: _d, ...rest }) => rest),
|
|
166
|
+
notMoved: plan.notMoved,
|
|
167
|
+
conflicts: plan.conflicts,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
@@ -11,6 +11,8 @@ import { afterAll, describe, expect, test } from "vitest";
|
|
|
11
11
|
import { cleanScratch, commitAll, contract, declaration, git, REPO, repo, scratchDir, validSchema } from "./__fixtures__/contract-repo";
|
|
12
12
|
import schema from "./check.schema.json";
|
|
13
13
|
import { BOX_FINDING_CODES } from "./checks/boxes";
|
|
14
|
+
import { RECORD_FINDING_CODES } from "./checks/records";
|
|
15
|
+
import { DIAGRAM_FINDING_CODES } from "./checks/diagrams";
|
|
14
16
|
import { WORKSPACE_ERROR_CODES } from "./declaration";
|
|
15
17
|
import { CHECK_CODES, CHECK_CONTRACT_VERSION, CHECK_ERROR_CODES, CHECK_OUTPUT_SCHEMA_ID, runChecks, type CheckDocument } from "./lineage-check";
|
|
16
18
|
|
|
@@ -36,8 +38,8 @@ describe("check output schema", () => {
|
|
|
36
38
|
test("lists exactly the codes the code can return", () => {
|
|
37
39
|
expect(schema.$defs.lockFinding.properties.code.enum).toEqual([...CHECK_CODES]);
|
|
38
40
|
expect(schema.$defs.failure.properties.error.properties.code.enum).toEqual([...CHECK_ERROR_CODES]);
|
|
39
|
-
expect(schema.$defs.diagnostic.properties.code.enum).toEqual([...WORKSPACE_ERROR_CODES, ...BOX_FINDING_CODES]);
|
|
40
|
-
expect(schema.$defs.suppressed.properties.code.enum).toEqual([...WORKSPACE_ERROR_CODES, ...BOX_FINDING_CODES]);
|
|
41
|
+
expect(schema.$defs.diagnostic.properties.code.enum).toEqual([...WORKSPACE_ERROR_CODES, ...BOX_FINDING_CODES, ...RECORD_FINDING_CODES, ...DIAGRAM_FINDING_CODES]);
|
|
42
|
+
expect(schema.$defs.suppressed.properties.code.enum).toEqual([...WORKSPACE_ERROR_CODES, ...BOX_FINDING_CODES, ...RECORD_FINDING_CODES, ...DIAGRAM_FINDING_CODES]);
|
|
41
43
|
});
|
|
42
44
|
});
|
|
43
45
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://intentius.io/chant/schemas/workspace/check/v1/check.schema.json",
|
|
4
4
|
"title": "chant workspace check output",
|
|
5
|
-
"description": "What `chant workspace check --format json` prints, version 1 of the read contract for checking a workspace (#2524 D9, D15, D16, #2535, #2536). The lineage lock findings carry reason codes; the declaration findings carry WSP ids from the check catalog, WSP001, a declaration that can't be read, also carries the declaration's reason code, and the box checks WSP121 to
|
|
5
|
+
"description": "What `chant workspace check --format json` prints, version 1 of the read contract for checking a workspace (#2524 D9, D15, D16, #2535, #2536). The lineage lock findings carry reason codes; the declaration findings carry WSP ids from the check catalog, WSP001, a declaration that can't be read, also carries the declaration's reason code, and the box checks WSP121 to WSP127 carry theirs. Readers ignore fields they do not know; a field is only ever added within a version. The reason and error codes are closed lists: a new code is a new contract version. Contract version 1 is written by chant 0.81.0 and newer; a reader that needs it refuses output whose `contract` it doesn't know. Every code is in the one closed list of `reason-codes.ts`.",
|
|
6
6
|
"oneOf": [
|
|
7
7
|
{
|
|
8
8
|
"$ref": "#/$defs/result"
|
|
@@ -226,9 +226,16 @@
|
|
|
226
226
|
"box-credential-declared",
|
|
227
227
|
"box-capability-unbrokered",
|
|
228
228
|
"box-isolation-collision",
|
|
229
|
-
"box-isolation-literal"
|
|
229
|
+
"box-isolation-literal",
|
|
230
|
+
"box-fountain-callback-undeclared",
|
|
231
|
+
"box-intent-unknown",
|
|
232
|
+
"box-intent-unconstrained",
|
|
233
|
+
"work-acceptance-unmet",
|
|
234
|
+
"diagram-source-missing",
|
|
235
|
+
"diagram-render-missing",
|
|
236
|
+
"diagram-render-drift"
|
|
230
237
|
],
|
|
231
|
-
"description": "For WSP001: why the declaration can't be read. For WSP121 to
|
|
238
|
+
"description": "For WSP001: why the declaration can't be read. For WSP121 to WSP127, the box finding (#2726, #2727, #2780, #2850). For WSP117, work-acceptance-unmet (#2772). For WSP131 to WSP133, the diagram finding (#2764)."
|
|
232
239
|
}
|
|
233
240
|
}
|
|
234
241
|
},
|
|
@@ -297,9 +304,16 @@
|
|
|
297
304
|
"box-credential-declared",
|
|
298
305
|
"box-capability-unbrokered",
|
|
299
306
|
"box-isolation-collision",
|
|
300
|
-
"box-isolation-literal"
|
|
307
|
+
"box-isolation-literal",
|
|
308
|
+
"box-fountain-callback-undeclared",
|
|
309
|
+
"box-intent-unknown",
|
|
310
|
+
"box-intent-unconstrained",
|
|
311
|
+
"work-acceptance-unmet",
|
|
312
|
+
"diagram-source-missing",
|
|
313
|
+
"diagram-render-missing",
|
|
314
|
+
"diagram-render-drift"
|
|
301
315
|
],
|
|
302
|
-
"description": "For WSP001: why the declaration can't be read. For WSP121 to
|
|
316
|
+
"description": "For WSP001: why the declaration can't be read. For WSP121 to WSP127, the box finding (#2726, #2727, #2780, #2850). For WSP117, work-acceptance-unmet (#2772). For WSP131 to WSP133, the diagram finding (#2764)."
|
|
303
317
|
},
|
|
304
318
|
"reason": {
|
|
305
319
|
"type": "string"
|
|
@@ -7,7 +7,7 @@ import { afterAll, describe, expect, test } from "vitest";
|
|
|
7
7
|
import { runDeclarationChecks } from "../checks";
|
|
8
8
|
import { parseDeclaration, WorkspaceReadError } from "../declaration";
|
|
9
9
|
import { gitTree } from "../tree";
|
|
10
|
-
import { isCredentialKey, isSecretReference, literalSecretsInCode, literalSecretsInText } from "./boxes";
|
|
10
|
+
import { fountainBoxCall, isCredentialKey, isSecretReference, literalSecretsInCode, literalSecretsInText } from "./boxes";
|
|
11
11
|
|
|
12
12
|
const scratch: string[] = [];
|
|
13
13
|
afterAll(() => {
|
|
@@ -89,7 +89,7 @@ describe("box-credential-declared (WSP121)", () => {
|
|
|
89
89
|
test("an env file and a shell script in the box's directory are read too; prose only for credential shapes", async () => {
|
|
90
90
|
const root = repo({
|
|
91
91
|
"chant.workspace.json": declaration(BROKERED),
|
|
92
|
-
"spec/.env": `FOUNTAIN_API_KEY=abc123def456\nPORT=8080\nGIT_TOKEN=\${
|
|
92
|
+
"spec/.env": `FOUNTAIN_API_KEY=abc123def456\nPORT=8080\nGIT_TOKEN=\${ACME_GIT_TOKEN}\n`,
|
|
93
93
|
"spec/run.sh": `#!/bin/sh\nexport GITHUB_TOKEN=${GITHUB_TOKEN}\necho "password=$GIT_TOKEN"\nTOKEN=$1\ntoken=~/box/llm-token\nSECRET=/run/secrets/x\n`,
|
|
94
94
|
"spec/README.md": `Token: rotate it monthly.\n`,
|
|
95
95
|
"spec/node_modules/pkg/index.js": `export const k = ${JSON.stringify(ANTHROPIC_KEY)};\n`,
|
|
@@ -156,6 +156,7 @@ describe("the box block in the declaration", () => {
|
|
|
156
156
|
expect(d.members[0].box).toEqual({
|
|
157
157
|
pointer: "/members/0/box",
|
|
158
158
|
isolation: null,
|
|
159
|
+
intent: null,
|
|
159
160
|
capabilities: [
|
|
160
161
|
{ name: "fountain", broker: "lobby", scope: ["agent", "vault"], pointer: "/members/0/box/capabilities/0" },
|
|
161
162
|
{ name: "inference", broker: null, scope: [], pointer: "/members/0/box/capabilities/1" },
|
|
@@ -195,3 +196,82 @@ describe("what counts as a literal secret", () => {
|
|
|
195
196
|
expect(literalSecretsInText(`# nothing\nkey: ${GITHUB_TOKEN}\n`).map((s) => s.line)).toEqual([2]);
|
|
196
197
|
});
|
|
197
198
|
});
|
|
199
|
+
|
|
200
|
+
describe("box-fountain-callback-undeclared (WSP125, #2780)", () => {
|
|
201
|
+
/** A studio-style fountain Box declaration. */
|
|
202
|
+
const fountainBox = `import { readFileSync } from "node:fs";
|
|
203
|
+
import { Box } from "@intentius/chant-lexicon-fountain";
|
|
204
|
+
|
|
205
|
+
export const { environment, agent } = Box({
|
|
206
|
+
name: "studio-box",
|
|
207
|
+
setupScript: readFileSync("box/setup.sh", "utf8"),
|
|
208
|
+
model: "anthropic/claude-sonnet-4-6",
|
|
209
|
+
permissionPolicy: { default: "auto_allow" },
|
|
210
|
+
});
|
|
211
|
+
`;
|
|
212
|
+
const CALLBACK = { name: "fountain-callback", broker: "fountain", scope: ["owner"] };
|
|
213
|
+
|
|
214
|
+
test("a box member that builds a fountain Box and does not declare the callback token fails, at the Box call", async () => {
|
|
215
|
+
const root = repo({ "chant.workspace.json": declaration(BROKERED), "spec/box.ts": fountainBox });
|
|
216
|
+
const d = await found(root);
|
|
217
|
+
expect(d.map((x) => [x.ruleId, x.code, x.entity, x.file, x.line])).toEqual([
|
|
218
|
+
["WSP125", "box-fountain-callback-undeclared", "spec", "spec/box.ts", 4],
|
|
219
|
+
]);
|
|
220
|
+
expect(d[0].message).toContain("callback token scoped to its owner (FOUNTAIN_TOKEN); the member's box block does not declare it");
|
|
221
|
+
expect(d[0].message).toContain('{ "name": "fountain-callback", "broker": "fountain", "scope": ["owner"] }');
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test("declaring fountain-callback, brokered by fountain with scope owner, passes, beside a fountain capability a lobby brokers", async () => {
|
|
225
|
+
const root = repo({
|
|
226
|
+
"chant.workspace.json": declaration({
|
|
227
|
+
capabilities: [...BROKERED.capabilities, { name: "fountain", broker: "lobby", scope: ["agent", "conversations"] }, CALLBACK],
|
|
228
|
+
}),
|
|
229
|
+
"spec/box.ts": fountainBox,
|
|
230
|
+
});
|
|
231
|
+
expect(await found(root)).toEqual([]);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test("fountain-callback behind another broker, or without owner in its scope, fails", async () => {
|
|
235
|
+
const lobby = repo({
|
|
236
|
+
"chant.workspace.json": declaration({ capabilities: [{ name: "fountain-callback", broker: "lobby", scope: ["conversations"] }] }),
|
|
237
|
+
"spec/box.ts": fountainBox,
|
|
238
|
+
});
|
|
239
|
+
const d = await found(lobby);
|
|
240
|
+
expect(d.map((x) => [x.ruleId, x.file])).toEqual([["WSP125", "spec/box.ts"]]);
|
|
241
|
+
expect(d[0].message).toContain("names the broker lobby, but fountain hands this token to the sandbox itself");
|
|
242
|
+
const narrow = repo({
|
|
243
|
+
"chant.workspace.json": declaration({ capabilities: [{ name: "fountain-callback", broker: "fountain", scope: ["conversations"] }] }),
|
|
244
|
+
"spec/box.ts": fountainBox,
|
|
245
|
+
});
|
|
246
|
+
expect((await found(narrow))[0].message).toContain("has scope [conversations], not owner");
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test("a box member that builds no fountain Box, and a member with no box block, are not flagged", async () => {
|
|
250
|
+
const other = repo({ "chant.workspace.json": declaration(BROKERED), "spec/box.ts": spec("${ANTHROPIC_API_KEY}") });
|
|
251
|
+
expect(await found(other)).toEqual([]);
|
|
252
|
+
const plain = repo({
|
|
253
|
+
"chant.workspace.json": JSON.stringify({ name: "acme", schema: 1, members: [{ name: "spec", dir: "spec", kind: "other", because: "x" }] }),
|
|
254
|
+
"spec/box.ts": fountainBox,
|
|
255
|
+
});
|
|
256
|
+
expect(await found(plain)).toEqual([]);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
test("it can be turned down while fountain has no way to drop the token (managoat/fountain#2497)", async () => {
|
|
260
|
+
const root = repo({
|
|
261
|
+
"chant.workspace.json": declaration(BROKERED, { suppress: [{ check: "WSP125", because: "waiting on fountain" }] }),
|
|
262
|
+
"spec/box.ts": fountainBox,
|
|
263
|
+
});
|
|
264
|
+
expect((await found(root)).filter((x) => x.ruleId === "WSP125")).toEqual([]);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
test("fountainBoxCall finds the composite imported by name, aliased or through a namespace, and nothing else", () => {
|
|
268
|
+
const at = (text: string) => fountainBoxCall(text, "box.ts", ts.ScriptKind.TS);
|
|
269
|
+
expect(at(fountainBox)).toEqual({ line: 4, column: 39 });
|
|
270
|
+
expect(at(`import { Box as FBox } from "@intentius/chant-lexicon-fountain";\nFBox({});\n`)).toEqual({ line: 2, column: 1 });
|
|
271
|
+
expect(at(`import * as fountain from "@intentius/chant-lexicon-fountain";\nfountain.Box({});\n`)).toEqual({ line: 2, column: 1 });
|
|
272
|
+
// Imported but never called, another package's Box, or a Box of one's own.
|
|
273
|
+
expect(at(`import { Box } from "@intentius/chant-lexicon-fountain";\nexport { Box };\n`)).toBeUndefined();
|
|
274
|
+
expect(at(`import { Box } from "@intentius/chant-lexicon-chaff";\nBox({});\n`)).toBeUndefined();
|
|
275
|
+
expect(at(`const Box = (o: object) => o;\nBox({}); // @intentius/chant-lexicon-fountain\n`)).toBeUndefined();
|
|
276
|
+
});
|
|
277
|
+
});
|
|
@@ -26,20 +26,114 @@
|
|
|
26
26
|
*
|
|
27
27
|
* WSP123 and WSP124, box isolation (#2727), are in `./box-isolation.ts` and
|
|
28
28
|
* listed here after these two.
|
|
29
|
+
*
|
|
30
|
+
* WSP125 (`box-fountain-callback-undeclared`, #2780) is the one credential a
|
|
31
|
+
* box gets without asking. fountain v0.21.0 gives every persistent sandbox a
|
|
32
|
+
* callback token scoped to its owner (`FOUNTAIN_TOKEN`, `sandbox_api_access:
|
|
33
|
+
* owner`), and a fountain `Box` is always persistent, so its member's block
|
|
34
|
+
* has to say so: the capability `fountain-callback`, brokered by `fountain`,
|
|
35
|
+
* with scope `owner`. It is named apart from `fountain`, the API access a
|
|
36
|
+
* lobby may broker for the same box. A member builds a Box when one of its TypeScript or JavaScript files
|
|
37
|
+
* imports `Box` from `@intentius/chant-lexicon-fountain` and calls it, read as
|
|
38
|
+
* syntax and never run. Upstream, managoat/fountain#2497 asks for a way to run
|
|
39
|
+
* a persistent agent with no callback token.
|
|
40
|
+
*
|
|
41
|
+
* WSP126 (`box-intent-unknown`) and WSP127 (`box-intent-unconstrained`), #2850,
|
|
42
|
+
* read the decision record a box block names as its intent (`box-intent.ts`).
|
|
43
|
+
* WSP126 fails when no record of a declared kind named decision has the id.
|
|
44
|
+
* WSP127 warns when the record's constrains names no member or path of this
|
|
45
|
+
* workspace at all: no `member:` entry for a declared member and no `path:`
|
|
46
|
+
* entry at, above or inside one's directory. It need not reach the box's own
|
|
47
|
+
* member: the decision an intent names can constrain the member whose app the
|
|
48
|
+
* box runs, on a workspace where the box block sits on a different member,
|
|
49
|
+
* the box's steward (studio's template, #2857). Both read the working
|
|
50
|
+
* tree's records, so neither runs under `--at`.
|
|
29
51
|
*/
|
|
30
52
|
|
|
31
53
|
import * as ts from "typescript";
|
|
32
54
|
import type { WorkspaceCheck, WorkspaceCheckContext, WorkspaceDiagnostic } from "../checks";
|
|
33
55
|
import type { Member } from "../declaration";
|
|
56
|
+
import { constrainsWorkspace } from "../box-intent";
|
|
34
57
|
import type { ReasonCode } from "../reason-codes";
|
|
35
58
|
import { joinPath, skippedDir, type WorkspaceTree } from "../tree";
|
|
36
59
|
import { BOX_ISOLATION_CHECKS } from "./box-isolation";
|
|
37
60
|
|
|
38
61
|
/** The read contract's codes for the box findings, carried as `code` on each. */
|
|
39
|
-
export const BOX_FINDING_CODES = [
|
|
62
|
+
export const BOX_FINDING_CODES = [
|
|
63
|
+
"box-credential-declared",
|
|
64
|
+
"box-capability-unbrokered",
|
|
65
|
+
"box-isolation-collision",
|
|
66
|
+
"box-isolation-literal",
|
|
67
|
+
"box-fountain-callback-undeclared",
|
|
68
|
+
"box-intent-unknown",
|
|
69
|
+
"box-intent-unconstrained",
|
|
70
|
+
] as const satisfies readonly ReasonCode[];
|
|
40
71
|
|
|
41
72
|
export const WSP_BOX_CREDENTIAL = "WSP121";
|
|
42
73
|
export const WSP_BOX_UNBROKERED = "WSP122";
|
|
74
|
+
export const WSP_BOX_FOUNTAIN_CALLBACK = "WSP125";
|
|
75
|
+
export const WSP_BOX_INTENT_UNKNOWN = "WSP126";
|
|
76
|
+
export const WSP_BOX_INTENT_UNCONSTRAINED = "WSP127";
|
|
77
|
+
|
|
78
|
+
/** The fountain lexicon's package, whose `Box` composite runs a persistent sandbox. */
|
|
79
|
+
const FOUNTAIN_LEXICON = "@intentius/chant-lexicon-fountain";
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* The capability a box block declares for fountain's callback token (#2780).
|
|
83
|
+
* The fountain lexicon exports the same value as `BOX_FOUNTAIN_CALLBACK_CAPABILITY`.
|
|
84
|
+
*/
|
|
85
|
+
export const FOUNTAIN_CALLBACK_CAPABILITY = { name: "fountain-callback", broker: "fountain", scope: ["owner"] } as const;
|
|
86
|
+
|
|
87
|
+
/** Where a file calls the fountain lexicon's `Box`, 1-based. */
|
|
88
|
+
export interface FountainBoxCall {
|
|
89
|
+
line: number;
|
|
90
|
+
column: number;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* The first call of the fountain lexicon's `Box` in a TypeScript or
|
|
95
|
+
* JavaScript file, read as syntax and never run: `Box` imported by name
|
|
96
|
+
* (aliased or not) and called, or called through a namespace import. Any
|
|
97
|
+
* other `Box` is not the composite.
|
|
98
|
+
*/
|
|
99
|
+
export function fountainBoxCall(text: string, file: string, kind: ts.ScriptKind): FountainBoxCall | undefined {
|
|
100
|
+
if (!text.includes(FOUNTAIN_LEXICON)) return undefined;
|
|
101
|
+
const source = ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true, kind);
|
|
102
|
+
const names = new Set<string>();
|
|
103
|
+
const namespaces = new Set<string>();
|
|
104
|
+
for (const stmt of source.statements) {
|
|
105
|
+
if (!ts.isImportDeclaration(stmt) || !ts.isStringLiteral(stmt.moduleSpecifier)) continue;
|
|
106
|
+
const from = stmt.moduleSpecifier.text;
|
|
107
|
+
if (from !== FOUNTAIN_LEXICON && !from.startsWith(`${FOUNTAIN_LEXICON}/`)) continue;
|
|
108
|
+
const bindings = stmt.importClause?.namedBindings;
|
|
109
|
+
if (!bindings) continue;
|
|
110
|
+
if (ts.isNamespaceImport(bindings)) namespaces.add(bindings.name.text);
|
|
111
|
+
else for (const el of bindings.elements) if ((el.propertyName ?? el.name).text === "Box") names.add(el.name.text);
|
|
112
|
+
}
|
|
113
|
+
if (names.size === 0 && namespaces.size === 0) return undefined;
|
|
114
|
+
let found: ts.Node | undefined;
|
|
115
|
+
const visit = (node: ts.Node) => {
|
|
116
|
+
if (found) return;
|
|
117
|
+
if (ts.isCallExpression(node)) {
|
|
118
|
+
const callee = node.expression;
|
|
119
|
+
if (
|
|
120
|
+
(ts.isIdentifier(callee) && names.has(callee.text)) ||
|
|
121
|
+
(ts.isPropertyAccessExpression(callee) &&
|
|
122
|
+
callee.name.text === "Box" &&
|
|
123
|
+
ts.isIdentifier(callee.expression) &&
|
|
124
|
+
namespaces.has(callee.expression.text))
|
|
125
|
+
) {
|
|
126
|
+
found = node;
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
ts.forEachChild(node, visit);
|
|
131
|
+
};
|
|
132
|
+
visit(source);
|
|
133
|
+
if (!found) return undefined;
|
|
134
|
+
const { line, character } = source.getLineAndCharacterOfPosition(found.getStart(source));
|
|
135
|
+
return { line: line + 1, column: character + 1 };
|
|
136
|
+
}
|
|
43
137
|
|
|
44
138
|
/** Values whose shape alone says they are a credential. The list FTN001 uses, plus Anthropic keys. */
|
|
45
139
|
export const CREDENTIAL_SHAPES: readonly { pattern: RegExp; label: string }[] = [
|
|
@@ -304,4 +398,111 @@ export const BOX_CHECKS: readonly WorkspaceCheck[] = [
|
|
|
304
398
|
},
|
|
305
399
|
// Box isolation (#2727), WSP123 and WSP124.
|
|
306
400
|
...BOX_ISOLATION_CHECKS,
|
|
401
|
+
{
|
|
402
|
+
id: WSP_BOX_FOUNTAIN_CALLBACK,
|
|
403
|
+
name: "box-fountain-callback-undeclared",
|
|
404
|
+
description:
|
|
405
|
+
"A box member that builds a fountain Box declares the callback token fountain gives its persistent sandbox: the fountain-callback capability, brokered by fountain, with scope owner.",
|
|
406
|
+
severity: "error",
|
|
407
|
+
configurable: true,
|
|
408
|
+
check(ctx) {
|
|
409
|
+
const out: WorkspaceDiagnostic[] = [];
|
|
410
|
+
const want = FOUNTAIN_CALLBACK_CAPABILITY;
|
|
411
|
+
const expected = `{ "name": "${want.name}", "broker": "${want.broker}", "scope": ["${want.scope[0]}"] }`;
|
|
412
|
+
for (const m of boxMembers(ctx)) {
|
|
413
|
+
const dir = m.dir === "." ? "" : m.dir;
|
|
414
|
+
if (ctx.tree.stat(dir) !== "dir") continue;
|
|
415
|
+
let at: { path: string; call: FountainBoxCall } | undefined;
|
|
416
|
+
for (const path of filesUnder(ctx.tree, dir)) {
|
|
417
|
+
const kind = CODE_EXTENSIONS[extension(path)];
|
|
418
|
+
if (kind === undefined || kind === ts.ScriptKind.JSON) continue;
|
|
419
|
+
let text: string;
|
|
420
|
+
try {
|
|
421
|
+
text = ctx.tree.read(path);
|
|
422
|
+
} catch {
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
if (text.length > MAX_FILE_BYTES) continue;
|
|
426
|
+
const call = fountainBoxCall(text, path, kind);
|
|
427
|
+
if (call) {
|
|
428
|
+
at = { path, call };
|
|
429
|
+
break;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
if (!at) continue;
|
|
433
|
+
const declared = m.box!.capabilities.find((c) => c.name === want.name);
|
|
434
|
+
if (declared && declared.broker === want.broker && declared.scope.includes(want.scope[0])) continue;
|
|
435
|
+
const why = !declared
|
|
436
|
+
? "the member's box block does not declare it"
|
|
437
|
+
: declared.broker !== want.broker
|
|
438
|
+
? `the box block's ${want.name} capability names the broker ${declared.broker ?? "(none)"}, but fountain hands this token to the sandbox itself`
|
|
439
|
+
: `the box block's ${want.name} capability has scope [${declared.scope.join(", ")}], not ${want.scope[0]}`;
|
|
440
|
+
out.push({
|
|
441
|
+
checkId: this.id,
|
|
442
|
+
severity: this.severity,
|
|
443
|
+
code: "box-fountain-callback-undeclared",
|
|
444
|
+
message:
|
|
445
|
+
`box-fountain-callback-undeclared: member ${m.name} builds a fountain Box (${at.path}), and fountain v0.21.0 gives a persistent box's sandbox ` +
|
|
446
|
+
`a callback token scoped to its owner (FOUNTAIN_TOKEN); ${why}. Declare ${expected} in the box block ` +
|
|
447
|
+
`(managoat/fountain#2497 tracks running without it)`,
|
|
448
|
+
entity: m.name,
|
|
449
|
+
pointer: declared?.pointer ?? m.box!.pointer,
|
|
450
|
+
treeFile: at.path,
|
|
451
|
+
line: at.call.line,
|
|
452
|
+
column: at.call.column,
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
return out;
|
|
456
|
+
},
|
|
457
|
+
},
|
|
458
|
+
{
|
|
459
|
+
id: WSP_BOX_INTENT_UNKNOWN,
|
|
460
|
+
name: "box-intent-unknown",
|
|
461
|
+
description: "The intent a box block names is the id of a decision record: a record of a declared kind named decision.",
|
|
462
|
+
severity: "error",
|
|
463
|
+
configurable: true,
|
|
464
|
+
check(ctx) {
|
|
465
|
+
const out: WorkspaceDiagnostic[] = [];
|
|
466
|
+
for (const i of ctx.facts?.boxIntents ?? []) {
|
|
467
|
+
if (i.record) continue;
|
|
468
|
+
out.push({
|
|
469
|
+
checkId: this.id,
|
|
470
|
+
severity: this.severity,
|
|
471
|
+
code: "box-intent-unknown",
|
|
472
|
+
message: `box-intent-unknown: member ${i.member}'s box names the intent ${i.id}, and ${i.why}; propose the decision with chant workspace records new, or fix the id`,
|
|
473
|
+
entity: i.member,
|
|
474
|
+
pointer: i.pointer,
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
return out;
|
|
478
|
+
},
|
|
479
|
+
},
|
|
480
|
+
{
|
|
481
|
+
id: WSP_BOX_INTENT_UNCONSTRAINED,
|
|
482
|
+
name: "box-intent-unconstrained",
|
|
483
|
+
description:
|
|
484
|
+
"The decision record a box names as its intent constrains a member or path of this workspace: member:<a declared member's name>, or a path: entry at, above or inside a declared member's directory. It need not be the box's own member: a box one member runs can be what a decision about another member constrains.",
|
|
485
|
+
severity: "warning",
|
|
486
|
+
configurable: true,
|
|
487
|
+
check(ctx) {
|
|
488
|
+
const out: WorkspaceDiagnostic[] = [];
|
|
489
|
+
const members = ctx.declaration.members;
|
|
490
|
+
for (const i of ctx.facts?.boxIntents ?? []) {
|
|
491
|
+
if (!i.record) continue;
|
|
492
|
+
if (i.record.constrains.some((c) => constrainsWorkspace(c, members))) continue;
|
|
493
|
+
out.push({
|
|
494
|
+
checkId: this.id,
|
|
495
|
+
severity: this.severity,
|
|
496
|
+
code: "box-intent-unconstrained",
|
|
497
|
+
message:
|
|
498
|
+
`box-intent-unconstrained: member ${i.member}'s box names the intent ${i.id} (${i.record.path}), whose constrains ` +
|
|
499
|
+
(i.record.constrains.length === 0 ? "is empty" : `names ${i.record.constrains.join(", ")}`) +
|
|
500
|
+
`, and none of it is a member or path of this workspace; add member:<name> for the member the intent is about, or a path: entry at, above or inside a member's directory`,
|
|
501
|
+
entity: i.member,
|
|
502
|
+
pointer: i.pointer,
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
return out;
|
|
506
|
+
},
|
|
507
|
+
},
|
|
307
508
|
];
|