@intentius/chant 0.90.0 → 0.93.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/lint.d.ts.map +1 -1
- package/dist/cli/handlers/components.d.ts +8 -0
- package/dist/cli/handlers/components.d.ts.map +1 -1
- package/dist/cli/handlers/graph.d.ts.map +1 -1
- package/dist/cli/handlers/operator.d.ts +20 -0
- package/dist/cli/handlers/operator.d.ts.map +1 -1
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/mcp/workspace-tools.d.ts +7 -4
- package/dist/cli/mcp/workspace-tools.d.ts.map +1 -1
- package/dist/cli/plugins.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +42 -1
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/codegen/pinned-upgrade.d.ts.map +1 -1
- package/dist/components/verbs/vuln-scan.d.ts +72 -0
- package/dist/components/verbs/vuln-scan.d.ts.map +1 -1
- package/dist/config.d.ts +22 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/lexicon-module.d.ts +22 -0
- package/dist/lexicon-module.d.ts.map +1 -1
- package/dist/lifecycle/converge-ledger.d.ts +19 -0
- package/dist/lifecycle/converge-ledger.d.ts.map +1 -1
- package/dist/lifecycle/git.d.ts +40 -5
- package/dist/lifecycle/git.d.ts.map +1 -1
- package/dist/lifecycle/lease.d.ts +72 -16
- package/dist/lifecycle/lease.d.ts.map +1 -1
- package/dist/lifecycle/member-ledger.d.ts +3 -2
- package/dist/lifecycle/member-ledger.d.ts.map +1 -1
- package/dist/lifecycle/plan-ledger.d.ts +114 -0
- package/dist/lifecycle/plan-ledger.d.ts.map +1 -0
- package/dist/lifecycle/run-ledger.d.ts +2 -0
- package/dist/lifecycle/run-ledger.d.ts.map +1 -1
- package/dist/lifecycle/symptoms.d.ts +51 -0
- package/dist/lifecycle/symptoms.d.ts.map +1 -1
- package/dist/lifecycle/work-lease.d.ts +140 -0
- package/dist/lifecycle/work-lease.d.ts.map +1 -0
- package/dist/lint/rules/index.d.ts +4 -2
- package/dist/lint/rules/index.d.ts.map +1 -1
- package/dist/lint/rules/op/index.d.ts +14 -3
- package/dist/lint/rules/op/index.d.ts.map +1 -1
- package/dist/lint/rules/op/ops014-converge-rule-refusals.d.ts.map +1 -1
- package/dist/lint/rules/op/sys010-key-over-plain-http.d.ts +29 -0
- package/dist/lint/rules/op/sys010-key-over-plain-http.d.ts.map +1 -0
- package/dist/lint/rules/sys001-no-literal-key.d.ts +3 -0
- package/dist/lint/rules/sys001-no-literal-key.d.ts.map +1 -0
- package/dist/op/__fixtures__/decide-stub-backend.d.ts +53 -0
- package/dist/op/__fixtures__/decide-stub-backend.d.ts.map +1 -0
- package/dist/op/activities/activity-contracts.d.ts +203 -2
- package/dist/op/activities/activity-contracts.d.ts.map +1 -1
- package/dist/op/activities/apply.d.ts.map +1 -1
- package/dist/op/activities/change-coverage.d.ts +44 -0
- package/dist/op/activities/change-coverage.d.ts.map +1 -0
- package/dist/op/activities/converge.d.ts +24 -1
- package/dist/op/activities/converge.d.ts.map +1 -1
- package/dist/op/activities/decide.d.ts +107 -0
- package/dist/op/activities/decide.d.ts.map +1 -0
- package/dist/op/activities/index.d.ts +10 -0
- package/dist/op/activities/index.d.ts.map +1 -1
- package/dist/op/activities/shell.d.ts +32 -0
- package/dist/op/activities/shell.d.ts.map +1 -1
- package/dist/op/activities/source-release.d.ts +86 -0
- package/dist/op/activities/source-release.d.ts.map +1 -0
- package/dist/op/activities/source-rollback.d.ts +100 -0
- package/dist/op/activities/source-rollback.d.ts.map +1 -0
- package/dist/op/activities/work-evidence.d.ts +42 -0
- package/dist/op/activities/work-evidence.d.ts.map +1 -0
- package/dist/op/activity-contract.d.ts +1 -0
- package/dist/op/activity-contract.d.ts.map +1 -1
- package/dist/op/activity-profiles.d.ts +16 -0
- package/dist/op/activity-profiles.d.ts.map +1 -1
- package/dist/op/builders.d.ts +84 -2
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/composites/converge-op.d.ts +31 -4
- package/dist/op/composites/converge-op.d.ts.map +1 -1
- package/dist/op/decide-backend.d.ts +116 -0
- package/dist/op/decide-backend.d.ts.map +1 -0
- package/dist/op/decide-config.d.ts +120 -0
- package/dist/op/decide-config.d.ts.map +1 -0
- package/dist/op/decide-read-inputs.d.ts +40 -0
- package/dist/op/decide-read-inputs.d.ts.map +1 -0
- package/dist/op/discover.d.ts +25 -0
- package/dist/op/discover.d.ts.map +1 -1
- package/dist/op/gate-wait.d.ts +43 -0
- package/dist/op/gate-wait.d.ts.map +1 -0
- package/dist/op/gate.d.ts +5 -2
- package/dist/op/gate.d.ts.map +1 -1
- package/dist/op/index.d.ts +18 -5
- package/dist/op/index.d.ts.map +1 -1
- package/dist/op/lifecycle-receipt-store.d.ts +34 -0
- package/dist/op/lifecycle-receipt-store.d.ts.map +1 -0
- package/dist/op/local-executor.d.ts +48 -4
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/local-output.d.ts +2 -0
- package/dist/op/local-output.d.ts.map +1 -1
- package/dist/op/op-ir.d.ts +12 -1
- package/dist/op/op-ir.d.ts.map +1 -1
- package/dist/op/op-verb-class.d.ts.map +1 -1
- package/dist/op/operator.d.ts +126 -1
- package/dist/op/operator.d.ts.map +1 -1
- package/dist/op/runtime.d.ts +46 -5
- package/dist/op/runtime.d.ts.map +1 -1
- package/dist/op/runtimes/local.d.ts.map +1 -1
- package/dist/op/source-archive.d.ts +63 -0
- package/dist/op/source-archive.d.ts.map +1 -0
- package/dist/op/step-output-ref.d.ts +2 -2
- package/dist/op/step-output-ref.d.ts.map +1 -1
- package/dist/op/steward-points.d.ts +136 -0
- package/dist/op/steward-points.d.ts.map +1 -0
- package/dist/op/steward-turn.d.ts +53 -0
- package/dist/op/steward-turn.d.ts.map +1 -0
- package/dist/op/steward.d.ts +161 -0
- package/dist/op/steward.d.ts.map +1 -0
- package/dist/op/types.d.ts +58 -0
- package/dist/op/types.d.ts.map +1 -1
- package/dist/op/work-lease-decl.d.ts +18 -0
- package/dist/op/work-lease-decl.d.ts.map +1 -0
- package/dist/op/work-lease-run.d.ts +173 -0
- package/dist/op/work-lease-run.d.ts.map +1 -0
- package/dist/workspace/__fixtures__/decide-workspace.d.ts +100 -0
- package/dist/workspace/__fixtures__/decide-workspace.d.ts.map +1 -0
- package/dist/workspace/answers-ledger.d.ts +76 -0
- package/dist/workspace/answers-ledger.d.ts.map +1 -0
- package/dist/workspace/box-isolation.d.ts +99 -0
- package/dist/workspace/box-isolation.d.ts.map +1 -0
- package/dist/workspace/changes-cli.d.ts +16 -0
- package/dist/workspace/changes-cli.d.ts.map +1 -0
- package/dist/workspace/changes.d.ts +200 -0
- package/dist/workspace/changes.d.ts.map +1 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-fly-site.d.ts +59 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-fly-site.d.ts.map +1 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-live-names.d.ts +51 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-live-names.d.ts.map +1 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-rollback.d.ts +26 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-rollback.d.ts.map +1 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-inputs.d.ts +59 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-inputs.d.ts.map +1 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-policy.d.ts +22 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-policy.d.ts.map +1 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit.d.ts +71 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit.d.ts.map +1 -0
- package/dist/workspace/chant-migrations.d.ts +99 -0
- package/dist/workspace/chant-migrations.d.ts.map +1 -0
- package/dist/workspace/checks/box-isolation.d.ts +18 -0
- package/dist/workspace/checks/box-isolation.d.ts.map +1 -0
- package/dist/workspace/checks/boxes.d.ts +104 -0
- package/dist/workspace/checks/boxes.d.ts.map +1 -0
- package/dist/workspace/checks/diagrams.d.ts +31 -0
- package/dist/workspace/checks/diagrams.d.ts.map +1 -0
- package/dist/workspace/checks/records.d.ts +8 -0
- package/dist/workspace/checks/records.d.ts.map +1 -1
- package/dist/workspace/checks.d.ts +13 -1
- package/dist/workspace/checks.d.ts.map +1 -1
- package/dist/workspace/compose-graph.d.ts +2 -0
- package/dist/workspace/compose-graph.d.ts.map +1 -1
- package/dist/workspace/conformance/index.d.ts.map +1 -1
- package/dist/workspace/decide.d.ts +229 -0
- package/dist/workspace/decide.d.ts.map +1 -0
- package/dist/workspace/decision-points.schema.json +137 -0
- package/dist/workspace/declaration.d.ts +103 -0
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.schema.json +285 -0
- package/dist/workspace/declared-kinds.d.ts +32 -0
- package/dist/workspace/declared-kinds.d.ts.map +1 -1
- package/dist/workspace/generated-files.d.ts +7 -0
- package/dist/workspace/generated-files.d.ts.map +1 -1
- package/dist/workspace/intent.d.ts.map +1 -1
- package/dist/workspace/lineage-check.d.ts +3 -0
- package/dist/workspace/lineage-check.d.ts.map +1 -1
- package/dist/workspace/lineage-upgrade.d.ts +6 -1
- package/dist/workspace/lineage-upgrade.d.ts.map +1 -1
- package/dist/workspace/ls.d.ts +39 -1
- package/dist/workspace/ls.d.ts.map +1 -1
- package/dist/workspace/point-answer.schema.json +95 -0
- package/dist/workspace/points-cli.d.ts +113 -0
- package/dist/workspace/points-cli.d.ts.map +1 -0
- package/dist/workspace/points.d.ts +320 -0
- package/dist/workspace/points.d.ts.map +1 -0
- package/dist/workspace/reason-codes.d.ts +38 -0
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/record-assets.d.ts.map +1 -1
- package/dist/workspace/records-cli.d.ts +23 -1
- package/dist/workspace/records-cli.d.ts.map +1 -1
- package/dist/workspace/records-close.d.ts +1 -1
- package/dist/workspace/records-close.d.ts.map +1 -1
- package/dist/workspace/records-write.d.ts +11 -5
- package/dist/workspace/records-write.d.ts.map +1 -1
- package/dist/workspace/records.d.ts +42 -6
- package/dist/workspace/records.d.ts.map +1 -1
- package/dist/workspace/status-gates.d.ts +7 -2
- package/dist/workspace/status-gates.d.ts.map +1 -1
- package/dist/workspace/status-stewards.d.ts +179 -0
- package/dist/workspace/status-stewards.d.ts.map +1 -0
- package/dist/workspace/status.d.ts +68 -1
- package/dist/workspace/status.d.ts.map +1 -1
- package/dist/workspace/work-cli.d.ts +189 -0
- package/dist/workspace/work-cli.d.ts.map +1 -0
- package/dist/workspace/work-evidence.d.ts +74 -0
- package/dist/workspace/work-evidence.d.ts.map +1 -0
- package/dist/workspace/work.d.ts +43 -1
- package/dist/workspace/work.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/cli/commands/audit.test.ts +9 -1
- package/src/cli/commands/build.ts +3 -2
- package/src/cli/commands/lint.ts +7 -1
- package/src/cli/handlers/components.test.ts +93 -0
- package/src/cli/handlers/components.ts +44 -3
- package/src/cli/handlers/graph.test.ts +20 -0
- package/src/cli/handlers/graph.ts +16 -2
- package/src/cli/handlers/operator.ts +141 -4
- package/src/cli/handlers/run.test.ts +173 -2
- package/src/cli/handlers/run.ts +174 -3
- package/src/cli/main.test.ts +50 -0
- package/src/cli/main.ts +97 -2
- package/src/cli/mcp/workspace-tools.test.ts +48 -19
- package/src/cli/mcp/workspace-tools.ts +110 -8
- package/src/cli/plugins.ts +11 -3
- package/src/cli/registry.ts +42 -1
- package/src/codegen/pinned-upgrade.ts +2 -1
- package/src/codegen/release-wiring.test.ts +49 -2
- package/src/components/capability-plugin-loader.ts +3 -3
- package/src/components/cli-support.ts +2 -2
- package/src/components/verbs/vuln-scan.test.ts +124 -1
- package/src/components/verbs/vuln-scan.ts +142 -1
- package/src/config.ts +11 -0
- package/src/lexicon-module.ts +56 -1
- package/src/lexicon-package.test.ts +96 -0
- package/src/lifecycle/converge-ledger.ts +20 -0
- package/src/lifecycle/git.ts +65 -15
- package/src/lifecycle/lease.test.ts +22 -0
- package/src/lifecycle/lease.ts +133 -29
- package/src/lifecycle/member-ledger.ts +3 -2
- package/src/lifecycle/plan-ledger.test.ts +148 -0
- package/src/lifecycle/plan-ledger.ts +158 -0
- package/src/lifecycle/run-ledger.ts +6 -0
- package/src/lifecycle/symptoms.ts +95 -0
- package/src/lifecycle/work-lease.test.ts +236 -0
- package/src/lifecycle/work-lease.ts +426 -0
- package/src/lint/rules/index.ts +6 -2
- package/src/lint/rules/op/index.ts +17 -4
- package/src/lint/rules/op/ops014-converge-rule-refusals.test.ts +23 -1
- package/src/lint/rules/op/ops014-converge-rule-refusals.ts +10 -6
- package/src/lint/rules/op/sys010-key-over-plain-http.test.ts +52 -0
- package/src/lint/rules/op/sys010-key-over-plain-http.ts +100 -0
- package/src/lint/rules/sys001-no-literal-key.test.ts +31 -0
- package/src/lint/rules/sys001-no-literal-key.ts +67 -0
- package/src/op/__fixtures__/decide-stub-backend.ts +143 -0
- package/src/op/activities/activity-contracts.ts +190 -1
- package/src/op/activities/apply.ts +7 -6
- package/src/op/activities/change-coverage.ts +60 -0
- package/src/op/activities/converge.test.ts +55 -1
- package/src/op/activities/converge.ts +208 -29
- package/src/op/activities/decide.live.test.ts +78 -0
- package/src/op/activities/decide.test.ts +300 -0
- package/src/op/activities/decide.ts +267 -0
- package/src/op/activities/index.ts +21 -0
- package/src/op/activities/shell.test.ts +91 -1
- package/src/op/activities/shell.ts +91 -2
- package/src/op/activities/source-release.test.ts +118 -0
- package/src/op/activities/source-release.ts +168 -0
- package/src/op/activities/source-rollback.test.ts +131 -0
- package/src/op/activities/source-rollback.ts +239 -0
- package/src/op/activities/work-evidence.ts +62 -0
- package/src/op/activity-contract-registry.test.ts +2 -0
- package/src/op/activity-contract-registry.ts +2 -2
- package/src/op/activity-contract.test.ts +12 -0
- package/src/op/activity-contract.ts +6 -1
- package/src/op/activity-profiles.ts +29 -0
- package/src/op/activity-registry.test.ts +1 -1
- package/src/op/activity-registry.ts +2 -2
- package/src/op/builders.ts +124 -5
- package/src/op/command-gate.test.ts +192 -0
- package/src/op/composites/converge-op.test.ts +27 -1
- package/src/op/composites/converge-op.ts +69 -15
- package/src/op/decide-backend.test.ts +44 -0
- package/src/op/decide-backend.ts +212 -0
- package/src/op/decide-config.test.ts +52 -0
- package/src/op/decide-config.ts +80 -0
- package/src/op/decide-read-inputs.ts +117 -0
- package/src/op/discover.ts +71 -0
- package/src/op/gate-approval.ts +2 -2
- package/src/op/gate-wait.ts +57 -0
- package/src/op/gate.ts +8 -3
- package/src/op/generate-pipeline.ts +2 -2
- package/src/op/index.ts +34 -6
- package/src/op/lifecycle-receipt-store.test.ts +60 -0
- package/src/op/lifecycle-receipt-store.ts +61 -0
- package/src/op/local-executor.ts +367 -33
- package/src/op/local-output.test.ts +33 -0
- package/src/op/local-output.ts +33 -2
- package/src/op/op-ir.test.ts +10 -0
- package/src/op/op-ir.ts +18 -0
- package/src/op/op-verb-class.ts +1 -0
- package/src/op/operator.ts +360 -10
- package/src/op/runtime.ts +37 -8
- package/src/op/runtimes/local.ts +6 -1
- package/src/op/source-archive.ts +161 -0
- package/src/op/step-output-ref.ts +6 -2
- package/src/op/steward-points.test.ts +313 -0
- package/src/op/steward-points.ts +194 -0
- package/src/op/steward-turn.ts +79 -0
- package/src/op/steward.test.ts +296 -0
- package/src/op/steward.ts +277 -0
- package/src/op/types.ts +60 -0
- package/src/op/work-lease-decl.ts +80 -0
- package/src/op/work-lease-run.test.ts +345 -0
- package/src/op/work-lease-run.ts +395 -0
- package/src/workspace/__fixtures__/decide-workspace.ts +129 -0
- package/src/workspace/__fixtures__/studio-template-84c8b21.json +67 -0
- package/src/workspace/answers-ledger.ts +118 -0
- package/src/workspace/box-isolation.test.ts +261 -0
- package/src/workspace/box-isolation.ts +205 -0
- package/src/workspace/changes-cli.ts +56 -0
- package/src/workspace/changes.schema.json +472 -0
- package/src/workspace/changes.test.ts +315 -0
- package/src/workspace/changes.ts +512 -0
- package/src/workspace/chant-migrations/chud-lexicon-exit-fly-site.ts +234 -0
- package/src/workspace/chant-migrations/chud-lexicon-exit-live-names.ts +164 -0
- package/src/workspace/chant-migrations/chud-lexicon-exit-rollback.ts +223 -0
- package/src/workspace/chant-migrations/chud-lexicon-exit-ship-inputs.ts +195 -0
- package/src/workspace/chant-migrations/chud-lexicon-exit-ship-policy.ts +108 -0
- package/src/workspace/chant-migrations/chud-lexicon-exit.ts +882 -0
- package/src/workspace/chant-migrations.e2e.test.ts +1089 -0
- package/src/workspace/chant-migrations.ts +164 -0
- package/src/workspace/check-contract.test.ts +5 -1
- package/src/workspace/check.schema.json +25 -7
- package/src/workspace/checks/box-isolation.ts +68 -0
- package/src/workspace/checks/boxes.test.ts +276 -0
- package/src/workspace/checks/boxes.ts +442 -0
- package/src/workspace/checks/diagrams.test.ts +134 -0
- package/src/workspace/checks/diagrams.ts +108 -0
- package/src/workspace/checks/records.ts +72 -0
- package/src/workspace/checks.test.ts +7 -0
- package/src/workspace/checks.ts +27 -3
- package/src/workspace/compose-graph.ts +2 -0
- package/src/workspace/conformance/__fixture__/decisions/decision.kind.mjs +10 -0
- package/src/workspace/conformance/__fixture__/decisions/decision.schema.json +26 -0
- package/src/workspace/conformance/index.ts +5 -0
- package/src/workspace/decide.test.ts +224 -0
- package/src/workspace/decide.ts +709 -0
- package/src/workspace/decision-points.schema.json +137 -0
- package/src/workspace/declaration.schema.json +285 -0
- package/src/workspace/declaration.test.ts +58 -0
- package/src/workspace/declaration.ts +241 -0
- package/src/workspace/declared-kinds.test.ts +5 -5
- package/src/workspace/declared-kinds.ts +71 -3
- package/src/workspace/generated-files.ts +14 -0
- package/src/workspace/graph-cli.ts +1 -1
- package/src/workspace/graph.schema.json +9 -1
- package/src/workspace/intent.schema.json +12 -3
- package/src/workspace/intent.ts +3 -10
- package/src/workspace/lineage-check.ts +16 -1
- package/src/workspace/lineage-upgrade-cli.ts +1 -1
- package/src/workspace/lineage-upgrade.test.ts +70 -0
- package/src/workspace/lineage-upgrade.ts +81 -10
- package/src/workspace/ls-contract.test.ts +57 -0
- package/src/workspace/ls.schema.json +46 -1
- package/src/workspace/ls.ts +62 -3
- package/src/workspace/point-answer.schema.json +95 -0
- package/src/workspace/points-cli.ts +284 -0
- package/src/workspace/points-write.schema.json +499 -0
- package/src/workspace/points.schema.json +743 -0
- package/src/workspace/points.test.ts +264 -0
- package/src/workspace/points.ts +564 -0
- package/src/workspace/read-contract.test.ts +18 -1
- package/src/workspace/reason-codes.test.ts +24 -1
- package/src/workspace/reason-codes.ts +49 -0
- package/src/workspace/record-assets.test.ts +3 -2
- package/src/workspace/record-assets.ts +4 -1
- package/src/workspace/records-amend.schema.json +2 -0
- package/src/workspace/records-cli.ts +28 -5
- package/src/workspace/records-close.schema.json +2 -0
- package/src/workspace/records-contract.test.ts +3 -2
- package/src/workspace/records-new.schema.json +2 -0
- package/src/workspace/records-review.schema.json +2 -0
- package/src/workspace/records-sessions-write.e2e.test.ts +77 -0
- package/src/workspace/records-sessions-write.test.ts +2 -34
- package/src/workspace/records-write.e2e.test.ts +108 -0
- package/src/workspace/records-write.test.ts +32 -41
- package/src/workspace/records-write.ts +33 -15
- package/src/workspace/records.schema.json +53 -2
- package/src/workspace/records.test.ts +48 -3
- package/src/workspace/records.ts +142 -6
- package/src/workspace/status-contract.test.ts +189 -6
- package/src/workspace/status-gates.ts +9 -4
- package/src/workspace/status-stewards.ts +329 -0
- package/src/workspace/status.schema.json +335 -5
- package/src/workspace/status.ts +142 -6
- package/src/workspace/work-acceptance.test.ts +274 -0
- package/src/workspace/work-cli.e2e.test.ts +180 -0
- package/src/workspace/work-cli.ts +429 -0
- package/src/workspace/work-evidence.schema.json +212 -0
- package/src/workspace/work-evidence.ts +169 -0
- package/src/workspace/work-history.schema.json +349 -0
- package/src/workspace/work-history.test.ts +156 -0
- package/src/workspace/work-lease.schema.json +233 -0
- package/src/workspace/work-readiness-chud.test.ts +145 -0
- package/src/workspace/work.test.ts +3 -2
- package/src/workspace/work.ts +100 -1
- /package/src/cli/{discovery-skip.test.ts → discovery-skip.e2e.test.ts} +0 -0
- /package/src/workspace/conformance/{conformance.test.ts → conformance.e2e.test.ts} +0 -0
- /package/src/workspace/{which-chant.test.ts → which-chant.e2e.test.ts} +0 -0
|
@@ -0,0 +1,164 @@
|
|
|
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
|
+
|
|
31
|
+
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
32
|
+
import { dirname, join } from "node:path";
|
|
33
|
+
import type { Lineage } from "./lineage-lock";
|
|
34
|
+
import { chudLexiconExit } from "./chant-migrations/chud-lexicon-exit";
|
|
35
|
+
import { chudLexiconExitRollback } from "./chant-migrations/chud-lexicon-exit-rollback";
|
|
36
|
+
import { chudLexiconExitShipInputs } from "./chant-migrations/chud-lexicon-exit-ship-inputs";
|
|
37
|
+
import { chudLexiconExitShipPolicy } from "./chant-migrations/chud-lexicon-exit-ship-policy";
|
|
38
|
+
import { chudLexiconExitFlySite } from "./chant-migrations/chud-lexicon-exit-fly-site";
|
|
39
|
+
import { chudLexiconExitLiveNames } from "./chant-migrations/chud-lexicon-exit-live-names";
|
|
40
|
+
|
|
41
|
+
/** One file a plan writes or deletes, relative to the scope. */
|
|
42
|
+
export interface PlannedChange {
|
|
43
|
+
path: string;
|
|
44
|
+
action: "write" | "delete";
|
|
45
|
+
/** Why, in a few words. */
|
|
46
|
+
why: string;
|
|
47
|
+
/** The new content, for a write. */
|
|
48
|
+
data?: Buffer;
|
|
49
|
+
/** True when the file had been edited since the template wrote it; the edit stays in git history. */
|
|
50
|
+
edited?: boolean;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** A part the migration does not move, and where it went instead. */
|
|
54
|
+
export interface NotMoved {
|
|
55
|
+
what: string;
|
|
56
|
+
where: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Why a plan cannot be applied. */
|
|
60
|
+
export interface PlanConflict {
|
|
61
|
+
path: string;
|
|
62
|
+
reason: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface ChantMigrationPlan {
|
|
66
|
+
id: string;
|
|
67
|
+
description: string;
|
|
68
|
+
changes: PlannedChange[];
|
|
69
|
+
notMoved: NotMoved[];
|
|
70
|
+
conflicts: PlanConflict[];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface ChantMigrationContext {
|
|
74
|
+
/** The scope directory (in the staging worktree). */
|
|
75
|
+
dir: string;
|
|
76
|
+
lineage: Lineage;
|
|
77
|
+
/** The chant version doing the upgrade, for dependency ranges. */
|
|
78
|
+
chantVersion: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface ChantMigration {
|
|
82
|
+
id: string;
|
|
83
|
+
description: string;
|
|
84
|
+
/** The plan for this scope, or null when there is nothing here for this migration to move. */
|
|
85
|
+
plan(ctx: ChantMigrationContext): ChantMigrationPlan | null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Every migration chant ships, in the order they are planned. */
|
|
89
|
+
export const CHANT_MIGRATIONS: readonly ChantMigration[] = [
|
|
90
|
+
chudLexiconExit,
|
|
91
|
+
chudLexiconExitRollback,
|
|
92
|
+
chudLexiconExitShipInputs,
|
|
93
|
+
chudLexiconExitShipPolicy,
|
|
94
|
+
chudLexiconExitFlySite,
|
|
95
|
+
chudLexiconExitLiveNames,
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
/** The plans for a scope: each migration not yet in the lineage that finds something to move. */
|
|
99
|
+
export function planChantMigrations(ctx: ChantMigrationContext, migrations: readonly ChantMigration[] = CHANT_MIGRATIONS): ChantMigrationPlan[] {
|
|
100
|
+
const applied = new Set(ctx.lineage.migrations);
|
|
101
|
+
const plans: ChantMigrationPlan[] = [];
|
|
102
|
+
for (const m of migrations) {
|
|
103
|
+
if (applied.has(m.id)) continue;
|
|
104
|
+
const plan = m.plan(ctx);
|
|
105
|
+
if (plan && (plan.changes.length > 0 || plan.conflicts.length > 0)) plans.push(plan);
|
|
106
|
+
}
|
|
107
|
+
return plans;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Plan each migration in order and apply each plan with no conflict before
|
|
112
|
+
* the next is planned, so a migration planned after another sees the tree
|
|
113
|
+
* the earlier one left (#2800: `chud-lexicon-exit-rollback` after
|
|
114
|
+
* `chud-lexicon-exit`). A plan with a conflict is not applied, and the ones
|
|
115
|
+
* after it plan from the tree as it is.
|
|
116
|
+
*/
|
|
117
|
+
export function runChantMigrations(ctx: ChantMigrationContext, migrations: readonly ChantMigration[] = CHANT_MIGRATIONS): Array<{ plan: ChantMigrationPlan; applied: boolean }> {
|
|
118
|
+
const out: Array<{ plan: ChantMigrationPlan; applied: boolean }> = [];
|
|
119
|
+
for (const m of migrations) {
|
|
120
|
+
if (ctx.lineage.migrations.includes(m.id)) continue;
|
|
121
|
+
const plan = m.plan(ctx);
|
|
122
|
+
if (!plan || (plan.changes.length === 0 && plan.conflicts.length === 0)) continue;
|
|
123
|
+
const applied = plan.conflicts.length === 0;
|
|
124
|
+
if (applied) applyChantMigration(plan, ctx.dir, ctx.lineage);
|
|
125
|
+
out.push({ plan, applied });
|
|
126
|
+
}
|
|
127
|
+
return out;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Apply a plan with no conflicts, and record its id in the lineage. */
|
|
131
|
+
export function applyChantMigration(plan: ChantMigrationPlan, dir: string, lineage: Lineage): void {
|
|
132
|
+
if (plan.conflicts.length > 0) throw new Error(`${plan.id} has conflicts and cannot be applied`);
|
|
133
|
+
for (const c of plan.changes) {
|
|
134
|
+
const abs = join(dir, c.path);
|
|
135
|
+
if (c.action === "delete") {
|
|
136
|
+
rmSync(abs, { force: true });
|
|
137
|
+
} else {
|
|
138
|
+
mkdirSync(dirname(abs), { recursive: true });
|
|
139
|
+
writeFileSync(abs, c.data!);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (!lineage.migrations.includes(plan.id)) lineage.migrations.push(plan.id);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** A plan as the upgrade reports it: no file contents. */
|
|
146
|
+
export interface ChantMigrationReport {
|
|
147
|
+
id: string;
|
|
148
|
+
description: string;
|
|
149
|
+
applied: boolean;
|
|
150
|
+
changes: Array<Omit<PlannedChange, "data">>;
|
|
151
|
+
notMoved: NotMoved[];
|
|
152
|
+
conflicts: PlanConflict[];
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function reportPlan(plan: ChantMigrationPlan, applied: boolean): ChantMigrationReport {
|
|
156
|
+
return {
|
|
157
|
+
id: plan.id,
|
|
158
|
+
description: plan.description,
|
|
159
|
+
applied,
|
|
160
|
+
changes: plan.changes.map(({ data: _d, ...rest }) => rest),
|
|
161
|
+
notMoved: plan.notMoved,
|
|
162
|
+
conflicts: plan.conflicts,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
@@ -10,6 +10,9 @@ import { join } from "node:path";
|
|
|
10
10
|
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
|
+
import { BOX_FINDING_CODES } from "./checks/boxes";
|
|
14
|
+
import { RECORD_FINDING_CODES } from "./checks/records";
|
|
15
|
+
import { DIAGRAM_FINDING_CODES } from "./checks/diagrams";
|
|
13
16
|
import { WORKSPACE_ERROR_CODES } from "./declaration";
|
|
14
17
|
import { CHECK_CODES, CHECK_CONTRACT_VERSION, CHECK_ERROR_CODES, CHECK_OUTPUT_SCHEMA_ID, runChecks, type CheckDocument } from "./lineage-check";
|
|
15
18
|
|
|
@@ -35,7 +38,8 @@ describe("check output schema", () => {
|
|
|
35
38
|
test("lists exactly the codes the code can return", () => {
|
|
36
39
|
expect(schema.$defs.lockFinding.properties.code.enum).toEqual([...CHECK_CODES]);
|
|
37
40
|
expect(schema.$defs.failure.properties.error.properties.code.enum).toEqual([...CHECK_ERROR_CODES]);
|
|
38
|
-
expect(schema.$defs.diagnostic.properties.code.enum).toEqual([...WORKSPACE_ERROR_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]);
|
|
39
43
|
});
|
|
40
44
|
});
|
|
41
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,
|
|
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 WSP125 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"
|
|
@@ -210,7 +210,7 @@
|
|
|
210
210
|
},
|
|
211
211
|
"entity": {
|
|
212
212
|
"type": "string",
|
|
213
|
-
"description": "The member or group the finding is about."
|
|
213
|
+
"description": "The member or group the finding is about. For a box check, the box member."
|
|
214
214
|
},
|
|
215
215
|
"code": {
|
|
216
216
|
"enum": [
|
|
@@ -222,9 +222,18 @@
|
|
|
222
222
|
"reader-too-old",
|
|
223
223
|
"root-chant-required",
|
|
224
224
|
"not-a-git-repository",
|
|
225
|
-
"revision-unknown"
|
|
225
|
+
"revision-unknown",
|
|
226
|
+
"box-credential-declared",
|
|
227
|
+
"box-capability-unbrokered",
|
|
228
|
+
"box-isolation-collision",
|
|
229
|
+
"box-isolation-literal",
|
|
230
|
+
"box-fountain-callback-undeclared",
|
|
231
|
+
"work-acceptance-unmet",
|
|
232
|
+
"diagram-source-missing",
|
|
233
|
+
"diagram-render-missing",
|
|
234
|
+
"diagram-render-drift"
|
|
226
235
|
],
|
|
227
|
-
"description": "For WSP001: why the declaration can't be read."
|
|
236
|
+
"description": "For WSP001: why the declaration can't be read. For WSP121 to WSP125, the box finding (#2726, #2727, #2780). For WSP117, work-acceptance-unmet (#2772). For WSP131 to WSP133, the diagram finding (#2764)."
|
|
228
237
|
}
|
|
229
238
|
}
|
|
230
239
|
},
|
|
@@ -277,7 +286,7 @@
|
|
|
277
286
|
},
|
|
278
287
|
"entity": {
|
|
279
288
|
"type": "string",
|
|
280
|
-
"description": "The member or group the finding is about."
|
|
289
|
+
"description": "The member or group the finding is about. For a box check, the box member."
|
|
281
290
|
},
|
|
282
291
|
"code": {
|
|
283
292
|
"enum": [
|
|
@@ -289,9 +298,18 @@
|
|
|
289
298
|
"reader-too-old",
|
|
290
299
|
"root-chant-required",
|
|
291
300
|
"not-a-git-repository",
|
|
292
|
-
"revision-unknown"
|
|
301
|
+
"revision-unknown",
|
|
302
|
+
"box-credential-declared",
|
|
303
|
+
"box-capability-unbrokered",
|
|
304
|
+
"box-isolation-collision",
|
|
305
|
+
"box-isolation-literal",
|
|
306
|
+
"box-fountain-callback-undeclared",
|
|
307
|
+
"work-acceptance-unmet",
|
|
308
|
+
"diagram-source-missing",
|
|
309
|
+
"diagram-render-missing",
|
|
310
|
+
"diagram-render-drift"
|
|
293
311
|
],
|
|
294
|
-
"description": "For WSP001: why the declaration can't be read."
|
|
312
|
+
"description": "For WSP001: why the declaration can't be read. For WSP121 to WSP125, the box finding (#2726, #2727, #2780). For WSP117, work-acceptance-unmet (#2772). For WSP131 to WSP133, the diagram finding (#2764)."
|
|
295
313
|
},
|
|
296
314
|
"reason": {
|
|
297
315
|
"type": "string"
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The box isolation checks, WSP123 and WSP124 (#2727).
|
|
3
|
+
*
|
|
4
|
+
* | Id | Code | Fails when |
|
|
5
|
+
* |---|---|---|
|
|
6
|
+
* | WSP123 | `box-isolation-collision` | two boxes on one host, or two ports in one box, resolve to the same port, state path or cookie name |
|
|
7
|
+
* | WSP124 | `box-isolation-literal` | a host's state root or a box's state entry is a literal machine path |
|
|
8
|
+
*
|
|
9
|
+
* Both are fixed errors. A collision means one box signs another's people out
|
|
10
|
+
* or reads another's identity (arugula-salad/studio#39), and a literal path
|
|
11
|
+
* means the declaration only holds on one machine. Neither has a case where
|
|
12
|
+
* turning the check down is the fix.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { WorkspaceCheck } from "../checks";
|
|
16
|
+
import { boxCollisions, boxLiterals } from "../box-isolation";
|
|
17
|
+
|
|
18
|
+
export const WSP_BOX_COLLISION = "WSP123";
|
|
19
|
+
export const WSP_BOX_LITERAL = "WSP124";
|
|
20
|
+
|
|
21
|
+
const WHAT = { port: "port", state: "state path", cookie: "cookie name" } as const;
|
|
22
|
+
|
|
23
|
+
export const BOX_ISOLATION_CHECKS: readonly WorkspaceCheck[] = [
|
|
24
|
+
{
|
|
25
|
+
id: WSP_BOX_COLLISION,
|
|
26
|
+
name: "box-isolation-collision",
|
|
27
|
+
description: "No two boxes on one host resolve to the same port, state path or cookie name, and no two ports in one box share an offset.",
|
|
28
|
+
severity: "error",
|
|
29
|
+
configurable: false,
|
|
30
|
+
check(ctx) {
|
|
31
|
+
return boxCollisions(ctx.declaration).map((c) => {
|
|
32
|
+
const [first, ...rest] = c.holders;
|
|
33
|
+
const names = c.holders.map((h) => `${h.box}.${h.name}`).join(" and ");
|
|
34
|
+
const hint =
|
|
35
|
+
c.what === "port"
|
|
36
|
+
? new Set(c.holders.map((h) => h.box)).size > 1
|
|
37
|
+
? "; give each box on a host its own slot"
|
|
38
|
+
: "; give each port in a box its own offset"
|
|
39
|
+
: "";
|
|
40
|
+
return {
|
|
41
|
+
checkId: this.id,
|
|
42
|
+
severity: this.severity,
|
|
43
|
+
code: "box-isolation-collision" as const,
|
|
44
|
+
message: `box-isolation-collision: ${names} on host ${c.host} resolve to the same ${WHAT[c.what]} ${c.value}${hint}`,
|
|
45
|
+
entity: rest.find((h) => h.box !== first.box)?.box ?? first.box,
|
|
46
|
+
pointer: (rest[0] ?? first).pointer,
|
|
47
|
+
};
|
|
48
|
+
});
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: WSP_BOX_LITERAL,
|
|
53
|
+
name: "box-isolation-literal",
|
|
54
|
+
description: "No host's state root and no box's state entry is a literal machine path: state paths derive from an environment reference and the member's name.",
|
|
55
|
+
severity: "error",
|
|
56
|
+
configurable: false,
|
|
57
|
+
check(ctx) {
|
|
58
|
+
return boxLiterals(ctx.declaration).map((l) => ({
|
|
59
|
+
checkId: this.id,
|
|
60
|
+
severity: this.severity,
|
|
61
|
+
code: "box-isolation-literal" as const,
|
|
62
|
+
message: `box-isolation-literal: ${l.message}`,
|
|
63
|
+
...(l.box !== null ? { entity: l.box } : {}),
|
|
64
|
+
pointer: l.pointer,
|
|
65
|
+
}));
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
];
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import * as ts from "typescript";
|
|
6
|
+
import { afterAll, describe, expect, test } from "vitest";
|
|
7
|
+
import { runDeclarationChecks } from "../checks";
|
|
8
|
+
import { parseDeclaration, WorkspaceReadError } from "../declaration";
|
|
9
|
+
import { gitTree } from "../tree";
|
|
10
|
+
import { fountainBoxCall, isCredentialKey, isSecretReference, literalSecretsInCode, literalSecretsInText } from "./boxes";
|
|
11
|
+
|
|
12
|
+
const scratch: string[] = [];
|
|
13
|
+
afterAll(() => {
|
|
14
|
+
for (const d of scratch) rmSync(d, { recursive: true, force: true });
|
|
15
|
+
});
|
|
16
|
+
function repo(files: Record<string, string>): string {
|
|
17
|
+
const root = realpathSync(mkdtempSync(join(tmpdir(), "chant-box-")));
|
|
18
|
+
scratch.push(root);
|
|
19
|
+
execFileSync("git", ["init", "-q"], { cwd: root });
|
|
20
|
+
for (const [path, text] of Object.entries(files)) {
|
|
21
|
+
mkdirSync(dirname(join(root, path)), { recursive: true });
|
|
22
|
+
writeFileSync(join(root, path), text);
|
|
23
|
+
}
|
|
24
|
+
return root;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Built at run time so no credential-shaped literal sits in this file.
|
|
28
|
+
const ANTHROPIC_KEY = ["sk", "ant", "api03", "A".repeat(40)].join("-");
|
|
29
|
+
const GITHUB_TOKEN = ["ghp", "B".repeat(36)].join("_");
|
|
30
|
+
|
|
31
|
+
const BROKERED = { capabilities: [{ name: "inference", broker: "lobby", scope: ["agent", "vault", "conversations", "sandboxes"] }] };
|
|
32
|
+
|
|
33
|
+
const declaration = (box: unknown, extra: Record<string, unknown> = {}) =>
|
|
34
|
+
JSON.stringify({ name: "acme", schema: 1, members: [{ name: "spec", dir: "spec", kind: "other", because: "the box's declarations", box, ...extra }] }, null, 2);
|
|
35
|
+
|
|
36
|
+
/** A chaff-style box declaration: a service whose env has `apiKey`. */
|
|
37
|
+
const spec = (apiKey: string) => `import { Service, Capability } from "@intentius/chant-lexicon-chaff";
|
|
38
|
+
|
|
39
|
+
export const app = new Service({
|
|
40
|
+
start: ["node", "app/server.mjs"],
|
|
41
|
+
env: {
|
|
42
|
+
HUD_SRC: "\${HOME}/alecraso/hud-live",
|
|
43
|
+
ANTHROPIC_API_KEY: ${JSON.stringify(apiKey)},
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
export const inference = new Capability({ provider: "brokered", credential: "$INFERENCE_TOKEN", endpoint: "$INFERENCE_URL" });
|
|
48
|
+
`;
|
|
49
|
+
|
|
50
|
+
/** The findings, less WSP009, which every member of kind other gets. */
|
|
51
|
+
const found = async (root: string) => (await runDeclarationChecks(root, (f) => f, { gather: false })).diagnostics.filter((d) => d.ruleId !== "WSP009");
|
|
52
|
+
|
|
53
|
+
describe("box-credential-declared (WSP121)", () => {
|
|
54
|
+
test("a literal API key in a service's env fails the box", async () => {
|
|
55
|
+
const root = repo({ "chant.workspace.json": declaration(BROKERED), "spec/box.ts": spec(ANTHROPIC_KEY) });
|
|
56
|
+
const d = await found(root);
|
|
57
|
+
expect(d.map((x) => [x.ruleId, x.code, x.entity, x.file, x.line])).toEqual([["WSP121", "box-credential-declared", "spec", "spec/box.ts", 7]]);
|
|
58
|
+
expect(d[0].message).toContain("an Anthropic API key");
|
|
59
|
+
// The message names what was found, never the value.
|
|
60
|
+
expect(d[0].message).not.toContain(ANTHROPIC_KEY);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("the same box with the key as a reference and the capability brokered passes", async () => {
|
|
64
|
+
for (const ref of ["${ANTHROPIC_API_KEY}", "$ANTHROPIC_API_KEY", "op://box/anthropic/key", "bws://0f5b/anthropic", "infisical:///dev/ANTHROPIC_API_KEY"]) {
|
|
65
|
+
const root = repo({ "chant.workspace.json": declaration(BROKERED), "spec/box.ts": spec(ref) });
|
|
66
|
+
expect(await found(root), ref).toEqual([]);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("a literal of no known shape under a credential key fails, and so does a vault secret's literal value", async () => {
|
|
71
|
+
const root = repo({
|
|
72
|
+
"chant.workspace.json": declaration(BROKERED),
|
|
73
|
+
"spec/box.ts": `export const box = Box({
|
|
74
|
+
envVars: { GITHUB_TOKEN: "hunter2hunter2" },
|
|
75
|
+
vault: { secrets: [{ key: "STUDIO_SECRET", value: "plain-text" }, { key: "OK", value: "\${STUDIO_SECRET}" }] },
|
|
76
|
+
repo: { url: "https://github.com/x/y", secretKey: "GIT_TOKEN" },
|
|
77
|
+
names: { credential: "INFERENCE_TOKEN", tokenPath: "/run/secrets/t", token: "~/box/llm-token", endpoint: { apiKey: "https://broker.local/key" } },
|
|
78
|
+
process: { token: process.env.TOKEN! },
|
|
79
|
+
});
|
|
80
|
+
`,
|
|
81
|
+
});
|
|
82
|
+
const d = await found(root);
|
|
83
|
+
expect(d.map((x) => `${x.line}: ${x.message.split(" holds ")[1].split(", and")[0]}`)).toEqual([
|
|
84
|
+
"2: a literal value for GITHUB_TOKEN",
|
|
85
|
+
"3: a literal vault secret value for STUDIO_SECRET",
|
|
86
|
+
]);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("an env file and a shell script in the box's directory are read too; prose only for credential shapes", async () => {
|
|
90
|
+
const root = repo({
|
|
91
|
+
"chant.workspace.json": declaration(BROKERED),
|
|
92
|
+
"spec/.env": `FOUNTAIN_API_KEY=abc123def456\nPORT=8080\nGIT_TOKEN=\${CHUD_GIT_TOKEN}\n`,
|
|
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
|
+
"spec/README.md": `Token: rotate it monthly.\n`,
|
|
95
|
+
"spec/node_modules/pkg/index.js": `export const k = ${JSON.stringify(ANTHROPIC_KEY)};\n`,
|
|
96
|
+
});
|
|
97
|
+
const d = await found(root);
|
|
98
|
+
expect(d.map((x) => `${x.file}:${x.line}`)).toEqual(["spec/.env:1", "spec/run.sh:2"]);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("a member without a box block is not read, and a box's finding can't be suppressed", async () => {
|
|
102
|
+
const plain = repo({
|
|
103
|
+
"chant.workspace.json": JSON.stringify({ name: "acme", schema: 1, members: [{ name: "spec", dir: "spec", kind: "other", because: "x" }] }),
|
|
104
|
+
"spec/box.ts": spec(ANTHROPIC_KEY),
|
|
105
|
+
});
|
|
106
|
+
expect(await found(plain)).toEqual([]);
|
|
107
|
+
const suppressed = repo({
|
|
108
|
+
"chant.workspace.json": declaration(BROKERED, { suppress: [{ check: "WSP121", because: "a test key" }] }),
|
|
109
|
+
"spec/box.ts": spec(ANTHROPIC_KEY),
|
|
110
|
+
});
|
|
111
|
+
expect((await found(suppressed)).map((x) => x.ruleId).sort()).toEqual(["WSP011", "WSP121"]);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("under --at the revision's files are read", async () => {
|
|
115
|
+
const root = repo({ "chant.workspace.json": declaration(BROKERED), "spec/box.ts": spec(ANTHROPIC_KEY) });
|
|
116
|
+
execFileSync("git", ["add", "-A"], { cwd: root });
|
|
117
|
+
execFileSync("git", ["-c", "user.email=t@t", "-c", "user.name=t", "commit", "-qm", "box"], { cwd: root });
|
|
118
|
+
const commit = execFileSync("git", ["rev-parse", "HEAD"], { cwd: root, encoding: "utf8" }).trim();
|
|
119
|
+
writeFileSync(join(root, "spec/box.ts"), spec("${ANTHROPIC_API_KEY}"));
|
|
120
|
+
expect(await found(root)).toEqual([]);
|
|
121
|
+
const at = await runDeclarationChecks(root, (f) => f, { tree: gitTree(root, commit) });
|
|
122
|
+
expect(at.diagnostics.map((x) => x.ruleId).filter((id) => id !== "WSP009")).toEqual(["WSP121"]);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
describe("box-capability-unbrokered (WSP122)", () => {
|
|
127
|
+
test("a capability with no broker fails, at the capability", async () => {
|
|
128
|
+
const root = repo({
|
|
129
|
+
"chant.workspace.json": declaration({ capabilities: [{ name: "fountain", broker: "lobby", scope: ["agent"] }, { name: "inference" }] }),
|
|
130
|
+
"spec/box.ts": spec("${ANTHROPIC_API_KEY}"),
|
|
131
|
+
});
|
|
132
|
+
const d = await found(root);
|
|
133
|
+
expect(d.map((x) => [x.ruleId, x.code, x.entity])).toEqual([["WSP122", "box-capability-unbrokered", "spec"]]);
|
|
134
|
+
expect(d[0].message).toContain("needs inference and names no broker");
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("it can be turned down, since a box may be moving to a broker", async () => {
|
|
138
|
+
const root = repo({
|
|
139
|
+
"chant.workspace.json": JSON.stringify({
|
|
140
|
+
name: "acme",
|
|
141
|
+
schema: 1,
|
|
142
|
+
checks: { WSP122: "warning" },
|
|
143
|
+
members: [{ name: "spec", dir: "spec", kind: "other", because: "x", box: { capabilities: [{ name: "inference" }] } }],
|
|
144
|
+
}),
|
|
145
|
+
"spec/box.ts": "",
|
|
146
|
+
});
|
|
147
|
+
const report = await runDeclarationChecks(root, (f) => f, { gather: false });
|
|
148
|
+
expect(report.ok).toBe(true);
|
|
149
|
+
expect(report.diagnostics.filter((x) => x.ruleId !== "WSP009").map((x) => `${x.ruleId}:${x.severity}`)).toEqual(["WSP122:warning"]);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe("the box block in the declaration", () => {
|
|
154
|
+
test("parses, with the broker null and the scope empty when left out", () => {
|
|
155
|
+
const d = parseDeclaration(declaration({ capabilities: [{ name: "fountain", broker: "lobby", scope: ["agent", "vault"] }, { name: "inference" }] }), "chant.workspace.json");
|
|
156
|
+
expect(d.members[0].box).toEqual({
|
|
157
|
+
pointer: "/members/0/box",
|
|
158
|
+
isolation: null,
|
|
159
|
+
capabilities: [
|
|
160
|
+
{ name: "fountain", broker: "lobby", scope: ["agent", "vault"], pointer: "/members/0/box/capabilities/0" },
|
|
161
|
+
{ name: "inference", broker: null, scope: [], pointer: "/members/0/box/capabilities/1" },
|
|
162
|
+
],
|
|
163
|
+
});
|
|
164
|
+
expect(parseDeclaration(JSON.stringify({ name: "a", schema: 1, members: [{ name: "m", dir: "m", kind: "chant" }] }), "chant.workspace.json").members[0].box).toBeNull();
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test("a capability named twice, or an unknown field, is declaration-invalid", () => {
|
|
168
|
+
const twice = () => parseDeclaration(declaration({ capabilities: [{ name: "fountain", broker: "lobby" }, { name: "fountain", broker: "door" }] }), "chant.workspace.json");
|
|
169
|
+
expect(twice).toThrow(WorkspaceReadError);
|
|
170
|
+
expect(twice).toThrow(/lists the capability fountain twice/);
|
|
171
|
+
expect(() => parseDeclaration(declaration({ capabilities: [{ name: "fountain", credential: "x" }] }), "chant.workspace.json")).toThrow(/unknown field "credential"/);
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
describe("what counts as a literal secret", () => {
|
|
176
|
+
test("references are not secrets", () => {
|
|
177
|
+
for (const v of ["${X}", "${HOME}/.config", "$X", "$1", "$(cat f)", "op://v/i/f", "bws://id", "infisical:///dev/X", "{{chant:name}}", ""]) {
|
|
178
|
+
expect(isSecretReference(v), v).toBe(true);
|
|
179
|
+
}
|
|
180
|
+
for (const v of ["hunter2", "abc${", "sk-live"]) expect(isSecretReference(v), v).toBe(v === "abc${");
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test("keys that name a credential, and keys that name where one is", () => {
|
|
184
|
+
for (const k of ["API_KEY", "ANTHROPIC_API_KEY", "GITHUB_TOKEN", "apiKey", "token", "credential", "password", "AWS_SECRET_ACCESS_KEY", "authToken", "clientSecret"]) {
|
|
185
|
+
expect(isCredentialKey(k), k).toBe(true);
|
|
186
|
+
}
|
|
187
|
+
for (const k of ["secretKey", "secret_key", "TOKEN_FILE", "tokenHash", "HUD_IDENTITY_PATH", "CHAFF_AGENT_VAULT", "key", "value", "maxTokens"]) {
|
|
188
|
+
expect(isCredentialKey(k), k).toBe(false);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test("a credential shape is found anywhere in code, and not in a template with a substitution", () => {
|
|
193
|
+
const code = `const a = [${JSON.stringify(GITHUB_TOKEN)}];\nconst b = \`\${prefix}${"x".repeat(3)}\`;\n`;
|
|
194
|
+
expect(literalSecretsInCode(code, "a.ts", ts.ScriptKind.TS).map((s) => [s.line, s.what])).toEqual([[1, "a GitHub token"]]);
|
|
195
|
+
expect(literalSecretsInText(`# nothing\nkey: ${GITHUB_TOKEN}\n`).map((s) => s.line)).toEqual([2]);
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
describe("box-fountain-callback-undeclared (WSP125, #2780)", () => {
|
|
200
|
+
/** A studio-style fountain Box declaration. */
|
|
201
|
+
const fountainBox = `import { readFileSync } from "node:fs";
|
|
202
|
+
import { Box } from "@intentius/chant-lexicon-fountain";
|
|
203
|
+
|
|
204
|
+
export const { environment, agent } = Box({
|
|
205
|
+
name: "studio-box",
|
|
206
|
+
setupScript: readFileSync("box/setup.sh", "utf8"),
|
|
207
|
+
model: "anthropic/claude-sonnet-4-6",
|
|
208
|
+
permissionPolicy: { default: "auto_allow" },
|
|
209
|
+
});
|
|
210
|
+
`;
|
|
211
|
+
const CALLBACK = { name: "fountain-callback", broker: "fountain", scope: ["owner"] };
|
|
212
|
+
|
|
213
|
+
test("a box member that builds a fountain Box and does not declare the callback token fails, at the Box call", async () => {
|
|
214
|
+
const root = repo({ "chant.workspace.json": declaration(BROKERED), "spec/box.ts": fountainBox });
|
|
215
|
+
const d = await found(root);
|
|
216
|
+
expect(d.map((x) => [x.ruleId, x.code, x.entity, x.file, x.line])).toEqual([
|
|
217
|
+
["WSP125", "box-fountain-callback-undeclared", "spec", "spec/box.ts", 4],
|
|
218
|
+
]);
|
|
219
|
+
expect(d[0].message).toContain("callback token scoped to its owner (FOUNTAIN_TOKEN); the member's box block does not declare it");
|
|
220
|
+
expect(d[0].message).toContain('{ "name": "fountain-callback", "broker": "fountain", "scope": ["owner"] }');
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
test("declaring fountain-callback, brokered by fountain with scope owner, passes, beside a fountain capability a lobby brokers", async () => {
|
|
224
|
+
const root = repo({
|
|
225
|
+
"chant.workspace.json": declaration({
|
|
226
|
+
capabilities: [...BROKERED.capabilities, { name: "fountain", broker: "lobby", scope: ["agent", "conversations"] }, CALLBACK],
|
|
227
|
+
}),
|
|
228
|
+
"spec/box.ts": fountainBox,
|
|
229
|
+
});
|
|
230
|
+
expect(await found(root)).toEqual([]);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
test("fountain-callback behind another broker, or without owner in its scope, fails", async () => {
|
|
234
|
+
const lobby = repo({
|
|
235
|
+
"chant.workspace.json": declaration({ capabilities: [{ name: "fountain-callback", broker: "lobby", scope: ["conversations"] }] }),
|
|
236
|
+
"spec/box.ts": fountainBox,
|
|
237
|
+
});
|
|
238
|
+
const d = await found(lobby);
|
|
239
|
+
expect(d.map((x) => [x.ruleId, x.file])).toEqual([["WSP125", "spec/box.ts"]]);
|
|
240
|
+
expect(d[0].message).toContain("names the broker lobby, but fountain hands this token to the sandbox itself");
|
|
241
|
+
const narrow = repo({
|
|
242
|
+
"chant.workspace.json": declaration({ capabilities: [{ name: "fountain-callback", broker: "fountain", scope: ["conversations"] }] }),
|
|
243
|
+
"spec/box.ts": fountainBox,
|
|
244
|
+
});
|
|
245
|
+
expect((await found(narrow))[0].message).toContain("has scope [conversations], not owner");
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
test("a box member that builds no fountain Box, and a member with no box block, are not flagged", async () => {
|
|
249
|
+
const other = repo({ "chant.workspace.json": declaration(BROKERED), "spec/box.ts": spec("${ANTHROPIC_API_KEY}") });
|
|
250
|
+
expect(await found(other)).toEqual([]);
|
|
251
|
+
const plain = repo({
|
|
252
|
+
"chant.workspace.json": JSON.stringify({ name: "acme", schema: 1, members: [{ name: "spec", dir: "spec", kind: "other", because: "x" }] }),
|
|
253
|
+
"spec/box.ts": fountainBox,
|
|
254
|
+
});
|
|
255
|
+
expect(await found(plain)).toEqual([]);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
test("it can be turned down while fountain has no way to drop the token (managoat/fountain#2497)", async () => {
|
|
259
|
+
const root = repo({
|
|
260
|
+
"chant.workspace.json": declaration(BROKERED, { suppress: [{ check: "WSP125", because: "waiting on fountain" }] }),
|
|
261
|
+
"spec/box.ts": fountainBox,
|
|
262
|
+
});
|
|
263
|
+
expect((await found(root)).filter((x) => x.ruleId === "WSP125")).toEqual([]);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
test("fountainBoxCall finds the composite imported by name, aliased or through a namespace, and nothing else", () => {
|
|
267
|
+
const at = (text: string) => fountainBoxCall(text, "box.ts", ts.ScriptKind.TS);
|
|
268
|
+
expect(at(fountainBox)).toEqual({ line: 4, column: 39 });
|
|
269
|
+
expect(at(`import { Box as FBox } from "@intentius/chant-lexicon-fountain";\nFBox({});\n`)).toEqual({ line: 2, column: 1 });
|
|
270
|
+
expect(at(`import * as fountain from "@intentius/chant-lexicon-fountain";\nfountain.Box({});\n`)).toEqual({ line: 2, column: 1 });
|
|
271
|
+
// Imported but never called, another package's Box, or a Box of one's own.
|
|
272
|
+
expect(at(`import { Box } from "@intentius/chant-lexicon-fountain";\nexport { Box };\n`)).toBeUndefined();
|
|
273
|
+
expect(at(`import { Box } from "@intentius/chant-lexicon-chaff";\nBox({});\n`)).toBeUndefined();
|
|
274
|
+
expect(at(`const Box = (o: object) => o;\nBox({}); // @intentius/chant-lexicon-fountain\n`)).toBeUndefined();
|
|
275
|
+
});
|
|
276
|
+
});
|