@intentius/chant 0.79.0 → 0.81.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/audit/core.d.ts.map +1 -1
- package/dist/audit/discover.d.ts +30 -0
- package/dist/audit/discover.d.ts.map +1 -1
- package/dist/audit/report-html.d.ts +5 -0
- package/dist/audit/report-html.d.ts.map +1 -1
- package/dist/audit/report-model.d.ts +13 -0
- package/dist/audit/report-model.d.ts.map +1 -1
- package/dist/audit/terraform-state.d.ts +13 -0
- package/dist/audit/terraform-state.d.ts.map +1 -1
- package/dist/cli/command-group.d.ts +6 -0
- package/dist/cli/command-group.d.ts.map +1 -1
- package/dist/cli/commands/audit.d.ts +44 -3
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon-examples.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon.d.ts +8 -0
- package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
- package/dist/cli/commands/doctor.d.ts.map +1 -1
- package/dist/cli/commands/import-agents.d.ts.map +1 -1
- package/dist/cli/commands/init.d.ts +6 -0
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/lint.d.ts.map +1 -1
- package/dist/cli/commands/onboard.d.ts.map +1 -1
- package/dist/cli/commands/update.d.ts +6 -0
- package/dist/cli/commands/update.d.ts.map +1 -1
- package/dist/cli/commands/vendor.d.ts +5 -0
- package/dist/cli/commands/vendor.d.ts.map +1 -1
- package/dist/cli/conflict-check.d.ts +6 -0
- package/dist/cli/conflict-check.d.ts.map +1 -1
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/components.d.ts.map +1 -1
- package/dist/cli/handlers/dev.d.ts.map +1 -1
- package/dist/cli/handlers/fan-out.d.ts.map +1 -1
- package/dist/cli/handlers/init.d.ts.map +1 -1
- package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
- package/dist/cli/handlers/misc.d.ts.map +1 -1
- package/dist/cli/handlers/operator.d.ts +7 -0
- package/dist/cli/handlers/operator.d.ts.map +1 -1
- package/dist/cli/handlers/promote.d.ts +20 -0
- package/dist/cli/handlers/promote.d.ts.map +1 -0
- package/dist/cli/handlers/run-generate.d.ts +39 -0
- package/dist/cli/handlers/run-generate.d.ts.map +1 -0
- package/dist/cli/handlers/run.d.ts +4 -1
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/handlers/vendor.d.ts +8 -3
- package/dist/cli/handlers/vendor.d.ts.map +1 -1
- package/dist/cli/main.d.ts +12 -0
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/mcp/op-tools.d.ts.map +1 -1
- package/dist/cli/plugins.d.ts +14 -0
- package/dist/cli/plugins.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +46 -1
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/codegen/fetch.d.ts +10 -0
- package/dist/codegen/fetch.d.ts.map +1 -1
- package/dist/components/capability-plugin-loader.d.ts.map +1 -1
- package/dist/components/cli-support.d.ts +3 -1
- package/dist/components/cli-support.d.ts.map +1 -1
- package/dist/components/discover.d.ts.map +1 -1
- package/dist/components/driver.d.ts +16 -2
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/fan-out-support.d.ts.map +1 -1
- package/dist/components/gate-plan.d.ts +40 -0
- package/dist/components/gate-plan.d.ts.map +1 -0
- package/dist/components/pilots/alb-ecs.pilot.d.ts +7 -1
- package/dist/components/pilots/alb-ecs.pilot.d.ts.map +1 -1
- package/dist/components/presets/ecs-fargate.d.ts +10 -4
- package/dist/components/presets/ecs-fargate.d.ts.map +1 -1
- package/dist/components/promote.d.ts +264 -0
- package/dist/components/promote.d.ts.map +1 -0
- package/dist/components/sandbox/driver.d.ts.map +1 -1
- package/dist/components/sandbox/run.d.ts.map +1 -1
- package/dist/components/verbs/build-archive.d.ts +22 -11
- package/dist/components/verbs/build-archive.d.ts.map +1 -1
- package/dist/components/verbs/run-agent.d.ts.map +1 -1
- package/dist/config-static.d.ts +59 -0
- package/dist/config-static.d.ts.map +1 -0
- package/dist/config.d.ts +104 -5
- package/dist/config.d.ts.map +1 -1
- package/dist/content-digest.d.ts +17 -0
- package/dist/content-digest.d.ts.map +1 -0
- package/dist/discovery/files.d.ts +48 -4
- package/dist/discovery/files.d.ts.map +1 -1
- package/dist/discovery/fold-import.d.ts +32 -1
- package/dist/discovery/fold-import.d.ts.map +1 -1
- package/dist/discovery/index.d.ts.map +1 -1
- package/dist/discovery/sandbox/driver.d.ts +20 -0
- package/dist/discovery/sandbox/driver.d.ts.map +1 -1
- package/dist/discovery/sandbox/fork.d.ts +20 -0
- package/dist/discovery/sandbox/fork.d.ts.map +1 -1
- package/dist/discovery/walk.d.ts +119 -0
- package/dist/discovery/walk.d.ts.map +1 -0
- package/dist/graph-ir.d.ts +17 -1
- package/dist/graph-ir.d.ts.map +1 -1
- package/dist/join-key.d.ts +31 -0
- package/dist/join-key.d.ts.map +1 -0
- package/dist/lexicon-module.d.ts +110 -0
- package/dist/lexicon-module.d.ts.map +1 -0
- package/dist/lexicon.d.ts +82 -1
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/build-ledger-store.d.ts.map +1 -1
- package/dist/lifecycle/digest.d.ts +5 -1
- package/dist/lifecycle/digest.d.ts.map +1 -1
- package/dist/lifecycle/gate-ledger.d.ts +20 -5
- package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
- package/dist/lifecycle/git.d.ts +64 -0
- package/dist/lifecycle/git.d.ts.map +1 -1
- package/dist/lifecycle/lease.d.ts +8 -1
- package/dist/lifecycle/lease.d.ts.map +1 -1
- package/dist/lifecycle/legacy-digest.d.ts +59 -0
- package/dist/lifecycle/legacy-digest.d.ts.map +1 -0
- package/dist/lifecycle/member-ledger.d.ts +67 -0
- package/dist/lifecycle/member-ledger.d.ts.map +1 -0
- package/dist/lifecycle/plan-digest.d.ts +4 -3
- package/dist/lifecycle/plan-digest.d.ts.map +1 -1
- package/dist/lifecycle/release-ledger.d.ts +42 -1
- package/dist/lifecycle/release-ledger.d.ts.map +1 -1
- package/dist/lifecycle/status.d.ts +90 -5
- package/dist/lifecycle/status.d.ts.map +1 -1
- package/dist/lint/rules/comp/comp003-mutating-no-rollback.d.ts +2 -3
- package/dist/lint/rules/comp/comp003-mutating-no-rollback.d.ts.map +1 -1
- package/dist/op/activities/activity-contracts.d.ts +32 -0
- package/dist/op/activities/activity-contracts.d.ts.map +1 -1
- package/dist/op/activities/index.d.ts +2 -0
- package/dist/op/activities/index.d.ts.map +1 -1
- package/dist/op/activities/propose-upgrade.d.ts +75 -0
- package/dist/op/activities/propose-upgrade.d.ts.map +1 -0
- package/dist/op/activity-contract-registry.d.ts.map +1 -1
- package/dist/op/activity-registry.d.ts.map +1 -1
- package/dist/op/discover.d.ts.map +1 -1
- package/dist/op/gate-approval.d.ts.map +1 -1
- package/dist/op/gate-name.d.ts +7 -0
- package/dist/op/gate-name.d.ts.map +1 -1
- package/dist/op/gate-summary.d.ts +16 -0
- package/dist/op/gate-summary.d.ts.map +1 -1
- package/dist/op/gate.d.ts +16 -2
- package/dist/op/gate.d.ts.map +1 -1
- package/dist/op/generate-pipeline.d.ts.map +1 -1
- package/dist/op/runtimes/local.d.ts.map +1 -1
- package/dist/project-root.d.ts +26 -0
- package/dist/project-root.d.ts.map +1 -1
- package/dist/workspace/__fixtures__/contract-repo.d.ts +37 -0
- package/dist/workspace/__fixtures__/contract-repo.d.ts.map +1 -0
- package/dist/workspace/__fixtures__/two-member-workspace.d.ts +11 -0
- package/dist/workspace/__fixtures__/two-member-workspace.d.ts.map +1 -0
- package/dist/workspace/checks/generated.d.ts +156 -0
- package/dist/workspace/checks/generated.d.ts.map +1 -0
- package/dist/workspace/checks/ledgers.d.ts +78 -0
- package/dist/workspace/checks/ledgers.d.ts.map +1 -0
- package/dist/workspace/checks/links.d.ts +25 -0
- package/dist/workspace/checks/links.d.ts.map +1 -0
- package/dist/workspace/checks/pipelines.d.ts +76 -0
- package/dist/workspace/checks/pipelines.d.ts.map +1 -0
- package/dist/workspace/checks.d.ts +161 -0
- package/dist/workspace/checks.d.ts.map +1 -0
- package/dist/workspace/compose-graph.d.ts +115 -0
- package/dist/workspace/compose-graph.d.ts.map +1 -0
- package/dist/workspace/compose-reports.d.ts +68 -0
- package/dist/workspace/compose-reports.d.ts.map +1 -0
- package/dist/workspace/declaration.d.ts +220 -0
- package/dist/workspace/declaration.d.ts.map +1 -0
- package/dist/workspace/declaration.schema.json +434 -0
- package/dist/workspace/generated-files.d.ts +65 -0
- package/dist/workspace/generated-files.d.ts.map +1 -0
- package/dist/workspace/glob.d.ts +13 -0
- package/dist/workspace/glob.d.ts.map +1 -0
- package/dist/workspace/graph-cli.d.ts +75 -0
- package/dist/workspace/graph-cli.d.ts.map +1 -0
- package/dist/workspace/init.d.ts +96 -0
- package/dist/workspace/init.d.ts.map +1 -0
- package/dist/workspace/jsonc.d.ts +38 -0
- package/dist/workspace/jsonc.d.ts.map +1 -0
- package/dist/workspace/kinds.d.ts +192 -0
- package/dist/workspace/kinds.d.ts.map +1 -0
- package/dist/workspace/lineage-check.d.ts +84 -0
- package/dist/workspace/lineage-check.d.ts.map +1 -0
- package/dist/workspace/lineage-cli.d.ts +31 -0
- package/dist/workspace/lineage-cli.d.ts.map +1 -0
- package/dist/workspace/lineage-init.d.ts +101 -0
- package/dist/workspace/lineage-init.d.ts.map +1 -0
- package/dist/workspace/lineage-lock.d.ts +258 -0
- package/dist/workspace/lineage-lock.d.ts.map +1 -0
- package/dist/workspace/lineage-merge.d.ts +14 -0
- package/dist/workspace/lineage-merge.d.ts.map +1 -0
- package/dist/workspace/lineage-migrations.d.ts +178 -0
- package/dist/workspace/lineage-migrations.d.ts.map +1 -0
- package/dist/workspace/lineage-update.d.ts +59 -0
- package/dist/workspace/lineage-update.d.ts.map +1 -0
- package/dist/workspace/lineage-upgrade-cli.d.ts +40 -0
- package/dist/workspace/lineage-upgrade-cli.d.ts.map +1 -0
- package/dist/workspace/lineage-upgrade.d.ts +146 -0
- package/dist/workspace/lineage-upgrade.d.ts.map +1 -0
- package/dist/workspace/lineage-vendor.d.ts +63 -0
- package/dist/workspace/lineage-vendor.d.ts.map +1 -0
- package/dist/workspace/lineage-version.d.ts +32 -0
- package/dist/workspace/lineage-version.d.ts.map +1 -0
- package/dist/workspace/links.d.ts +150 -0
- package/dist/workspace/links.d.ts.map +1 -0
- package/dist/workspace/ls.d.ts +99 -0
- package/dist/workspace/ls.d.ts.map +1 -0
- package/dist/workspace/member-commands.d.ts +139 -0
- package/dist/workspace/member-commands.d.ts.map +1 -0
- package/dist/workspace/member-handles.d.ts +54 -0
- package/dist/workspace/member-handles.d.ts.map +1 -0
- package/dist/workspace/member-pipeline.d.ts +143 -0
- package/dist/workspace/member-pipeline.d.ts.map +1 -0
- package/dist/workspace/member-run.d.ts +67 -0
- package/dist/workspace/member-run.d.ts.map +1 -0
- package/dist/workspace/reason-codes.d.ts +65 -0
- package/dist/workspace/reason-codes.d.ts.map +1 -0
- package/dist/workspace/record-source.d.ts +22 -0
- package/dist/workspace/record-source.d.ts.map +1 -0
- package/dist/workspace/records-cli.d.ts +81 -0
- package/dist/workspace/records-cli.d.ts.map +1 -0
- package/dist/workspace/records.d.ts +118 -0
- package/dist/workspace/records.d.ts.map +1 -0
- package/dist/workspace/root-refusal.d.ts +53 -0
- package/dist/workspace/root-refusal.d.ts.map +1 -0
- package/dist/workspace/status.d.ts +150 -0
- package/dist/workspace/status.d.ts.map +1 -0
- package/dist/workspace/template-manifest.d.ts +90 -0
- package/dist/workspace/template-manifest.d.ts.map +1 -0
- package/dist/workspace/tree.d.ts +36 -0
- package/dist/workspace/tree.d.ts.map +1 -0
- package/dist/workspace/trust/attestor.d.ts +67 -0
- package/dist/workspace/trust/attestor.d.ts.map +1 -0
- package/dist/workspace/trust/policy.d.ts +111 -0
- package/dist/workspace/trust/policy.d.ts.map +1 -0
- package/dist/workspace/trust/provenance.d.ts +67 -0
- package/dist/workspace/trust/provenance.d.ts.map +1 -0
- package/dist/workspace/trust/ssh-commit.d.ts +43 -0
- package/dist/workspace/trust/ssh-commit.d.ts.map +1 -0
- package/dist/workspace/trust/test-repo.d.ts +42 -0
- package/dist/workspace/trust/test-repo.d.ts.map +1 -0
- package/dist/workspace/trust/verify-cli.d.ts +9 -0
- package/dist/workspace/trust/verify-cli.d.ts.map +1 -0
- package/dist/workspace/trust/verify.d.ts +66 -0
- package/dist/workspace/trust/verify.d.ts.map +1 -0
- package/dist/workspace/which-chant.d.ts +58 -0
- package/dist/workspace/which-chant.d.ts.map +1 -0
- package/dist/workspace/workspace-kinds.schema.json +133 -0
- package/package.json +4 -3
- package/src/audit/core.ts +13 -2
- package/src/audit/discover.ts +102 -36
- package/src/audit/report-html.ts +3 -1
- package/src/audit/report-model.ts +14 -1
- package/src/audit/terraform-state.test.ts +81 -1
- package/src/audit/terraform-state.ts +25 -0
- package/src/cli/command-group.ts +7 -0
- package/src/cli/commands/audit-truncation.test.ts +167 -0
- package/src/cli/commands/audit.ts +132 -29
- package/src/cli/commands/check-lexicon-examples.ts +2 -1
- package/src/cli/commands/check-lexicon.ts +24 -0
- package/src/cli/commands/doctor.test.ts +30 -0
- package/src/cli/commands/doctor.ts +21 -0
- package/src/cli/commands/import-agents.ts +15 -4
- package/src/cli/commands/init.ts +76 -25
- package/src/cli/commands/lint.test.ts +52 -1
- package/src/cli/commands/lint.ts +41 -71
- package/src/cli/commands/onboard.ts +15 -0
- package/src/cli/commands/update.ts +18 -5
- package/src/cli/commands/vendor.ts +11 -6
- package/src/cli/conflict-check.test.ts +18 -1
- package/src/cli/conflict-check.ts +13 -0
- package/src/cli/discovery-skip.test.ts +168 -0
- package/src/cli/handlers/__snapshots__/build-member.test.ts.snap +38 -0
- package/src/cli/handlers/build-member.test.ts +127 -0
- package/src/cli/handlers/build.test.ts +11 -0
- package/src/cli/handlers/build.ts +35 -1
- package/src/cli/handlers/components.test.ts +127 -0
- package/src/cli/handlers/components.ts +70 -8
- package/src/cli/handlers/dev.ts +6 -1
- package/src/cli/handlers/fan-out.ts +9 -2
- package/src/cli/handlers/graph.test.ts +20 -0
- package/src/cli/handlers/graph.ts +4 -2
- package/src/cli/handlers/init.ts +54 -1
- package/src/cli/handlers/lifecycle.ts +2 -1
- package/src/cli/handlers/misc.ts +16 -0
- package/src/cli/handlers/operator.test.ts +68 -0
- package/src/cli/handlers/operator.ts +39 -10
- package/src/cli/handlers/promote.test.ts +377 -0
- package/src/cli/handlers/promote.ts +391 -0
- package/src/cli/handlers/run-generate-member.test.ts +83 -0
- package/src/cli/handlers/run-generate.test.ts +158 -0
- package/src/cli/handlers/run-generate.ts +168 -0
- package/src/cli/handlers/run.test.ts +47 -0
- package/src/cli/handlers/run.ts +41 -6
- package/src/cli/handlers/vendor.ts +147 -43
- package/src/cli/main.test.ts +137 -1
- package/src/cli/main.ts +290 -24
- package/src/cli/mcp/op-tools.ts +2 -1
- package/src/cli/path-lexicon-messages.test.ts +152 -0
- package/src/cli/plugins.ts +43 -5
- package/src/cli/registry.ts +46 -1
- package/src/cli/static-config-read.test.ts +208 -0
- package/src/codegen/fetch.test.ts +63 -1
- package/src/codegen/fetch.ts +30 -1
- package/src/components/__fixtures__/alb-ecs-service.json +0 -8
- package/src/components/capability-plugin-loader.ts +3 -1
- package/src/components/cli-support.ts +9 -3
- package/src/components/discover.test.ts +27 -57
- package/src/components/discover.ts +20 -51
- package/src/components/driver.test.ts +90 -12
- package/src/components/driver.ts +43 -5
- package/src/components/fan-out-support.ts +2 -1
- package/src/components/gate-plan.ts +53 -0
- package/src/components/pilots/README.md +1 -1
- package/src/components/pilots/alb-ecs.pilot.ts +7 -7
- package/src/components/presets/ecs-fargate.ts +10 -5
- package/src/components/promote.test.ts +645 -0
- package/src/components/promote.ts +641 -0
- package/src/components/sandbox/driver.ts +8 -8
- package/src/components/sandbox/run.ts +4 -1
- package/src/components/verbs/build-archive.ts +27 -21
- package/src/components/verbs/run-agent.ts +3 -5
- package/src/config-static.test.ts +136 -0
- package/src/config-static.ts +232 -0
- package/src/config.test.ts +40 -0
- package/src/config.ts +161 -11
- package/src/content-digest.test.ts +120 -0
- package/src/content-digest.ts +21 -0
- package/src/discovery/files.test.ts +127 -1
- package/src/discovery/files.ts +91 -59
- package/src/discovery/fold-import.ts +127 -9
- package/src/discovery/index.ts +1 -0
- package/src/discovery/sandbox/driver.ts +73 -35
- package/src/discovery/sandbox/fork-diagnostic.test.ts +136 -13
- package/src/discovery/sandbox/fork.ts +83 -43
- package/src/discovery/sandbox/path-lexicon-multifile.test.ts +285 -0
- package/src/discovery/sandbox/path-lexicon-parity.test.ts +162 -0
- package/src/discovery/walk.test.ts +263 -0
- package/src/discovery/walk.ts +334 -0
- package/src/effect-receipt.ts +2 -2
- package/src/graph-ir.ts +18 -1
- package/src/join-key.test.ts +25 -0
- package/src/join-key.ts +39 -0
- package/src/lexicon-module.test.ts +203 -0
- package/src/lexicon-module.ts +215 -0
- package/src/lexicon.ts +105 -1
- package/src/lifecycle/build-ledger-store.ts +13 -2
- package/src/lifecycle/digest.ts +9 -5
- package/src/lifecycle/gate-ledger.test.ts +20 -0
- package/src/lifecycle/gate-ledger.ts +28 -6
- package/src/lifecycle/git.test.ts +13 -1
- package/src/lifecycle/git.ts +221 -176
- package/src/lifecycle/lease.ts +25 -10
- package/src/lifecycle/legacy-digest.test.ts +206 -0
- package/src/lifecycle/legacy-digest.ts +103 -0
- package/src/lifecycle/member-ledger.test.ts +319 -0
- package/src/lifecycle/member-ledger.ts +145 -0
- package/src/lifecycle/plan-digest.ts +7 -7
- package/src/lifecycle/release-ledger.test.ts +20 -0
- package/src/lifecycle/release-ledger.ts +53 -3
- package/src/lifecycle/status.test.ts +151 -1
- package/src/lifecycle/status.ts +228 -7
- package/src/lint/rules/comp/comp003-mutating-no-rollback.ts +2 -3
- package/src/op/activities/activity-contracts.ts +37 -0
- package/src/op/activities/index.ts +10 -0
- package/src/op/activities/propose-upgrade.ts +206 -0
- package/src/op/activity-contract-registry.ts +18 -4
- package/src/op/activity-registry.ts +26 -0
- package/src/op/discover.test.ts +14 -4
- package/src/op/discover.ts +19 -23
- package/src/op/gate-approval.ts +3 -1
- package/src/op/gate-name.ts +8 -0
- package/src/op/gate-summary.test.ts +14 -0
- package/src/op/gate-summary.ts +25 -2
- package/src/op/gate.ts +36 -7
- package/src/op/generate-pipeline.test.ts +14 -5
- package/src/op/generate-pipeline.ts +2 -1
- package/src/op/runtimes/local.ts +2 -1
- package/src/project-root.test.ts +66 -1
- package/src/project-root.ts +48 -0
- package/src/workspace/__fixtures__/contract-repo.ts +100 -0
- package/src/workspace/__fixtures__/two-member-workspace.ts +35 -0
- package/src/workspace/behold-kinds.test.ts +162 -0
- package/src/workspace/check-contract.test.ts +127 -0
- package/src/workspace/check.schema.json +347 -0
- package/src/workspace/checks/generated.test.ts +287 -0
- package/src/workspace/checks/generated.ts +475 -0
- package/src/workspace/checks/ledgers.test.ts +139 -0
- package/src/workspace/checks/ledgers.ts +246 -0
- package/src/workspace/checks/links.ts +183 -0
- package/src/workspace/checks/pipelines.test.ts +133 -0
- package/src/workspace/checks/pipelines.ts +225 -0
- package/src/workspace/checks-repo.test.ts +71 -0
- package/src/workspace/checks.test.ts +450 -0
- package/src/workspace/checks.ts +461 -0
- package/src/workspace/compose-graph.test.ts +146 -0
- package/src/workspace/compose-graph.ts +228 -0
- package/src/workspace/compose-reports.test.ts +109 -0
- package/src/workspace/compose-reports.ts +186 -0
- package/src/workspace/declaration.schema.json +434 -0
- package/src/workspace/declaration.test.ts +347 -0
- package/src/workspace/declaration.ts +623 -0
- package/src/workspace/generated-files.test.ts +114 -0
- package/src/workspace/generated-files.ts +94 -0
- package/src/workspace/glob.ts +49 -0
- package/src/workspace/graph-cli.ts +220 -0
- package/src/workspace/graph-contract.test.ts +204 -0
- package/src/workspace/graph.schema.json +662 -0
- package/src/workspace/init.test.ts +223 -0
- package/src/workspace/init.ts +585 -0
- package/src/workspace/jsonc.test.ts +65 -0
- package/src/workspace/jsonc.ts +231 -0
- package/src/workspace/kinds.test.ts +288 -0
- package/src/workspace/kinds.ts +506 -0
- package/src/workspace/lineage-check.ts +264 -0
- package/src/workspace/lineage-cli.ts +114 -0
- package/src/workspace/lineage-init.test.ts +231 -0
- package/src/workspace/lineage-init.ts +365 -0
- package/src/workspace/lineage-lock.test.ts +224 -0
- package/src/workspace/lineage-lock.ts +357 -0
- package/src/workspace/lineage-merge.ts +44 -0
- package/src/workspace/lineage-migrations.test.ts +178 -0
- package/src/workspace/lineage-migrations.ts +343 -0
- package/src/workspace/lineage-update.ts +160 -0
- package/src/workspace/lineage-upgrade-cli.ts +151 -0
- package/src/workspace/lineage-upgrade.test.ts +474 -0
- package/src/workspace/lineage-upgrade.ts +624 -0
- package/src/workspace/lineage-vendor.test.ts +126 -0
- package/src/workspace/lineage-vendor.ts +215 -0
- package/src/workspace/lineage-version.ts +127 -0
- package/src/workspace/links.test.ts +344 -0
- package/src/workspace/links.ts +331 -0
- package/src/workspace/ls-contract.test.ts +200 -0
- package/src/workspace/ls.schema.json +190 -0
- package/src/workspace/ls.ts +254 -0
- package/src/workspace/member-commands.e2e.test.ts +244 -0
- package/src/workspace/member-commands.test.ts +217 -0
- package/src/workspace/member-commands.ts +540 -0
- package/src/workspace/member-handles.test.ts +76 -0
- package/src/workspace/member-handles.ts +228 -0
- package/src/workspace/member-pipeline.test.ts +135 -0
- package/src/workspace/member-pipeline.ts +317 -0
- package/src/workspace/member-run.ts +146 -0
- package/src/workspace/read-contract.test.ts +129 -0
- package/src/workspace/reason-codes.test.ts +113 -0
- package/src/workspace/reason-codes.ts +81 -0
- package/src/workspace/record-source.ts +117 -0
- package/src/workspace/records-cli.ts +198 -0
- package/src/workspace/records-contract.test.ts +93 -0
- package/src/workspace/records.schema.json +150 -0
- package/src/workspace/records.test.ts +243 -0
- package/src/workspace/records.ts +384 -0
- package/src/workspace/root-refusal.ts +97 -0
- package/src/workspace/status-contract.test.ts +308 -0
- package/src/workspace/status.schema.json +210 -0
- package/src/workspace/status.ts +412 -0
- package/src/workspace/template-manifest.test.ts +79 -0
- package/src/workspace/template-manifest.ts +221 -0
- package/src/workspace/tree.ts +132 -0
- package/src/workspace/trust/attestor.ts +93 -0
- package/src/workspace/trust/policy.ts +306 -0
- package/src/workspace/trust/provenance.ts +181 -0
- package/src/workspace/trust/ssh-commit.ts +138 -0
- package/src/workspace/trust/test-repo.ts +138 -0
- package/src/workspace/trust/trust.test.ts +397 -0
- package/src/workspace/trust/verify-cli.ts +56 -0
- package/src/workspace/trust/verify.ts +194 -0
- package/src/workspace/which-chant.test.ts +117 -0
- package/src/workspace/which-chant.ts +151 -0
- package/src/workspace/workspace-kinds.schema.json +133 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The composed workspace IR that `chant workspace graph` prints (#2537, #2524
|
|
3
|
+
* D8, ws-009, ws-018, ws-022).
|
|
4
|
+
*
|
|
5
|
+
* Each member is read through its own `chant graph --format ir`, under its
|
|
6
|
+
* own toolchain. This module takes those documents and composes them:
|
|
7
|
+
*
|
|
8
|
+
* - Every node id becomes `<member>/<id>`. Member names can't hold `/`, and
|
|
9
|
+
* `::` keeps its meaning for stacks inside a member (`app/web::Bucket`).
|
|
10
|
+
* Edge ends, `$ref` values in attributes, `compositeInstance`,
|
|
11
|
+
* `runtimeOwner` and export and import nodes are rewritten the same way.
|
|
12
|
+
* - `groups.byMember` maps each member to its node ids. `byLexicon` and
|
|
13
|
+
* `byComposite` keep their keys (a lexicon or composite type is the same
|
|
14
|
+
* thing in every member) and merge the ids. `byStack`, `byContainer` and
|
|
15
|
+
* `byWave` name things inside one member, so their keys are prefixed too.
|
|
16
|
+
* - `links` holds the member links (#2524 D6, #2539): the declared links and
|
|
17
|
+
* the joins inferred from members' `imports` and `exports` with the core
|
|
18
|
+
* `joinKey()`, labelled `exact` or `folded` (`./links.ts`). A declared link
|
|
19
|
+
* suppresses the inferred edge it covers. `records` is the section record
|
|
20
|
+
* kinds fill (#2524 D4), empty so far.
|
|
21
|
+
*
|
|
22
|
+
* A member's IR with no `version` field comes from a chant older than #2529.
|
|
23
|
+
* It is version 1, and it is upgraded in place by stamping that version. An IR
|
|
24
|
+
* newer than {@link GRAPH_IR_VERSION} can't be read safely, so that member is
|
|
25
|
+
* listed with a reason code and left out, like any other member that can't be
|
|
26
|
+
* read (#2524 D15).
|
|
27
|
+
*
|
|
28
|
+
* `chant graph` itself is untouched: this is the only place members compose.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import type { Declaration } from "./declaration";
|
|
32
|
+
import type { KindRegistry } from "./kinds";
|
|
33
|
+
import { graphLinks, type LinkTableRow } from "./links";
|
|
34
|
+
import type { ReasonCode } from "./reason-codes";
|
|
35
|
+
import { GRAPH_IR_VERSION, type GraphIR, type IRExport, type IRGroups, type IRImport, type IREdge, type IRNode } from "../graph-ir";
|
|
36
|
+
|
|
37
|
+
/** The version of the composed document `chant workspace graph` writes. */
|
|
38
|
+
export const WORKSPACE_GRAPH_VERSION = 1;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Why a member is missing from the composed graph, or from any per-member
|
|
42
|
+
* run. Closed, like the `ls` reason codes: a new code is a contract change.
|
|
43
|
+
*/
|
|
44
|
+
export const MEMBER_RUN_REASON_CODES = [
|
|
45
|
+
/** The member's kind is one the workspace commands don't run, such as `other`. */
|
|
46
|
+
"kind-not-run",
|
|
47
|
+
/** The member's directory does not exist. */
|
|
48
|
+
"dir-missing",
|
|
49
|
+
/** No installed kind has the member's kind name. */
|
|
50
|
+
"unknown-kind",
|
|
51
|
+
/** The directory is not what its kind reads. */
|
|
52
|
+
"kind-probe-failed",
|
|
53
|
+
/** The member's command exited with a failure. */
|
|
54
|
+
"command-failed",
|
|
55
|
+
/** The member's command printed something that isn't the document asked for. */
|
|
56
|
+
"output-unreadable",
|
|
57
|
+
/** The member's IR has a version this chant can't read. */
|
|
58
|
+
"ir-version-unsupported",
|
|
59
|
+
] as const satisfies readonly ReasonCode[];
|
|
60
|
+
export type MemberRunReasonCode = (typeof MEMBER_RUN_REASON_CODES)[number];
|
|
61
|
+
|
|
62
|
+
export interface MemberReason {
|
|
63
|
+
code: MemberRunReasonCode;
|
|
64
|
+
message: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface ComposedMember {
|
|
68
|
+
name: string;
|
|
69
|
+
dir: string;
|
|
70
|
+
kind: string;
|
|
71
|
+
status: "composed" | "skipped" | "failed";
|
|
72
|
+
reason: MemberReason | null;
|
|
73
|
+
/** The chant version that read the member, when its toolchain said. */
|
|
74
|
+
chant: string | null;
|
|
75
|
+
/** The IR version the member printed; `null` when it printed none and was upgraded as version 1. */
|
|
76
|
+
irVersion: number | null;
|
|
77
|
+
/** Whole-read facts the member's IR carried (`meta`, `pipeline`), kept apart from the composed sections. */
|
|
78
|
+
meta?: Record<string, unknown>;
|
|
79
|
+
pipeline?: unknown;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export type ComposedNode = IRNode & { member: string };
|
|
83
|
+
export type ComposedEdge = IREdge & { member: string };
|
|
84
|
+
|
|
85
|
+
export interface WorkspaceGraph {
|
|
86
|
+
version: number;
|
|
87
|
+
workspace: { name: string; root: string };
|
|
88
|
+
members: ComposedMember[];
|
|
89
|
+
nodes: ComposedNode[];
|
|
90
|
+
edges: ComposedEdge[];
|
|
91
|
+
groups: IRGroups & { byMember: Record<string, string[]> };
|
|
92
|
+
exports: (IRExport & { member: string })[];
|
|
93
|
+
imports: (IRImport & { member: string })[];
|
|
94
|
+
derivedAttrs?: Record<string, string[]>;
|
|
95
|
+
/** Member links (#2524 D6, #2539), declared and inferred. Empty when no declaration is given. */
|
|
96
|
+
links: LinkTableRow[];
|
|
97
|
+
/** Record sections (#2524 D4). Empty until record kinds join the graph. */
|
|
98
|
+
records: unknown[];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Read one member's `chant graph --format ir` output, upgrading an unversioned (v1) IR in place. */
|
|
102
|
+
export function readMemberIr(text: string): { ir: GraphIR; irVersion: number | null } | { reason: MemberReason } {
|
|
103
|
+
let raw: unknown;
|
|
104
|
+
try {
|
|
105
|
+
raw = JSON.parse(text);
|
|
106
|
+
} catch {
|
|
107
|
+
return { reason: { code: "output-unreadable", message: "chant graph --format ir printed something that is not JSON" } };
|
|
108
|
+
}
|
|
109
|
+
if (raw === null || typeof raw !== "object" || !Array.isArray((raw as GraphIR).nodes) || !Array.isArray((raw as GraphIR).edges)) {
|
|
110
|
+
return { reason: { code: "output-unreadable", message: "chant graph --format ir printed JSON with no nodes and edges" } };
|
|
111
|
+
}
|
|
112
|
+
const ir = raw as GraphIR;
|
|
113
|
+
const printed = ir.version;
|
|
114
|
+
if (printed === undefined) {
|
|
115
|
+
ir.version = 1;
|
|
116
|
+
} else if (typeof printed !== "number" || printed > GRAPH_IR_VERSION) {
|
|
117
|
+
return {
|
|
118
|
+
reason: {
|
|
119
|
+
code: "ir-version-unsupported",
|
|
120
|
+
message: `the member's chant printed IR version ${String(printed)}, and this chant reads up to version ${GRAPH_IR_VERSION}`,
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
ir.groups ??= {};
|
|
125
|
+
return { ir, irVersion: printed === undefined ? null : printed };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export interface ComposeInput {
|
|
129
|
+
member: ComposedMember;
|
|
130
|
+
/** Present for a member whose IR was read. */
|
|
131
|
+
ir?: GraphIR;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const prefix = (member: string, id: string): string => `${member}/${id}`;
|
|
135
|
+
|
|
136
|
+
function prefixRefs(value: unknown, member: string): unknown {
|
|
137
|
+
if (Array.isArray(value)) return value.map((v) => prefixRefs(v, member));
|
|
138
|
+
if (value === null || typeof value !== "object") return value;
|
|
139
|
+
const out: Record<string, unknown> = {};
|
|
140
|
+
for (const [k, v] of Object.entries(value)) {
|
|
141
|
+
out[k] = k === "$ref" && typeof v === "string" ? prefix(member, v) : prefixRefs(v, member);
|
|
142
|
+
}
|
|
143
|
+
return out;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function mergeInto(target: Record<string, string[]>, key: string, ids: string[]): void {
|
|
147
|
+
(target[key] ??= []).push(...ids);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function sorted(rec: Record<string, string[]>): Record<string, string[]> {
|
|
151
|
+
const out: Record<string, string[]> = {};
|
|
152
|
+
for (const k of Object.keys(rec).sort()) out[k] = [...rec[k]].sort();
|
|
153
|
+
return out;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Compose the members' IRs into the workspace document. Members keep the
|
|
158
|
+
* order they are given in. With `links`, the `links` section is filled from
|
|
159
|
+
* the declaration; `kinds` lets a member that isn't composed but whose kind
|
|
160
|
+
* lists its outputs (`other`, kinds from a package) be a link target.
|
|
161
|
+
*/
|
|
162
|
+
export function composeWorkspaceGraph(
|
|
163
|
+
workspace: { name: string; root: string },
|
|
164
|
+
inputs: ComposeInput[],
|
|
165
|
+
links?: { declaration: Declaration; kinds?: KindRegistry },
|
|
166
|
+
): WorkspaceGraph {
|
|
167
|
+
const nodes: ComposedNode[] = [];
|
|
168
|
+
const edges: ComposedEdge[] = [];
|
|
169
|
+
const exports: WorkspaceGraph["exports"] = [];
|
|
170
|
+
const imports: WorkspaceGraph["imports"] = [];
|
|
171
|
+
const byMember: Record<string, string[]> = {};
|
|
172
|
+
const shared: Record<"byLexicon" | "byComposite", Record<string, string[]>> = { byLexicon: {}, byComposite: {} };
|
|
173
|
+
const scoped: Record<"byStack" | "byContainer" | "byWave", Record<string, string[]>> = { byStack: {}, byContainer: {}, byWave: {} };
|
|
174
|
+
const derivedAttrs: Record<string, Set<string>> = {};
|
|
175
|
+
|
|
176
|
+
for (const { member, ir } of inputs) {
|
|
177
|
+
if (!ir) continue;
|
|
178
|
+
const m = member.name;
|
|
179
|
+
const ids: string[] = [];
|
|
180
|
+
for (const node of ir.nodes) {
|
|
181
|
+
const composed: ComposedNode = { ...node, id: prefix(m, node.id), member: m, attrs: prefixRefs(node.attrs ?? {}, m) as Record<string, unknown> };
|
|
182
|
+
if (node.compositeInstance) composed.compositeInstance = prefix(m, node.compositeInstance);
|
|
183
|
+
if (node.runtimeOwner) composed.runtimeOwner = prefix(m, node.runtimeOwner);
|
|
184
|
+
nodes.push(composed);
|
|
185
|
+
ids.push(composed.id);
|
|
186
|
+
}
|
|
187
|
+
byMember[m] = ids.sort();
|
|
188
|
+
for (const edge of ir.edges) edges.push({ ...edge, from: prefix(m, edge.from), to: prefix(m, edge.to), member: m });
|
|
189
|
+
for (const key of ["byLexicon", "byComposite"] as const) {
|
|
190
|
+
for (const [k, v] of Object.entries(ir.groups[key] ?? {})) mergeInto(shared[key], k, v.map((id) => prefix(m, id)));
|
|
191
|
+
}
|
|
192
|
+
for (const key of ["byStack", "byContainer", "byWave"] as const) {
|
|
193
|
+
for (const [k, v] of Object.entries(ir.groups[key] ?? {})) mergeInto(scoped[key], prefix(m, k), v.map((id) => prefix(m, id)));
|
|
194
|
+
}
|
|
195
|
+
for (const e of ir.exports ?? []) exports.push({ ...e, ...(e.node ? { node: prefix(m, e.node) } : {}), member: m });
|
|
196
|
+
for (const i of ir.imports ?? []) imports.push({ ...i, node: prefix(m, i.node), member: m });
|
|
197
|
+
for (const [kind, attrs] of Object.entries(ir.derivedAttrs ?? {})) for (const a of attrs) (derivedAttrs[kind] ??= new Set()).add(a);
|
|
198
|
+
if (ir.meta) member.meta = ir.meta;
|
|
199
|
+
if (ir.pipeline) member.pipeline = ir.pipeline;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
nodes.sort((a, b) => a.id.localeCompare(b.id));
|
|
203
|
+
const edgeKey = (e: ComposedEdge) => `${e.from}\u0000${e.to}\u0000${e.viaAttr ?? ""}`;
|
|
204
|
+
edges.sort((a, b) => edgeKey(a).localeCompare(edgeKey(b)));
|
|
205
|
+
|
|
206
|
+
const groups: WorkspaceGraph["groups"] = { byMember };
|
|
207
|
+
for (const key of ["byLexicon", "byComposite"] as const) if (Object.keys(shared[key]).length) groups[key] = sorted(shared[key]);
|
|
208
|
+
for (const key of ["byStack", "byContainer", "byWave"] as const) if (Object.keys(scoped[key]).length) groups[key] = sorted(scoped[key]);
|
|
209
|
+
|
|
210
|
+
const doc: WorkspaceGraph = {
|
|
211
|
+
version: WORKSPACE_GRAPH_VERSION,
|
|
212
|
+
workspace,
|
|
213
|
+
members: inputs.map((i) => i.member),
|
|
214
|
+
nodes,
|
|
215
|
+
edges,
|
|
216
|
+
groups,
|
|
217
|
+
exports,
|
|
218
|
+
imports,
|
|
219
|
+
links: links
|
|
220
|
+
? graphLinks(links.declaration, { composed: inputs.filter((i) => i.ir).map((i) => i.member.name), exports, imports }, links.kinds)
|
|
221
|
+
: [],
|
|
222
|
+
records: [],
|
|
223
|
+
};
|
|
224
|
+
if (Object.keys(derivedAttrs).length) {
|
|
225
|
+
doc.derivedAttrs = Object.fromEntries(Object.keys(derivedAttrs).sort().map((k) => [k, [...derivedAttrs[k]].sort()]));
|
|
226
|
+
}
|
|
227
|
+
return doc;
|
|
228
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Merging per-member lint and audit output (#2537, #2524 D16): one SARIF run
|
|
3
|
+
* per member, and a `member` field on every audit finding.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { pathToFileURL } from "node:url";
|
|
7
|
+
import { describe, expect, test } from "vitest";
|
|
8
|
+
import { mergeAudit, mergeSarif, type MemberOutput } from "./compose-reports";
|
|
9
|
+
|
|
10
|
+
const sarif = (uris: string[]) =>
|
|
11
|
+
JSON.stringify({
|
|
12
|
+
version: "2.1.0",
|
|
13
|
+
runs: [
|
|
14
|
+
{
|
|
15
|
+
tool: { driver: { name: "chant", rules: [{ id: "COR001" }] } },
|
|
16
|
+
results: uris.map((uri) => ({
|
|
17
|
+
ruleId: "COR001",
|
|
18
|
+
ruleIndex: 0,
|
|
19
|
+
message: { text: "m" },
|
|
20
|
+
locations: [{ physicalLocation: { artifactLocation: { uri } } }],
|
|
21
|
+
})),
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const out = (id: string, dir: string, stdout: string, exitCode = 0, stderr = ""): MemberOutput => ({
|
|
27
|
+
id,
|
|
28
|
+
member: id.split(":")[0],
|
|
29
|
+
dir,
|
|
30
|
+
exitCode,
|
|
31
|
+
stdout,
|
|
32
|
+
stderr,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe("mergeSarif", () => {
|
|
36
|
+
const root = "/work/acme";
|
|
37
|
+
const log = mergeSarif(
|
|
38
|
+
[
|
|
39
|
+
out("platform", ".", sarif([pathToFileURL("/work/acme/src/a.ts").href])),
|
|
40
|
+
out("api", "services/api", sarif([pathToFileURL("/work/acme/services/api/src/b.ts").href, "src/c.ts"])),
|
|
41
|
+
out("examples:examples/demo", "examples/demo", "Error: boom", 1, "stack trace"),
|
|
42
|
+
],
|
|
43
|
+
root,
|
|
44
|
+
) as { runs: Array<Record<string, unknown>> };
|
|
45
|
+
|
|
46
|
+
test("writes one run per member, told apart by automationDetails.id", () => {
|
|
47
|
+
expect(log.runs).toHaveLength(3);
|
|
48
|
+
expect(log.runs.map((r) => (r.automationDetails as { id: string }).id)).toEqual(["platform/", "api/", "examples:examples/demo/"]);
|
|
49
|
+
expect(log.runs[1].properties).toEqual({ member: "api", dir: "services/api" });
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("makes artifact URIs relative to the workspace root", () => {
|
|
53
|
+
const uris = (i: number) =>
|
|
54
|
+
(log.runs[i].results as Array<{ locations: Array<{ physicalLocation: { artifactLocation: { uri: string } } }> }>).map(
|
|
55
|
+
(r) => r.locations[0].physicalLocation.artifactLocation.uri,
|
|
56
|
+
);
|
|
57
|
+
expect(uris(0)).toEqual(["src/a.ts"]);
|
|
58
|
+
expect(uris(1)).toEqual(["services/api/src/b.ts", "services/api/src/c.ts"]);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("gives a member whose lint printed no SARIF a failed invocation and no results", () => {
|
|
62
|
+
const failed = log.runs[2] as { results: unknown[]; invocations: Array<{ executionSuccessful: boolean; exitCode: number }> };
|
|
63
|
+
expect(failed.results).toEqual([]);
|
|
64
|
+
expect(failed.invocations[0]).toMatchObject({ executionSuccessful: false, exitCode: 1 });
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("folds a member's extra runs into its one run", () => {
|
|
68
|
+
const two = JSON.parse(sarif(["x.ts"]));
|
|
69
|
+
two.runs.push(JSON.parse(sarif(["y.ts"])).runs[0]);
|
|
70
|
+
const merged = mergeSarif([out("m", "m", JSON.stringify(two))], root) as { runs: Array<{ results: Array<Record<string, unknown>> }> };
|
|
71
|
+
expect(merged.runs).toHaveLength(1);
|
|
72
|
+
expect(merged.runs[0].results).toHaveLength(2);
|
|
73
|
+
expect(merged.runs[0].results[1].ruleIndex).toBeUndefined();
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe("mergeAudit", () => {
|
|
78
|
+
const report = (files: string[]) =>
|
|
79
|
+
JSON.stringify({
|
|
80
|
+
schemaVersion: "1.0",
|
|
81
|
+
status: "ok",
|
|
82
|
+
summary: { total: files.length },
|
|
83
|
+
findings: files.map((file) => ({ checkId: "WK8202", severity: "error", message: "m", file })),
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("adds a member field to every finding and keeps the rest", () => {
|
|
87
|
+
const doc = mergeAudit([out("api", "services/api", report(["manifests/pod.yaml"]))], { name: "acme", root: "/w" });
|
|
88
|
+
expect(doc.schemaVersion).toBe("1.0");
|
|
89
|
+
expect(doc.findings).toEqual([{ checkId: "WK8202", severity: "error", message: "m", file: "manifests/pod.yaml", member: "api" }]);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("leaves findings in another member's directory to that member", () => {
|
|
93
|
+
const root = { ...out("platform", ".", report(["ci.yml", "services/api/manifests/pod.yaml"])), exclude: ["services/api"] };
|
|
94
|
+
const doc = mergeAudit([root], { name: "acme", root: "/w" });
|
|
95
|
+
expect(doc.findings.map((f) => f.file)).toEqual(["ci.yml"]);
|
|
96
|
+
expect(doc.members[0].leftToMembers).toBe(1);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("records a member with nothing to audit, and one whose audit failed", () => {
|
|
100
|
+
const doc = mergeAudit(
|
|
101
|
+
[out("empty", "e", "No auditable files found under ..\n"), out("broken", "b", "", 1, "Error: boom")],
|
|
102
|
+
{ name: "acme", root: "/w" },
|
|
103
|
+
);
|
|
104
|
+
expect(doc.members.map((m) => [m.member, m.status, m.note ?? m.error])).toEqual([
|
|
105
|
+
["empty", "ok", "No auditable files found under .."],
|
|
106
|
+
["broken", "failed", "Error: boom"],
|
|
107
|
+
]);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Merging per-member lint and audit output (#2537, #2524 D16, ws-025, ws-026).
|
|
3
|
+
*
|
|
4
|
+
* `chant workspace lint --format sarif` prints one SARIF log with one run per
|
|
5
|
+
* member, each run being what that member's own `chant lint --format sarif`
|
|
6
|
+
* printed. `chant workspace audit --format json` prints one document whose
|
|
7
|
+
* findings each carry a `member` field. Both keep what the member's chant
|
|
8
|
+
* said and only add where it came from.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { isAbsolute, relative } from "node:path";
|
|
12
|
+
import { fileURLToPath } from "node:url";
|
|
13
|
+
|
|
14
|
+
const SARIF_SCHEMA = "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json";
|
|
15
|
+
|
|
16
|
+
/** One member's output, as the merge sees it. */
|
|
17
|
+
export interface MemberOutput {
|
|
18
|
+
/** The member's name, or `<group>:<dir>` for an example-group match. */
|
|
19
|
+
id: string;
|
|
20
|
+
/** The member or group name. */
|
|
21
|
+
member: string;
|
|
22
|
+
/** Relative to the workspace root, `"."` for the root member. */
|
|
23
|
+
dir: string;
|
|
24
|
+
exitCode: number;
|
|
25
|
+
stdout: string;
|
|
26
|
+
stderr: string;
|
|
27
|
+
/**
|
|
28
|
+
* Directories (relative to the member) that belong to other members. Set
|
|
29
|
+
* for member `.`: its audit walks the whole root, and findings in these
|
|
30
|
+
* directories are reported by the members they belong to.
|
|
31
|
+
*/
|
|
32
|
+
exclude?: string[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
type Json = Record<string, unknown>;
|
|
36
|
+
|
|
37
|
+
/** A root-relative path for a URI a member's SARIF wrote. Other URIs pass through. */
|
|
38
|
+
function rootRelativeUri(uri: string, memberDir: string, root: string): string {
|
|
39
|
+
let abs: string | undefined;
|
|
40
|
+
if (uri.startsWith("file://")) {
|
|
41
|
+
try {
|
|
42
|
+
abs = fileURLToPath(uri);
|
|
43
|
+
} catch {
|
|
44
|
+
return uri;
|
|
45
|
+
}
|
|
46
|
+
} else if (isAbsolute(uri)) {
|
|
47
|
+
abs = uri;
|
|
48
|
+
}
|
|
49
|
+
if (abs !== undefined) {
|
|
50
|
+
const rel = relative(root, abs).split("\\").join("/");
|
|
51
|
+
return rel.startsWith("..") || isAbsolute(rel) ? uri : rel;
|
|
52
|
+
}
|
|
53
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(uri)) return uri;
|
|
54
|
+
return memberDir === "." ? uri : `${memberDir}/${uri}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function rewriteUris(value: unknown, memberDir: string, root: string): unknown {
|
|
58
|
+
if (Array.isArray(value)) return value.map((v) => rewriteUris(v, memberDir, root));
|
|
59
|
+
if (value === null || typeof value !== "object") return value;
|
|
60
|
+
const out: Json = {};
|
|
61
|
+
for (const [k, v] of Object.entries(value)) {
|
|
62
|
+
out[k] = k === "artifactLocation" && v && typeof v === "object" && typeof (v as Json).uri === "string"
|
|
63
|
+
? { ...(v as Json), uri: rootRelativeUri((v as Json).uri as string, memberDir, root) }
|
|
64
|
+
: rewriteUris(v, memberDir, root);
|
|
65
|
+
}
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** A run for a member whose lint printed no SARIF: no results, and the failure as a notification. */
|
|
70
|
+
function failedRun(unit: MemberOutput, why: string): Json {
|
|
71
|
+
const tail = unit.stderr.trim().split("\n").slice(-20).join("\n");
|
|
72
|
+
return {
|
|
73
|
+
tool: { driver: { name: "chant", informationUri: "https://intentius.io/chant" } },
|
|
74
|
+
automationDetails: { id: `${unit.id}/` },
|
|
75
|
+
properties: { member: unit.member, dir: unit.dir },
|
|
76
|
+
invocations: [
|
|
77
|
+
{
|
|
78
|
+
executionSuccessful: false,
|
|
79
|
+
exitCode: unit.exitCode,
|
|
80
|
+
toolExecutionNotifications: [{ level: "error", message: { text: tail ? `${why}\n${tail}` : why } }],
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
results: [],
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* One SARIF log with one run per member. A member's own log normally holds
|
|
89
|
+
* one run; if it holds more, their results are folded into the first so the
|
|
90
|
+
* member still has exactly one. Artifact URIs become relative to the
|
|
91
|
+
* workspace root, and each run's `automationDetails.id` is `<member>/`, the
|
|
92
|
+
* SARIF way to tell runs of one tool apart.
|
|
93
|
+
*/
|
|
94
|
+
export function mergeSarif(units: MemberOutput[], root: string): Json {
|
|
95
|
+
const runs: Json[] = [];
|
|
96
|
+
for (const unit of units) {
|
|
97
|
+
let log: Json;
|
|
98
|
+
try {
|
|
99
|
+
log = JSON.parse(unit.stdout) as Json;
|
|
100
|
+
} catch {
|
|
101
|
+
runs.push(failedRun(unit, `chant lint exited ${unit.exitCode} and printed no SARIF`));
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
const own = Array.isArray(log.runs) ? (log.runs as Json[]) : [];
|
|
105
|
+
if (own.length === 0) {
|
|
106
|
+
runs.push(failedRun(unit, "chant lint printed a SARIF log with no runs"));
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
const [first, ...rest] = own.map((r) => rewriteUris(r, unit.dir, root) as Json);
|
|
110
|
+
const results = [...((first.results as unknown[]) ?? [])];
|
|
111
|
+
for (const r of rest) {
|
|
112
|
+
for (const res of (r.results as Json[]) ?? []) {
|
|
113
|
+
// A rule index points into its own run's rules; folded in, it would point at the wrong one.
|
|
114
|
+
const { ruleIndex: _dropped, ...kept } = res;
|
|
115
|
+
results.push(kept);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
runs.push({
|
|
119
|
+
...first,
|
|
120
|
+
results,
|
|
121
|
+
automationDetails: { ...((first.automationDetails as Json) ?? {}), id: `${unit.id}/` },
|
|
122
|
+
properties: { ...((first.properties as Json) ?? {}), member: unit.member, dir: unit.dir },
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
return { $schema: SARIF_SCHEMA, version: "2.1.0", runs };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export interface MemberAuditStatus {
|
|
129
|
+
member: string;
|
|
130
|
+
dir: string;
|
|
131
|
+
exitCode: number;
|
|
132
|
+
status: "ok" | "no-lexicons" | "failed";
|
|
133
|
+
summary: Json | null;
|
|
134
|
+
error: string | null;
|
|
135
|
+
/** What the member's audit printed instead of a report when it had nothing to audit. */
|
|
136
|
+
note?: string;
|
|
137
|
+
/** Findings dropped from member `.` because they sit in another member's directory. */
|
|
138
|
+
leftToMembers?: number;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export interface WorkspaceAuditDocument {
|
|
142
|
+
/** The audit report schema version the members wrote, when they all agree. */
|
|
143
|
+
schemaVersion: string | null;
|
|
144
|
+
workspace: { name: string; root: string };
|
|
145
|
+
members: MemberAuditStatus[];
|
|
146
|
+
findings: Json[];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Merge each member's `chant audit --format json` report. Every finding keeps
|
|
151
|
+
* its fields, with `file` still relative to its member, and gains `member`.
|
|
152
|
+
*/
|
|
153
|
+
export function mergeAudit(units: MemberOutput[], workspace: { name: string; root: string }): WorkspaceAuditDocument {
|
|
154
|
+
const members: MemberAuditStatus[] = [];
|
|
155
|
+
const findings: Json[] = [];
|
|
156
|
+
const versions = new Set<string>();
|
|
157
|
+
for (const unit of units) {
|
|
158
|
+
let report: Json;
|
|
159
|
+
try {
|
|
160
|
+
report = JSON.parse(unit.stdout) as Json;
|
|
161
|
+
} catch {
|
|
162
|
+
if (unit.exitCode === 0) {
|
|
163
|
+
// `chant audit` says so in plain text when a directory holds nothing it audits.
|
|
164
|
+
members.push({ member: unit.id, dir: unit.dir, exitCode: 0, status: "ok", summary: null, error: null, note: unit.stdout.trim() });
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
const tail = unit.stderr.trim().split("\n").slice(-5).join("\n");
|
|
168
|
+
members.push({ member: unit.id, dir: unit.dir, exitCode: unit.exitCode, status: "failed", summary: null, error: tail || "chant audit printed no JSON report" });
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
if (typeof report.schemaVersion === "string") versions.add(report.schemaVersion);
|
|
172
|
+
const own = (report.findings as Json[]) ?? [];
|
|
173
|
+
const kept = own.filter((f) => !unit.exclude?.some((d) => typeof f.file === "string" && (f.file === d || f.file.startsWith(`${d}/`))));
|
|
174
|
+
members.push({
|
|
175
|
+
member: unit.id,
|
|
176
|
+
dir: unit.dir,
|
|
177
|
+
exitCode: unit.exitCode,
|
|
178
|
+
status: report.status === "no-lexicons" ? "no-lexicons" : "ok",
|
|
179
|
+
summary: (report.summary as Json) ?? null,
|
|
180
|
+
error: null,
|
|
181
|
+
...(kept.length < own.length ? { leftToMembers: own.length - kept.length } : {}),
|
|
182
|
+
});
|
|
183
|
+
for (const f of kept) findings.push({ ...f, member: unit.id });
|
|
184
|
+
}
|
|
185
|
+
return { schemaVersion: versions.size === 1 ? [...versions][0] : null, workspace, members, findings };
|
|
186
|
+
}
|