@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
|
@@ -171,7 +171,54 @@ describe("release wiring: preflight gates both recipes (#1255)", () => {
|
|
|
171
171
|
}
|
|
172
172
|
});
|
|
173
173
|
|
|
174
|
-
|
|
175
|
-
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* #2816 — main's HEAD is rarely green, so a release tags the newest green
|
|
178
|
+
* commit on main (or the one named) and merges the bump back into main.
|
|
179
|
+
* scripts/release-preflight.test.ts runs the mechanics; this pins the wiring.
|
|
180
|
+
*/
|
|
181
|
+
describe("release wiring: release the newest green commit (#2816)", () => {
|
|
182
|
+
it("both recipes tag the commit the preflight chose, not HEAD", () => {
|
|
183
|
+
for (const recipe of ["release", "release-lexicon"]) {
|
|
184
|
+
const body = recipeBody(recipe);
|
|
185
|
+
expect(body).toMatch(/sha=\$\(bash scripts\/release-preflight\.sh/);
|
|
186
|
+
expect(body).toMatch(/release_open "\$sha"/);
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it("both recipes merge the tag back into main instead of pushing HEAD", () => {
|
|
191
|
+
for (const recipe of ["release", "release-lexicon"]) {
|
|
192
|
+
const body = recipeBody(recipe);
|
|
193
|
+
expect(body).toMatch(/release_ship "\$tag"/);
|
|
194
|
+
expect(body).not.toMatch(/git push/);
|
|
195
|
+
expect(body.indexOf("git tag")).toBeLessThan(body.indexOf("release_ship"));
|
|
196
|
+
}
|
|
197
|
+
const lib = readFileSync(join(repoRoot, "scripts", "release-lib.sh"), "utf-8");
|
|
198
|
+
// main and the tag land together or not at all, and nothing is forced.
|
|
199
|
+
expect(lib).toMatch(/git push --quiet --atomic origin "HEAD:refs\/heads\/main" "refs\/tags\/\$tag"/);
|
|
200
|
+
expect(lib).not.toMatch(/git push[^\n]*(--force|\s-f\b)/);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it("both recipes offer a dry run that stops before committing", () => {
|
|
204
|
+
for (const recipe of ["release", "release-lexicon"]) {
|
|
205
|
+
const body = recipeBody(recipe);
|
|
206
|
+
expect(body.indexOf("RELEASE_DRY_RUN")).toBeGreaterThan(-1);
|
|
207
|
+
expect(body.indexOf("RELEASE_DRY_RUN")).toBeLessThan(body.indexOf("git commit"));
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it("the whole-repo floor reads main as well as the green commit", () => {
|
|
212
|
+
expect(recipeBody("release")).toMatch(/release_version_at "\$RELEASE_MAIN"/);
|
|
213
|
+
expect(recipeBody("release-lexicon")).toMatch(/release_version_at "\$RELEASE_MAIN"/);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it("a push to main does not cancel main's chant run; PR runs still cancel", () => {
|
|
217
|
+
const workflow = readFileSync(join(repoRoot, ".github", "workflows", "chant.yml"), "utf-8");
|
|
218
|
+
const block = workflow.match(/^concurrency:\n((?:[ ]+.*\n)+)/m)?.[1] ?? "";
|
|
219
|
+
// One group per commit on main, so a later push neither cancels a running
|
|
220
|
+
// run nor replaces a queued one.
|
|
221
|
+
expect(block).toMatch(/group:.*github\.ref == 'refs\/heads\/main' && github\.sha \|\| github\.ref/);
|
|
222
|
+
expect(block).toMatch(/cancel-in-progress: \$\{\{ github\.ref != 'refs\/heads\/main' \}\}/);
|
|
176
223
|
});
|
|
177
224
|
});
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
24
|
import { CapabilityRegistry } from "./capability";
|
|
25
|
-
import { lexiconModulePath } from "../lexicon-module";
|
|
25
|
+
import { lexiconModulePath, importLexiconPackage } from "../lexicon-module";
|
|
26
26
|
import type { CapabilityPlugin } from "./capability-plugin";
|
|
27
27
|
import { isCapabilityPlugin } from "./capability-plugin";
|
|
28
28
|
import { starterCapabilityPlugin } from "./starter-plugin";
|
|
@@ -62,7 +62,7 @@ export async function loadCapabilityPlugin(name: string): Promise<CapabilityPlug
|
|
|
62
62
|
const packageName = `@intentius/chant-capability-${name}`;
|
|
63
63
|
let mod: Record<string, unknown>;
|
|
64
64
|
try {
|
|
65
|
-
mod = (await
|
|
65
|
+
mod = (await importLexiconPackage(packageName)) as Record<string, unknown>;
|
|
66
66
|
} catch (err) {
|
|
67
67
|
throw new Error(
|
|
68
68
|
`capability plugin package "${packageName}" could not be loaded: ${err instanceof Error ? err.message : String(err)}`,
|
|
@@ -118,7 +118,7 @@ export async function loadCapabilityPluginFromLexicon(name: string): Promise<Cap
|
|
|
118
118
|
const packageName = lexiconModulePath(name) ?? `@intentius/chant-lexicon-${name}`;
|
|
119
119
|
let mod: Record<string, unknown>;
|
|
120
120
|
try {
|
|
121
|
-
mod = (await
|
|
121
|
+
mod = (await importLexiconPackage(packageName)) as Record<string, unknown>;
|
|
122
122
|
} catch {
|
|
123
123
|
return null; // lexicon not installed / not resolvable here — nothing to contribute.
|
|
124
124
|
}
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* executor, the CLI entrypoint the driver (#556) never had.
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
|
-
import { lexiconModulePath, lexiconNames } from "../lexicon-module";
|
|
25
|
+
import { lexiconModulePath, lexiconNames, importLexiconPackage } from "../lexicon-module";
|
|
26
26
|
import { discoverComponents } from "./discover";
|
|
27
27
|
import type { BuildParamProvenance } from "../provenance";
|
|
28
28
|
import { inferArchetype, projectToJson, type Archetype } from "./component";
|
|
@@ -244,7 +244,7 @@ export interface GenerateComponentsResult {
|
|
|
244
244
|
async function loadLexiconPlugin(name: string): Promise<LexiconPlugin | null> {
|
|
245
245
|
let mod: Record<string, unknown>;
|
|
246
246
|
try {
|
|
247
|
-
mod = (await
|
|
247
|
+
mod = (await importLexiconPackage(lexiconModulePath(name) ?? `@intentius/chant-lexicon-${name}`)) as Record<string, unknown>;
|
|
248
248
|
} catch {
|
|
249
249
|
return null;
|
|
250
250
|
}
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* `scan-vulnerabilities` capability over an injected scanner.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { readFileSync } from "node:fs";
|
|
8
|
+
import { readFileSync, mkdtempSync, writeFileSync } from "node:fs";
|
|
9
|
+
import { tmpdir } from "node:os";
|
|
9
10
|
import { join } from "node:path";
|
|
10
11
|
import { describe, test, expect } from "vitest";
|
|
11
12
|
import {
|
|
@@ -15,8 +16,13 @@ import {
|
|
|
15
16
|
createToolVulnScanner,
|
|
16
17
|
createScanVulnerabilitiesCapability,
|
|
17
18
|
autoDetectVulnScanner,
|
|
19
|
+
sbomPackages,
|
|
20
|
+
compareVersions,
|
|
21
|
+
scanWithDatabase,
|
|
22
|
+
createDatabaseVulnScanner,
|
|
18
23
|
type VulnFinding,
|
|
19
24
|
type VulnScanner,
|
|
25
|
+
type AdvisoryDatabase,
|
|
20
26
|
} from "./vuln-scan";
|
|
21
27
|
import { ToolNotAvailableError } from "./process-runner";
|
|
22
28
|
import type { SbomDocument } from "./sbom-generator";
|
|
@@ -178,6 +184,123 @@ describe("exploitability parsing (#1463)", () => {
|
|
|
178
184
|
});
|
|
179
185
|
});
|
|
180
186
|
|
|
187
|
+
// ── offline advisory database (ws-056, INTENTIUS/chant#2735) ─────────────────
|
|
188
|
+
// Ported from chud's supply-chain.mjs tests: an SBOM's purl-derived packages
|
|
189
|
+
// matched against a local, offline advisory database — no scanner binary, no
|
|
190
|
+
// network.
|
|
191
|
+
|
|
192
|
+
const SPDX_SBOM: SbomDocument = {
|
|
193
|
+
format: "spdx",
|
|
194
|
+
mediaType: "application/spdx+json",
|
|
195
|
+
bytes: JSON.stringify({
|
|
196
|
+
packages: [
|
|
197
|
+
{ name: "lodash", externalRefs: [{ referenceCategory: "PACKAGE-MANAGER", referenceType: "purl", referenceLocator: "pkg:npm/lodash@4.17.20" }] },
|
|
198
|
+
{ name: "left-pad", externalRefs: [{ referenceCategory: "PACKAGE-MANAGER", referenceType: "purl", referenceLocator: "pkg:npm/left-pad@1.3.0" }] },
|
|
199
|
+
{ name: "no-purl" },
|
|
200
|
+
],
|
|
201
|
+
}),
|
|
202
|
+
generator: "lockfile",
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const CYCLONEDX_SBOM: SbomDocument = {
|
|
206
|
+
format: "cyclonedx",
|
|
207
|
+
mediaType: "application/vnd.cyclonedx+json",
|
|
208
|
+
bytes: JSON.stringify({
|
|
209
|
+
components: [{ name: "lodash", purl: "pkg:npm/lodash@4.17.20" }, { name: "left-pad", purl: "pkg:npm/left-pad@1.3.0" }],
|
|
210
|
+
}),
|
|
211
|
+
generator: "lockfile",
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const DB: AdvisoryDatabase = {
|
|
215
|
+
advisories: [
|
|
216
|
+
{ id: "CVE-2024-0001", package: "lodash", severity: "critical", introduced: "4.0.0", fixed: "4.17.21" },
|
|
217
|
+
{ id: "CVE-2024-0002", package: "left-pad", severity: "low" },
|
|
218
|
+
{ id: "CVE-2024-0003", package: "lodash", ecosystem: "pypi", severity: "high" },
|
|
219
|
+
],
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
describe("sbomPackages", () => {
|
|
223
|
+
test("reads purls from an SPDX document's externalRefs, skipping entries with none", () => {
|
|
224
|
+
const packages = sbomPackages(SPDX_SBOM);
|
|
225
|
+
expect(packages).toEqual([
|
|
226
|
+
{ ecosystem: "npm", name: "lodash", version: "4.17.20" },
|
|
227
|
+
{ ecosystem: "npm", name: "left-pad", version: "1.3.0" },
|
|
228
|
+
]);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test("reads purls from a CycloneDX document's components", () => {
|
|
232
|
+
expect(sbomPackages(CYCLONEDX_SBOM)).toEqual([
|
|
233
|
+
{ ecosystem: "npm", name: "lodash", version: "4.17.20" },
|
|
234
|
+
{ ecosystem: "npm", name: "left-pad", version: "1.3.0" },
|
|
235
|
+
]);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test("deduplicates identical ecosystem/name/version", () => {
|
|
239
|
+
const doc = { bytes: JSON.stringify({ components: [{ purl: "pkg:npm/x@1.0.0" }, { purl: "pkg:npm/x@1.0.0" }] }) };
|
|
240
|
+
expect(sbomPackages(doc)).toHaveLength(1);
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
describe("compareVersions", () => {
|
|
245
|
+
test("compares numerically, not lexically (1.9.0 < 1.10.0)", () => {
|
|
246
|
+
expect(compareVersions("1.9.0", "1.10.0")).toBe(-1);
|
|
247
|
+
expect(compareVersions("1.10.0", "1.9.0")).toBe(1);
|
|
248
|
+
expect(compareVersions("1.2.3", "1.2.3")).toBe(0);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
test("a pre-release sorts before its release", () => {
|
|
252
|
+
expect(compareVersions("1.2.0-rc.1", "1.2.0")).toBe(-1);
|
|
253
|
+
expect(compareVersions("1.2.0", "1.2.0-rc.1")).toBe(1);
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
describe("scanWithDatabase", () => {
|
|
258
|
+
test("matches an SBOM's packages against the database by ecosystem + name, introduced <= version < fixed", () => {
|
|
259
|
+
const findings = scanWithDatabase(SPDX_SBOM, DB);
|
|
260
|
+
expect(findings).toHaveLength(2);
|
|
261
|
+
const lodash = findings.find((f) => f.cveId === "CVE-2024-0001");
|
|
262
|
+
expect(lodash).toEqual({ cveId: "CVE-2024-0001", severity: "critical", package: "lodash", installedVersion: "4.17.20", fixedVersion: "4.17.21", fixable: true });
|
|
263
|
+
const leftPad = findings.find((f) => f.cveId === "CVE-2024-0002");
|
|
264
|
+
expect(leftPad).toMatchObject({ package: "left-pad", fixable: false });
|
|
265
|
+
expect(leftPad!.fixedVersion).toBeUndefined();
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
test("a version at or after `fixed` is not affected", () => {
|
|
269
|
+
const doc = { bytes: JSON.stringify({ components: [{ purl: "pkg:npm/lodash@4.17.21" }] }) };
|
|
270
|
+
expect(scanWithDatabase(doc, DB)).toHaveLength(0);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
test("an ecosystem mismatch does not match (pypi advisory, npm package)", () => {
|
|
274
|
+
const findings = scanWithDatabase(SPDX_SBOM, DB);
|
|
275
|
+
expect(findings.some((f) => f.cveId === "CVE-2024-0003")).toBe(false);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
test("accepts a bare advisories array as well as { advisories: [...] }", () => {
|
|
279
|
+
expect(scanWithDatabase(SPDX_SBOM, DB.advisories)).toHaveLength(2);
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
describe("createDatabaseVulnScanner", () => {
|
|
284
|
+
test("scans by reading the database file at scan time", async () => {
|
|
285
|
+
const dir = mkdtempSync(join(tmpdir(), "chant-vulndb-"));
|
|
286
|
+
const dbFile = join(dir, "advisories.json");
|
|
287
|
+
writeFileSync(dbFile, JSON.stringify(DB));
|
|
288
|
+
const scanner = createDatabaseVulnScanner(dbFile);
|
|
289
|
+
const findings = await scanner.scan({ sbom: SPDX_SBOM });
|
|
290
|
+
expect(findings).toHaveLength(2);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
test("composes with scan-vulnerabilities like any other VulnScanner", async () => {
|
|
294
|
+
const dir = mkdtempSync(join(tmpdir(), "chant-vulndb-"));
|
|
295
|
+
const dbFile = join(dir, "advisories.json");
|
|
296
|
+
writeFileSync(dbFile, JSON.stringify(DB));
|
|
297
|
+
const cap = createScanVulnerabilitiesCapability(createDatabaseVulnScanner(dbFile));
|
|
298
|
+
const out = await cap.run(ctx, { sbom: SPDX_SBOM, digest: "sha256:abc" });
|
|
299
|
+
expect(out.findings).toHaveLength(2);
|
|
300
|
+
expect(out.digest).toBe("sha256:abc");
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
|
|
181
304
|
describe("createToolVulnScanner (grype, via MockProcessRunner)", () => {
|
|
182
305
|
test("scans the SBOM with `grype sbom:<file>` and parses the result", async () => {
|
|
183
306
|
const mock = createMockProcessRunner({ tools: { grype: true }, responses: { "grype sbom:": GRYPE_JSON } });
|
|
@@ -13,9 +13,17 @@
|
|
|
13
13
|
* inline fake `VulnScanner` (an object literal with a canned `scan()`, see
|
|
14
14
|
* ./vuln-gate.test.ts) and never invoke a real scanner, network, or vuln DB.
|
|
15
15
|
* The scanner's vuln DB currency is the tool's job, not chant's.
|
|
16
|
+
*
|
|
17
|
+
* `createDatabaseVulnScanner` (ws-056, INTENTIUS/chant#2735) is the one
|
|
18
|
+
* hermetic exception: a `VulnScanner` matched against a local, offline
|
|
19
|
+
* advisory-database file instead of a real scanner binary — no `grype`/
|
|
20
|
+
* `trivy` required, deterministic, at the cost of covering only the
|
|
21
|
+
* advisories the database names. Ported from chud's `supply-chain.mjs`
|
|
22
|
+
* (`scanWithDatabase`/`sbomPackages`/`compareVersions`), which built its own
|
|
23
|
+
* copy of this because `scan-vulnerabilities` had no offline backend.
|
|
16
24
|
*/
|
|
17
25
|
|
|
18
|
-
import { writeFileSync } from "node:fs";
|
|
26
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
19
27
|
import { tmpdir } from "node:os";
|
|
20
28
|
import { join } from "node:path";
|
|
21
29
|
import { createHash } from "node:crypto";
|
|
@@ -174,6 +182,139 @@ export function parseTrivyOutput(stdout: string): VulnFinding[] {
|
|
|
174
182
|
return out;
|
|
175
183
|
}
|
|
176
184
|
|
|
185
|
+
// ── offline advisory database ────────────────────────────────────────────────
|
|
186
|
+
|
|
187
|
+
/** One package an SBOM names, by purl: the ecosystem, its name and its installed version. */
|
|
188
|
+
export interface SbomPackage {
|
|
189
|
+
ecosystem: string;
|
|
190
|
+
name: string;
|
|
191
|
+
version: string;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* The packages an SPDX or CycloneDX document lists, by purl (SPDX's
|
|
196
|
+
* `externalRefs` with `referenceType: "purl"`, CycloneDX's `components[].purl`),
|
|
197
|
+
* deduplicated by ecosystem/name/version. Parses `doc.bytes` (a string, as
|
|
198
|
+
* every `SbomDocument` carries it) rather than requiring a pre-parsed object,
|
|
199
|
+
* so a caller can pass a `scan-vulnerabilities` input's `sbom` straight
|
|
200
|
+
* through.
|
|
201
|
+
*/
|
|
202
|
+
export function sbomPackages(doc: Pick<SbomDocument, "bytes">): SbomPackage[] {
|
|
203
|
+
const parsed = JSON.parse(doc.bytes) as {
|
|
204
|
+
packages?: Array<{ externalRefs?: Array<{ referenceType?: string; referenceLocator?: string }> }>;
|
|
205
|
+
components?: Array<{ purl?: string }>;
|
|
206
|
+
};
|
|
207
|
+
const purls = [
|
|
208
|
+
...(parsed.packages ?? []).flatMap((p) =>
|
|
209
|
+
(p.externalRefs ?? []).filter((r) => r.referenceType === "purl").map((r) => r.referenceLocator ?? ""),
|
|
210
|
+
),
|
|
211
|
+
...(parsed.components ?? []).map((c) => c.purl).filter((p): p is string => Boolean(p)),
|
|
212
|
+
];
|
|
213
|
+
const seen = new Map<string, SbomPackage>();
|
|
214
|
+
for (const purl of purls) {
|
|
215
|
+
const m = /^pkg:([^/]+)\/(.+)@([^?#]+)/.exec(purl);
|
|
216
|
+
if (!m) continue;
|
|
217
|
+
const pkg: SbomPackage = { ecosystem: m[1], name: decodeURIComponent(m[2]), version: decodeURIComponent(m[3]) };
|
|
218
|
+
seen.set(`${pkg.ecosystem}:${pkg.name}@${pkg.version}`, pkg);
|
|
219
|
+
}
|
|
220
|
+
return [...seen.values()];
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Compare two dotted version strings numerically, a pre-release sorting
|
|
225
|
+
* before its release (`1.2.0-rc.1` < `1.2.0`). Not full semver (no build
|
|
226
|
+
* metadata, no ordering between differing pre-release labels beyond "a
|
|
227
|
+
* pre-release exists") — just enough to place an installed version between
|
|
228
|
+
* an advisory's `introduced` and `fixed`.
|
|
229
|
+
*/
|
|
230
|
+
export function compareVersions(a: string, b: string): -1 | 0 | 1 {
|
|
231
|
+
const parse = (v: string) => {
|
|
232
|
+
const [core, pre] = String(v).replace(/^v/, "").split("-", 2);
|
|
233
|
+
return { nums: core.split(".").map((n) => Number.parseInt(n, 10) || 0), pre };
|
|
234
|
+
};
|
|
235
|
+
const x = parse(a);
|
|
236
|
+
const y = parse(b);
|
|
237
|
+
for (let i = 0; i < Math.max(x.nums.length, y.nums.length); i++) {
|
|
238
|
+
const d = (x.nums[i] ?? 0) - (y.nums[i] ?? 0);
|
|
239
|
+
if (d) return Math.sign(d) as -1 | 1;
|
|
240
|
+
}
|
|
241
|
+
if (x.pre && !y.pre) return -1;
|
|
242
|
+
if (!x.pre && y.pre) return 1;
|
|
243
|
+
if (x.pre === y.pre) return 0;
|
|
244
|
+
return x.pre! < y.pre! ? -1 : 1;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** One advisory in a local, offline advisory database (`AdvisoryDatabase.advisories`). */
|
|
248
|
+
export interface Advisory {
|
|
249
|
+
/** Advisory id, e.g. `"CVE-2024-12345"` or `"GHSA-…"`. */
|
|
250
|
+
id: string;
|
|
251
|
+
/** Ecosystem the advisory applies to (npm, pypi, …). Default: `"npm"`. */
|
|
252
|
+
ecosystem?: string;
|
|
253
|
+
/** Affected package name, matched against the SBOM's purl-derived package name. */
|
|
254
|
+
package: string;
|
|
255
|
+
severity?: string;
|
|
256
|
+
/** First affected version. Default: `"0"` (affects every version up to `fixed`). */
|
|
257
|
+
introduced?: string;
|
|
258
|
+
/** First version that fixes it. Omitted for a vulnerability with no fix yet. */
|
|
259
|
+
fixed?: string;
|
|
260
|
+
summary?: string;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/** A local, offline advisory database: `{ "advisories": [...] }` (or a bare array, for convenience). */
|
|
264
|
+
export interface AdvisoryDatabase {
|
|
265
|
+
advisories: Advisory[];
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Match an SBOM's packages against a local, offline advisory database —
|
|
270
|
+
* no scanner binary, no network, deterministic. A package is affected when
|
|
271
|
+
* `introduced <= installedVersion < fixed` (or unconditionally affected when
|
|
272
|
+
* the advisory names no `fixed`). Findings are `VulnFinding`-shaped, so they
|
|
273
|
+
* feed `vuln-gate` exactly like `grype`/`trivy` output does; every
|
|
274
|
+
* exploitability field (`epss`, `inKev`, …) an offline database can't supply
|
|
275
|
+
* is left `undefined`, never defaulted to a false negative.
|
|
276
|
+
*/
|
|
277
|
+
export function scanWithDatabase(doc: Pick<SbomDocument, "bytes">, db: AdvisoryDatabase | Advisory[]): VulnFinding[] {
|
|
278
|
+
const advisories = Array.isArray(db) ? db : (db.advisories ?? []);
|
|
279
|
+
const findings: VulnFinding[] = [];
|
|
280
|
+
for (const pkg of sbomPackages(doc)) {
|
|
281
|
+
for (const a of advisories) {
|
|
282
|
+
if ((a.ecosystem ?? "npm") !== pkg.ecosystem || a.package !== pkg.name) continue;
|
|
283
|
+
if (compareVersions(pkg.version, a.introduced ?? "0") < 0) continue;
|
|
284
|
+
if (a.fixed && compareVersions(pkg.version, a.fixed) >= 0) continue;
|
|
285
|
+
findings.push({
|
|
286
|
+
cveId: a.id,
|
|
287
|
+
severity: normalizeSeverity(a.severity),
|
|
288
|
+
package: pkg.name,
|
|
289
|
+
installedVersion: pkg.version,
|
|
290
|
+
...(a.fixed ? { fixedVersion: a.fixed } : {}),
|
|
291
|
+
fixable: Boolean(a.fixed),
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return findings;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* A `VulnScanner` backed by a local, offline advisory database file (JSON:
|
|
300
|
+
* `{ "advisories": [...] }`, see `AdvisoryDatabase`) instead of a real
|
|
301
|
+
* scanner binary — the hermetic fallback `autoDetectVulnScanner`'s doc
|
|
302
|
+
* comment says a scan doesn't have ("there is no hermetic fallback, a scan
|
|
303
|
+
* needs a real vuln DB"): deterministic, offline, no `grype`/`trivy`
|
|
304
|
+
* install required, at the cost of covering only the advisories the
|
|
305
|
+
* database names. Reads the file once per call (a database pinned in the
|
|
306
|
+
* repo changes rarely; callers that scan repeatedly should cache the parsed
|
|
307
|
+
* `AdvisoryDatabase` and call `scanWithDatabase` directly instead).
|
|
308
|
+
*/
|
|
309
|
+
export function createDatabaseVulnScanner(dbFile: string): VulnScanner {
|
|
310
|
+
return {
|
|
311
|
+
async scan(input) {
|
|
312
|
+
const db = JSON.parse(readFileSync(dbFile, "utf8")) as AdvisoryDatabase | Advisory[];
|
|
313
|
+
return scanWithDatabase(input.sbom, db);
|
|
314
|
+
},
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
177
318
|
/**
|
|
178
319
|
* A real `VulnScanner` that writes the SBOM to a temp file and scans it with
|
|
179
320
|
* `grype` (default) or `trivy`, through the injectable `ProcessRunner`.
|
package/src/config.ts
CHANGED
|
@@ -11,6 +11,7 @@ import type { BuildParamProvenance } from "./provenance";
|
|
|
11
11
|
import { findProjectConfig } from "./project-root";
|
|
12
12
|
import { evaluateProjectConfig } from "./config-sandbox";
|
|
13
13
|
import { lexiconNames, pathLexiconRoot, registerLexiconDeclarations, type LexiconDeclaration } from "./lexicon-module";
|
|
14
|
+
import { decideConfigSchema, type DecideConfig } from "./op/decide-config";
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* One project-declared environment (chant #1166). Historically always a bare
|
|
@@ -194,6 +195,7 @@ export const ChantConfigSchema = z.object({
|
|
|
194
195
|
knowledge: z.object({
|
|
195
196
|
dir: z.string().min(1).optional(),
|
|
196
197
|
}).optional(),
|
|
198
|
+
decide: decideConfigSchema.optional(),
|
|
197
199
|
exclude: z.array(z.string().min(1)).optional(),
|
|
198
200
|
include: z.array(z.string().min(1)).optional(),
|
|
199
201
|
rootOnly: z.boolean().optional(),
|
|
@@ -449,6 +451,15 @@ export interface ChantConfig {
|
|
|
449
451
|
dir?: string;
|
|
450
452
|
};
|
|
451
453
|
|
|
454
|
+
/**
|
|
455
|
+
* The backends the `decide` Op activity calls (#2828): backend name, as a
|
|
456
|
+
* decision point's model decider names it, to `{ url, key?, timeoutMs? }`.
|
|
457
|
+
* A key is `{ env }` or a brokered capability, never a literal (SYS001). A
|
|
458
|
+
* `decide` step's own `backends` take the place of these. See
|
|
459
|
+
* `./op/decide-config.ts`.
|
|
460
|
+
*/
|
|
461
|
+
decide?: DecideConfig;
|
|
462
|
+
|
|
452
463
|
/**
|
|
453
464
|
* Globs, relative to the directory holding this config, naming files that
|
|
454
465
|
* source discovery must skip (#2519): `build`, `lint`, `list`, `explain`
|
package/src/lexicon-module.ts
CHANGED
|
@@ -24,7 +24,9 @@
|
|
|
24
24
|
* a project that names lexicons by package takes exactly the path it always did.
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
|
-
import {
|
|
27
|
+
import { createRequire } from "node:module";
|
|
28
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
29
|
+
import { pathToFileURL } from "node:url";
|
|
28
30
|
|
|
29
31
|
/**
|
|
30
32
|
* One `lexicons` entry: a package-backed name, or a name plus the module that
|
|
@@ -213,3 +215,56 @@ export function lexiconPackagesToInstall(
|
|
|
213
215
|
): string[] {
|
|
214
216
|
return names.filter((name) => !paths.has(name)).map((name) => `@intentius/chant-lexicon-${name}`);
|
|
215
217
|
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* chant#2845 — the npm package a bare specifier names: `@scope/name` for a
|
|
221
|
+
* scoped one, else its first segment. `undefined` for a relative or absolute
|
|
222
|
+
* path, which is not a package.
|
|
223
|
+
*/
|
|
224
|
+
export function packageNameOf(spec: string): string | undefined {
|
|
225
|
+
if (spec.startsWith(".") || isAbsolute(spec) || spec.startsWith("file:")) return undefined;
|
|
226
|
+
const parts = spec.split("/");
|
|
227
|
+
return spec.startsWith("@") ? parts.slice(0, 2).join("/") : parts[0];
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** True when `err` says the package `pkg` itself could not be found, not something it imports. */
|
|
231
|
+
function isPackageNotFound(err: unknown, pkg: string): boolean {
|
|
232
|
+
const code = (err as { code?: unknown } | null)?.code;
|
|
233
|
+
if (code !== "ERR_MODULE_NOT_FOUND" && code !== "MODULE_NOT_FOUND") return false;
|
|
234
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
235
|
+
return message.includes(`'${pkg}'`) || message.includes(`"${pkg}"`) || message.includes(`${pkg}/`);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* chant#2845 — resolve `spec` (a lexicon package or one of its subpaths) from
|
|
240
|
+
* `fromDir` instead of from chant's own install, or `undefined` when the
|
|
241
|
+
* project doesn't have it either.
|
|
242
|
+
*/
|
|
243
|
+
export function resolveFromProject(spec: string, fromDir: string = process.cwd()): string | undefined {
|
|
244
|
+
try {
|
|
245
|
+
return createRequire(join(fromDir, "package.json")).resolve(spec);
|
|
246
|
+
} catch {
|
|
247
|
+
return undefined;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* chant#2845 — import a lexicon package, or one of its subpaths, by its bare
|
|
253
|
+
* specifier. chant's own install is tried first, as a bare `import()` always
|
|
254
|
+
* did, so a lexicon chant can reach loads exactly as before. When the package
|
|
255
|
+
* isn't there, which is the case for a chant installed globally and a project
|
|
256
|
+
* that installs its lexicons in its own node_modules, it is resolved from
|
|
257
|
+
* `fromDir` (the project) and imported from there. Any other failure, and a
|
|
258
|
+
* package the project doesn't have either, rethrows the first error.
|
|
259
|
+
*/
|
|
260
|
+
export async function importLexiconPackage(spec: string, fromDir: string = process.cwd()): Promise<Record<string, unknown>> {
|
|
261
|
+
try {
|
|
262
|
+
return (await import(spec)) as Record<string, unknown>;
|
|
263
|
+
} catch (err) {
|
|
264
|
+
const pkg = packageNameOf(spec);
|
|
265
|
+
if (pkg === undefined || !isPackageNotFound(err, pkg)) throw err;
|
|
266
|
+
const resolved = resolveFromProject(spec, fromDir);
|
|
267
|
+
if (resolved === undefined) throw err;
|
|
268
|
+
return (await import(pathToFileURL(resolved).href)) as Record<string, unknown>;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* chant#2845 — a lexicon the project installs in its own node_modules loads
|
|
3
|
+
* when chant's own install can't reach it (a global chant): the plugin, its
|
|
4
|
+
* op activities and its activity contracts, resolved from the working
|
|
5
|
+
* directory after the bare import from chant's location fails.
|
|
6
|
+
*/
|
|
7
|
+
import { describe, test, expect, beforeAll, afterAll } from "vitest";
|
|
8
|
+
import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises";
|
|
9
|
+
import { tmpdir } from "node:os";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { importLexiconPackage, packageNameOf, resolveFromProject } from "./lexicon-module";
|
|
12
|
+
import { loadActivities } from "./op/activity-registry";
|
|
13
|
+
import { loadActivityContracts } from "./op/activity-contract-registry";
|
|
14
|
+
import { resolveLexiconVersions } from "./cli/plugins";
|
|
15
|
+
|
|
16
|
+
const NAME = "zz2845";
|
|
17
|
+
const PKG = `@intentius/chant-lexicon-${NAME}`;
|
|
18
|
+
let project: string;
|
|
19
|
+
|
|
20
|
+
beforeAll(async () => {
|
|
21
|
+
project = await mkdtemp(join(tmpdir(), "chant-2845-"));
|
|
22
|
+
const dir = join(project, "node_modules", "@intentius", `chant-lexicon-${NAME}`);
|
|
23
|
+
await mkdir(join(dir, "op"), { recursive: true });
|
|
24
|
+
await writeFile(
|
|
25
|
+
join(dir, "package.json"),
|
|
26
|
+
JSON.stringify({
|
|
27
|
+
name: PKG,
|
|
28
|
+
version: "9.9.9",
|
|
29
|
+
type: "module",
|
|
30
|
+
exports: { ".": { default: "./index.js" }, "./op/activities": { default: "./op/activities.js" }, "./op/activity-contracts": { default: "./op/contracts.js" } },
|
|
31
|
+
}),
|
|
32
|
+
);
|
|
33
|
+
await writeFile(join(dir, "index.js"), "export const marker = 'plugin';\n");
|
|
34
|
+
await writeFile(join(dir, "op", "activities.js"), "export async function zz2845Probe() { return { ok: true }; }\n");
|
|
35
|
+
// A contract is branded with a global symbol, so a plain object built here is one.
|
|
36
|
+
await writeFile(
|
|
37
|
+
join(dir, "op", "contracts.js"),
|
|
38
|
+
"export const zz2845ProbeContract = { [Symbol.for('chant.op.activityContract')]: true, name: 'zz2845Probe', args: {}, returns: {} };\n",
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
afterAll(async () => {
|
|
43
|
+
await rm(project, { recursive: true, force: true });
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe("importLexiconPackage (#2845)", () => {
|
|
47
|
+
test("a package chant can't reach is imported from the project", async () => {
|
|
48
|
+
await expect(import(PKG)).rejects.toThrow();
|
|
49
|
+
const mod = await importLexiconPackage(PKG, project);
|
|
50
|
+
expect(mod.marker).toBe("plugin");
|
|
51
|
+
const op = await importLexiconPackage(`${PKG}/op/activities`, project);
|
|
52
|
+
expect(typeof op.zz2845Probe).toBe("function");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("a package chant reaches loads from chant's install, whatever the directory", async () => {
|
|
56
|
+
const mod = await importLexiconPackage("zod", project);
|
|
57
|
+
expect(typeof mod.z).toBe("object");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("a package neither has fails with the bare import's own error", async () => {
|
|
61
|
+
await expect(importLexiconPackage("@intentius/chant-lexicon-nope-2845", project)).rejects.toThrow(/chant-lexicon-nope-2845/);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("a path is not looked up in the project", () => {
|
|
65
|
+
expect(packageNameOf("/abs/lexicon.ts")).toBeUndefined();
|
|
66
|
+
expect(packageNameOf("./lexicon.ts")).toBeUndefined();
|
|
67
|
+
expect(packageNameOf(`${PKG}/op/activities`)).toBe(PKG);
|
|
68
|
+
expect(resolveFromProject(PKG, project)).toMatch(/index\.js$/);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe("the loaders take the project's lexicon (#2845)", () => {
|
|
73
|
+
const inProject = async <T>(fn: () => Promise<T> | T): Promise<T> => {
|
|
74
|
+
const cwd = process.cwd();
|
|
75
|
+
process.chdir(project);
|
|
76
|
+
try {
|
|
77
|
+
return await fn();
|
|
78
|
+
} finally {
|
|
79
|
+
process.chdir(cwd);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
test("loadActivities finds its op activities", async () => {
|
|
84
|
+
const activities = await inProject(() => loadActivities([NAME]));
|
|
85
|
+
expect(activities.has("zz2845Probe")).toBe(true);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("loadActivityContracts finds its contracts", async () => {
|
|
89
|
+
const contracts = await inProject(() => loadActivityContracts([NAME]));
|
|
90
|
+
expect(contracts.has("zz2845Probe")).toBe(true);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("resolveLexiconVersions reads its version", async () => {
|
|
94
|
+
expect(await inProject(() => resolveLexiconVersions([NAME]))).toEqual({ [NAME]: "9.9.9" });
|
|
95
|
+
});
|
|
96
|
+
});
|
|
@@ -66,6 +66,12 @@ export interface ConvergeRuleOutcome {
|
|
|
66
66
|
action: "ran" | "reported" | "skipped-budget" | "skipped-flap" | "gated";
|
|
67
67
|
/** The dispatched Op name, for `action: "ran"` or `"gated"`. */
|
|
68
68
|
op?: string;
|
|
69
|
+
/**
|
|
70
|
+
* The resource the rule fired for, on a tick of a ConvergeOp with an
|
|
71
|
+
* observer step (#2778), which evaluates its rules once per resource. The
|
|
72
|
+
* dispatched Op reads it from `CHANT_CONVERGE_RESOURCE`.
|
|
73
|
+
*/
|
|
74
|
+
resource?: string;
|
|
69
75
|
/** The gate's signal name, for `action: "gated"`. */
|
|
70
76
|
gateName?: string;
|
|
71
77
|
/**
|
|
@@ -131,6 +137,14 @@ export function componentVerdicts(rows: readonly ComponentStatusRow[]): Converge
|
|
|
131
137
|
}));
|
|
132
138
|
}
|
|
133
139
|
|
|
140
|
+
/** One resource an observer step reported on a tick (#2778). */
|
|
141
|
+
export interface ConvergeResourceVerdict {
|
|
142
|
+
name: string;
|
|
143
|
+
status: "in-sync" | "drifted" | "unknown";
|
|
144
|
+
/** The observer's reason, capped to one line. */
|
|
145
|
+
detail?: string;
|
|
146
|
+
}
|
|
147
|
+
|
|
134
148
|
/** One immutable converge-tick record. */
|
|
135
149
|
export interface ConvergeTickRecord {
|
|
136
150
|
/** Schema version, so an incompatible future shape is detected before being misread. */
|
|
@@ -168,6 +182,12 @@ export interface ConvergeTickRecord {
|
|
|
168
182
|
* records written before #2027 have none; absent is not "no components".
|
|
169
183
|
*/
|
|
170
184
|
components?: ConvergeComponentVerdict[];
|
|
185
|
+
/**
|
|
186
|
+
* What a ConvergeOp's observer step reported this tick (#2778), one entry
|
|
187
|
+
* per resource: in its place of `components` for resources no lexicon
|
|
188
|
+
* observes. Absent on a tick observed through a lexicon environment.
|
|
189
|
+
*/
|
|
190
|
+
resources?: ConvergeResourceVerdict[];
|
|
171
191
|
/** Aggregate counts backing the tick's one log line. */
|
|
172
192
|
summary: {
|
|
173
193
|
drifted: number;
|