@vibe-agent-toolkit/cli 0.1.42 → 0.2.0-rc.10
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/README.md +4 -5
- package/dist/bin/vat +95 -28
- package/dist/bin/vat.js +95 -28
- package/dist/bin/vat.js.map +1 -1
- package/dist/bin.js +137 -76
- package/dist/bin.js.map +1 -1
- package/dist/command-loaders.d.ts +44 -0
- package/dist/command-loaders.d.ts.map +1 -0
- package/dist/command-loaders.js +60 -0
- package/dist/command-loaders.js.map +1 -0
- package/dist/commands/agent/build.d.ts.map +1 -1
- package/dist/commands/agent/build.js +2 -1
- package/dist/commands/agent/build.js.map +1 -1
- package/dist/commands/agent/import.d.ts.map +1 -1
- package/dist/commands/agent/import.js +3 -2
- package/dist/commands/agent/import.js.map +1 -1
- package/dist/commands/agent/index.js +1 -1
- package/dist/commands/agent/install-path.d.ts +36 -0
- package/dist/commands/agent/install-path.d.ts.map +1 -0
- package/dist/commands/agent/install-path.js +44 -0
- package/dist/commands/agent/install-path.js.map +1 -0
- package/dist/commands/agent/install.d.ts.map +1 -1
- package/dist/commands/agent/install.js +66 -13
- package/dist/commands/agent/install.js.map +1 -1
- package/dist/commands/agent/installed.d.ts.map +1 -1
- package/dist/commands/agent/installed.js +22 -12
- package/dist/commands/agent/installed.js.map +1 -1
- package/dist/commands/agent/list.d.ts.map +1 -1
- package/dist/commands/agent/list.js +2 -1
- package/dist/commands/agent/list.js.map +1 -1
- package/dist/commands/agent/run.d.ts.map +1 -1
- package/dist/commands/agent/run.js +2 -1
- package/dist/commands/agent/run.js.map +1 -1
- package/dist/commands/agent/uninstall.d.ts.map +1 -1
- package/dist/commands/agent/uninstall.js +13 -6
- package/dist/commands/agent/uninstall.js.map +1 -1
- package/dist/commands/agent/validate.d.ts.map +1 -1
- package/dist/commands/agent/validate.js +3 -2
- package/dist/commands/agent/validate.js.map +1 -1
- package/dist/commands/ard/emit.d.ts +153 -0
- package/dist/commands/ard/emit.d.ts.map +1 -0
- package/dist/commands/ard/emit.js +276 -0
- package/dist/commands/ard/emit.js.map +1 -0
- package/dist/commands/ard/index.d.ts +12 -0
- package/dist/commands/ard/index.d.ts.map +1 -0
- package/dist/commands/ard/index.js +80 -0
- package/dist/commands/ard/index.js.map +1 -0
- package/dist/commands/ard/surfaces.d.ts +56 -0
- package/dist/commands/ard/surfaces.d.ts.map +1 -0
- package/dist/commands/ard/surfaces.js +150 -0
- package/dist/commands/ard/surfaces.js.map +1 -0
- package/dist/commands/audit/distributed-tree.d.ts +60 -2
- package/dist/commands/audit/distributed-tree.d.ts.map +1 -1
- package/dist/commands/audit/distributed-tree.js +73 -1
- package/dist/commands/audit/distributed-tree.js.map +1 -1
- package/dist/commands/audit/git-url-clone.d.ts +1 -1
- package/dist/commands/audit/git-url-clone.d.ts.map +1 -1
- package/dist/commands/audit/git-url-clone.js.map +1 -1
- package/dist/commands/audit/hierarchical-output.d.ts +1 -1
- package/dist/commands/audit/hierarchical-output.d.ts.map +1 -1
- package/dist/commands/audit/hierarchical-output.js +1 -1
- package/dist/commands/audit/hierarchical-output.js.map +1 -1
- package/dist/commands/audit/provenance.d.ts.map +1 -1
- package/dist/commands/audit/provenance.js +4 -3
- package/dist/commands/audit/provenance.js.map +1 -1
- package/dist/commands/audit/scan-population.d.ts +158 -0
- package/dist/commands/audit/scan-population.d.ts.map +1 -0
- package/dist/commands/audit/scan-population.js +292 -0
- package/dist/commands/audit/scan-population.js.map +1 -0
- package/dist/commands/audit-settings.d.ts.map +1 -1
- package/dist/commands/audit-settings.js +6 -5
- package/dist/commands/audit-settings.js.map +1 -1
- package/dist/commands/audit.d.ts +98 -8
- package/dist/commands/audit.d.ts.map +1 -1
- package/dist/commands/audit.js +1133 -395
- package/dist/commands/audit.js.map +1 -1
- package/dist/commands/build.d.ts +1 -1
- package/dist/commands/build.d.ts.map +1 -1
- package/dist/commands/build.js +40 -36
- package/dist/commands/build.js.map +1 -1
- package/dist/commands/cache/cache-control.d.ts +82 -0
- package/dist/commands/cache/cache-control.d.ts.map +1 -0
- package/dist/commands/cache/cache-control.js +86 -0
- package/dist/commands/cache/cache-control.js.map +1 -0
- package/dist/commands/cache/clear.d.ts +105 -0
- package/dist/commands/cache/clear.d.ts.map +1 -0
- package/dist/commands/cache/clear.js +248 -0
- package/dist/commands/cache/clear.js.map +1 -0
- package/dist/commands/cache/index.d.ts +18 -0
- package/dist/commands/cache/index.d.ts.map +1 -0
- package/dist/commands/cache/index.js +94 -0
- package/dist/commands/cache/index.js.map +1 -0
- package/dist/commands/claude/claude-config.d.ts.map +1 -1
- package/dist/commands/claude/claude-config.js +6 -0
- package/dist/commands/claude/claude-config.js.map +1 -1
- package/dist/commands/claude/context.d.ts +340 -0
- package/dist/commands/claude/context.d.ts.map +1 -0
- package/dist/commands/claude/context.js +1080 -0
- package/dist/commands/claude/context.js.map +1 -0
- package/dist/commands/claude/index.d.ts +14 -0
- package/dist/commands/claude/index.d.ts.map +1 -1
- package/dist/commands/claude/index.js +22 -2
- package/dist/commands/claude/index.js.map +1 -1
- package/dist/commands/claude/marketplace/changelog-utils.d.ts +1 -1
- package/dist/commands/claude/marketplace/changelog-utils.d.ts.map +1 -1
- package/dist/commands/claude/marketplace/changelog-utils.js +1 -2
- package/dist/commands/claude/marketplace/changelog-utils.js.map +1 -1
- package/dist/commands/claude/marketplace/git-publish.d.ts.map +1 -1
- package/dist/commands/claude/marketplace/git-publish.js +40 -31
- package/dist/commands/claude/marketplace/git-publish.js.map +1 -1
- package/dist/commands/claude/marketplace/license-utils.d.ts.map +1 -1
- package/dist/commands/claude/marketplace/license-utils.js +0 -1
- package/dist/commands/claude/marketplace/license-utils.js.map +1 -1
- package/dist/commands/claude/marketplace/publish-tree.d.ts.map +1 -1
- package/dist/commands/claude/marketplace/publish-tree.js +0 -6
- package/dist/commands/claude/marketplace/publish-tree.js.map +1 -1
- package/dist/commands/claude/marketplace/publish.d.ts.map +1 -1
- package/dist/commands/claude/marketplace/publish.js +2 -1
- package/dist/commands/claude/marketplace/publish.js.map +1 -1
- package/dist/commands/claude/marketplace/validate.d.ts +73 -22
- package/dist/commands/claude/marketplace/validate.d.ts.map +1 -1
- package/dist/commands/claude/marketplace/validate.js +407 -72
- package/dist/commands/claude/marketplace/validate.js.map +1 -1
- package/dist/commands/claude/org/api-keys.d.ts +0 -3
- package/dist/commands/claude/org/api-keys.d.ts.map +1 -1
- package/dist/commands/claude/org/api-keys.js +2 -1
- package/dist/commands/claude/org/api-keys.js.map +1 -1
- package/dist/commands/claude/org/helpers.d.ts +54 -1
- package/dist/commands/claude/org/helpers.d.ts.map +1 -1
- package/dist/commands/claude/org/helpers.js +42 -7
- package/dist/commands/claude/org/helpers.js.map +1 -1
- package/dist/commands/claude/org/index.d.ts.map +1 -1
- package/dist/commands/claude/org/index.js +24 -16
- package/dist/commands/claude/org/index.js.map +1 -1
- package/dist/commands/claude/org/invites.d.ts +0 -3
- package/dist/commands/claude/org/invites.d.ts.map +1 -1
- package/dist/commands/claude/org/invites.js +3 -2
- package/dist/commands/claude/org/invites.js.map +1 -1
- package/dist/commands/claude/org/skills.d.ts +527 -0
- package/dist/commands/claude/org/skills.d.ts.map +1 -1
- package/dist/commands/claude/org/skills.js +1414 -108
- package/dist/commands/claude/org/skills.js.map +1 -1
- package/dist/commands/claude/org/users.d.ts +0 -3
- package/dist/commands/claude/org/users.d.ts.map +1 -1
- package/dist/commands/claude/org/users.js +6 -3
- package/dist/commands/claude/org/users.js.map +1 -1
- package/dist/commands/claude/org/workspaces.d.ts +0 -3
- package/dist/commands/claude/org/workspaces.d.ts.map +1 -1
- package/dist/commands/claude/org/workspaces.js +11 -7
- package/dist/commands/claude/org/workspaces.js.map +1 -1
- package/dist/commands/claude/plugin/build.d.ts +41 -3
- package/dist/commands/claude/plugin/build.d.ts.map +1 -1
- package/dist/commands/claude/plugin/build.js +147 -80
- package/dist/commands/claude/plugin/build.js.map +1 -1
- package/dist/commands/claude/plugin/helpers.d.ts.map +1 -1
- package/dist/commands/claude/plugin/helpers.js +2 -7
- package/dist/commands/claude/plugin/helpers.js.map +1 -1
- package/dist/commands/claude/plugin/install.d.ts +12 -0
- package/dist/commands/claude/plugin/install.d.ts.map +1 -1
- package/dist/commands/claude/plugin/install.js +93 -62
- package/dist/commands/claude/plugin/install.js.map +1 -1
- package/dist/commands/claude/plugin/list.d.ts.map +1 -1
- package/dist/commands/claude/plugin/list.js +3 -2
- package/dist/commands/claude/plugin/list.js.map +1 -1
- package/dist/commands/claude/plugin/marketplace-json.d.ts +24 -9
- package/dist/commands/claude/plugin/marketplace-json.d.ts.map +1 -1
- package/dist/commands/claude/plugin/marketplace-json.js +10 -9
- package/dist/commands/claude/plugin/marketplace-json.js.map +1 -1
- package/dist/commands/claude/plugin/plugin-changelog.d.ts.map +1 -1
- package/dist/commands/claude/plugin/plugin-changelog.js +0 -2
- package/dist/commands/claude/plugin/plugin-changelog.js.map +1 -1
- package/dist/commands/claude/plugin/plugin-files.d.ts.map +1 -1
- package/dist/commands/claude/plugin/plugin-files.js +3 -6
- package/dist/commands/claude/plugin/plugin-files.js.map +1 -1
- package/dist/commands/claude/plugin/plugin-validators.d.ts.map +1 -1
- package/dist/commands/claude/plugin/plugin-validators.js +3 -6
- package/dist/commands/claude/plugin/plugin-validators.js.map +1 -1
- package/dist/commands/claude/plugin/tree-copy.d.ts +73 -0
- package/dist/commands/claude/plugin/tree-copy.d.ts.map +1 -1
- package/dist/commands/claude/plugin/tree-copy.js +221 -14
- package/dist/commands/claude/plugin/tree-copy.js.map +1 -1
- package/dist/commands/claude/plugin/uninstall.d.ts.map +1 -1
- package/dist/commands/claude/plugin/uninstall.js +4 -4
- package/dist/commands/claude/plugin/uninstall.js.map +1 -1
- package/dist/commands/consistency-check.d.ts +17 -15
- package/dist/commands/consistency-check.d.ts.map +1 -1
- package/dist/commands/consistency-check.js +152 -106
- package/dist/commands/consistency-check.js.map +1 -1
- package/dist/commands/corpus/index.d.ts.map +1 -1
- package/dist/commands/corpus/index.js +3 -0
- package/dist/commands/corpus/index.js.map +1 -1
- package/dist/commands/corpus/report.d.ts +1 -2
- package/dist/commands/corpus/report.d.ts.map +1 -1
- package/dist/commands/corpus/report.js +1 -3
- package/dist/commands/corpus/report.js.map +1 -1
- package/dist/commands/corpus/runner.d.ts +58 -1
- package/dist/commands/corpus/runner.d.ts.map +1 -1
- package/dist/commands/corpus/runner.js +95 -41
- package/dist/commands/corpus/runner.js.map +1 -1
- package/dist/commands/corpus/scan.d.ts.map +1 -1
- package/dist/commands/corpus/scan.js +20 -12
- package/dist/commands/corpus/scan.js.map +1 -1
- package/dist/commands/corpus/seed.d.ts +4 -4
- package/dist/commands/corpus/seed.d.ts.map +1 -1
- package/dist/commands/corpus/seed.js +1 -3
- package/dist/commands/corpus/seed.js.map +1 -1
- package/dist/commands/doctor.d.ts +50 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +245 -52
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/inventory.d.ts +13 -1
- package/dist/commands/inventory.d.ts.map +1 -1
- package/dist/commands/inventory.js +128 -17
- package/dist/commands/inventory.js.map +1 -1
- package/dist/commands/mcp/collections.d.ts +1 -1
- package/dist/commands/mcp/collections.d.ts.map +1 -1
- package/dist/commands/mcp/list-collections.d.ts.map +1 -1
- package/dist/commands/mcp/list-collections.js +2 -1
- package/dist/commands/mcp/list-collections.js.map +1 -1
- package/dist/commands/mcp/serve.d.ts.map +1 -1
- package/dist/commands/mcp/serve.js +3 -2
- package/dist/commands/mcp/serve.js.map +1 -1
- package/dist/commands/okf/index.d.ts +11 -0
- package/dist/commands/okf/index.d.ts.map +1 -0
- package/dist/commands/okf/index.js +114 -0
- package/dist/commands/okf/index.js.map +1 -0
- package/dist/commands/okf/validate.d.ts +191 -0
- package/dist/commands/okf/validate.d.ts.map +1 -0
- package/dist/commands/okf/validate.js +190 -0
- package/dist/commands/okf/validate.js.map +1 -0
- package/dist/commands/phase-utils.d.ts +182 -114
- package/dist/commands/phase-utils.d.ts.map +1 -1
- package/dist/commands/phase-utils.js +154 -163
- package/dist/commands/phase-utils.js.map +1 -1
- package/dist/commands/positional-args.d.ts.map +1 -1
- package/dist/commands/positional-args.js +3 -2
- package/dist/commands/positional-args.js.map +1 -1
- package/dist/commands/rag/clear-command.d.ts.map +1 -1
- package/dist/commands/rag/clear-command.js +2 -1
- package/dist/commands/rag/clear-command.js.map +1 -1
- package/dist/commands/rag/index-command.d.ts +64 -0
- package/dist/commands/rag/index-command.d.ts.map +1 -1
- package/dist/commands/rag/index-command.js +72 -6
- package/dist/commands/rag/index-command.js.map +1 -1
- package/dist/commands/rag/index.d.ts.map +1 -1
- package/dist/commands/rag/index.js +23 -10
- package/dist/commands/rag/index.js.map +1 -1
- package/dist/commands/rag/query-command.d.ts.map +1 -1
- package/dist/commands/rag/query-command.js +2 -1
- package/dist/commands/rag/query-command.js.map +1 -1
- package/dist/commands/rag/stats-command.d.ts.map +1 -1
- package/dist/commands/rag/stats-command.js +2 -1
- package/dist/commands/rag/stats-command.js.map +1 -1
- package/dist/commands/resources/builtin-help.d.ts +27 -0
- package/dist/commands/resources/builtin-help.d.ts.map +1 -0
- package/dist/commands/resources/builtin-help.js +46 -0
- package/dist/commands/resources/builtin-help.js.map +1 -0
- package/dist/commands/resources/check-progress.d.ts +154 -0
- package/dist/commands/resources/check-progress.d.ts.map +1 -0
- package/dist/commands/resources/check-progress.js +222 -0
- package/dist/commands/resources/check-progress.js.map +1 -0
- package/dist/commands/resources/check-supervisor.d.ts +465 -0
- package/dist/commands/resources/check-supervisor.d.ts.map +1 -0
- package/dist/commands/resources/check-supervisor.js +573 -0
- package/dist/commands/resources/check-supervisor.js.map +1 -0
- package/dist/commands/resources/check.d.ts +593 -0
- package/dist/commands/resources/check.d.ts.map +1 -0
- package/dist/commands/resources/check.js +1365 -0
- package/dist/commands/resources/check.js.map +1 -0
- package/dist/commands/resources/index.d.ts.map +1 -1
- package/dist/commands/resources/index.js +425 -6
- package/dist/commands/resources/index.js.map +1 -1
- package/dist/commands/resources/query.d.ts +106 -0
- package/dist/commands/resources/query.d.ts.map +1 -0
- package/dist/commands/resources/query.js +177 -0
- package/dist/commands/resources/query.js.map +1 -0
- package/dist/commands/resources/scan.d.ts +24 -0
- package/dist/commands/resources/scan.d.ts.map +1 -1
- package/dist/commands/resources/scan.js +17 -8
- package/dist/commands/resources/scan.js.map +1 -1
- package/dist/commands/resources/validate.d.ts +57 -4
- package/dist/commands/resources/validate.d.ts.map +1 -1
- package/dist/commands/resources/validate.js +224 -56
- package/dist/commands/resources/validate.js.map +1 -1
- package/dist/commands/skill/review-checklist.d.ts +3 -2
- package/dist/commands/skill/review-checklist.d.ts.map +1 -1
- package/dist/commands/skill/review-checklist.js +40 -2
- package/dist/commands/skill/review-checklist.js.map +1 -1
- package/dist/commands/skill/review.d.ts +247 -0
- package/dist/commands/skill/review.d.ts.map +1 -1
- package/dist/commands/skill/review.js +99 -62
- package/dist/commands/skill/review.js.map +1 -1
- package/dist/commands/skill/test/configure.d.ts +56 -0
- package/dist/commands/skill/test/configure.d.ts.map +1 -1
- package/dist/commands/skill/test/configure.js +75 -16
- package/dist/commands/skill/test/configure.js.map +1 -1
- package/dist/commands/skill/test/run.d.ts +63 -10
- package/dist/commands/skill/test/run.d.ts.map +1 -1
- package/dist/commands/skill/test/run.js +258 -77
- package/dist/commands/skill/test/run.js.map +1 -1
- package/dist/commands/skills/build.d.ts +94 -4
- package/dist/commands/skills/build.d.ts.map +1 -1
- package/dist/commands/skills/build.js +325 -97
- package/dist/commands/skills/build.js.map +1 -1
- package/dist/commands/skills/install.d.ts +2 -1
- package/dist/commands/skills/install.d.ts.map +1 -1
- package/dist/commands/skills/install.js +20 -31
- package/dist/commands/skills/install.js.map +1 -1
- package/dist/commands/skills/list.d.ts +2 -1
- package/dist/commands/skills/list.d.ts.map +1 -1
- package/dist/commands/skills/list.js +44 -26
- package/dist/commands/skills/list.js.map +1 -1
- package/dist/commands/skills/package.d.ts +24 -3
- package/dist/commands/skills/package.d.ts.map +1 -1
- package/dist/commands/skills/package.js +42 -17
- package/dist/commands/skills/package.js.map +1 -1
- package/dist/commands/skills/scope-guard.d.ts.map +1 -1
- package/dist/commands/skills/scope-guard.js +3 -5
- package/dist/commands/skills/scope-guard.js.map +1 -1
- package/dist/commands/skills/shared.d.ts.map +1 -1
- package/dist/commands/skills/shared.js +2 -4
- package/dist/commands/skills/shared.js.map +1 -1
- package/dist/commands/skills/skill-discovery.d.ts +47 -6
- package/dist/commands/skills/skill-discovery.d.ts.map +1 -1
- package/dist/commands/skills/skill-discovery.js +66 -16
- package/dist/commands/skills/skill-discovery.js.map +1 -1
- package/dist/commands/skills/source-resolvers.d.ts +15 -0
- package/dist/commands/skills/source-resolvers.d.ts.map +1 -1
- package/dist/commands/skills/source-resolvers.js +25 -4
- package/dist/commands/skills/source-resolvers.js.map +1 -1
- package/dist/commands/skills/validate-command.d.ts.map +1 -1
- package/dist/commands/skills/validate-command.js +11 -3
- package/dist/commands/skills/validate-command.js.map +1 -1
- package/dist/commands/skills/validate.d.ts +140 -4
- package/dist/commands/skills/validate.d.ts.map +1 -1
- package/dist/commands/skills/validate.js +224 -67
- package/dist/commands/skills/validate.js.map +1 -1
- package/dist/commands/validate.d.ts +1 -1
- package/dist/commands/validate.d.ts.map +1 -1
- package/dist/commands/validate.js +63 -27
- package/dist/commands/validate.js.map +1 -1
- package/dist/commands/verify.d.ts +119 -15
- package/dist/commands/verify.d.ts.map +1 -1
- package/dist/commands/verify.js +357 -142
- package/dist/commands/verify.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/report-schemas.d.ts +45 -0
- package/dist/report-schemas.d.ts.map +1 -0
- package/dist/report-schemas.js +100 -0
- package/dist/report-schemas.js.map +1 -0
- package/dist/skill-resolution/classify.js +2 -2
- package/dist/skill-resolution/classify.js.map +1 -1
- package/dist/skill-resolution/packaging-config.d.ts +49 -5
- package/dist/skill-resolution/packaging-config.d.ts.map +1 -1
- package/dist/skill-resolution/packaging-config.js +59 -19
- package/dist/skill-resolution/packaging-config.js.map +1 -1
- package/dist/skill-resolution/resolve-skill-reference.d.ts.map +1 -1
- package/dist/skill-resolution/resolve-skill-reference.js +55 -57
- package/dist/skill-resolution/resolve-skill-reference.js.map +1 -1
- package/dist/utils/agent-discovery.d.ts.map +1 -1
- package/dist/utils/agent-discovery.js +22 -11
- package/dist/utils/agent-discovery.js.map +1 -1
- package/dist/utils/agent-runner.d.ts.map +1 -1
- package/dist/utils/agent-runner.js +7 -4
- package/dist/utils/agent-runner.js.map +1 -1
- package/dist/utils/claude-context-lens.d.ts +34 -0
- package/dist/utils/claude-context-lens.d.ts.map +1 -0
- package/dist/utils/claude-context-lens.js +39 -0
- package/dist/utils/claude-context-lens.js.map +1 -0
- package/dist/utils/command-error.d.ts +144 -7
- package/dist/utils/command-error.d.ts.map +1 -1
- package/dist/utils/command-error.js +179 -16
- package/dist/utils/command-error.js.map +1 -1
- package/dist/utils/config-loader.d.ts +2 -1
- package/dist/utils/config-loader.d.ts.map +1 -1
- package/dist/utils/config-loader.js +40 -14
- package/dist/utils/config-loader.js.map +1 -1
- package/dist/utils/corpus-target.d.ts +26 -0
- package/dist/utils/corpus-target.d.ts.map +1 -0
- package/dist/utils/corpus-target.js +35 -0
- package/dist/utils/corpus-target.js.map +1 -0
- package/dist/utils/edge-lens-evaluation.d.ts +83 -0
- package/dist/utils/edge-lens-evaluation.d.ts.map +1 -0
- package/dist/utils/edge-lens-evaluation.js +110 -0
- package/dist/utils/edge-lens-evaluation.js.map +1 -0
- package/dist/utils/help-loader.d.ts.map +1 -1
- package/dist/utils/help-loader.js +0 -1
- package/dist/utils/help-loader.js.map +1 -1
- package/dist/utils/issue-anchor.d.ts +2 -2
- package/dist/utils/issue-anchor.d.ts.map +1 -1
- package/dist/utils/issue-anchor.js +1 -1
- package/dist/utils/issue-rendering.d.ts +3 -3
- package/dist/utils/issue-rendering.d.ts.map +1 -1
- package/dist/utils/issue-rendering.js +3 -3
- package/dist/utils/issue-rendering.js.map +1 -1
- package/dist/utils/issue-severity.d.ts +12 -5
- package/dist/utils/issue-severity.d.ts.map +1 -1
- package/dist/utils/issue-severity.js +1 -1
- package/dist/utils/issue-severity.js.map +1 -1
- package/dist/utils/optional-backend.d.ts +78 -0
- package/dist/utils/optional-backend.d.ts.map +1 -0
- package/dist/utils/optional-backend.js +117 -0
- package/dist/utils/optional-backend.js.map +1 -0
- package/dist/utils/output.d.ts +26 -0
- package/dist/utils/output.d.ts.map +1 -1
- package/dist/utils/output.js +35 -0
- package/dist/utils/output.js.map +1 -1
- package/dist/utils/package-json.d.ts +24 -0
- package/dist/utils/package-json.d.ts.map +1 -0
- package/dist/utils/package-json.js +49 -0
- package/dist/utils/package-json.js.map +1 -0
- package/dist/utils/population-wiring.d.ts +100 -0
- package/dist/utils/population-wiring.d.ts.map +1 -0
- package/dist/utils/population-wiring.js +98 -0
- package/dist/utils/population-wiring.js.map +1 -0
- package/dist/utils/project-root-policy.d.ts +20 -0
- package/dist/utils/project-root-policy.d.ts.map +1 -1
- package/dist/utils/project-root-policy.js +30 -0
- package/dist/utils/project-root-policy.js.map +1 -1
- package/dist/utils/projection-lenses.d.ts +98 -0
- package/dist/utils/projection-lenses.d.ts.map +1 -0
- package/dist/utils/projection-lenses.js +203 -0
- package/dist/utils/projection-lenses.js.map +1 -0
- package/dist/utils/projection-query.d.ts +324 -0
- package/dist/utils/projection-query.d.ts.map +1 -0
- package/dist/utils/projection-query.js +357 -0
- package/dist/utils/projection-query.js.map +1 -0
- package/dist/utils/projection-store.d.ts +335 -0
- package/dist/utils/projection-store.d.ts.map +1 -0
- package/dist/utils/projection-store.js +522 -0
- package/dist/utils/projection-store.js.map +1 -0
- package/dist/utils/relation-limits.d.ts +50 -0
- package/dist/utils/relation-limits.d.ts.map +1 -0
- package/dist/utils/relation-limits.js +50 -0
- package/dist/utils/relation-limits.js.map +1 -0
- package/dist/utils/repeatable-option.d.ts +37 -0
- package/dist/utils/repeatable-option.d.ts.map +1 -0
- package/dist/utils/repeatable-option.js +39 -0
- package/dist/utils/repeatable-option.js.map +1 -0
- package/dist/utils/resource-loader.d.ts +132 -2
- package/dist/utils/resource-loader.d.ts.map +1 -1
- package/dist/utils/resource-loader.js +288 -27
- package/dist/utils/resource-loader.js.map +1 -1
- package/dist/utils/root-argv.d.ts +53 -0
- package/dist/utils/root-argv.d.ts.map +1 -0
- package/dist/utils/root-argv.js +215 -0
- package/dist/utils/root-argv.js.map +1 -0
- package/dist/utils/run-integrity.d.ts +95 -0
- package/dist/utils/run-integrity.d.ts.map +1 -0
- package/dist/utils/run-integrity.js +102 -0
- package/dist/utils/run-integrity.js.map +1 -0
- package/dist/utils/skill-discovery.d.ts.map +1 -1
- package/dist/utils/skill-discovery.js +2 -1
- package/dist/utils/skill-discovery.js.map +1 -1
- package/dist/utils/skill-packaging-config.d.ts +63 -1
- package/dist/utils/skill-packaging-config.d.ts.map +1 -1
- package/dist/utils/skill-packaging-config.js +56 -1
- package/dist/utils/skill-packaging-config.js.map +1 -1
- package/dist/utils/sqlite-experimental-warning.d.ts +69 -0
- package/dist/utils/sqlite-experimental-warning.d.ts.map +1 -0
- package/dist/utils/sqlite-experimental-warning.js +144 -0
- package/dist/utils/sqlite-experimental-warning.js.map +1 -0
- package/dist/utils/url-redact.d.ts.map +1 -1
- package/dist/utils/url-redact.js +4 -6
- package/dist/utils/url-redact.js.map +1 -1
- package/dist/utils/user-context-scanner.d.ts +8 -0
- package/dist/utils/user-context-scanner.d.ts.map +1 -1
- package/dist/utils/user-context-scanner.js +4 -2
- package/dist/utils/user-context-scanner.js.map +1 -1
- package/dist/utils/validate-help-files.d.ts.map +1 -1
- package/dist/utils/validate-help-files.js +12 -6
- package/dist/utils/validate-help-files.js.map +1 -1
- package/dist/utils/vat-bin-path.d.ts +36 -0
- package/dist/utils/vat-bin-path.d.ts.map +1 -0
- package/dist/utils/vat-bin-path.js +48 -0
- package/dist/utils/vat-bin-path.js.map +1 -0
- package/dist/utils/verdict-helpers.d.ts +1 -1
- package/dist/utils/verdict-helpers.d.ts.map +1 -1
- package/dist/utils/verdict-helpers.js +1 -1
- package/dist/utils/verdict-helpers.js.map +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -3
- package/dist/version.js.map +1 -1
- package/docs/agent.md +2 -2
- package/docs/audit.md +141 -37
- package/docs/doctor.md +7 -6
- package/docs/index.md +40 -12
- package/docs/mcp.md +2 -2
- package/docs/rag.md +3 -2
- package/docs/resources.md +13 -3
- package/docs/skill-test.md +10 -9
- package/docs/skills.md +53 -69
- package/package.json +38 -27
- package/schemas/ard-emit.json +149 -0
- package/schemas/okf-validate.json +170 -0
- package/schemas/resources-check.json +229 -0
- package/schemas/skill-review.json +230 -0
- package/dist/commands/resources/command-helpers.d.ts +0 -5
- package/dist/commands/resources/command-helpers.d.ts.map +0 -1
- package/dist/commands/resources/command-helpers.js +0 -6
- package/dist/commands/resources/command-helpers.js.map +0 -1
|
@@ -0,0 +1,1365 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `vat resources check` — run the project's own assertions over its resource
|
|
3
|
+
* projection, as a gate.
|
|
4
|
+
*
|
|
5
|
+
* The invariants this file implements are written down once, in
|
|
6
|
+
* `docs/architecture/cli.md` under "`vat resources check [path]`". Read that
|
|
7
|
+
* before changing anything here; the headlines, so a reader knows when to:
|
|
8
|
+
*
|
|
9
|
+
* - 🔑 **Four ways to check nothing are all refusals**, at error severity under
|
|
10
|
+
* the non-overridable `RESOURCE_CHECK_BROKEN`: a statement that will not run
|
|
11
|
+
* (the `catch` in `runUnits`), a corpus that enumerated empty
|
|
12
|
+
* ({@link emptyCorpusFinding}), a `--check` name nothing declares
|
|
13
|
+
* (`requireKnownCheck`), and not one check executed at all
|
|
14
|
+
* ({@link noCheckRanFinding}, derived in {@link buildCheckOutputData} so an
|
|
15
|
+
* emptied `BUILTIN_CHECKS` cannot publish a clean document).
|
|
16
|
+
* - ⚠️ That last one does NOT make the step always able to fail: the only
|
|
17
|
+
* built-in ships at `info`. Do not invent a second refusal for it — that
|
|
18
|
+
* would refuse the no-config run the default set exists to serve.
|
|
19
|
+
* - 🔑 **No built-in check may be SQL.** {@link checkCommand} reads
|
|
20
|
+
* `config?.resources?.checks` and nothing else for statements; built-ins are
|
|
21
|
+
* TypeScript predicates over the row model, and a built-in's `sqlTwin` is
|
|
22
|
+
* documentation an adopter copies, never executed here.
|
|
23
|
+
* - 🔑 **The default set belongs to the pipeline, not the config file.**
|
|
24
|
+
* `BUILTIN_CHECKS` reaches {@link runProjectChecks} as a REQUIRED `builtins`
|
|
25
|
+
* argument via {@link runOutcome}; nothing on that path reads `config`.
|
|
26
|
+
* - 🚨 **Adopter SQL runs unsandboxed on purpose, and contributed SQL is not
|
|
27
|
+
* adopter SQL** — a contributed statement ships as a reviewed example.
|
|
28
|
+
* - ⛔ A TypeScript/SQL twin is documentation and a differential oracle, never
|
|
29
|
+
* a runtime optimizer to schedule between.
|
|
30
|
+
*/
|
|
31
|
+
import { performance } from 'node:perf_hooks';
|
|
32
|
+
import { bindBuiltinChecks, BUILTIN_CHECK_NAMES, issuesFromCheckRows, LIMIT_DIRECTIONS, } from '@vibe-agent-toolkit/resources';
|
|
33
|
+
import { buildReport, CUSTOM_CHECK_CODE_PREFIX, ExitCode, isCustomCheckCode, reportSchema, toFindings, } from '@vibe-agent-toolkit/schema';
|
|
34
|
+
import { safePath } from '@vibe-agent-toolkit/utils';
|
|
35
|
+
import { z } from 'zod';
|
|
36
|
+
import { handleReportCommandError } from '../../utils/command-error.js';
|
|
37
|
+
import { loadConfigCached } from '../../utils/config-loader.js';
|
|
38
|
+
import { formatDurationSecs } from '../../utils/duration.js';
|
|
39
|
+
import { resolveIssueSeverity } from '../../utils/issue-severity.js';
|
|
40
|
+
import { createLogger } from '../../utils/logger.js';
|
|
41
|
+
import { writeStdoutSync, writeStructuredOutput } from '../../utils/output.js';
|
|
42
|
+
import { assertDirectoryArgument, projectRootOrLoudCwd, projectRootOrNull } from '../../utils/project-root-policy.js';
|
|
43
|
+
import { withQueriedProjection, } from '../../utils/projection-query.js';
|
|
44
|
+
import { relationBoundsFor } from '../../utils/relation-limits.js';
|
|
45
|
+
import { nothingCheckedFinding, runIntegrityFinding } from '../../utils/run-integrity.js';
|
|
46
|
+
import { createProgressWriter, parseProgressLog, unitInFlight, } from './check-progress.js';
|
|
47
|
+
import { parseBudgetSeconds, requireSupervisableFlags, runsInThisProcess, superviseCheck, withProgressLog, } from './check-supervisor.js';
|
|
48
|
+
/** What one rule cost, as the document publishes it. */
|
|
49
|
+
const PublishedCheckCostSchema = z.object({
|
|
50
|
+
name: z.string(),
|
|
51
|
+
/** Three significant figures, so a 0.4 ms rule serializes as 0.0004 rather than a zero that reads as "not measured". */
|
|
52
|
+
durationSecs: z.number().nonnegative(),
|
|
53
|
+
/** Rows the statement returned. ABSENT, never 0, when the statement did not complete. */
|
|
54
|
+
rows: z.number().int().nonnegative().optional(),
|
|
55
|
+
broken: z.literal(true).optional(),
|
|
56
|
+
/** Present and `true` only for a rule VAT supplied — see {@link CheckCost.builtin}. */
|
|
57
|
+
builtin: z.literal(true).optional(),
|
|
58
|
+
}).strict();
|
|
59
|
+
/**
|
|
60
|
+
* The published copy of {@link relationBoundsFor}'s answer.
|
|
61
|
+
*
|
|
62
|
+
* The registry lists are `readonly` and this document is handed to a serializer,
|
|
63
|
+
* so the arrays are copied rather than referenced — the same rule
|
|
64
|
+
* `lensesEvaluated` follows one field above: a published document owns no
|
|
65
|
+
* reference into the run's own state.
|
|
66
|
+
*
|
|
67
|
+
* @param lensesEvaluated - The lenses this run evaluated
|
|
68
|
+
* @returns The bounds keys to spread, or nothing
|
|
69
|
+
*/
|
|
70
|
+
function structuredBounds(lensesEvaluated) {
|
|
71
|
+
const bounds = relationBoundsFor(lensesEvaluated);
|
|
72
|
+
if (bounds.boundsStatement === undefined || bounds.limits === undefined)
|
|
73
|
+
return {};
|
|
74
|
+
return { boundsStatement: bounds.boundsStatement, limits: bounds.limits.map((limit) => ({ ...limit })) };
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* A signed bound on what the derived rows settle, as `@vibe-agent-toolkit/resources`
|
|
78
|
+
* states it.
|
|
79
|
+
*
|
|
80
|
+
* ⛔ The direction vocabulary is IMPORTED, never respelled: `LIMIT_DIRECTIONS`
|
|
81
|
+
* is the one list, tied to `StatedLimit['direction']` by `satisfies`, so a
|
|
82
|
+
* fifth direction cannot leave this schema quietly rejecting it.
|
|
83
|
+
*/
|
|
84
|
+
const StatedLimitSchema = z.object({
|
|
85
|
+
id: z.string(),
|
|
86
|
+
direction: z.enum(LIMIT_DIRECTIONS),
|
|
87
|
+
statement: z.string(),
|
|
88
|
+
}).strict();
|
|
89
|
+
/**
|
|
90
|
+
* What the check run reports beyond its findings.
|
|
91
|
+
*
|
|
92
|
+
* The envelope's `examined` is `membersEnumerated` — what the rules ran
|
|
93
|
+
* AGAINST. `checksRun` is the other denominator, the number of RULES, and both
|
|
94
|
+
* are needed: zero findings is the pass condition and either number at zero
|
|
95
|
+
* makes that pass vacuous.
|
|
96
|
+
*/
|
|
97
|
+
export const CheckDataSchema = z.object({
|
|
98
|
+
root: z.string(),
|
|
99
|
+
/** Whether the projection was derived this run or read from the store. */
|
|
100
|
+
population: z.enum(['derived', 'store']),
|
|
101
|
+
/** What the population cost — charged to no check, see `CheckCost`. */
|
|
102
|
+
populationSecs: z.number().nonnegative(),
|
|
103
|
+
/** What evaluating the lenses cost, paid before the first statement ran. */
|
|
104
|
+
lensSecs: z.number().nonnegative(),
|
|
105
|
+
/**
|
|
106
|
+
* Which lenses that covers — the derived relations this run's checks could
|
|
107
|
+
* actually read. 🔑 An empty list says "no check asked for a derived relation"
|
|
108
|
+
* rather than "a lens stopped running"; without it `lensSecs: 0` is the same
|
|
109
|
+
* document either way, and a gate whose rules silently read empty relations is
|
|
110
|
+
* a gate that cannot fail.
|
|
111
|
+
*/
|
|
112
|
+
lensesEvaluated: z.array(z.string()),
|
|
113
|
+
/**
|
|
114
|
+
* The prose frame {@link CheckDataSchema.limits} is read under. Present
|
|
115
|
+
* exactly when a lens with stated bounds was evaluated.
|
|
116
|
+
*/
|
|
117
|
+
boundsStatement: z.string().optional(),
|
|
118
|
+
/**
|
|
119
|
+
* What the derived rows this run could read do NOT settle — signed and
|
|
120
|
+
* directional, the same list `vat claude context` publishes beside its own
|
|
121
|
+
* answer. Absent when no bounded lens ran: an empty list would claim nothing
|
|
122
|
+
* bounds the answer, which is stronger than "the answer holds no such row".
|
|
123
|
+
*/
|
|
124
|
+
limits: z.array(StatedLimitSchema).optional(),
|
|
125
|
+
/** The number of rules that EXECUTED. Derived from `checks`, never carried beside it. */
|
|
126
|
+
checksRun: z.number().int().nonnegative(),
|
|
127
|
+
/** What each rule cost, directly under the denominator it is the breakdown of. */
|
|
128
|
+
checks: z.array(PublishedCheckCostSchema),
|
|
129
|
+
}).strict();
|
|
130
|
+
/** The document this command publishes. */
|
|
131
|
+
export const CHECK_REPORT_SCHEMA = reportSchema(CheckDataSchema);
|
|
132
|
+
/**
|
|
133
|
+
* The refusal for a run in which NO check executed.
|
|
134
|
+
*
|
|
135
|
+
* 🚨 **The defect this closes shipped, and it is the shape by which a gate gets
|
|
136
|
+
* silently deleted.** A project with a valid `vibe-agent-toolkit.config.yaml`
|
|
137
|
+
* and no `checks:` block produced `status: success`, `checksRun: 0`, exit 0 — a
|
|
138
|
+
* passing gate document. Delete the `checks:` block and the CI step keeps going
|
|
139
|
+
* green forever; the only thing that said otherwise was a stderr warning nothing
|
|
140
|
+
* parses. **A gate that checked nothing is indistinguishable from a gate that
|
|
141
|
+
* was removed, and the whole argument for having these gates is that you never
|
|
142
|
+
* let those two look alike.**
|
|
143
|
+
*
|
|
144
|
+
* It is the third instance of the shape in this release — `vat claude budget`
|
|
145
|
+
* answered `success` on a path that matched no working location, and the repo's
|
|
146
|
+
* audit quality gate had a green path over zero parsed findings — so it is
|
|
147
|
+
* closed the way those were rather than a third way: a non-overridable
|
|
148
|
+
* `RUN_INTEGRITY_CODE` at `error`, derived in the report builder.
|
|
149
|
+
*
|
|
150
|
+
* 🔑 **Derived in {@link buildCheckOutputData}, not asserted by the handler.**
|
|
151
|
+
* That placement is the guarantee. The builder is where `checksRun` is computed
|
|
152
|
+
* and every lane's document goes through it — the completed run, the recovered
|
|
153
|
+
* document of an interrupted one, the child a supervisor forwards — so "the
|
|
154
|
+
* denominator is zero and the status is clean" is unrepresentable by
|
|
155
|
+
* construction, and a fourth lane added later inherits the refusal instead of
|
|
156
|
+
* having to remember it. It also lands after `resolveIssueSeverity`, which is
|
|
157
|
+
* belt and braces: the code is already unreachable from a `severity` key.
|
|
158
|
+
*
|
|
159
|
+
* The mechanism — ask the DENOMINATOR never the config, one report per
|
|
160
|
+
* situation, stand down behind an existing run-integrity finding — is the shared
|
|
161
|
+
* {@link nothingCheckedFinding}; this wrapper owns only the message. The
|
|
162
|
+
* stand-down matters here for an interrupted run, which recovers zero costs when
|
|
163
|
+
* the kill landed before the first statement finished and already carries
|
|
164
|
+
* {@link interruptedRunFinding}, the more specific of the two claims. A `--check`
|
|
165
|
+
* filter that matched nothing is refused earlier by {@link requireKnownCheck},
|
|
166
|
+
* and must stay refused even if that guard is ever widened.
|
|
167
|
+
*
|
|
168
|
+
* 🔑 **An absent `checks:` block does not empty the denominator** — the built-in
|
|
169
|
+
* set is in the pipeline, so reaching zero here means not even a default ran and
|
|
170
|
+
* the remedy is not "declare some checks". The guard stays because it is the
|
|
171
|
+
* mechanism-level guarantee that `checksRun: 0` can never be published clean,
|
|
172
|
+
* whatever empties it.
|
|
173
|
+
*
|
|
174
|
+
* @param costs - One record per check that EXECUTED; its length is `checksRun`
|
|
175
|
+
* @param issues - What the run already found, so an existing run-integrity
|
|
176
|
+
* report is not duplicated
|
|
177
|
+
* @returns The finding, or nothing when at least one check ran
|
|
178
|
+
*/
|
|
179
|
+
function noCheckRanFinding(costs, issues) {
|
|
180
|
+
return nothingCheckedFinding(costs.length, issues, () => 'No check ran at all, so this document is not a verdict: a gate that checked nothing'
|
|
181
|
+
+ ' produces the same report as a gate that was removed, and this run cannot tell'
|
|
182
|
+
+ ' you which happened.'
|
|
183
|
+
+ ' This is not an absent `resources.checks` block — VAT\'s own built-in checks run'
|
|
184
|
+
+ ' whether or not a project declares any, so reaching zero means not even those'
|
|
185
|
+
+ ' executed. Re-run with --debug and report it; if the run was filtered, check that'
|
|
186
|
+
+ ' --check named a rule this build still ships.');
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Build the check report.
|
|
190
|
+
*
|
|
191
|
+
* Pure: no file system, no clock, no `process.exit` — the same contract every
|
|
192
|
+
* other payload builder here follows, so the document's shape stays under unit
|
|
193
|
+
* test rather than only under a CLI spawn.
|
|
194
|
+
*
|
|
195
|
+
* `checksRun` is published beside the counts and is not decoration: zero
|
|
196
|
+
* findings from four checks and zero findings from **no** checks are the same
|
|
197
|
+
* document without it, and they mean opposite things. A project whose config key
|
|
198
|
+
* is misspelled would otherwise read as passing. It is DERIVED from the `checks`
|
|
199
|
+
* list rather than carried alongside it, so the denominator and the entries that
|
|
200
|
+
* account for it cannot disagree.
|
|
201
|
+
*
|
|
202
|
+
* 🚨 `populationSecs` and the per-check `durationSecs` do not overlap and do not
|
|
203
|
+
* sum to `durationMs`. The population is paid once and shared by every check;
|
|
204
|
+
* see {@link CheckCost} for why it is charged to none of them.
|
|
205
|
+
*
|
|
206
|
+
* 🔑 A denominator of zero is refused HERE rather than published — see
|
|
207
|
+
* {@link noCheckRanFinding}. This is the one function every lane's document
|
|
208
|
+
* passes through, so deriving the refusal in it is what makes "checked nothing,
|
|
209
|
+
* status clean" unrepresentable rather than merely unwritten.
|
|
210
|
+
*
|
|
211
|
+
* 🪤 The findings are NOT run through `relativizePathEntries`. Every other
|
|
212
|
+
* payload builder re-bases here because its producer keeps absolute paths
|
|
213
|
+
* internally; these arrive project-relative instead, and re-basing an
|
|
214
|
+
* already-relative path would resolve it against the wrong base and silently
|
|
215
|
+
* corrupt it. The guarantee is `locationOf` in `sql-checks.ts` — the one place
|
|
216
|
+
* a projection column becomes a finding's `location`, and which refuses an
|
|
217
|
+
* absolute or backslashed value there. Nothing on this path parses findings
|
|
218
|
+
* through the schema at runtime, and do not "fix" that by parsing at this
|
|
219
|
+
* boundary: it would convert a slip in `locationOf` from "this finding loses
|
|
220
|
+
* its anchor" into "the whole run throws", which is strictly worse for a gate.
|
|
221
|
+
*
|
|
222
|
+
* @param input - The findings and the provenance of the run behind them
|
|
223
|
+
* @returns The document to serialize
|
|
224
|
+
*/
|
|
225
|
+
export function buildCheckOutputData(input) {
|
|
226
|
+
const issues = [...noCheckRanFinding(input.costs, input.issues), ...input.issues];
|
|
227
|
+
return buildReport({
|
|
228
|
+
// The OTHER denominator, and the one whose absence shipped a green gate over
|
|
229
|
+
// an empty repository. `checksRun` counts rules; this counts what they ran
|
|
230
|
+
// against.
|
|
231
|
+
examined: input.membersEnumerated,
|
|
232
|
+
findings: toFindings(issues),
|
|
233
|
+
durationMs: input.durationMs,
|
|
234
|
+
data: {
|
|
235
|
+
root: input.root,
|
|
236
|
+
population: input.population,
|
|
237
|
+
// Beside the origin, because a `population: store` a reader cannot price is
|
|
238
|
+
// a label taken on faith. Charged to no check — see {@link CheckCost}.
|
|
239
|
+
populationSecs: formatDurationSecs(input.populationMs),
|
|
240
|
+
// Published here too, and not only by `query`, because this verb pays it
|
|
241
|
+
// identically — the lens is evaluated before the first statement runs. A
|
|
242
|
+
// document that priced the population but not the lens would attribute the
|
|
243
|
+
// lens's cost to whichever rule the reader happened to be looking at, which
|
|
244
|
+
// is the same defect `populationSecs` exists to prevent.
|
|
245
|
+
lensSecs: formatDurationSecs(input.lensMs),
|
|
246
|
+
// What that number covers — see `CheckDataSchema.lensesEvaluated`. Copied
|
|
247
|
+
// into a plain array so the published document owns no reference into the
|
|
248
|
+
// run's own state.
|
|
249
|
+
lensesEvaluated: [...input.lensesEvaluated],
|
|
250
|
+
// The bounds of the rows those lenses produced, stated ONCE and only when
|
|
251
|
+
// a bounded lens ran. This is the verb an adopter makes GATE, so it is the
|
|
252
|
+
// one that most owes them — `utils/relation-limits.ts` carries why.
|
|
253
|
+
// Copied out of the readonly registry lists: the published document owns
|
|
254
|
+
// no reference into module state a later render could mutate.
|
|
255
|
+
...structuredBounds(input.lensesEvaluated),
|
|
256
|
+
// The denominator of rules. Derived from `checks`, never carried beside it.
|
|
257
|
+
checksRun: input.costs.length,
|
|
258
|
+
// What each rule cost, directly under the denominator it is a breakdown
|
|
259
|
+
// of. A SQL surface is an unbounded cost — a project can declare a
|
|
260
|
+
// statement that scans every row of every table — and a single total
|
|
261
|
+
// attributes nothing: ten seconds is one expensive rule, a slow
|
|
262
|
+
// population, or forty cheap rules, and only this list tells them apart.
|
|
263
|
+
//
|
|
264
|
+
// 🪤 `rows` and `broken` are spread conditionally rather than defaulted.
|
|
265
|
+
// `rows: 0` on a statement that never returned would read as a clean pass,
|
|
266
|
+
// which is the exact confusion `RESOURCE_CHECK_BROKEN` exists to prevent —
|
|
267
|
+
// so a check that threw publishes no row count at all.
|
|
268
|
+
checks: input.costs.map((cost) => ({
|
|
269
|
+
name: cost.name,
|
|
270
|
+
// Three significant figures, so a 0.4 ms rule serializes as 0.0004 rather
|
|
271
|
+
// than rounding to a zero that reads as "not measured".
|
|
272
|
+
durationSecs: formatDurationSecs(cost.durationMs),
|
|
273
|
+
...(cost.rows === undefined ? {} : { rows: cost.rows }),
|
|
274
|
+
...(cost.broken === undefined ? {} : { broken: cost.broken }),
|
|
275
|
+
// Absent, never `false`, on a declared rule — so the key's presence is
|
|
276
|
+
// the whole claim and a reader never has to tell `false` from unset.
|
|
277
|
+
...(cost.builtin === undefined ? {} : { builtin: cost.builtin }),
|
|
278
|
+
})),
|
|
279
|
+
},
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Run a thunk between two readings of the clock, and report both.
|
|
284
|
+
*
|
|
285
|
+
* ## 🚨 Why this is a helper and not a stopwatch inline in the loop
|
|
286
|
+
*
|
|
287
|
+
* The loop used to read the clock into a `startedAt`, call `ask`, and subtract
|
|
288
|
+
* two lines later — and the claim below that "the span is `ask(check.sql)` and
|
|
289
|
+
* nothing else" had NO guard behind it. A reviewer moved the subtraction below
|
|
290
|
+
* the row-to-finding mapping, widening the measured span to cover work that is
|
|
291
|
+
* not the statement, and **the entire unit suite stayed green (36/36)**. That is
|
|
292
|
+
* measured, not assumed.
|
|
293
|
+
*
|
|
294
|
+
* No cleverer fake clock closes it. The injected clock advances per READING, so
|
|
295
|
+
* every mutation that keeps two readings per check yields the same number; a
|
|
296
|
+
* clock that advanced only inside `ask` could not tell the two positions apart
|
|
297
|
+
* either, because the real mapping never touches the clock — and making the
|
|
298
|
+
* mapping slow in real time would buy a flake, not a guard.
|
|
299
|
+
*
|
|
300
|
+
* So the span is a THUNK. What is timed is what is passed in, and widening it
|
|
301
|
+
* stops being a one-line move of a subtraction and becomes a visible rewrite of
|
|
302
|
+
* the call site.
|
|
303
|
+
*
|
|
304
|
+
* 🪤 The throw is CAUGHT rather than propagated, so the failing path has a
|
|
305
|
+
* duration too. Keep it that way: a `try` in the caller around a helper that
|
|
306
|
+
* only times the happy path re-opens the two-read stopwatch this closes.
|
|
307
|
+
*
|
|
308
|
+
* @param now - The clock, in milliseconds
|
|
309
|
+
* @param run - The work whose duration is wanted, and nothing else
|
|
310
|
+
* @returns What `run` produced or threw, and how long that took
|
|
311
|
+
*/
|
|
312
|
+
function timed(now, run) {
|
|
313
|
+
const startedAt = now();
|
|
314
|
+
try {
|
|
315
|
+
return { ok: true, value: run(), durationMs: now() - startedAt };
|
|
316
|
+
}
|
|
317
|
+
catch (error) {
|
|
318
|
+
return { ok: false, error, durationMs: now() - startedAt };
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* The finding for a statement that would not run.
|
|
323
|
+
*
|
|
324
|
+
* 🔑 A finding, not a skip, at `error`, and under its OWN code.
|
|
325
|
+
*
|
|
326
|
+
* The check's declared severity describes how bad a VIOLATION is; it says
|
|
327
|
+
* nothing about how bad it is that the check cannot run, and a `warning` check
|
|
328
|
+
* whose SQL broke would otherwise fail nothing while asserting nothing. That
|
|
329
|
+
* much was always true of the DECLARATION.
|
|
330
|
+
*
|
|
331
|
+
* 🪤 It was never true of an adopter OVERRIDE, and this finding used to carry
|
|
332
|
+
* `customCheckCode(name)` — the same code as a violation of that very check. So
|
|
333
|
+
* `severity: { 'CUSTOM:foo': 'ignore' }`, the documented way to stand down a
|
|
334
|
+
* check you inherited, also silenced "foo could not run", and `'warning'`
|
|
335
|
+
* demoted it below the exit threshold. A renamed projection column then produced
|
|
336
|
+
* exit 0 from a gate. `RESOURCE_CHECK_BROKEN` is a non-overridable code
|
|
337
|
+
* precisely so that config line cannot reach here.
|
|
338
|
+
*
|
|
339
|
+
* 🪤 Only `ask` reaches this. Turning the selected rows INTO findings is not a
|
|
340
|
+
* statement failure, and reporting it as one told the operator a working rule
|
|
341
|
+
* was broken — see the append in {@link runUnits}.
|
|
342
|
+
*
|
|
343
|
+
* @param name - The check's key in `resources.checks`
|
|
344
|
+
* @param error - Whatever the statement threw
|
|
345
|
+
* @returns The run-integrity finding
|
|
346
|
+
*/
|
|
347
|
+
function brokenCheckFinding(name, error) {
|
|
348
|
+
return runIntegrityFinding(`The check "${name}" could not run, so it is asserting nothing: `
|
|
349
|
+
+ (error instanceof Error ? error.message : String(error)));
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* One declared statement, as a unit.
|
|
353
|
+
*
|
|
354
|
+
* @param name - The check's key in `resources.checks`
|
|
355
|
+
* @param check - Its declaration
|
|
356
|
+
* @param ask - Runs one statement against the populated projection
|
|
357
|
+
* @returns The runnable unit
|
|
358
|
+
*/
|
|
359
|
+
function sqlCheckUnit(name, check, ask) {
|
|
360
|
+
return {
|
|
361
|
+
name,
|
|
362
|
+
select: () => {
|
|
363
|
+
const rows = ask(check.sql);
|
|
364
|
+
return { rows: rows.length, report: () => issuesFromCheckRows(name, check, rows) };
|
|
365
|
+
},
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* One built-in predicate, as a unit.
|
|
370
|
+
*
|
|
371
|
+
* 🪤 Its `rows` is the finding count, and that is the same quantity a statement
|
|
372
|
+
* reports: one finding per violating row. There is no conversion step to leave
|
|
373
|
+
* outside the clock, so the whole predicate is timed.
|
|
374
|
+
*
|
|
375
|
+
* @param builtin - The built-in, already bound to this run's projection
|
|
376
|
+
* @returns The runnable unit
|
|
377
|
+
*/
|
|
378
|
+
function builtinCheckUnit(builtin) {
|
|
379
|
+
return {
|
|
380
|
+
name: builtin.name,
|
|
381
|
+
builtin: true,
|
|
382
|
+
select: () => {
|
|
383
|
+
const issues = builtin.run();
|
|
384
|
+
return { rows: issues.length, report: () => issues };
|
|
385
|
+
},
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Every check this run will attempt, in the order it will attempt them.
|
|
390
|
+
*
|
|
391
|
+
* 🔑 **Built-ins FIRST, and never interleaved with the declared ones**, so the
|
|
392
|
+
* document's `checks[]` opens with the same rules in the same order for every
|
|
393
|
+
* project and a reader comparing two runs compares like with like.
|
|
394
|
+
*
|
|
395
|
+
* 🪤 A project MAY declare a check whose key equals a built-in's name. Both run,
|
|
396
|
+
* both are priced, `builtin` on the cost record tells them apart, and
|
|
397
|
+
* `--check <name>` selects both. Not refused: a new config error is a breaking
|
|
398
|
+
* change and nothing about the collision is unsafe.
|
|
399
|
+
*
|
|
400
|
+
* @param builtins - The default set, bound to this run's projection
|
|
401
|
+
* @param checks - The project's `resources.checks`
|
|
402
|
+
* @param ask - Runs one statement against the populated projection
|
|
403
|
+
* @returns The units, built-ins first
|
|
404
|
+
*/
|
|
405
|
+
function checkUnits(builtins, checks, ask) {
|
|
406
|
+
return [
|
|
407
|
+
...builtins.map((builtin) => builtinCheckUnit(builtin)),
|
|
408
|
+
...Object.entries(checks).map(([name, check]) => sqlCheckUnit(name, check, ask)),
|
|
409
|
+
];
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Run every unit, collect its findings, and price it.
|
|
413
|
+
*
|
|
414
|
+
* 🚨 **The timed span is `unit.select()` and nothing else** — structurally,
|
|
415
|
+
* because it is the thunk handed to {@link timed}. 🚨 **And the loop announces a
|
|
416
|
+
* check BEFORE it can be stopped**, because a runaway statement can only be
|
|
417
|
+
* ended by an external `SIGKILL` that publishes nothing of the killed process's
|
|
418
|
+
* memory. Both, with the clock-granularity and one-record-per-check rules they
|
|
419
|
+
* rest on: `docs/architecture/cli.md`, "The population is charged to NOBODY" and
|
|
420
|
+
* "Why the loop announces a check before it can be stopped".
|
|
421
|
+
*
|
|
422
|
+
* @param units - Every check this run will attempt, built-ins first
|
|
423
|
+
* @param only - A single check name to run, or undefined for all of them
|
|
424
|
+
* @param now - The clock, in milliseconds; injected so a test can assert an
|
|
425
|
+
* exact duration rather than a range that passes on a timer that never started
|
|
426
|
+
* @param onProgress - Told what is about to run and what it cost, or undefined
|
|
427
|
+
* when nothing is watching (the in-process lane, and `--budget 0`)
|
|
428
|
+
* @returns The findings, and one cost record per check that ran
|
|
429
|
+
*/
|
|
430
|
+
function runUnits(units, only, now, onProgress) {
|
|
431
|
+
const issues = [];
|
|
432
|
+
const costs = [];
|
|
433
|
+
/** File the cost with the observer as well as with the caller — one place. */
|
|
434
|
+
const price = (cost) => {
|
|
435
|
+
costs.push(cost);
|
|
436
|
+
onProgress?.({ kind: 'check', ...cost });
|
|
437
|
+
};
|
|
438
|
+
for (const unit of units) {
|
|
439
|
+
const { name } = unit;
|
|
440
|
+
if (only !== undefined && name !== only)
|
|
441
|
+
continue;
|
|
442
|
+
// Spread onto every cost record this unit files, so the two arms below
|
|
443
|
+
// cannot disagree about which set the rule came from.
|
|
444
|
+
const origin = unit.builtin === undefined ? {} : { builtin: unit.builtin };
|
|
445
|
+
// 🪤 BEFORE `timed`, and not one line later. `ask` is where a runaway
|
|
446
|
+
// statement never returns from, so an announcement below it is filed by
|
|
447
|
+
// every check except the one whose name the operator needs.
|
|
448
|
+
onProgress?.({ kind: 'start', name });
|
|
449
|
+
const outcome = timed(now, unit.select);
|
|
450
|
+
if (!outcome.ok) {
|
|
451
|
+
// Priced up to the throw, and with NO row count: a check that did not
|
|
452
|
+
// complete selected nothing, and `rows: 0` would say it selected nothing
|
|
453
|
+
// and passed. A built-in reaches this arm too — a defect in VAT's own
|
|
454
|
+
// predicate must fail the gate rather than vanish.
|
|
455
|
+
price({ name, durationMs: outcome.durationMs, broken: true, ...origin });
|
|
456
|
+
issues.push(brokenCheckFinding(name, outcome.error));
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
price({ name, durationMs: outcome.durationMs, rows: outcome.value.rows, ...origin });
|
|
460
|
+
// 🪤 Appended one at a time, NEVER `issues.push(...findings)`. A spread
|
|
461
|
+
// becomes an ARGUMENT LIST, which throws `RangeError: Maximum call stack
|
|
462
|
+
// size exceeded` past roughly 125,000 elements on the main thread — and a
|
|
463
|
+
// check's result set is unbounded by construction. VAT's own
|
|
464
|
+
// `blob_references` table already holds 29,645 rows, so `SELECT * FROM
|
|
465
|
+
// blob_references` over a repository a few times this size reached it, and
|
|
466
|
+
// the throw landed in the arm above: a rule that ran perfectly was reported
|
|
467
|
+
// as one that "could not run, so it is asserting nothing". A loop does not
|
|
468
|
+
// put the argument limit in play at all.
|
|
469
|
+
for (const issue of outcome.value.report())
|
|
470
|
+
issues.push(issue);
|
|
471
|
+
}
|
|
472
|
+
return { issues, costs };
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Refuse a `--check` name that names neither a built-in nor a declared check.
|
|
476
|
+
*
|
|
477
|
+
* 🔑 **An unknown name is otherwise a silent green.** With nothing comparing the
|
|
478
|
+
* flag against the known names, `--check nope` filters every check away, runs
|
|
479
|
+
* none, and exits 0 with `checksRun: 0` and `issues: []` on empty stderr — and
|
|
480
|
+
* the "no checks are declared" warning does not fire, because it asks whether
|
|
481
|
+
* the `checks` MAP is empty, never whether the FILTER matched anything. Not a
|
|
482
|
+
* hypothetical typo: rename or delete a check and the CI step keeps passing
|
|
483
|
+
* forever while asserting nothing.
|
|
484
|
+
*
|
|
485
|
+
* Thrown rather than reported as a finding, because it is an OPERATOR error and
|
|
486
|
+
* not a content violation — the two have different exit codes in this command's
|
|
487
|
+
* contract (2 versus 1) and different audiences. Thrown before the projection is
|
|
488
|
+
* populated, so a mistyped flag costs no crawl.
|
|
489
|
+
*
|
|
490
|
+
* 🔑 **A built-in is nameable here exactly like a declared one.** A guard that
|
|
491
|
+
* only knew the config would refuse the very rules that run when there is no
|
|
492
|
+
* config at all. The refusal names both sets.
|
|
493
|
+
*
|
|
494
|
+
* @param builtinNames - The default set's names — {@link BUILTIN_CHECK_NAMES} in
|
|
495
|
+
* production, and passed in rather than imported here so a test can prove the
|
|
496
|
+
* guard rather than restate the constant
|
|
497
|
+
* @param checks - The project's `resources.checks`
|
|
498
|
+
* @param only - The `--check` value, or undefined
|
|
499
|
+
* @throws When `only` names no check this run would attempt
|
|
500
|
+
*/
|
|
501
|
+
export function requireKnownCheck(builtinNames, checks, only) {
|
|
502
|
+
// 🪤 `Object.hasOwn`, not `only in checks`. `in` walks the prototype chain, so
|
|
503
|
+
// `--check toString` would have looked declared, matched nothing, and restored
|
|
504
|
+
// the exact silent green this guard closes.
|
|
505
|
+
if (only === undefined || builtinNames.includes(only) || Object.hasOwn(checks, only))
|
|
506
|
+
return;
|
|
507
|
+
const declared = Object.keys(checks);
|
|
508
|
+
throw new Error(`No check named "${only}" exists. `
|
|
509
|
+
+ `Built in: ${builtinNames.join(', ')}. `
|
|
510
|
+
+ (declared.length === 0
|
|
511
|
+
? 'This project declares none of its own — add them under `resources.checks` in'
|
|
512
|
+
+ ' vibe-agent-toolkit.config.yaml.'
|
|
513
|
+
: `Declared under \`resources.checks\`: ${declared.join(', ')}.`));
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* Name every `CUSTOM:` severity override that overrides nothing.
|
|
517
|
+
*
|
|
518
|
+
* 🔑 **The same silent-green shape as an unknown `--check` name, one surface
|
|
519
|
+
* over.** {@link requireKnownCheck} refuses `--check nope` because a filter
|
|
520
|
+
* that matched nothing ran nothing and exited 0. A `validation.severity` entry
|
|
521
|
+
* keyed `CUSTOM:<name>` for a check that is not declared is the mirror image: it
|
|
522
|
+
* parses (the schema validates the KEY's shape and cannot see
|
|
523
|
+
* `resources.checks`), applies to no finding, and says nothing. An adopter
|
|
524
|
+
* renames a check and leaves the override behind, or mistypes it while standing
|
|
525
|
+
* down an inherited one — and in the second direction the check they meant to
|
|
526
|
+
* silence keeps failing their build with no clue why.
|
|
527
|
+
*
|
|
528
|
+
* ⚠️ **A warning, not a refusal, and the asymmetry with `--check` is
|
|
529
|
+
* deliberate.** `--check` is a flag typed for THIS run, so a mistake is an
|
|
530
|
+
* operator error and exiting 2 costs nothing. A `severity` entry lives in a
|
|
531
|
+
* committed config that may legitimately be shared across repositories or
|
|
532
|
+
* outlive a check through a rename in flight; refusing it would be a new
|
|
533
|
+
* breaking config error, which is exactly the change this release already
|
|
534
|
+
* decided not to make twice (see `ResourcesConfigSchema`). Stderr, so the
|
|
535
|
+
* document on stdout stays parseable — the same channel and the same reasoning
|
|
536
|
+
* as the "no checks are declared" warning in {@link checkCommand}.
|
|
537
|
+
*
|
|
538
|
+
* 📌 The registry half of the key space needs no equivalent: a misspelled
|
|
539
|
+
* REGISTRY code is already refused by `SeverityOverrideCodeSchema`, whose
|
|
540
|
+
* `SEVERITY_KEY_PATTERN` alternates every registry code name with the `CUSTOM:`
|
|
541
|
+
* namespace — so a key has to BE one of the shipped codes to match that half.
|
|
542
|
+
* Only the open `CUSTOM:` half can name something that does not exist, because
|
|
543
|
+
* only it matches on shape rather than on identity.
|
|
544
|
+
*
|
|
545
|
+
* @param checks - The project's `resources.checks`
|
|
546
|
+
* @param validation - The project's `resources.validation`, or undefined
|
|
547
|
+
* @param logger - Where the warning goes (stderr)
|
|
548
|
+
*/
|
|
549
|
+
export function warnUndeclaredOverrides(checks, validation, logger) {
|
|
550
|
+
const stale = Object.keys(validation?.severity ?? {})
|
|
551
|
+
.filter((code) => isCustomCheckCode(code))
|
|
552
|
+
// 🪤 `Object.hasOwn`, not `in` — same reason {@link requireKnownCheck}
|
|
553
|
+
// uses it: `in` walks the prototype chain, so an override keyed
|
|
554
|
+
// `CUSTOM:toString` would look declared and stay silent.
|
|
555
|
+
.filter((code) => !Object.hasOwn(checks, code.slice(CUSTOM_CHECK_CODE_PREFIX.length)));
|
|
556
|
+
if (stale.length === 0)
|
|
557
|
+
return;
|
|
558
|
+
const declared = Object.keys(checks);
|
|
559
|
+
logger.warn(`resources.validation.severity names ${stale.length} check(s) this project does not declare,`
|
|
560
|
+
+ ` so ${stale.length === 1 ? 'it overrides' : 'they override'} nothing: ${stale.join(', ')}.`
|
|
561
|
+
+ (declared.length === 0
|
|
562
|
+
? ' No checks are declared at all.'
|
|
563
|
+
: ` Declared under \`resources.checks\`: ${declared.join(', ')}.`)
|
|
564
|
+
+ ' Correct the spelling, or delete the entry — a severity override for a check that no longer'
|
|
565
|
+
+ ' exists is not silencing anything, and a MISTYPED one leaves the check you meant to stand'
|
|
566
|
+
+ ' down still failing the build.');
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
* The finding for a run whose checks executed over an empty corpus.
|
|
570
|
+
*
|
|
571
|
+
* 🔑 **The defect this closes shipped.** A scratch repository with
|
|
572
|
+
* `.gitignore = *` and two declared checks reported `checksRun: 2`,
|
|
573
|
+
* `status: success`, exit 0 and empty stderr over a corpus of ZERO files. The
|
|
574
|
+
* rules reported nothing because there was nothing to report on, and the
|
|
575
|
+
* document could not say so: `checksRun` is the denominator of rules, not of
|
|
576
|
+
* rows, so a gate over eight thousand files and a gate over none serialized
|
|
577
|
+
* identically. A broad ignore pattern, a shallow or sparse CI checkout, a root
|
|
578
|
+
* that resolved to the wrong directory, or an extent source that enumerated
|
|
579
|
+
* nothing all produce it — and because population DECLINES ignored members
|
|
580
|
+
* rather than flagging them, the tables carry no trace of the difference.
|
|
581
|
+
*
|
|
582
|
+
* The precedent is `population-wiring.ts`, which makes `onBlobPopulation`
|
|
583
|
+
* mandatory because "a tree whose every document was declined as binary
|
|
584
|
+
* otherwise populates as empty and reports success". That guard covers blob
|
|
585
|
+
* refusals only; this one covers the extent.
|
|
586
|
+
*
|
|
587
|
+
* 🪤 **Only when checks actually RAN.** A run with no rules at all is refused
|
|
588
|
+
* too — it is the same class one level further out — but by
|
|
589
|
+
* {@link noCheckRanFinding}, whose message is about the missing RULES rather
|
|
590
|
+
* than about the missing rows. Firing both would hand the operator two reports
|
|
591
|
+
* about one situation, and this is the less specific of the two: "the 0 checks
|
|
592
|
+
* that ran asserted nothing over 0 members" sends them to inspect a
|
|
593
|
+
* `.gitignore` when what they are missing is a `checks:` block.
|
|
594
|
+
*
|
|
595
|
+
* 🪤 **`RUN_INTEGRITY_CODE`, not a code of its own** — see that constant
|
|
596
|
+
* for why all four refusals in this verb share one.
|
|
597
|
+
*
|
|
598
|
+
* @param checksRun - How many checks EXECUTED. Under `--check` that is fewer
|
|
599
|
+
* than the project declares, which is why the message says "ran" and not
|
|
600
|
+
* "declared": the number is the size of the filtered run
|
|
601
|
+
* @param membersEnumerated - How many members the population enumerated
|
|
602
|
+
* @returns The finding, or nothing when the run had a corpus (or no checks)
|
|
603
|
+
*/
|
|
604
|
+
function emptyCorpusFinding(checksRun, membersEnumerated) {
|
|
605
|
+
if (checksRun === 0 || membersEnumerated > 0)
|
|
606
|
+
return [];
|
|
607
|
+
return [runIntegrityFinding(`The projection enumerated 0 members, so the ${checksRun} check(s) that ran`
|
|
608
|
+
+ ' asserted nothing: there were no rows for any statement to select and'
|
|
609
|
+
+ ' zero findings means only that the corpus was empty.'
|
|
610
|
+
+ ' Look at `.gitignore` (one broad pattern declines every file), at whether'
|
|
611
|
+
+ ' the checkout is complete rather than shallow or sparse, and at whether'
|
|
612
|
+
+ ' `root` in this report is the directory you meant.'
|
|
613
|
+
+ ' `vat resources scan` over the same path lists what an enumeration finds.')];
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Where the run was when it stopped, as a phrase.
|
|
617
|
+
*
|
|
618
|
+
* @param inFlight - What the log's last state says
|
|
619
|
+
* @returns A clause naming the unit
|
|
620
|
+
*/
|
|
621
|
+
function inFlightPhrase(inFlight) {
|
|
622
|
+
if (inFlight.kind === 'check')
|
|
623
|
+
return `while the check "${inFlight.name}" was running`;
|
|
624
|
+
if (inFlight.kind === 'reporting') {
|
|
625
|
+
return 'after the last check had finished, while its document was being assembled';
|
|
626
|
+
}
|
|
627
|
+
// 🪤 The HEDGE is load-bearing and was once deleted on the strength of
|
|
628
|
+
// `checks-complete`. It does not cover this window. `price()` files the last
|
|
629
|
+
// check's cost BEFORE `issuesFromCheckRows` turns its rows into issues, and
|
|
630
|
+
// `checks-complete` is written after that conversion — so a kill in between
|
|
631
|
+
// leaves a log of nothing but completed checks, which reads as `idle`. The
|
|
632
|
+
// unhedged sentence then told an operator whose run had finished every rule
|
|
633
|
+
// that it died before the first statement. The window is narrow (the
|
|
634
|
+
// conversion runs ~0.74x the statement it follows, so a budget that survived
|
|
635
|
+
// the statement survives the conversion) but a narrow window is not a closed
|
|
636
|
+
// one, and the sentence costs nothing to keep honest.
|
|
637
|
+
return 'while no check was running — either between the population and the first'
|
|
638
|
+
+ ' statement, or after the last one had filed its cost';
|
|
639
|
+
}
|
|
640
|
+
/**
|
|
641
|
+
* What ended the child, as a phrase.
|
|
642
|
+
*
|
|
643
|
+
* @param death - The ending the supervisor resolved
|
|
644
|
+
* @returns A clause naming the mechanism
|
|
645
|
+
*/
|
|
646
|
+
function deathPhrase(death) {
|
|
647
|
+
if (death.kind === 'signal')
|
|
648
|
+
return `the child process was terminated by ${death.signal}`;
|
|
649
|
+
if (death.kind === 'spawn-failed') {
|
|
650
|
+
return `the child process could not be started at all (${death.binary}: ${death.detail})`;
|
|
651
|
+
}
|
|
652
|
+
if (death.kind === 'kill-failed') {
|
|
653
|
+
return 'the budget was blown and the SIGKILL that should have ended the run was REFUSED'
|
|
654
|
+
+ ` (${death.detail})`;
|
|
655
|
+
}
|
|
656
|
+
if (death.kind === 'no-output') {
|
|
657
|
+
return `the child process exited ${death.code} and published no report at all`;
|
|
658
|
+
}
|
|
659
|
+
return 'the child process ended with neither an exit code nor a signal';
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* What to do about a child that never started.
|
|
663
|
+
*
|
|
664
|
+
* 🚨 **"Check your PATH" is the wrong thing to tell a saturated runner.**
|
|
665
|
+
* `spawn` fails with `EAGAIN` when the machine has no free process slot and
|
|
666
|
+
* `ENOMEM` when it has no free memory — which is precisely the memory-pressured
|
|
667
|
+
* environment this whole feature exists for, and precisely where an operator
|
|
668
|
+
* least deserves to be sent hunting an installation problem that is not there.
|
|
669
|
+
*
|
|
670
|
+
* @param detail - What `spawn` reported
|
|
671
|
+
* @returns The remedy sentence
|
|
672
|
+
*/
|
|
673
|
+
function spawnFailedRemedy(detail) {
|
|
674
|
+
if (/\b(?:EAGAIN|ENOMEM)\b/.test(detail)) {
|
|
675
|
+
return ' The child could not be started because the RUNNER had nothing left to start it'
|
|
676
|
+
+ ' with — EAGAIN is "no free process slot" and ENOMEM is "no free memory" — so this is'
|
|
677
|
+
+ ' about how loaded the machine is, not about the installation and not about the SQL.'
|
|
678
|
+
+ ' Give the job a less contended runner, or reduce what else is running beside it.';
|
|
679
|
+
}
|
|
680
|
+
return ' Nothing ran at all, so this is an installation or PATH problem rather than'
|
|
681
|
+
+ ' anything about the SQL.';
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* What to do about a kill the kernel refused.
|
|
685
|
+
*
|
|
686
|
+
* ⛔ This must never be composed from the spawn-failure sentence, however
|
|
687
|
+
* similar the plumbing is. The two are opposites: one child never existed, and
|
|
688
|
+
* this one is, as far as this process can tell, STILL RUNNING.
|
|
689
|
+
*
|
|
690
|
+
* @param death - The refused kill, and the pid it left behind
|
|
691
|
+
* @returns The remedy sentence
|
|
692
|
+
*/
|
|
693
|
+
function killFailedRemedy(detail, pid) {
|
|
694
|
+
const handle = pid === undefined
|
|
695
|
+
? ' The child\'s pid is not known to this process.'
|
|
696
|
+
: ` The child was process ${pid}.`;
|
|
697
|
+
return ' The run started, ran past its budget, and then REFUSED TO DIE: the watchdog sent'
|
|
698
|
+
+ ` SIGKILL and the kernel would not deliver it (${detail}), which normally means EPERM.`
|
|
699
|
+
+ handle
|
|
700
|
+
+ ' It may still be running and still holding whatever the statement was holding, so'
|
|
701
|
+
+ ' check for it and end it by hand before starting another run. Nothing about the SQL'
|
|
702
|
+
+ ' or the installation is implicated by the refusal itself — but the bound could not be'
|
|
703
|
+
+ ' enforced, so treat this run as unsupervised.'
|
|
704
|
+
// 🚨 Its OWN budget-role sentence, because the generic one below is false
|
|
705
|
+
// here: this is the single abnormal death the watchdog causes. Saying "the
|
|
706
|
+
// watchdog never fired" would repeat, in the report, the exact confusion
|
|
707
|
+
// that produced the defect.
|
|
708
|
+
+ ' The watchdog DID fire — the budget is what decided this run should stop — but the'
|
|
709
|
+
+ ' kill was refused, so raising the budget only moves when the refusal happens. It does'
|
|
710
|
+
+ ' not make the run stoppable.';
|
|
711
|
+
}
|
|
712
|
+
/**
|
|
713
|
+
* What to do about a particular signal.
|
|
714
|
+
*
|
|
715
|
+
* 🚨 **The defect this closes: the fallback contradicted the sentence beside
|
|
716
|
+
* it.** Only `SIGABRT` and `SIGKILL` were partitioned on and everything else
|
|
717
|
+
* fell through to a sentence written for `no-status` — so a real `kill -TERM`
|
|
718
|
+
* produced "terminated by SIGTERM … The cause is outside anything this command
|
|
719
|
+
* can observe; the child left no exit code to report". It names the signal, and
|
|
720
|
+
* then says it can see nothing and was told nothing. SIGSEGV, SIGBUS, SIGHUP and
|
|
721
|
+
* SIGXCPU read the same way.
|
|
722
|
+
*
|
|
723
|
+
* ⚠️ **SIGTERM is not exotic; it is how CI kills things** — `timeout(1)`, a
|
|
724
|
+
* cancelled job, a container stopping. The old claim that "SIGABRT and SIGKILL
|
|
725
|
+
* are the two that actually happen" was wrong about the commonest one.
|
|
726
|
+
*
|
|
727
|
+
* @param signal - The signal `close` reported
|
|
728
|
+
* @returns The remedy sentence
|
|
729
|
+
*/
|
|
730
|
+
function signalRemedy(signal) {
|
|
731
|
+
if (signal === 'SIGABRT') {
|
|
732
|
+
return ' SIGABRT here is Node aborting on its own heap limit: the run exhausted memory'
|
|
733
|
+
+ ' MATERIALISING a result set, which is what an unbounded statement does as soon as it'
|
|
734
|
+
+ ' selects rows rather than an aggregate. The remedy is a narrower statement or a'
|
|
735
|
+
+ ' `LIMIT`, not a bigger machine.';
|
|
736
|
+
}
|
|
737
|
+
if (signal === 'SIGKILL') {
|
|
738
|
+
return ' Nothing inside this run sends SIGKILL except the budget watchdog, and the watchdog'
|
|
739
|
+
+ ' did not fire — so something OUTSIDE killed the child: a kernel OOM killer on a'
|
|
740
|
+
+ ' memory-capped runner (it picks the largest process, which is this child and never the'
|
|
741
|
+
+ ' idle parent), a container memory limit, or an operator. Give the job more memory, or'
|
|
742
|
+
+ ' narrow the statement so it needs less.';
|
|
743
|
+
}
|
|
744
|
+
if (signal === 'SIGTERM' || signal === 'SIGINT' || signal === 'SIGHUP') {
|
|
745
|
+
return ` Nothing inside this run sends ${signal}: something OUTSIDE asked the child to stop.`
|
|
746
|
+
+ ' On CI that is almost always a step or job timeout (`timeout(1)`, a workflow'
|
|
747
|
+
+ ' `timeout-minutes`), a cancelled pipeline, or a container being shut down. Look at the'
|
|
748
|
+
+ ' job\'s own time limit and at whether the run was cancelled — not at the SQL, which was'
|
|
749
|
+
+ ' still working when it was interrupted.';
|
|
750
|
+
}
|
|
751
|
+
if (signal === 'SIGSEGV' || signal === 'SIGBUS') {
|
|
752
|
+
return ` ${signal} is a CRASH inside native code, not anything an adopter's SQL can express:`
|
|
753
|
+
+ ' the process was killed by the kernel for touching memory it should not have. That'
|
|
754
|
+
+ ' points at a native module (`node:sqlite` is one) or at the platform, and it is worth'
|
|
755
|
+
+ ' reporting with the Node version, the platform and the statement that was running.';
|
|
756
|
+
}
|
|
757
|
+
return ` ${signal} was not sent by anything inside this run, and this command has no specific`
|
|
758
|
+
+ ' remedy for it: look at what else on this machine signals its child processes — a'
|
|
759
|
+
+ ' supervisor, a resource limit, or an operator.';
|
|
760
|
+
}
|
|
761
|
+
/**
|
|
762
|
+
* The exit code Node uses for its own fatal abort, on EVERY platform.
|
|
763
|
+
*
|
|
764
|
+
* 🚨 **This is measured, and it is NOT the local platform's signal number.** On
|
|
765
|
+
* macOS and Linux 134 is the shell encoding `128 + SIGABRT`, where `SIGABRT` is
|
|
766
|
+
* 6. On Windows `os.constants.signals.SIGABRT` is **22** — MSVC numbers it
|
|
767
|
+
* differently — so deriving the code from that table gives 150 and matches
|
|
768
|
+
* nothing Node ever emits. A `windows-latest` run of the heap-abort case
|
|
769
|
+
* reported exactly **134**, the same as Unix: the number comes from Node's own
|
|
770
|
+
* abort path, not from the platform's signal table.
|
|
771
|
+
*
|
|
772
|
+
* 🪤 An earlier version of this file DID derive it from `os.constants.signals`,
|
|
773
|
+
* and its unit test derived the same way — so the test agreed with the code, the
|
|
774
|
+
* round-trip passed on every machine, and the pair was wrong together on the one
|
|
775
|
+
* platform the branch exists for. The value is therefore pinned as a measured
|
|
776
|
+
* fact, and a test asserts the number itself rather than re-deriving it.
|
|
777
|
+
*
|
|
778
|
+
* ⛔ Not a version constant: it decides nothing about whether stored data is
|
|
779
|
+
* still valid. It is an external protocol value, like an API header.
|
|
780
|
+
*/
|
|
781
|
+
export const NODE_FATAL_ABORT_EXIT_CODE = 134;
|
|
782
|
+
/**
|
|
783
|
+
* What to do about a child that exited with a code and said nothing.
|
|
784
|
+
*
|
|
785
|
+
* 🚨 **This is the SAME event as a signal death, seen on a platform without
|
|
786
|
+
* signals.** Windows has none, so Node reports its own fatal abort — the heap
|
|
787
|
+
* limit among them — as an exit code with `signal === null`, where macOS and
|
|
788
|
+
* Linux report `SIGABRT` with no code at all. An operator hitting the heap limit
|
|
789
|
+
* on Windows CI deserves the identical remedy as one hitting it on Linux, so the
|
|
790
|
+
* abort is recognised by its code and {@link signalRemedy} answers both.
|
|
791
|
+
*
|
|
792
|
+
* ⚠️ Every OTHER code gets a deliberately non-committal sentence. This command
|
|
793
|
+
* cannot tell an external termination it has no name for from a bug of its own,
|
|
794
|
+
* and claiming either would send half its readers to the wrong place — the exact
|
|
795
|
+
* defect {@link signalRemedy}'s fall-through was written to fix.
|
|
796
|
+
*
|
|
797
|
+
* @param code - The exit code the child reported
|
|
798
|
+
* @returns The remedy sentence
|
|
799
|
+
*/
|
|
800
|
+
function noOutputRemedy(code) {
|
|
801
|
+
if (code === NODE_FATAL_ABORT_EXIT_CODE) {
|
|
802
|
+
return ` Exit ${code} is Node's own fatal abort. A platform with no signal to report — Windows`
|
|
803
|
+
+ ' — surfaces as an exit code what macOS and Linux report as SIGABRT itself, and it is the'
|
|
804
|
+
+ ' same event.' + signalRemedy('SIGABRT');
|
|
805
|
+
}
|
|
806
|
+
return ` Exit ${code} is not Node's abort code, and the child published none of the document`
|
|
807
|
+
+ ' every path through this command ends by writing — so this is either a termination from'
|
|
808
|
+
+ ' outside that left an exit code instead of a signal, or a defect in vat. The child\'s stderr'
|
|
809
|
+
+ ' was inherited, so whatever it managed to say is above this report; if there is nothing'
|
|
810
|
+
+ ' there either, report this exit code with the platform and the statement that was running.';
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* What to DO about it — and the reason the death is carried this far as a union
|
|
814
|
+
* rather than as a string.
|
|
815
|
+
*
|
|
816
|
+
* 🔑 Every kind wants a DIFFERENT file looked at: SIGABRT is the statement,
|
|
817
|
+
* SIGKILL is the runner's memory, SIGTERM is the job's own timeout, a failed
|
|
818
|
+
* spawn is either the installation or the runner's load, and a refused kill is a
|
|
819
|
+
* process still running. A message that conflated any two of them would send
|
|
820
|
+
* half its readers to the wrong place.
|
|
821
|
+
*
|
|
822
|
+
* ⛔ **The last line is for `no-status` and nothing else.** It is the only
|
|
823
|
+
* ending about which this command genuinely knows nothing, and it used to be
|
|
824
|
+
* the fall-through for every unpartitioned signal — which produced messages
|
|
825
|
+
* that named a signal and then denied having any information about it.
|
|
826
|
+
*
|
|
827
|
+
* @param death - The ending the supervisor resolved
|
|
828
|
+
* @returns The remedy sentence
|
|
829
|
+
*/
|
|
830
|
+
function deathRemedy(death) {
|
|
831
|
+
if (death.kind === 'kill-failed')
|
|
832
|
+
return killFailedRemedy(death.detail, death.pid);
|
|
833
|
+
if (death.kind === 'spawn-failed')
|
|
834
|
+
return spawnFailedRemedy(death.detail) + WATCHDOG_UNINVOLVED;
|
|
835
|
+
if (death.kind === 'signal')
|
|
836
|
+
return signalRemedy(death.signal) + WATCHDOG_UNINVOLVED;
|
|
837
|
+
if (death.kind === 'no-output')
|
|
838
|
+
return noOutputRemedy(death.code) + WATCHDOG_UNINVOLVED;
|
|
839
|
+
return ' The cause is outside anything this command can observe; the child left no exit code'
|
|
840
|
+
+ ' to report.' + WATCHDOG_UNINVOLVED;
|
|
841
|
+
}
|
|
842
|
+
/**
|
|
843
|
+
* The sentence that keeps an abnormal death from being blamed on the bound.
|
|
844
|
+
*
|
|
845
|
+
* ⛔ Appended by every arm of {@link deathRemedy} EXCEPT the refused kill, which
|
|
846
|
+
* writes its own — because there the watchdog is exactly what fired, and this
|
|
847
|
+
* sentence would repeat in the report the confusion that produced the defect:
|
|
848
|
+
* an `error` event read as a spawn failure, so `killed` was never consulted.
|
|
849
|
+
*/
|
|
850
|
+
const WATCHDOG_UNINVOLVED = ' The budget was not what ended this run — the watchdog never fired — so raising it'
|
|
851
|
+
+ ' would not help.';
|
|
852
|
+
/**
|
|
853
|
+
* The sentence that says what this document does NOT contain.
|
|
854
|
+
*
|
|
855
|
+
* 🚨 Load-bearing, and shared by both endings. The progress log records COSTS,
|
|
856
|
+
* not findings: a check that completed contributes its `rows` (how many rows its
|
|
857
|
+
* statement selected) but not the violations those rows would have become,
|
|
858
|
+
* because the child never got to turn them into issues. A reader who took the
|
|
859
|
+
* issue list as the complete account would conclude the finished rules found
|
|
860
|
+
* nothing.
|
|
861
|
+
*/
|
|
862
|
+
const INCOMPLETE_NOTICE = ' ⚠️ The checks listed under `checks` DID complete and `rows` is what each'
|
|
863
|
+
+ ' statement selected, but their individual violations are NOT in `issues`: the'
|
|
864
|
+
+ ' progress log records costs, not findings. Read that list as incomplete.';
|
|
865
|
+
/**
|
|
866
|
+
* The finding for a run that was interrupted, however it was interrupted.
|
|
867
|
+
*
|
|
868
|
+
* 🪤 **`RUN_INTEGRITY_CODE`, for the reason that constant gives.** An
|
|
869
|
+
* interrupted run must never be silenceable by the config of the very project
|
|
870
|
+
* whose SQL hung.
|
|
871
|
+
*
|
|
872
|
+
* 🔑 **This is the MORE SPECIFIC of two claims that can be true at once.** A run
|
|
873
|
+
* killed before its first statement completed recovers zero costs, which
|
|
874
|
+
* {@link noCheckRanFinding} also refuses — so that one stands down when this
|
|
875
|
+
* one is present. The dedupe there is order-independent (`issues.some(code ===
|
|
876
|
+
* RUN_INTEGRITY_CODE)`), so this finding only has to be IN the issue list of an
|
|
877
|
+
* interrupted document, not first in it.
|
|
878
|
+
*
|
|
879
|
+
* @param inFlight - What the run was doing when the log stopped growing
|
|
880
|
+
* @param ending - Which way the run ended, and what the operator can do
|
|
881
|
+
* @returns The run-integrity finding
|
|
882
|
+
*/
|
|
883
|
+
function interruptedRunFinding(inFlight, ending) {
|
|
884
|
+
const where = inFlightPhrase(inFlight);
|
|
885
|
+
const message = ending.kind === 'budget'
|
|
886
|
+
? budgetKillMessage(inFlight, ending.budgetSecs, where)
|
|
887
|
+
: `This run DIED before it finished: ${deathPhrase(ending.death)} ${where}, so the checks`
|
|
888
|
+
+ ' after it never executed and this document is not a verdict.'
|
|
889
|
+
+ deathRemedy(ending.death);
|
|
890
|
+
return runIntegrityFinding(message + INCOMPLETE_NOTICE);
|
|
891
|
+
}
|
|
892
|
+
/**
|
|
893
|
+
* What a WATCHDOG kill means, which depends entirely on what was in flight.
|
|
894
|
+
*
|
|
895
|
+
* 🚨 **The defect this closes: the `reporting` state got the statement-hang
|
|
896
|
+
* body.** Creating that state adapted only the *where* clause, so a run whose
|
|
897
|
+
* ten checks had ALL completed and whose 1,000,000-issue document blew the
|
|
898
|
+
* budget was told "the checks after it never executed" and "a statement that
|
|
899
|
+
* will not finish cannot be stopped from inside the process — the query is
|
|
900
|
+
* synchronous and holds the event loop". Measured on that run: `checksRun: 10`,
|
|
901
|
+
* all ten costs present, no statement running, nothing holding the loop for any
|
|
902
|
+
* SQL reason. The operator was sent to hunt a hanging rule that does not exist,
|
|
903
|
+
* and the message never mentioned the thing that actually killed the run.
|
|
904
|
+
*
|
|
905
|
+
* ⚠️ It is easy to reach on a real gate, not a curiosity: serialising 2,000,000
|
|
906
|
+
* issues takes ~17 s after `checks-complete` is filed, which is one budget
|
|
907
|
+
* window on its own.
|
|
908
|
+
*
|
|
909
|
+
* @param inFlight - What the log's last state says
|
|
910
|
+
* @param budgetSecs - The bound that was blown
|
|
911
|
+
* @param where - The clause naming the unit, from {@link inFlightPhrase}
|
|
912
|
+
* @returns The message body
|
|
913
|
+
*/
|
|
914
|
+
function budgetKillMessage(inFlight, budgetSecs, where) {
|
|
915
|
+
if (inFlight.kind === 'reporting') {
|
|
916
|
+
return `This run made no progress for ${budgetSecs}s and was killed ${where}.`
|
|
917
|
+
+ ' Every declared check had already finished and filed its cost, so no statement was'
|
|
918
|
+
+ ' running and there was nothing left to execute: the cause is the SIZE of the document'
|
|
919
|
+
+ ' being assembled, not a rule that would not return. Serialising a large result is'
|
|
920
|
+
+ ' linear in how much there is — 2,000,000 issues take about 17 seconds — so look at'
|
|
921
|
+
+ ' the rules under `checks` that produced the most rows and narrow them (a `LIMIT`, or'
|
|
922
|
+
+ ' one row per violation rather than one per member), or raise the bound with'
|
|
923
|
+
+ ' `--budget <seconds>` so the document has time to be written.';
|
|
924
|
+
}
|
|
925
|
+
return `This run made no progress for ${budgetSecs}s and was killed ${where}, so the`
|
|
926
|
+
+ ' checks after it never executed and this document is not a verdict.'
|
|
927
|
+
+ ' A statement that will not finish cannot be stopped from inside the process —'
|
|
928
|
+
+ ' the query is synchronous and holds the event loop — so the bound is enforced by'
|
|
929
|
+
+ ' killing the run. Raise it with `--budget <seconds>` if the work is legitimately'
|
|
930
|
+
+ ' slow, or `--budget 0` to remove it (the run can then hang forever).';
|
|
931
|
+
}
|
|
932
|
+
/**
|
|
933
|
+
* The refusal when there is no population line to build a document from.
|
|
934
|
+
*
|
|
935
|
+
* Separate wording per ending for the same reason the finding is: telling an
|
|
936
|
+
* operator whose child was OOM-killed to raise `--budget` sends them to change
|
|
937
|
+
* the one thing that had nothing to do with it.
|
|
938
|
+
*
|
|
939
|
+
* @param ending - Which way the run ended
|
|
940
|
+
* @returns The operator-error message
|
|
941
|
+
*/
|
|
942
|
+
function noPopulationMessage(ending) {
|
|
943
|
+
const tail = ' There is no projection to report on and no honest document to publish.';
|
|
944
|
+
if (ending.kind === 'abnormal') {
|
|
945
|
+
return `This run DIED before its population completed: ${deathPhrase(ending.death)}.`
|
|
946
|
+
+ tail + deathRemedy(ending.death);
|
|
947
|
+
}
|
|
948
|
+
return `The run made no progress for ${ending.budgetSecs}s and was killed before its`
|
|
949
|
+
+ ' population completed.' + tail
|
|
950
|
+
+ ' Population is legitimately slow on a large tree, and it reports progress only when it'
|
|
951
|
+
+ ' FINISHES, so the budget is a total bound for that one unit — raise it with'
|
|
952
|
+
+ ' `--budget <seconds>` before assuming the crawl is stuck.'
|
|
953
|
+
+ ' For scale, two MEASURED trees, neither of them yours: VAT\'s own repository is ~1.2s'
|
|
954
|
+
+ ' warm and 33-35s with a cold parse cache; a 10,000-file adopter tree is ~5s warm and'
|
|
955
|
+
+ ' 16.5s cold. Your tree is a third number.';
|
|
956
|
+
}
|
|
957
|
+
/**
|
|
958
|
+
* Rebuild the document from what an INTERRUPTED run left on disk.
|
|
959
|
+
*
|
|
960
|
+
* Both interruptions come here — the watchdog's kill and an abnormal death —
|
|
961
|
+
* because everything below this line is identical for them: the same recovered
|
|
962
|
+
* costs, the same non-overridable finding, the same refusal when there is no
|
|
963
|
+
* population. Only the sentences differ, and {@link CheckRunEnding} carries that.
|
|
964
|
+
*
|
|
965
|
+
* 🔑 It feeds {@link buildCheckOutputData}, the SAME builder a completed run
|
|
966
|
+
* uses. A second payload builder for this lane would be a second shape to
|
|
967
|
+
* keep in step with every future field — and the failure mode is silent, because
|
|
968
|
+
* nobody reads an interrupted run's document until the day they need it.
|
|
969
|
+
*
|
|
970
|
+
* 🚨 **A run interrupted during POPULATION gets no document at all.** There is no
|
|
971
|
+
* projection, so `population`, `populationSecs` and `membersEnumerated` have no
|
|
972
|
+
* honest value — and inventing `membersEnumerated: 0` would be worse than a
|
|
973
|
+
* blank, because that value already MEANS "this gate ran over an empty corpus",
|
|
974
|
+
* which is a different and wrong claim. The throw becomes an operator error
|
|
975
|
+
* (exit 2) through the same `handleReportCommandError` path everything else here uses.
|
|
976
|
+
*
|
|
977
|
+
* @param options - The wreckage
|
|
978
|
+
* @param options.entries - What `parseProgressLog` recovered
|
|
979
|
+
* @param options.root - The corpus root the parent resolved
|
|
980
|
+
* @param options.ending - Which way the run was interrupted
|
|
981
|
+
* @param options.durationMs - Wall time from spawn to ending
|
|
982
|
+
* @returns The input to {@link buildCheckOutputData}
|
|
983
|
+
* @throws When the child never reported a completed population
|
|
984
|
+
*/
|
|
985
|
+
export function buildInterruptedCheckInput(options) {
|
|
986
|
+
const { entries, root, ending, durationMs } = options;
|
|
987
|
+
const population = entries.find((entry) => entry.kind === 'population');
|
|
988
|
+
if (population === undefined)
|
|
989
|
+
throw new Error(noPopulationMessage(ending));
|
|
990
|
+
return {
|
|
991
|
+
root,
|
|
992
|
+
durationMs,
|
|
993
|
+
population: population.population,
|
|
994
|
+
populationMs: population.populationMs,
|
|
995
|
+
lensMs: population.lensMs,
|
|
996
|
+
lensesEvaluated: population.lensesEvaluated,
|
|
997
|
+
membersEnumerated: population.membersEnumerated,
|
|
998
|
+
issues: [interruptedRunFinding(unitInFlight(entries), ending)],
|
|
999
|
+
// 🪤 Rebuilt field by field rather than passed through. The log's check
|
|
1000
|
+
// entry carries a `kind` discriminator that `CheckCost` does not, and the
|
|
1001
|
+
// conditional spreads keep `rows`/`broken` ABSENT rather than `undefined` —
|
|
1002
|
+
// which is what makes `rows: 0` on a statement that never returned
|
|
1003
|
+
// impossible, exactly as it is on the completed path.
|
|
1004
|
+
costs: entries.filter((entry) => entry.kind === 'check').map((entry) => ({
|
|
1005
|
+
name: entry.name,
|
|
1006
|
+
durationMs: entry.durationMs,
|
|
1007
|
+
...(entry.rows === undefined ? {} : { rows: entry.rows }),
|
|
1008
|
+
...(entry.broken === undefined ? {} : { broken: entry.broken }),
|
|
1009
|
+
...(entry.builtin === undefined ? {} : { builtin: entry.builtin }),
|
|
1010
|
+
})),
|
|
1011
|
+
};
|
|
1012
|
+
}
|
|
1013
|
+
/**
|
|
1014
|
+
* Run this project's checks — VAT's default set plus whatever the project
|
|
1015
|
+
* declared — and resolve their severities: the whole of this verb's logic, with
|
|
1016
|
+
* the database taken out.
|
|
1017
|
+
*
|
|
1018
|
+
* 🔑 **`builtins` is a required argument beside `checks`, never a default inside
|
|
1019
|
+
* this function.** That is what makes "the default set belongs to the pipeline,
|
|
1020
|
+
* not to the config file" a property of the signature — nobody can get the
|
|
1021
|
+
* defaults by omitting an argument or lose them by supplying no config.
|
|
1022
|
+
*
|
|
1023
|
+
* Exported and pure so the loop is provable without a spawn. It was not: every
|
|
1024
|
+
* spawned case declared ONE check (the `--check` case declares two and filters
|
|
1025
|
+
* to one, which the loop cannot distinguish), so `checksRun` was never observed
|
|
1026
|
+
* above 1 and a `break` at the end of the loop would have left the suite green.
|
|
1027
|
+
*
|
|
1028
|
+
* Pure in the same sense the payload builder is, with ONE seam: it reads a
|
|
1029
|
+
* clock. That is why the clock is a parameter — the measurement is the point of
|
|
1030
|
+
* the `costs` it returns, and a duration nothing can pin is a number nobody can
|
|
1031
|
+
* trust.
|
|
1032
|
+
*
|
|
1033
|
+
* @param options - The run
|
|
1034
|
+
* @param options.builtins - The default set, already bound to this run's
|
|
1035
|
+
* projection. Required, and `[]` is a deliberate statement rather than an
|
|
1036
|
+
* omission
|
|
1037
|
+
* @param options.checks - The declared checks
|
|
1038
|
+
* @param options.only - A single check name, or undefined for all
|
|
1039
|
+
* @param options.ask - Runs one statement against the populated projection
|
|
1040
|
+
* @param options.validation - The project's severity overrides, or undefined
|
|
1041
|
+
* @param options.membersEnumerated - How many members the population enumerated.
|
|
1042
|
+
* Told rather than asked, because no answer can reveal it: an `ask` over an
|
|
1043
|
+
* empty projection and an `ask` over a clean one both return no rows
|
|
1044
|
+
* @param options.now - The measurement clock in milliseconds, defaulting to
|
|
1045
|
+
* `performance.now()`. Injected only so a unit test can pin an exact per-check
|
|
1046
|
+
* duration; nothing in production passes it
|
|
1047
|
+
* @param options.onProgress - Where each check's `start` and cost are announced
|
|
1048
|
+
* so an outside supervisor can see them, or undefined when the run is not
|
|
1049
|
+
* supervised. Optional rather than required because `--budget 0` and every
|
|
1050
|
+
* unit case run with nobody watching
|
|
1051
|
+
* @returns The resolved findings, and one cost record per check that ran
|
|
1052
|
+
*/
|
|
1053
|
+
export function runProjectChecks(options) {
|
|
1054
|
+
const now = options.now ?? (() => performance.now());
|
|
1055
|
+
const { issues, costs } = runUnits(checkUnits(options.builtins, options.checks, options.ask), options.only, now, options.onProgress);
|
|
1056
|
+
// 🚨 HERE, and this line moved to get here. `checks-complete` buys the phase
|
|
1057
|
+
// after the last statement a fresh watchdog window, and three places
|
|
1058
|
+
// documented that phase as "resolving severities and serialising". It was
|
|
1059
|
+
// emitted a level up, by `runOutcome`, AFTER this function returned — so
|
|
1060
|
+
// `resolveIssueSeverity` below ran inside the LAST CHECK's window and the
|
|
1061
|
+
// documented claim was false for the half of the phase that is quadratic in
|
|
1062
|
+
// the issue count. Emitting it before that call is what makes the claim true,
|
|
1063
|
+
// which is the useful direction: the alternative was to weaken three docs.
|
|
1064
|
+
//
|
|
1065
|
+
// ⚠️ It does NOT cover the last check's row-to-issue conversion, which
|
|
1066
|
+
// `runUnits` does after filing that check's cost — see {@link inFlightPhrase}
|
|
1067
|
+
// for why the `idle` sentence still has to hedge for that window.
|
|
1068
|
+
options.onProgress?.({ kind: 'checks-complete' });
|
|
1069
|
+
// The run-integrity report leads. An aggregate check selects a row whatever
|
|
1070
|
+
// the corpus is, so its findings can outnumber this one — and every one of
|
|
1071
|
+
// them is noise until the operator knows the gate ran over nothing.
|
|
1072
|
+
const reported = [...emptyCorpusFinding(costs.length, options.membersEnumerated), ...issues];
|
|
1073
|
+
return {
|
|
1074
|
+
// The adopter's own `resources.validation.severity` still applies, so a
|
|
1075
|
+
// check inherited from a shared config can be downgraded or ignored without
|
|
1076
|
+
// editing it. Applied AFTER the run, never before: a check that is going to
|
|
1077
|
+
// be ignored must still be EXECUTED, or "ignored" would quietly become
|
|
1078
|
+
// "never checked" and those are different claims.
|
|
1079
|
+
//
|
|
1080
|
+
// It reaches a check's VIOLATIONS only. `RESOURCE_CHECK_BROKEN` is not an
|
|
1081
|
+
// overridable code, so the news that a check stopped checking survives every
|
|
1082
|
+
// override an adopter can write about the check itself.
|
|
1083
|
+
//
|
|
1084
|
+
// 🔑 A BUILT-IN's findings pass through the identical call, under their
|
|
1085
|
+
// ordinary registry code: `severity: { CLAUDE_RULE_GLOB_INERT: ignore }`
|
|
1086
|
+
// silences the default exactly as it silences any other code VAT emits, with
|
|
1087
|
+
// no `CUSTOM:` spelling and no second override surface. That the two lanes
|
|
1088
|
+
// share this one line is the reason there is nothing to keep in step.
|
|
1089
|
+
issues: resolveIssueSeverity(reported, options.validation),
|
|
1090
|
+
costs,
|
|
1091
|
+
};
|
|
1092
|
+
}
|
|
1093
|
+
/**
|
|
1094
|
+
* Serialize the document in the format the operator asked for.
|
|
1095
|
+
*
|
|
1096
|
+
* Extracted because BOTH endings publish one — a completed run and a killed one
|
|
1097
|
+
* — and two copies of a two-branch format switch is how one of them ends up
|
|
1098
|
+
* ignoring `--format json`.
|
|
1099
|
+
*
|
|
1100
|
+
* @param payload - The document
|
|
1101
|
+
* @param format - `json`, or anything else for YAML
|
|
1102
|
+
*/
|
|
1103
|
+
function emitCheckDocument(payload, format) {
|
|
1104
|
+
writeStructuredOutput(payload, format);
|
|
1105
|
+
}
|
|
1106
|
+
/**
|
|
1107
|
+
* The child's argv — this very command, plus the hidden flag that stops it
|
|
1108
|
+
* spawning a child of its own.
|
|
1109
|
+
*
|
|
1110
|
+
* 🚨 `--cost-log` is ALWAYS appended, and that is the recursion guard. Its
|
|
1111
|
+
* presence is the sole discriminator between "supervise a child" and "do the
|
|
1112
|
+
* work"; a path that built these arguments without it would fork bomb.
|
|
1113
|
+
*
|
|
1114
|
+
* @param pathArg - The corpus root the operator named, or undefined
|
|
1115
|
+
* @param options - What the operator passed
|
|
1116
|
+
* @param logPath - The progress log both sides watch
|
|
1117
|
+
* @returns The child's arguments after the CLI entry point
|
|
1118
|
+
*/
|
|
1119
|
+
function childArgs(pathArg, options, logPath) {
|
|
1120
|
+
return [
|
|
1121
|
+
'resources',
|
|
1122
|
+
'check',
|
|
1123
|
+
...(pathArg === undefined ? [] : [pathArg]),
|
|
1124
|
+
...(options.check === undefined ? [] : ['--check', options.check]),
|
|
1125
|
+
...(options.format === undefined ? [] : ['--format', options.format]),
|
|
1126
|
+
...(options.debug === true ? ['--debug'] : []),
|
|
1127
|
+
'--cost-log',
|
|
1128
|
+
logPath,
|
|
1129
|
+
];
|
|
1130
|
+
}
|
|
1131
|
+
/**
|
|
1132
|
+
* Run the checks in a child bounded by `budgetSecs`, and report either what it
|
|
1133
|
+
* said or what killing it revealed.
|
|
1134
|
+
*
|
|
1135
|
+
* 🪤 The endings are RETURNED rather than exited on from inside
|
|
1136
|
+
* `withProgressLog`. `process.exit` skips a `finally`, so exiting in there would
|
|
1137
|
+
* leak the temp directory on every single run — and it is the successful runs,
|
|
1138
|
+
* which are all of them but one, that would accumulate.
|
|
1139
|
+
*
|
|
1140
|
+
* @param options - The run
|
|
1141
|
+
* @param options.pathArg - The corpus root the operator named, or undefined
|
|
1142
|
+
* @param options.options - What the operator passed
|
|
1143
|
+
* @param options.budgetSecs - The bound, in seconds
|
|
1144
|
+
* @returns What to publish, and with what exit code
|
|
1145
|
+
* @throws When the child was killed before its population completed
|
|
1146
|
+
*/
|
|
1147
|
+
async function superviseCheckRun(options) {
|
|
1148
|
+
const startDir = options.pathArg ?? process.cwd();
|
|
1149
|
+
// 🪤 The SILENT resolution, deliberately. `projectRootOrLoudCwd` warns on
|
|
1150
|
+
// stderr when there is no project ancestor, and the CHILD runs that policy for
|
|
1151
|
+
// real — a parent that ran it too would print the same warning twice for one
|
|
1152
|
+
// run. The parent needs a root only to label a KILLED run's document.
|
|
1153
|
+
const root = projectRootOrNull(startDir) ?? safePath.resolve(startDir);
|
|
1154
|
+
return withProgressLog(async (logPath) => {
|
|
1155
|
+
const run = await superviseCheck({
|
|
1156
|
+
args: childArgs(options.pathArg, options.options, logPath),
|
|
1157
|
+
logPath,
|
|
1158
|
+
budgetMs: options.budgetSecs * 1000,
|
|
1159
|
+
});
|
|
1160
|
+
if (run.outcome === 'completed')
|
|
1161
|
+
return { forward: run.stdout, code: run.code };
|
|
1162
|
+
return {
|
|
1163
|
+
payload: buildCheckOutputData(buildInterruptedCheckInput({
|
|
1164
|
+
entries: parseProgressLog(run.log),
|
|
1165
|
+
root,
|
|
1166
|
+
// 🚨 The abnormal ending reaches the SAME fail-closed document the
|
|
1167
|
+
// watchdog's kill does. It used to reach `{ outcome: 'completed', code:
|
|
1168
|
+
// 0 }` instead, because the close handler never read `close`'s signal
|
|
1169
|
+
// argument — so a child that aborted on its heap limit printed nothing
|
|
1170
|
+
// and exited 0, on precisely the runaway shape the bound exists for.
|
|
1171
|
+
ending: run.outcome === 'killed'
|
|
1172
|
+
? { kind: 'budget', budgetSecs: options.budgetSecs }
|
|
1173
|
+
: { kind: 'abnormal', death: run.death },
|
|
1174
|
+
durationMs: run.elapsedMs,
|
|
1175
|
+
})),
|
|
1176
|
+
};
|
|
1177
|
+
});
|
|
1178
|
+
}
|
|
1179
|
+
/**
|
|
1180
|
+
* Run the project's declared checks against its projection.
|
|
1181
|
+
*
|
|
1182
|
+
* ## 🚨 Why this verb spawns a child and `vat resources query` does not
|
|
1183
|
+
*
|
|
1184
|
+
* Both verbs run SQL an adopter wrote, over the same projection, through the
|
|
1185
|
+
* same `withQueriedProjection` — which does NOT diverge here, and must not. The
|
|
1186
|
+
* fork is this function and nothing below it.
|
|
1187
|
+
*
|
|
1188
|
+
* `query`'s author is at the keyboard, and Ctrl-C already works for them. It
|
|
1189
|
+
* works for one reason worth stating, because it is fragile: NO signal handler
|
|
1190
|
+
* is installed anywhere on this path. A process blocked in synchronous
|
|
1191
|
+
* `node:sqlite` dies instantly on SIGINT while that remains true, and SURVIVES
|
|
1192
|
+
* SIGINT and SIGTERM the moment a handler exists — the handler is a JS callback
|
|
1193
|
+
* and the event loop that would schedule it is the blocked resource. Adding one
|
|
1194
|
+
* would take Ctrl-C away from `query` without adding anything to `check`.
|
|
1195
|
+
*
|
|
1196
|
+
* `check` runs unattended in CI, where nobody is there to press it. So it gets a
|
|
1197
|
+
* bound enforced from outside itself, and a hang becomes a bounded failure
|
|
1198
|
+
* instead of a job that burns its runner minutes and reports nothing.
|
|
1199
|
+
*
|
|
1200
|
+
* ## `[path]` locates the project; a `WHERE` clause is the only scope a check has
|
|
1201
|
+
*
|
|
1202
|
+
* Same contract as `vat resources query`, for the same reason (see
|
|
1203
|
+
* `queryCommand`): the projection is the whole tracked tree, so the argument is
|
|
1204
|
+
* where root discovery starts and nothing more. A locator that names no
|
|
1205
|
+
* directory is refused HERE, before the fork below — the parent would otherwise
|
|
1206
|
+
* resolve it silently to the cwd's project and spawn a child to run every check
|
|
1207
|
+
* over a tree the operator did not name, at exit 0.
|
|
1208
|
+
*
|
|
1209
|
+
* @param pathArg - Where to look for the project, or omitted for the current directory
|
|
1210
|
+
* @param options - Parsed command-line options
|
|
1211
|
+
*/
|
|
1212
|
+
export async function checkCommand(pathArg, options) {
|
|
1213
|
+
const logger = createLogger({ debug: options.debug ?? false });
|
|
1214
|
+
const startTime = Date.now();
|
|
1215
|
+
try {
|
|
1216
|
+
if (pathArg !== undefined)
|
|
1217
|
+
assertDirectoryArgument(pathArg);
|
|
1218
|
+
const budgetSecs = parseBudgetSeconds(options.budget);
|
|
1219
|
+
// Before anything is spawned: a budget the fork below would silently ignore
|
|
1220
|
+
// is an operator error, not a bound.
|
|
1221
|
+
requireSupervisableFlags({
|
|
1222
|
+
costLog: options.costLog,
|
|
1223
|
+
budgetRaw: options.budget,
|
|
1224
|
+
budgetSecs,
|
|
1225
|
+
});
|
|
1226
|
+
// The presence of `--cost-log` says "you ARE the child". `--budget 0` says
|
|
1227
|
+
// the operator declined the bound; both run the work here, and the second
|
|
1228
|
+
// can therefore hang forever, which its help text says.
|
|
1229
|
+
if (!runsInThisProcess({ costLog: options.costLog, budgetSecs })) {
|
|
1230
|
+
const ending = await superviseCheckRun({ pathArg, options, budgetSecs });
|
|
1231
|
+
if ('forward' in ending) {
|
|
1232
|
+
// Verbatim. The child already built the document the operator's
|
|
1233
|
+
// `--format` asked for, and re-serializing it here would be a second
|
|
1234
|
+
// place for that shape to live.
|
|
1235
|
+
//
|
|
1236
|
+
// ⚠️ `durationSecs` in it is therefore the CHILD's wall time and does
|
|
1237
|
+
// not include this process's own startup and spawn (~0.15 s measured).
|
|
1238
|
+
// Deliberate: the field is a breakdown that `populationSecs` and the
|
|
1239
|
+
// per-rule costs have to reconcile against, and folding in a supervisor
|
|
1240
|
+
// overhead none of them can account for would leave a remainder a
|
|
1241
|
+
// reader would attribute to whichever rule they were looking at — the
|
|
1242
|
+
// exact defect `CheckCost` documents.
|
|
1243
|
+
writeStdoutSync(ending.forward);
|
|
1244
|
+
process.exit(ending.code);
|
|
1245
|
+
}
|
|
1246
|
+
emitCheckDocument(ending.payload, options.format);
|
|
1247
|
+
// ⛔ Never OK. A run that did not finish — killed by the watchdog OR dead of
|
|
1248
|
+
// its own memory — must not look like a pass. FINDINGS, because the
|
|
1249
|
+
// document it just published says `status: findings` with the killed
|
|
1250
|
+
// check named as one: the checks that completed are kept, and the exit
|
|
1251
|
+
// code follows the document, as everywhere else.
|
|
1252
|
+
process.exit(ExitCode.FINDINGS);
|
|
1253
|
+
}
|
|
1254
|
+
const projectRoot = projectRootOrLoudCwd(pathArg ?? process.cwd(), logger);
|
|
1255
|
+
const config = loadConfigCached(projectRoot);
|
|
1256
|
+
const checks = config?.resources?.checks;
|
|
1257
|
+
if (checks === undefined || Object.keys(checks).length === 0) {
|
|
1258
|
+
// 🪤 Stderr only, and it decides NOTHING — the document is what the exit
|
|
1259
|
+
// code is computed from and what CI parses. ⚠️ It does not stand beside a
|
|
1260
|
+
// refusal: the built-in set runs here, so this run DID assert something.
|
|
1261
|
+
// What the operator needs to know is that the project is relying entirely
|
|
1262
|
+
// on VAT's defaults, which is what a deleted `checks:` block looks like.
|
|
1263
|
+
logger.warn(`No checks of this project's own are declared, so this run asserted only VAT's`
|
|
1264
|
+
+ ` built-in set (${BUILTIN_CHECK_NAMES.join(', ')}).`
|
|
1265
|
+
+ ' Add your own under `resources.checks` in vibe-agent-toolkit.config.yaml;'
|
|
1266
|
+
+ ' each is a description plus one SQL statement selecting the rows that VIOLATE it.'
|
|
1267
|
+
+ ' `vat resources check --help` prints the SQL each built-in is equivalent to,'
|
|
1268
|
+
+ ' as a starting point.');
|
|
1269
|
+
}
|
|
1270
|
+
// Before the crawl: a mistyped flag is an operator error and pays nothing.
|
|
1271
|
+
requireKnownCheck(BUILTIN_CHECK_NAMES, checks ?? {}, options.check);
|
|
1272
|
+
// Also before the crawl, and for the same reason: an override that overrides
|
|
1273
|
+
// nothing is knowable from the config alone, and the operator should read it
|
|
1274
|
+
// beside the other config news rather than after a full population.
|
|
1275
|
+
warnUndeclaredOverrides(checks ?? {}, config?.resources?.validation, logger);
|
|
1276
|
+
const outcome = await runOutcome({
|
|
1277
|
+
root: projectRoot,
|
|
1278
|
+
checks: checks ?? {},
|
|
1279
|
+
only: options.check,
|
|
1280
|
+
logger,
|
|
1281
|
+
validation: config?.resources?.validation,
|
|
1282
|
+
onProgress: options.costLog === undefined
|
|
1283
|
+
? undefined
|
|
1284
|
+
: createProgressWriter(options.costLog),
|
|
1285
|
+
});
|
|
1286
|
+
const payload = buildCheckOutputData({
|
|
1287
|
+
...outcome,
|
|
1288
|
+
root: projectRoot,
|
|
1289
|
+
durationMs: Date.now() - startTime,
|
|
1290
|
+
});
|
|
1291
|
+
emitCheckDocument(payload, options.format);
|
|
1292
|
+
process.exit(payload.summary.errors > 0 ? ExitCode.FINDINGS : ExitCode.OK);
|
|
1293
|
+
}
|
|
1294
|
+
catch (error) {
|
|
1295
|
+
handleReportCommandError(error, logger, startTime, 'Check', options.format);
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
/**
|
|
1299
|
+
* Populate once, run the checks, resolve their severities.
|
|
1300
|
+
*
|
|
1301
|
+
* @param options - The run
|
|
1302
|
+
* @param options.root - Absolute corpus root
|
|
1303
|
+
* @param options.checks - The declared checks
|
|
1304
|
+
* @param options.only - A single check key, or undefined for all
|
|
1305
|
+
* @param options.logger - Where blob-stage refusals are reported
|
|
1306
|
+
* @param options.validation - The project's severity overrides, or undefined
|
|
1307
|
+
* @param options.onProgress - Where each unit is announced for a supervisor
|
|
1308
|
+
* outside this process, or undefined when nothing is watching
|
|
1309
|
+
* @returns The findings and the provenance of the population behind them
|
|
1310
|
+
*/
|
|
1311
|
+
async function runOutcome(options) {
|
|
1312
|
+
const { root, checks, only, logger, validation, onProgress } = options;
|
|
1313
|
+
// 🪤 **No `preflight` here, deliberately.** `withQueriedProjection` can compile
|
|
1314
|
+
// statements against the empty schema before populating — `vat resources query`
|
|
1315
|
+
// passes its one statement and gets a typo back in milliseconds instead of
|
|
1316
|
+
// after a full population. This verb must NOT, because a check that cannot run
|
|
1317
|
+
// is not an operator error here: it is a `RESOURCE_CHECK_BROKEN` finding naming
|
|
1318
|
+
// the check, at `error`, in a document that also reports how big the corpus was
|
|
1319
|
+
// and what the population cost. Failing early would trade that document for a
|
|
1320
|
+
// bare throw, and the finding exists precisely so a renamed column cannot end a
|
|
1321
|
+
// gate quietly. The population is the price of an honest report, and it is
|
|
1322
|
+
// charged once for every check rather than per broken one.
|
|
1323
|
+
// 🔑 `statements` IS passed, and it is a different question from `preflight`:
|
|
1324
|
+
// it only decides which derived relations are evaluated. Every declared
|
|
1325
|
+
// statement goes in, including one `--check` narrows away — the lens is per
|
|
1326
|
+
// RUN, and narrowing it would make `--check foo` and a full run disagree about
|
|
1327
|
+
// which relations exist.
|
|
1328
|
+
const statements = Object.values(checks).map((check) => check.sql);
|
|
1329
|
+
return withQueriedProjection({ root, logger, statements }, (ask, provenance, extent, projection) => {
|
|
1330
|
+
// 🔑 Emitted HERE, inside the callback, because this is the one place where
|
|
1331
|
+
// the provenance and the extent are both exactly in hand — and emitted the
|
|
1332
|
+
// INSTANT population completes, because its arrival is what tells a
|
|
1333
|
+
// supervisor that a kill has a projection to report on. It also resets the
|
|
1334
|
+
// watchdog, so the population is bounded by the budget without being charged
|
|
1335
|
+
// the first check's share of it.
|
|
1336
|
+
onProgress?.({
|
|
1337
|
+
kind: 'population',
|
|
1338
|
+
population: provenance.population,
|
|
1339
|
+
populationMs: provenance.populationMs,
|
|
1340
|
+
lensMs: provenance.lensMs,
|
|
1341
|
+
lensesEvaluated: [...provenance.lensesEvaluated],
|
|
1342
|
+
membersEnumerated: extent.membersEnumerated,
|
|
1343
|
+
});
|
|
1344
|
+
// 🪤 `checks-complete` is NOT emitted here. It used to be, and that put
|
|
1345
|
+
// `resolveIssueSeverity` — which runs inside `runProjectChecks` — on the
|
|
1346
|
+
// wrong side of the line, still charged to the last check's budget window
|
|
1347
|
+
// while three separate docs said otherwise. It is emitted by
|
|
1348
|
+
// `runProjectChecks` itself now, the instant the last statement is done.
|
|
1349
|
+
//
|
|
1350
|
+
// 🔑 The DEFAULT SET enters HERE, from a module constant bound to the
|
|
1351
|
+
// projection — never from `config`, which this line does not read. That is
|
|
1352
|
+
// the placement the "defaults belong to the pipeline" invariant is about.
|
|
1353
|
+
const ran = runProjectChecks({
|
|
1354
|
+
builtins: bindBuiltinChecks(projection),
|
|
1355
|
+
checks,
|
|
1356
|
+
only,
|
|
1357
|
+
ask,
|
|
1358
|
+
validation,
|
|
1359
|
+
...extent,
|
|
1360
|
+
onProgress,
|
|
1361
|
+
});
|
|
1362
|
+
return { ...ran, ...provenance, ...extent };
|
|
1363
|
+
});
|
|
1364
|
+
}
|
|
1365
|
+
//# sourceMappingURL=check.js.map
|