@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,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SYS010: a `decide` backend sends its key over plain HTTP (#2740, core's
|
|
3
|
+
* since #2828).
|
|
4
|
+
*
|
|
5
|
+
* A backend in `decide.backends` in chant.config, or in a `decide` step's own
|
|
6
|
+
* `backends`, may have a key and an `http://` URL. The bearer key then crosses
|
|
7
|
+
* the network in clear text. A loopback address (127.0.0.1, ::1, localhost)
|
|
8
|
+
* is exempt, since that is where a local server such as `von serve` or the
|
|
9
|
+
* stub runs, and where a broker that is itself the endpoint listens.
|
|
10
|
+
*
|
|
11
|
+
* The steps are read off the Op entities, which covers a step written with
|
|
12
|
+
* the `decide` builder and one written with `activity("decide", ...)`. The
|
|
13
|
+
* configured backends are not entities: `chant build` and `chant lint` read
|
|
14
|
+
* them from the project's config and hand them to {@link sys010Check}. The id
|
|
15
|
+
* keeps the SYS prefix it had in the systemone lexicon.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "../../post-synth";
|
|
19
|
+
import type { OpConfig, PhaseDefinition, ActivityStep } from "../../../op/types";
|
|
20
|
+
import type { DecideBackend } from "../../../op/decide-config";
|
|
21
|
+
import { isOpEntity } from "./support";
|
|
22
|
+
|
|
23
|
+
const LOOPBACK = new Set(["127.0.0.1", "localhost", "[::1]", "::1"]);
|
|
24
|
+
|
|
25
|
+
function* decideSteps(phases: PhaseDefinition[] | undefined): Generator<ActivityStep> {
|
|
26
|
+
for (const phase of phases ?? []) {
|
|
27
|
+
for (const step of phase.steps ?? []) {
|
|
28
|
+
if (step.kind === "activity" && step.fn === "decide") yield step;
|
|
29
|
+
else if (step.kind === "effect") {
|
|
30
|
+
for (const nested of step.steps ?? []) if (nested.kind === "activity" && nested.fn === "decide") yield nested;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** The host of an http:// URL that is not loopback, or undefined. */
|
|
37
|
+
export function plainRemoteHost(url: unknown): string | undefined {
|
|
38
|
+
if (typeof url !== "string") return undefined;
|
|
39
|
+
let parsed: URL;
|
|
40
|
+
try {
|
|
41
|
+
parsed = new URL(url);
|
|
42
|
+
} catch {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
if (parsed.protocol !== "http:") return undefined;
|
|
46
|
+
return LOOPBACK.has(parsed.hostname) ? undefined : parsed.hostname;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const DESCRIPTION = "A decide backend sends its key over plain HTTP to a host that is not loopback";
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* SYS010 over the Op entities and, when given, the backends configured in
|
|
53
|
+
* `decide.backends`. A configured backend's diagnostic names it as the entity
|
|
54
|
+
* `decide.backends.<name>`.
|
|
55
|
+
*/
|
|
56
|
+
export function sys010Check(configured?: Record<string, Pick<DecideBackend, "url" | "key">>): PostSynthCheck {
|
|
57
|
+
return {
|
|
58
|
+
id: "SYS010",
|
|
59
|
+
description: DESCRIPTION,
|
|
60
|
+
|
|
61
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
62
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
63
|
+
for (const [name, b] of Object.entries(configured ?? {})) {
|
|
64
|
+
const host = b?.key !== undefined ? plainRemoteHost(b.url) : undefined;
|
|
65
|
+
if (!host) continue;
|
|
66
|
+
diagnostics.push({
|
|
67
|
+
checkId: "SYS010",
|
|
68
|
+
severity: "error",
|
|
69
|
+
message: `decide.backends.${name} sends its key over http:// to ${host}; use https://, or a loopback broker`,
|
|
70
|
+
entity: `decide.backends.${name}`,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
for (const [entityKey, entity] of ctx.entities) {
|
|
74
|
+
if (!isOpEntity(entity)) continue;
|
|
75
|
+
const rec = entity as unknown as Record<string, unknown>;
|
|
76
|
+
const props = ((entity as { props?: Record<string, unknown> }).props ?? {}) as unknown as OpConfig;
|
|
77
|
+
for (const step of [...decideSteps(props.phases), ...decideSteps(props.onFailure)]) {
|
|
78
|
+
const backends = (step.args as { backends?: unknown } | undefined)?.backends;
|
|
79
|
+
if (backends === null || typeof backends !== "object") continue;
|
|
80
|
+
for (const [name, b] of Object.entries(backends as Record<string, { url?: unknown; key?: unknown }>)) {
|
|
81
|
+
if (b?.key === undefined) continue;
|
|
82
|
+
const host = plainRemoteHost(b.url);
|
|
83
|
+
if (!host) continue;
|
|
84
|
+
diagnostics.push({
|
|
85
|
+
checkId: "SYS010",
|
|
86
|
+
severity: "error",
|
|
87
|
+
message: `Op "${props.name}", decide ${JSON.stringify((step.args as { point?: unknown }).point)}: backend ${name} sends its key over http:// to ${host}; use https://, or a loopback broker`,
|
|
88
|
+
entity: entityKey,
|
|
89
|
+
lexicon: typeof rec.lexicon === "string" ? rec.lexicon : undefined,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return diagnostics;
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** SYS010 over the Op entities alone, with no configured backends. */
|
|
100
|
+
export const sys010: PostSynthCheck = sys010Check();
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import ts from "typescript";
|
|
3
|
+
import { noLiteralKeyRule } from "./sys001-no-literal-key";
|
|
4
|
+
import { loadCoreRules } from "./index";
|
|
5
|
+
|
|
6
|
+
const run = (code: string) =>
|
|
7
|
+
noLiteralKeyRule.check({ sourceFile: ts.createSourceFile("x.ts", code, ts.ScriptTarget.Latest, true), filePath: "x.ts", entities: [] } as never);
|
|
8
|
+
|
|
9
|
+
describe("SYS001", () => {
|
|
10
|
+
it("is one of core's rules", () => {
|
|
11
|
+
expect(loadCoreRules().map((r) => r.id)).toContain("SYS001");
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("fires on a literal key in the config's decide block", () => {
|
|
15
|
+
const d = run(`export default { decide: { backends: { s: { url: "https://x", key: "sk-abc" } } } };`);
|
|
16
|
+
expect(d.map((x) => x.ruleId)).toEqual(["SYS001"]);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("fires on a literal key in a decide step's backends", () => {
|
|
20
|
+
expect(run('decide("p", { backends: { s: { url: "https://x", key: `k` } } });')).toHaveLength(1);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("is quiet for { env } and a brokered capability", () => {
|
|
24
|
+
expect(run(`export default { decide: { backends: { s: { url: "https://x", key: { env: "K" } }, b: { url: "http://127.0.0.1", key: { capability: "inference" } } } } };`)).toEqual([]);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("is quiet for a key property outside decide, and for another tool's backends", () => {
|
|
28
|
+
expect(run(`const x = { key: "not a backend" };`)).toEqual([]);
|
|
29
|
+
expect(run(`const tf = { backends: { s3: { bucket: "b", key: "state/terraform.tfstate" } } };`)).toEqual([]);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import type { LintRule, LintDiagnostic, LintContext } from "../rule";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* SYS001: a `decide` backend's key is never a literal (#2740, core's since
|
|
6
|
+
* #2828).
|
|
7
|
+
*
|
|
8
|
+
* A key is `{ env: "VARIABLE" }` or a brokered capability
|
|
9
|
+
* (`{ capability: "inference", member: "box" }`, #2726). A string written
|
|
10
|
+
* where a key goes is a credential in git history, and a box that holds one
|
|
11
|
+
* fails `workspace check` with WSP121 as well.
|
|
12
|
+
*
|
|
13
|
+
* Fires on a `key` property whose value is a string or template literal,
|
|
14
|
+
* inside a backend of a `backends` object that belongs to `decide`: the
|
|
15
|
+
* `decide` block of a config object (`decide: { backends: ... }`) or the
|
|
16
|
+
* options of a `decide(...)` step. A `backends` object anywhere else, such as
|
|
17
|
+
* a Terraform backend's `key`, is not a decide backend and is left alone.
|
|
18
|
+
* The id keeps the SYS prefix it had in the systemone lexicon.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
function keyText(name: ts.PropertyName): string | undefined {
|
|
22
|
+
if (ts.isIdentifier(name) || ts.isStringLiteral(name)) return name.text;
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const isLiteral = (node: ts.Expression): boolean => ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node) || ts.isTemplateExpression(node);
|
|
27
|
+
|
|
28
|
+
const isDecideCall = (node: ts.Node): node is ts.CallExpression =>
|
|
29
|
+
ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === "decide";
|
|
30
|
+
|
|
31
|
+
/** Where the walk is: outside decide, inside decide's options or config block, or inside its `backends`. */
|
|
32
|
+
type Scope = "outside" | "decide" | "backends";
|
|
33
|
+
|
|
34
|
+
export const noLiteralKeyRule: LintRule = {
|
|
35
|
+
id: "SYS001",
|
|
36
|
+
severity: "error",
|
|
37
|
+
category: "security",
|
|
38
|
+
description: "A decide backend key is { env } or a brokered capability, never a literal string",
|
|
39
|
+
|
|
40
|
+
check(context: LintContext): LintDiagnostic[] {
|
|
41
|
+
const diagnostics: LintDiagnostic[] = [];
|
|
42
|
+
const source = context.sourceFile;
|
|
43
|
+
const visit = (node: ts.Node, scope: Scope) => {
|
|
44
|
+
let within = scope;
|
|
45
|
+
if (isDecideCall(node)) within = "decide";
|
|
46
|
+
else if (ts.isPropertyAssignment(node)) {
|
|
47
|
+
const key = keyText(node.name);
|
|
48
|
+
if (key === "decide") within = "decide";
|
|
49
|
+
else if (key === "backends" && scope === "decide") within = "backends";
|
|
50
|
+
if (scope === "backends" && key === "key" && isLiteral(node.initializer)) {
|
|
51
|
+
const { line, character } = source.getLineAndCharacterOfPosition(node.initializer.getStart(source));
|
|
52
|
+
diagnostics.push({
|
|
53
|
+
ruleId: "SYS001",
|
|
54
|
+
severity: "error",
|
|
55
|
+
message: 'Literal backend key: use { env: "VARIABLE" } or a brokered capability ({ capability, member }) instead',
|
|
56
|
+
file: context.filePath,
|
|
57
|
+
line: line + 1,
|
|
58
|
+
column: character + 1,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
ts.forEachChild(node, (child) => visit(child, within));
|
|
63
|
+
};
|
|
64
|
+
visit(source, "outside");
|
|
65
|
+
return diagnostics;
|
|
66
|
+
},
|
|
67
|
+
};
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A stub `POST /v1/systemone` server for tests of the `decide` activity
|
|
3
|
+
* (`@intentius/chant/op/__fixtures__/decide-stub-backend`): a real HTTP server on
|
|
4
|
+
* 127.0.0.1 that checks a request the way a Jev-compatible server would and
|
|
5
|
+
* answers with what the test scripts. No model runs.
|
|
6
|
+
*
|
|
7
|
+
* ```ts
|
|
8
|
+
* const stub = await startStubBackend({ key: "k", answers: { slice_tier: { type: "choice", choice: "large", probabilities: {...}, confidence: 0.9 } } });
|
|
9
|
+
* // decide.backends, or a decide step's backends: { systemone: { url: stub.url, key: { env: "STUB_KEY" } } }
|
|
10
|
+
* await stub.close();
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* It answers 401 when a key is set and the request's bearer differs, 422 when
|
|
14
|
+
* the body is not a request in the wire format, and `status` when the test
|
|
15
|
+
* forces one (429 or 529, say). Every request it read is kept in `requests`.
|
|
16
|
+
*
|
|
17
|
+
* It lives under `__fixtures__` because it listens on a port, and no module
|
|
18
|
+
* chant runs does (#2657, `test/no-listener.test.ts`): only tests start it.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { createServer, type IncomingMessage, type ServerResponse } from "node:http";
|
|
22
|
+
import type { AddressInfo } from "node:net";
|
|
23
|
+
import type { WireAnswer } from "../../workspace/points";
|
|
24
|
+
import { SYSTEMONE_PATH, type BackendRequest } from "../decide-backend";
|
|
25
|
+
|
|
26
|
+
export interface StubQuestion {
|
|
27
|
+
name: string;
|
|
28
|
+
type: string;
|
|
29
|
+
instructions: string;
|
|
30
|
+
criteria: Record<string, string> | string[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface StubBackendOptions {
|
|
34
|
+
/** The bearer key the stub requires. Without it, any request is taken. */
|
|
35
|
+
key?: string;
|
|
36
|
+
/** The model id the response reports. Defaults to the id the request sent. */
|
|
37
|
+
model?: string;
|
|
38
|
+
/** The answer to a question, by question name, or computed from the question. Defaults to {@link defaultAnswer}. */
|
|
39
|
+
answers?: Record<string, WireAnswer> | ((question: StubQuestion, request: BackendRequest) => WireAnswer);
|
|
40
|
+
/** Answer every request with this status and no answers. */
|
|
41
|
+
status?: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface StubBackend {
|
|
45
|
+
/** The base URL, without `/v1/systemone`. */
|
|
46
|
+
url: string;
|
|
47
|
+
/** Each request body the stub read, in order. */
|
|
48
|
+
requests: BackendRequest[];
|
|
49
|
+
/** The Authorization header of each request, or null. */
|
|
50
|
+
authorizations: (string | null)[];
|
|
51
|
+
close(): Promise<void>;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** A confident answer of the question's own type: true, the first option, or the last level. */
|
|
55
|
+
export function defaultAnswer(question: StubQuestion): WireAnswer {
|
|
56
|
+
if (question.type === "noul") return { type: "noul", noul: 0.95 };
|
|
57
|
+
if (question.type === "choice") {
|
|
58
|
+
const options = Object.keys(question.criteria);
|
|
59
|
+
const rest = options.length > 1 ? 0.05 / (options.length - 1) : 0;
|
|
60
|
+
const probabilities = Object.fromEntries(options.map((o, i) => [o, i === 0 ? 0.95 : rest]));
|
|
61
|
+
return { type: "choice", choice: options[0], probabilities, confidence: 0.925 };
|
|
62
|
+
}
|
|
63
|
+
if (question.type === "score") {
|
|
64
|
+
const levels = question.criteria as string[];
|
|
65
|
+
const probabilities = Object.fromEntries(levels.map((l, i) => [l, i === levels.length - 1 ? 0.95 : 0.05 / Math.max(1, levels.length - 1)]));
|
|
66
|
+
return { type: "score", score: levels.length - 0.05, probabilities, confidence: 0.925 };
|
|
67
|
+
}
|
|
68
|
+
return { type: "unsupported" };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Why a body is not a request in the wire format, or null when it is one. */
|
|
72
|
+
export function requestProblem(body: unknown): string | null {
|
|
73
|
+
if (body === null || typeof body !== "object" || Array.isArray(body)) return "the body is not a JSON object";
|
|
74
|
+
const b = body as Record<string, unknown>;
|
|
75
|
+
if (typeof b.model !== "string" || b.model === "") return "model is required";
|
|
76
|
+
if (b.state === undefined) return "state is required";
|
|
77
|
+
if (b.questions === null || typeof b.questions !== "object" || Array.isArray(b.questions) || Object.keys(b.questions).length === 0) return "questions must name at least one question";
|
|
78
|
+
for (const [name, q] of Object.entries(b.questions as Record<string, unknown>)) {
|
|
79
|
+
const question = q as Record<string, unknown> | null;
|
|
80
|
+
if (question === null || typeof question !== "object") return `questions.${name} is not an object`;
|
|
81
|
+
if (typeof question.instructions !== "string") return `questions.${name}.instructions is required`;
|
|
82
|
+
if (question.type === "noul" || question.type === "choice") {
|
|
83
|
+
if (question.criteria === null || typeof question.criteria !== "object" || Array.isArray(question.criteria)) return `questions.${name}.criteria must be an object`;
|
|
84
|
+
if (question.type === "noul" && !("true" in question.criteria && "false" in question.criteria)) return `questions.${name}.criteria must describe true and false`;
|
|
85
|
+
} else if (question.type === "score") {
|
|
86
|
+
if (!Array.isArray(question.criteria) || question.criteria.length < 2 || question.criteria.length > 10) return `questions.${name}.criteria must be 2 to 10 levels`;
|
|
87
|
+
} else {
|
|
88
|
+
return `questions.${name}.type must be noul, choice or score`;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function send(res: ServerResponse, status: number, body: unknown): void {
|
|
95
|
+
res.writeHead(status, { "content-type": "application/json" });
|
|
96
|
+
res.end(JSON.stringify(body));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function readBody(req: IncomingMessage): Promise<string> {
|
|
100
|
+
const chunks: Buffer[] = [];
|
|
101
|
+
for await (const chunk of req) chunks.push(chunk as Buffer);
|
|
102
|
+
return Buffer.concat(chunks).toString("utf-8");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Start the stub on a free port on 127.0.0.1. */
|
|
106
|
+
export async function startStubBackend(opts: StubBackendOptions = {}): Promise<StubBackend> {
|
|
107
|
+
const requests: BackendRequest[] = [];
|
|
108
|
+
const authorizations: (string | null)[] = [];
|
|
109
|
+
const server = createServer((req, res) => {
|
|
110
|
+
void (async () => {
|
|
111
|
+
if (req.method !== "POST" || req.url !== SYSTEMONE_PATH) return send(res, 404, { error: { type: "not_found", message: `${req.method} ${req.url}` } });
|
|
112
|
+
const auth = req.headers.authorization ?? null;
|
|
113
|
+
authorizations.push(auth);
|
|
114
|
+
if (opts.key !== undefined && auth !== `Bearer ${opts.key}`) return send(res, 401, { error: { type: "authentication_error", message: "invalid key" } });
|
|
115
|
+
let body: unknown;
|
|
116
|
+
try {
|
|
117
|
+
body = JSON.parse(await readBody(req));
|
|
118
|
+
} catch {
|
|
119
|
+
return send(res, 422, { error: { type: "invalid_request", message: "the body is not JSON" } });
|
|
120
|
+
}
|
|
121
|
+
const problem = requestProblem(body);
|
|
122
|
+
if (problem) return send(res, 422, { error: { type: "invalid_request", message: problem } });
|
|
123
|
+
const request = body as BackendRequest;
|
|
124
|
+
requests.push(request);
|
|
125
|
+
if (opts.status !== undefined) return send(res, opts.status, { error: { type: "stub", message: `the stub answers ${opts.status}` } });
|
|
126
|
+
const answers: Record<string, WireAnswer> = {};
|
|
127
|
+
for (const [name, q] of Object.entries(request.questions)) {
|
|
128
|
+
const question: StubQuestion = { name, ...q };
|
|
129
|
+
const scripted = typeof opts.answers === "function" ? opts.answers(question, request) : opts.answers?.[name];
|
|
130
|
+
answers[name] = scripted ?? defaultAnswer(question);
|
|
131
|
+
}
|
|
132
|
+
send(res, 200, { model: opts.model ?? request.model, answers, usage: { input_tokens: JSON.stringify(request).length, output_tokens: 0 } });
|
|
133
|
+
})().catch((err: unknown) => send(res, 500, { error: { type: "stub", message: err instanceof Error ? err.message : String(err) } }));
|
|
134
|
+
});
|
|
135
|
+
await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
|
|
136
|
+
const { port } = server.address() as AddressInfo;
|
|
137
|
+
return {
|
|
138
|
+
url: `http://127.0.0.1:${port}`,
|
|
139
|
+
requests,
|
|
140
|
+
authorizations,
|
|
141
|
+
close: () => new Promise<void>((resolve, reject) => server.close((err) => (err ? reject(err) : resolve()))),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
|
|
26
26
|
import { z } from "zod";
|
|
27
27
|
import { activityContract } from "../activity-contract";
|
|
28
|
+
import { backendSchema } from "../decide-config";
|
|
28
29
|
|
|
29
30
|
export const lifecycleSnapshotContract = activityContract(
|
|
30
31
|
"lifecycleSnapshot",
|
|
@@ -54,8 +55,14 @@ export const shellCmdContract = activityContract(
|
|
|
54
55
|
env: z.record(z.string(), z.string()).optional(),
|
|
55
56
|
cwd: z.string().optional(),
|
|
56
57
|
okExit: z.array(z.number()).optional(),
|
|
58
|
+
gatedExit: z.number().optional(),
|
|
59
|
+
gate: z.strictObject({ op: z.string(), gate: z.string() }).optional(),
|
|
60
|
+
json: z.boolean().optional(),
|
|
57
61
|
}),
|
|
58
|
-
|
|
62
|
+
// `json` is stdout parsed, when the step sets `json` (#2787): whatever the
|
|
63
|
+
// command printed, so its shape is the author's and only the whole value is
|
|
64
|
+
// a reference target (`sh.out.json`).
|
|
65
|
+
z.object({ stdout: z.string(), stderr: z.string(), exitCode: z.number(), json: z.unknown().optional() }),
|
|
59
66
|
);
|
|
60
67
|
|
|
61
68
|
export const httpCheckContract = activityContract(
|
|
@@ -166,3 +173,185 @@ export const proposeWorkspaceUpgradeContract = activityContract(
|
|
|
166
173
|
summary: z.string(),
|
|
167
174
|
}),
|
|
168
175
|
);
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* The forward coverage check over an Op's own diff (#2773). `args` mirrors
|
|
179
|
+
* `ChangeCoverageArgs`. `returns` names what a step reads: whether the check
|
|
180
|
+
* passed, the range and work item checked, and each finding with the
|
|
181
|
+
* `triage` a work item seeded from it takes as its `source`.
|
|
182
|
+
*/
|
|
183
|
+
export const changeCoverageContract = activityContract(
|
|
184
|
+
"changeCoverage",
|
|
185
|
+
z.strictObject({
|
|
186
|
+
cwd: z.string().optional(),
|
|
187
|
+
range: z.string().optional(),
|
|
188
|
+
work: z.string().optional(),
|
|
189
|
+
severity: z.enum(["off", "warn", "fail"]).optional(),
|
|
190
|
+
}),
|
|
191
|
+
z.object({
|
|
192
|
+
ok: z.boolean(),
|
|
193
|
+
range: z.object({ spec: z.string(), base: z.string(), head: z.string() }),
|
|
194
|
+
work: z.string().nullable(),
|
|
195
|
+
severity: z.enum(["off", "warn", "fail"]),
|
|
196
|
+
findings: z.array(
|
|
197
|
+
z.object({
|
|
198
|
+
code: z.enum(["change-uncovered", "change-out-of-scope"]),
|
|
199
|
+
path: z.string(),
|
|
200
|
+
message: z.string(),
|
|
201
|
+
severity: z.enum(["warn", "fail"]),
|
|
202
|
+
records: z.array(z.string()),
|
|
203
|
+
triage: z.object({ finding: z.enum(["change-uncovered", "change-out-of-scope"]), region: z.string() }),
|
|
204
|
+
}),
|
|
205
|
+
),
|
|
206
|
+
summary: z.object({
|
|
207
|
+
paths: z.number(),
|
|
208
|
+
covered: z.number(),
|
|
209
|
+
uncovered: z.number(),
|
|
210
|
+
outOfScope: z.number(),
|
|
211
|
+
ignored: z.number(),
|
|
212
|
+
records: z.number(),
|
|
213
|
+
}),
|
|
214
|
+
}),
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Evidence for an acceptance criterion, under the run's work lease (#2772).
|
|
219
|
+
* `lease` is the run's lease, a reference the builder fills in, so it is not
|
|
220
|
+
* checked here. `returns` names what a step reads: the item, the record's
|
|
221
|
+
* path, the entry appended and the criteria counted with it.
|
|
222
|
+
*/
|
|
223
|
+
export const workEvidenceContract = activityContract(
|
|
224
|
+
"workEvidence",
|
|
225
|
+
z.strictObject({
|
|
226
|
+
lease: z.unknown(),
|
|
227
|
+
criterion: z.string(),
|
|
228
|
+
result: z.enum(["pass", "fail"]),
|
|
229
|
+
title: z.string(),
|
|
230
|
+
url: z.string().optional(),
|
|
231
|
+
path: z.string().optional(),
|
|
232
|
+
kind: z.string().optional(),
|
|
233
|
+
cwd: z.string().optional(),
|
|
234
|
+
}),
|
|
235
|
+
z.object({
|
|
236
|
+
item: z.string(),
|
|
237
|
+
path: z.string(),
|
|
238
|
+
evidence: z.record(z.string(), z.unknown()),
|
|
239
|
+
acceptance: z.object({ met: z.number(), total: z.number(), criteria: z.array(z.object({ id: z.string(), verification: z.string(), met: z.boolean() })) }),
|
|
240
|
+
}),
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* The source-release activities (#2782, ./source-release.ts). A release Op
|
|
245
|
+
* reads `archive.out.digest` into its plan, `plan.out.digest` into its gate
|
|
246
|
+
* and its ship step, and `plan.out.file` into its record step.
|
|
247
|
+
*/
|
|
248
|
+
export const sourceArchiveContract = activityContract(
|
|
249
|
+
"sourceArchive",
|
|
250
|
+
z.strictObject({ path: z.string(), ref: z.string().optional(), out: z.string().optional(), cwd: z.string().optional() }),
|
|
251
|
+
z.object({ digest: z.string(), archive: z.string(), commit: z.string(), dir: z.string(), files: z.number(), bytes: z.number() }),
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
export const releasePlanContract = activityContract(
|
|
255
|
+
"releasePlan",
|
|
256
|
+
z.strictObject({
|
|
257
|
+
component: z.string(),
|
|
258
|
+
env: z.string(),
|
|
259
|
+
gitSha: z.string(),
|
|
260
|
+
content: z.record(z.string(), z.unknown()),
|
|
261
|
+
dir: z.string().optional(),
|
|
262
|
+
cwd: z.string().optional(),
|
|
263
|
+
}),
|
|
264
|
+
z.object({ digest: z.string(), file: z.string(), gitSha: z.string() }),
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
export const releaseRecordContract = activityContract(
|
|
268
|
+
"releaseRecord",
|
|
269
|
+
z.strictObject({
|
|
270
|
+
plan: z.string(),
|
|
271
|
+
digest: z.string().optional(),
|
|
272
|
+
approval: z.strictObject({ op: z.string(), gate: z.string() }).optional(),
|
|
273
|
+
actor: z.string().optional(),
|
|
274
|
+
runId: z.string().optional(),
|
|
275
|
+
cwd: z.string().optional(),
|
|
276
|
+
}),
|
|
277
|
+
z.object({ recorded: z.boolean(), digest: z.string(), env: z.string(), component: z.string(), approver: z.string().nullable() }),
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* `decide` (#2740, core's since #2828). OPS012 checks a step's args against
|
|
282
|
+
* it and OPS013 a later step's reference into its result, so a misspelled
|
|
283
|
+
* `point` or a key written as a literal string fails `chant build` rather
|
|
284
|
+
* than a run.
|
|
285
|
+
*/
|
|
286
|
+
const decideEscalation = z.object({ kind: z.enum(["table", "model"]), reason: z.string() }).loose();
|
|
287
|
+
|
|
288
|
+
export const decideContract = activityContract(
|
|
289
|
+
"decide",
|
|
290
|
+
z.strictObject({
|
|
291
|
+
point: z.string().min(1),
|
|
292
|
+
inputs: z.record(z.string(), z.unknown()).optional(),
|
|
293
|
+
read: z.record(z.string(), z.string()).optional(),
|
|
294
|
+
subject: z.string().optional(),
|
|
295
|
+
kind: z.string().optional(),
|
|
296
|
+
cwd: z.string().optional(),
|
|
297
|
+
backends: z.record(z.string(), backendSchema).optional(),
|
|
298
|
+
dryRun: z.boolean().optional(),
|
|
299
|
+
}),
|
|
300
|
+
z.strictObject({
|
|
301
|
+
id: z.string(),
|
|
302
|
+
path: z.string(),
|
|
303
|
+
state: z.enum(["escalated", "proposed", "answered"]),
|
|
304
|
+
open: z.boolean(),
|
|
305
|
+
answer: z.union([z.string(), z.boolean(), z.null()]),
|
|
306
|
+
decider: z.string(),
|
|
307
|
+
model: z.string().nullable(),
|
|
308
|
+
backend: z.string().nullable(),
|
|
309
|
+
confidence: z.number().nullable(),
|
|
310
|
+
threshold: z.number().nullable(),
|
|
311
|
+
answeredBy: z.array(z.string()),
|
|
312
|
+
escalations: z.array(decideEscalation),
|
|
313
|
+
missing: z.array(z.string()),
|
|
314
|
+
}),
|
|
315
|
+
{ entities: ["point"] },
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* The rollback activities for a source release (#2800, ./source-rollback.ts).
|
|
320
|
+
* A rollback Op reads `plan.out.digest` into its gate, `plan.out.to` and the
|
|
321
|
+
* archive fields into the step that restores the Machine, and
|
|
322
|
+
* `plan.out.file` into its record step.
|
|
323
|
+
*/
|
|
324
|
+
export const releaseRollbackPlanContract = activityContract(
|
|
325
|
+
"releaseRollbackPlan",
|
|
326
|
+
z.strictObject({
|
|
327
|
+
component: z.string(),
|
|
328
|
+
env: z.string(),
|
|
329
|
+
to: z.string().optional(),
|
|
330
|
+
path: z.string().optional(),
|
|
331
|
+
dir: z.string().optional(),
|
|
332
|
+
cwd: z.string().optional(),
|
|
333
|
+
}),
|
|
334
|
+
z.object({
|
|
335
|
+
digest: z.string(),
|
|
336
|
+
file: z.string(),
|
|
337
|
+
to: z.string(),
|
|
338
|
+
from: z.string(),
|
|
339
|
+
gitSha: z.string(),
|
|
340
|
+
archive: z.string(),
|
|
341
|
+
archiveDigest: z.string(),
|
|
342
|
+
dir: z.string(),
|
|
343
|
+
}),
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
export const releaseRollbackRecordContract = activityContract(
|
|
347
|
+
"releaseRollbackRecord",
|
|
348
|
+
z.strictObject({
|
|
349
|
+
plan: z.string(),
|
|
350
|
+
digest: z.string().optional(),
|
|
351
|
+
approval: z.strictObject({ op: z.string(), gate: z.string() }).optional(),
|
|
352
|
+
actor: z.string().optional(),
|
|
353
|
+
runId: z.string().optional(),
|
|
354
|
+
cwd: z.string().optional(),
|
|
355
|
+
}),
|
|
356
|
+
z.object({ recorded: z.boolean(), digest: z.string(), env: z.string(), component: z.string(), approver: z.string().nullable() }),
|
|
357
|
+
);
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
import { exec } from "node:child_process";
|
|
36
36
|
import { promisify } from "node:util";
|
|
37
37
|
import { normalizeApply, type ApplyResult } from "../../apply";
|
|
38
|
+
import { importLexiconPackage } from "../../lexicon-module";
|
|
38
39
|
|
|
39
40
|
const execAsync = promisify(exec);
|
|
40
41
|
|
|
@@ -241,7 +242,7 @@ async function loadK8sApplier(): Promise<K8sApplier> {
|
|
|
241
242
|
const spec = "@intentius/chant-lexicon-k8s/op/activities";
|
|
242
243
|
let mod: { applyManifest?: K8sApplier };
|
|
243
244
|
try {
|
|
244
|
-
mod = (await
|
|
245
|
+
mod = (await importLexiconPackage(spec)) as { applyManifest?: K8sApplier };
|
|
245
246
|
} catch (err) {
|
|
246
247
|
throw new Error(
|
|
247
248
|
`apply target "kubectl" needs @intentius/chant-lexicon-k8s, which could not be loaded ` +
|
|
@@ -269,7 +270,7 @@ async function loadAzureApplier(): Promise<AzureApplier> {
|
|
|
269
270
|
};
|
|
270
271
|
let mod: AzureModule;
|
|
271
272
|
try {
|
|
272
|
-
mod = (await
|
|
273
|
+
mod = (await importLexiconPackage(spec)) as AzureModule;
|
|
273
274
|
} catch (err) {
|
|
274
275
|
throw new Error(
|
|
275
276
|
`apply target "arm" needs @intentius/chant-lexicon-azure, which could not be loaded ` +
|
|
@@ -304,7 +305,7 @@ async function loadGcpApplier(): Promise<GcpApplier> {
|
|
|
304
305
|
};
|
|
305
306
|
let mod: GcpModule;
|
|
306
307
|
try {
|
|
307
|
-
mod = (await
|
|
308
|
+
mod = (await importLexiconPackage(spec)) as GcpModule;
|
|
308
309
|
} catch (err) {
|
|
309
310
|
throw new Error(
|
|
310
311
|
`apply target "gcp" needs @intentius/chant-lexicon-gcp, which could not be loaded ` +
|
|
@@ -339,7 +340,7 @@ async function loadFlyApplier(): Promise<FlyApplier> {
|
|
|
339
340
|
};
|
|
340
341
|
let mod: FlyModule;
|
|
341
342
|
try {
|
|
342
|
-
mod = (await
|
|
343
|
+
mod = (await importLexiconPackage(spec)) as FlyModule;
|
|
343
344
|
} catch (err) {
|
|
344
345
|
throw new Error(
|
|
345
346
|
`apply target "fly" needs @intentius/chant-lexicon-fly, which could not be loaded ` +
|
|
@@ -370,7 +371,7 @@ async function loadAwsApplier(): Promise<AwsApplier> {
|
|
|
370
371
|
const spec = "@intentius/chant-lexicon-aws/op/activities";
|
|
371
372
|
let mod: { awsApply?: AwsApplier };
|
|
372
373
|
try {
|
|
373
|
-
mod = (await
|
|
374
|
+
mod = (await importLexiconPackage(spec)) as { awsApply?: AwsApplier };
|
|
374
375
|
} catch (err) {
|
|
375
376
|
throw new Error(
|
|
376
377
|
`apply target "cloudformation" needs @intentius/chant-lexicon-aws, which could not be loaded ` +
|
|
@@ -395,7 +396,7 @@ async function loadAwsRollback(): Promise<AwsRollback> {
|
|
|
395
396
|
const spec = "@intentius/chant-lexicon-aws/op/activities";
|
|
396
397
|
let mod: { rollbackStack?: AwsRollback };
|
|
397
398
|
try {
|
|
398
|
-
mod = (await
|
|
399
|
+
mod = (await importLexiconPackage(spec)) as { rollbackStack?: AwsRollback };
|
|
399
400
|
} catch (err) {
|
|
400
401
|
throw new Error(
|
|
401
402
|
`rollback for target "cloudformation" needs @intentius/chant-lexicon-aws, which could not be ` +
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* changeCoverage — the forward coverage check (#2773) as an Op activity, for
|
|
3
|
+
* an Op that changes the checkout (#2748) to run on its own diff.
|
|
4
|
+
*
|
|
5
|
+
* Such an Op's leased steps work in a worktree of their own on
|
|
6
|
+
* `chant/work/[<member>/]<item>`. Given that worktree as `cwd`
|
|
7
|
+
* (`workLeaseOutput("worktree")`) and no `range`, the activity checks the
|
|
8
|
+
* commits the branch holds beyond the main worktree's HEAD, with the branch's
|
|
9
|
+
* item as the work item in hand, exactly as `chant workspace check --changes
|
|
10
|
+
* <base>..HEAD --work <item>` would. A `range` or `work` given replaces what
|
|
11
|
+
* the branch says.
|
|
12
|
+
*
|
|
13
|
+
* The step fails when the check's severity is `fail` and it has a finding,
|
|
14
|
+
* or when the check can't run; otherwise it returns the document's findings
|
|
15
|
+
* and summary for later steps. The workspace code loads on first call, so
|
|
16
|
+
* importing core's activities loads nothing under `workspace/` (#2525 rule 5).
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import type { ChangeFinding, ChangesDocument } from "../../workspace/changes";
|
|
20
|
+
|
|
21
|
+
export interface ChangeCoverageArgs {
|
|
22
|
+
/** The checkout to check: the Op's worktree. Default: the working directory. */
|
|
23
|
+
cwd?: string;
|
|
24
|
+
/** `<base>..<head>`, `<base>...<head>` or `<base>`. Default: the work branch's own commits. */
|
|
25
|
+
range?: string;
|
|
26
|
+
/** The work item in hand. Default: the work branch's item. */
|
|
27
|
+
work?: string;
|
|
28
|
+
/** In place of the declaration's `changes.severity`. */
|
|
29
|
+
severity?: "off" | "warn" | "fail";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface ChangeCoverageResult {
|
|
33
|
+
ok: boolean;
|
|
34
|
+
range: { spec: string; base: string; head: string };
|
|
35
|
+
work: string | null;
|
|
36
|
+
severity: "off" | "warn" | "fail";
|
|
37
|
+
findings: Pick<ChangeFinding, "code" | "path" | "message" | "severity" | "records" | "triage">[];
|
|
38
|
+
summary: Exclude<ChangesDocument, { error: unknown }>["summary"];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function changeCoverage(args: ChangeCoverageArgs = {}): Promise<ChangeCoverageResult> {
|
|
42
|
+
const { checkChanges, workBranchChanges } = await import("../../workspace/changes");
|
|
43
|
+
const cwd = args.cwd ?? process.cwd();
|
|
44
|
+
const branch = args.range === undefined ? workBranchChanges(cwd) : undefined;
|
|
45
|
+
const range = args.range ?? branch?.range;
|
|
46
|
+
if (range === undefined) throw new Error(`changeCoverage: ${cwd} is not on a chant/work/ branch, so give the range to check`);
|
|
47
|
+
const work = args.work ?? branch?.work;
|
|
48
|
+
const { doc } = await checkChanges({ cwd, range, ...(work !== undefined ? { work } : {}), ...(args.severity ? { severity: args.severity } : {}) });
|
|
49
|
+
if ("error" in doc) throw new Error(`changeCoverage: ${doc.error.code}: ${doc.error.message}`);
|
|
50
|
+
const result: ChangeCoverageResult = {
|
|
51
|
+
ok: doc.ok,
|
|
52
|
+
range: doc.range,
|
|
53
|
+
work: doc.work?.record ?? null,
|
|
54
|
+
severity: doc.severity,
|
|
55
|
+
findings: doc.findings.map(({ code, path, message, severity, records, triage }) => ({ code, path, message, severity, records, triage })),
|
|
56
|
+
summary: doc.summary,
|
|
57
|
+
};
|
|
58
|
+
if (!doc.ok) throw new Error(`changeCoverage: ${doc.findings.length} change ${doc.findings.length === 1 ? "finding" : "findings"} at severity fail: ${doc.findings.map((f) => `${f.code} ${f.path}`).join(", ")}`);
|
|
59
|
+
return result;
|
|
60
|
+
}
|