@vibe-agent-toolkit/cli 0.2.0-rc.8 → 0.2.0-rc.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/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/build.d.ts.map +1 -1
- package/dist/commands/build.js +2 -1
- package/dist/commands/build.js.map +1 -1
- package/dist/commands/cache/clear.d.ts +17 -3
- package/dist/commands/cache/clear.d.ts.map +1 -1
- package/dist/commands/cache/clear.js +17 -3
- package/dist/commands/cache/clear.js.map +1 -1
- package/dist/commands/cache/index.d.ts.map +1 -1
- package/dist/commands/cache/index.js +19 -10
- package/dist/commands/cache/index.js.map +1 -1
- package/dist/commands/claude/context.d.ts +2 -2
- package/dist/commands/claude/context.js +4 -4
- package/dist/commands/claude/context.js.map +1 -1
- package/dist/commands/claude/index.d.ts +12 -11
- package/dist/commands/claude/index.d.ts.map +1 -1
- package/dist/commands/claude/index.js +14 -16
- package/dist/commands/claude/index.js.map +1 -1
- package/dist/commands/claude/marketplace/git-publish.d.ts.map +1 -1
- package/dist/commands/claude/marketplace/git-publish.js +19 -10
- package/dist/commands/claude/marketplace/git-publish.js.map +1 -1
- package/dist/commands/claude/marketplace/validate.js +2 -2
- package/dist/commands/claude/marketplace/validate.js.map +1 -1
- package/dist/commands/claude/org/skills.js +8 -8
- package/dist/commands/claude/org/skills.js.map +1 -1
- package/dist/commands/claude/plugin/build.d.ts.map +1 -1
- package/dist/commands/claude/plugin/build.js +11 -21
- package/dist/commands/claude/plugin/build.js.map +1 -1
- package/dist/commands/claude/plugin/plugin-files.js +3 -3
- package/dist/commands/claude/plugin/plugin-files.js.map +1 -1
- package/dist/commands/consistency-check.d.ts +11 -5
- package/dist/commands/consistency-check.d.ts.map +1 -1
- package/dist/commands/consistency-check.js +113 -78
- package/dist/commands/consistency-check.js.map +1 -1
- package/dist/commands/resources/check-progress.d.ts +6 -0
- package/dist/commands/resources/check-progress.d.ts.map +1 -1
- package/dist/commands/resources/check-progress.js +10 -2
- package/dist/commands/resources/check-progress.js.map +1 -1
- package/dist/commands/resources/check.d.ts +182 -203
- package/dist/commands/resources/check.d.ts.map +1 -1
- package/dist/commands/resources/check.js +272 -248
- package/dist/commands/resources/check.js.map +1 -1
- package/dist/commands/resources/index.d.ts.map +1 -1
- package/dist/commands/resources/index.js +140 -23
- package/dist/commands/resources/index.js.map +1 -1
- package/dist/commands/resources/query.d.ts +2 -2
- package/dist/commands/resources/query.d.ts.map +1 -1
- package/dist/commands/resources/query.js +20 -10
- package/dist/commands/resources/query.js.map +1 -1
- package/dist/commands/skill/test/run.js +3 -3
- package/dist/commands/skill/test/run.js.map +1 -1
- package/dist/commands/skills/build.d.ts +34 -21
- package/dist/commands/skills/build.d.ts.map +1 -1
- package/dist/commands/skills/build.js +99 -50
- package/dist/commands/skills/build.js.map +1 -1
- package/dist/commands/skills/skill-discovery.d.ts +15 -13
- package/dist/commands/skills/skill-discovery.d.ts.map +1 -1
- package/dist/commands/skills/skill-discovery.js +23 -4
- package/dist/commands/skills/skill-discovery.js.map +1 -1
- package/dist/commands/validate.d.ts.map +1 -1
- package/dist/commands/validate.js +31 -8
- package/dist/commands/validate.js.map +1 -1
- package/dist/commands/verify.d.ts +10 -4
- package/dist/commands/verify.d.ts.map +1 -1
- package/dist/commands/verify.js +92 -65
- package/dist/commands/verify.js.map +1 -1
- package/dist/report-schemas.d.ts.map +1 -1
- package/dist/report-schemas.js +0 -2
- package/dist/report-schemas.js.map +1 -1
- package/dist/skill-resolution/classify.js +2 -2
- package/dist/skill-resolution/classify.js.map +1 -1
- package/dist/skill-resolution/resolve-skill-reference.d.ts.map +1 -1
- package/dist/skill-resolution/resolve-skill-reference.js +48 -52
- package/dist/skill-resolution/resolve-skill-reference.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/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 +38 -14
- package/dist/utils/projection-query.d.ts.map +1 -1
- package/dist/utils/projection-query.js +78 -17
- package/dist/utils/projection-query.js.map +1 -1
- package/dist/utils/projection-store.d.ts +61 -42
- package/dist/utils/projection-store.d.ts.map +1 -1
- package/dist/utils/projection-store.js +155 -59
- package/dist/utils/projection-store.js.map +1 -1
- 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/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 +38 -3
- package/dist/utils/skill-packaging-config.d.ts.map +1 -1
- package/dist/utils/skill-packaging-config.js +28 -3
- package/dist/utils/skill-packaging-config.js.map +1 -1
- package/package.json +16 -16
- package/schemas/resources-check.json +43 -0
- package/dist/commands/claude/budget.d.ts +0 -269
- package/dist/commands/claude/budget.d.ts.map +0 -1
- package/dist/commands/claude/budget.js +0 -507
- package/dist/commands/claude/budget.js.map +0 -1
- package/dist/utils/context-budget-issues.d.ts +0 -108
- package/dist/utils/context-budget-issues.d.ts.map +0 -1
- package/dist/utils/context-budget-issues.js +0 -280
- package/dist/utils/context-budget-issues.js.map +0 -1
- package/schemas/claude-budget.json +0 -197
|
@@ -1,168 +1,35 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `vat resources check` — run the project's own
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* `
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* The same class one level further out, and the one that shipped. `checksRun` is
|
|
32
|
-
* the denominator of RULES; nothing counted the ROWS, so a repository whose
|
|
33
|
-
* enumeration came back empty — a broad `.gitignore`, a shallow or sparse CI
|
|
34
|
-
* checkout, a root that resolved somewhere else — ran every declared check over
|
|
35
|
-
* nothing and reported `status: success` on exit 0 with empty stderr. Population
|
|
36
|
-
* declines ignored members rather than flagging them, so the tables held no
|
|
37
|
-
* trace of it either.
|
|
38
|
-
*
|
|
39
|
-
* So the population's size is published (`membersEnumerated`), and a run whose
|
|
40
|
-
* checks executed over zero members is an ERROR under the same non-overridable
|
|
41
|
-
* code as a broken statement — see {@link emptyCorpusFinding}. A gate that
|
|
42
|
-
* asserts nothing over nothing has not passed.
|
|
43
|
-
*
|
|
44
|
-
* ## 🔑 An unknown `--check` name is a refusal, not an empty pass
|
|
45
|
-
*
|
|
46
|
-
* The same failure class one flag over: a filter that matched nothing ran nothing
|
|
47
|
-
* and exited 0. `requireDeclaredCheck` refuses it before the crawl, naming the
|
|
48
|
-
* declared set. A gate that cannot fail is worse than no gate.
|
|
49
|
-
*
|
|
50
|
-
* ## 🔑 NO checks at all is the same refusal, and the one that shipped longest
|
|
51
|
-
*
|
|
52
|
-
* A project with a valid config and no `checks:` block reported `status:
|
|
53
|
-
* success`, `checksRun: 0`, exit 0 — so DELETING the `checks:` block silently
|
|
54
|
-
* deleted the gate and nothing anywhere said so. A gate that checked nothing is
|
|
55
|
-
* indistinguishable from a gate that was removed, and the entire argument for
|
|
56
|
-
* having these gates is that you never let those two look alike. It is now a
|
|
57
|
-
* `RESOURCE_CHECK_BROKEN` error like the other three — see
|
|
58
|
-
* {@link noCheckRanFinding}, and `RUN_INTEGRITY_CODE` for why one code
|
|
59
|
-
* covers all four. Declaring no checks is a legitimate CHOICE; running a gate
|
|
60
|
-
* that can only pass is not, so the remedy is to take the command out of the
|
|
61
|
-
* pipeline.
|
|
62
|
-
*
|
|
63
|
-
* ## 🔑 SQL is the surface a user OPTED IN to, so no BUILT-IN check may be SQL
|
|
64
|
-
*
|
|
65
|
-
* Every check this verb runs is adopter-declared: {@link checkCommand} reads
|
|
66
|
-
* `config?.resources?.checks` and nothing else, and warns loudly when a project
|
|
67
|
-
* declares none. There are no built-ins, and the one way to end that quietly is
|
|
68
|
-
* to author the first one as SQL. A default-on rule written as a statement makes
|
|
69
|
-
* the engine, the projection and the population mandatory for everyone who
|
|
70
|
-
* inherits it, without anybody deciding that. So: **built-ins are TypeScript
|
|
71
|
-
* predicates over the projection's row model.** SQL stays the two places a user
|
|
72
|
-
* asked for it — this verb's declared checks, and `vat resources query`.
|
|
73
|
-
*
|
|
74
|
-
* ⚠️ The engine today is `node:sqlite`, built into Node, so the thing at stake is
|
|
75
|
-
* not an install — it is the POPULATION. On an ~11,700-member adopter tree
|
|
76
|
-
* building the projection is **>99.9%** of a check run (16.5 s cold, ~1.3 s from
|
|
77
|
-
* the store) against **0.0008–0.004 s** for the statements themselves; see
|
|
78
|
-
* `vat-knowledge-resources.md`, which carries that measurement. That is exactly
|
|
79
|
-
* the cost "What this does NOT do" below refuses to put on every adopter's
|
|
80
|
-
* pre-commit path, and a default-on SQL check would put it there without the
|
|
81
|
-
* decision. The INSTALL cost is deferred rather than gone: the open question
|
|
82
|
-
* about a columnar engine is a 108 MB platform binding on a published toolkit,
|
|
83
|
-
* and this invariant is what keeps that question still answerable either way.
|
|
84
|
-
*
|
|
85
|
-
* ## 🔑 The DEFAULT check set belongs to the pipeline, not to the config file
|
|
86
|
-
*
|
|
87
|
-
* A directory with no `vibe-agent-toolkit.config.yaml` must run exactly the same
|
|
88
|
-
* default-on checks as one with a config; config only ADDS to that set or
|
|
89
|
-
* OVERRIDES a severity in it. This is load-bearing rather than a convenience:
|
|
90
|
-
* *"'default-on error' is meaningless as a category if being default-on requires
|
|
91
|
-
* a config file to say so."*
|
|
92
|
-
*
|
|
93
|
-
* It is therefore a constraint on WHERE the defaults live in code, not only on
|
|
94
|
-
* what they are. They cannot be defaulted into the parsed config object, because
|
|
95
|
-
* an absent config is `undefined` and every default would vanish with it — for
|
|
96
|
-
* the project that never wrote one, which is the population the category exists
|
|
97
|
-
* for. The rest of VAT already behaves this way and is the shape to match:
|
|
98
|
-
* `vat audit` completes its entire scan first and reads config only to apply
|
|
99
|
-
* severity overrides, warning and CONTINUING when the file cannot be read; and
|
|
100
|
-
* `loadResourcesWithConfig` hands `vat resources validate` a registry whose
|
|
101
|
-
* built-in validators run whether `loadConfig` returned a config or nothing.
|
|
102
|
-
* This verb has no defaults yet, so the invariant is a rule for the first one —
|
|
103
|
-
* and the day it is written is the easiest day to break it.
|
|
104
|
-
*
|
|
105
|
-
* ## Keeping a TypeScript check and its SQL twin: two good reasons, one trap
|
|
106
|
-
*
|
|
107
|
-
* The proposal was "write the predicate twice, in TypeScript and in SQL, and run
|
|
108
|
-
* whichever is faster". Two thirds of it are worth having.
|
|
109
|
-
*
|
|
110
|
-
* ✅ **As documentation.** An SQL twin is executable documentation of the row
|
|
111
|
-
* model, and strictly better than prose about it: prose can describe a column
|
|
112
|
-
* that no longer exists and never say so, while a statement stops compiling —
|
|
113
|
-
* which this verb reports as `RESOURCE_CHECK_BROKEN` rather than as a skip.
|
|
114
|
-
*
|
|
115
|
-
* ✅ **As a differential oracle.** Run both over the frozen corpus and assert
|
|
116
|
-
* IDENTICAL findings. That is the only thing which makes a dual implementation
|
|
117
|
-
* better than a single one rather than worse: two implementations with no
|
|
118
|
-
* mandatory differential test are simply two places for one rule to be wrong.
|
|
119
|
-
* Apply it selectively, to genuinely relational checks — done by default, every
|
|
120
|
-
* new rule costs three artifacts instead of one.
|
|
121
|
-
*
|
|
122
|
-
* ⛔ **As a runtime optimizer — rejected, on two INDEPENDENT grounds.** It would
|
|
123
|
-
* essentially never choose SQL: `buildResourceProjection` hands the rows back in
|
|
124
|
-
* memory before the ephemeral database exists at all, so the TypeScript arm is a
|
|
125
|
-
* for-loop over data already in hand, and the SQL arm's whole measured share of a
|
|
126
|
-
* run is the 0.0008–0.004 s above. A scheduler would be choosing between two
|
|
127
|
-
* arms of the 0.1%. And it reintroduces the dependency through the back door: an
|
|
128
|
-
* optimizer that MAY choose SQL means SQL MAY be needed at check time, which for
|
|
129
|
-
* anyone shipping the thing is the same as needed. **Optionality is not a
|
|
130
|
-
* property you can keep while also letting a scheduler reach for the optional
|
|
131
|
-
* thing.**
|
|
132
|
-
*
|
|
133
|
-
* ## 🔑 Adopter SQL runs UNSANDBOXED on purpose — and contributed SQL is not adopter SQL
|
|
134
|
-
*
|
|
135
|
-
* What a statement may BE is gated hard, and documented where it is enforced:
|
|
136
|
-
* `assertIsQuery` and `detachForeignSchemas` in
|
|
137
|
-
* `packages/projection-sqlite/src/store.ts` refuse `ATTACH` and `PRAGMA`, with
|
|
138
|
-
* the measured cross-repository leak that motivated both, and CHANGELOG.md's
|
|
139
|
-
* `--budget` entry carries why nothing inside this process can interrupt a
|
|
140
|
-
* runaway statement. Read those; do not restate them here.
|
|
141
|
-
*
|
|
142
|
-
* What is written down nowhere else is that everything PAST those gates is
|
|
143
|
-
* unsandboxed deliberately. A `.sql` file the adopter points their own config at
|
|
144
|
-
* is adopter code, at the same trust boundary as their eslint config: they wrote
|
|
145
|
-
* it or they chose to inherit it, and VAT is not the thing standing between a
|
|
146
|
-
* project and its own repository.
|
|
147
|
-
*
|
|
148
|
-
* 🚨 That reasoning covers exactly ONE trust domain, and the rule scoping it is
|
|
149
|
-
* the other half: **contributed SQL is a reviewed EXAMPLE, never an
|
|
150
|
-
* auto-executed check.** A contribution channel is third-party text, and a
|
|
151
|
-
* statement arriving through one has none of the "they chose it" the paragraph
|
|
152
|
-
* above rests on. Ship it as documentation an adopter copies into their own
|
|
153
|
-
* `resources.checks` deliberately. One paragraph must not be stretched to cover
|
|
154
|
-
* both domains.
|
|
155
|
-
*
|
|
156
|
-
* ## What this does NOT do
|
|
157
|
-
*
|
|
158
|
-
* It does not fold into `vat resources validate`. That command is on every
|
|
159
|
-
* adopter's pre-commit path, and adding a store-backed population to it is a
|
|
160
|
-
* cost and a risk that deserves its own decision with its own evidence. Wiring
|
|
161
|
-
* this into CI is one line in a workflow; making it unavoidable is not this
|
|
162
|
-
* change's call to make.
|
|
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.
|
|
163
30
|
*/
|
|
164
31
|
import { performance } from 'node:perf_hooks';
|
|
165
|
-
import { issuesFromCheckRows } from '@vibe-agent-toolkit/resources';
|
|
32
|
+
import { bindBuiltinChecks, BUILTIN_CHECK_NAMES, issuesFromCheckRows, LIMIT_DIRECTIONS, } from '@vibe-agent-toolkit/resources';
|
|
166
33
|
import { buildReport, CUSTOM_CHECK_CODE_PREFIX, ExitCode, isCustomCheckCode, reportSchema, toFindings, } from '@vibe-agent-toolkit/schema';
|
|
167
34
|
import { safePath } from '@vibe-agent-toolkit/utils';
|
|
168
35
|
import { z } from 'zod';
|
|
@@ -174,6 +41,7 @@ import { createLogger } from '../../utils/logger.js';
|
|
|
174
41
|
import { writeStdoutSync, writeStructuredOutput } from '../../utils/output.js';
|
|
175
42
|
import { assertDirectoryArgument, projectRootOrLoudCwd, projectRootOrNull } from '../../utils/project-root-policy.js';
|
|
176
43
|
import { withQueriedProjection, } from '../../utils/projection-query.js';
|
|
44
|
+
import { relationBoundsFor } from '../../utils/relation-limits.js';
|
|
177
45
|
import { nothingCheckedFinding, runIntegrityFinding } from '../../utils/run-integrity.js';
|
|
178
46
|
import { createProgressWriter, parseProgressLog, unitInFlight, } from './check-progress.js';
|
|
179
47
|
import { parseBudgetSeconds, requireSupervisableFlags, runsInThisProcess, superviseCheck, withProgressLog, } from './check-supervisor.js';
|
|
@@ -185,6 +53,38 @@ const PublishedCheckCostSchema = z.object({
|
|
|
185
53
|
/** Rows the statement returned. ABSENT, never 0, when the statement did not complete. */
|
|
186
54
|
rows: z.number().int().nonnegative().optional(),
|
|
187
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(),
|
|
188
88
|
}).strict();
|
|
189
89
|
/**
|
|
190
90
|
* What the check run reports beyond its findings.
|
|
@@ -200,8 +100,28 @@ export const CheckDataSchema = z.object({
|
|
|
200
100
|
population: z.enum(['derived', 'store']),
|
|
201
101
|
/** What the population cost — charged to no check, see `CheckCost`. */
|
|
202
102
|
populationSecs: z.number().nonnegative(),
|
|
203
|
-
/** What evaluating the
|
|
103
|
+
/** What evaluating the lenses cost, paid before the first statement ran. */
|
|
204
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(),
|
|
205
125
|
/** The number of rules that EXECUTED. Derived from `checks`, never carried beside it. */
|
|
206
126
|
checksRun: z.number().int().nonnegative(),
|
|
207
127
|
/** What each rule cost, directly under the denominator it is the breakdown of. */
|
|
@@ -242,22 +162,28 @@ export const CHECK_REPORT_SCHEMA = reportSchema(CheckDataSchema);
|
|
|
242
162
|
* stand-down matters here for an interrupted run, which recovers zero costs when
|
|
243
163
|
* the kill landed before the first statement finished and already carries
|
|
244
164
|
* {@link interruptedRunFinding}, the more specific of the two claims. A `--check`
|
|
245
|
-
* filter that matched nothing is refused earlier by {@link
|
|
165
|
+
* filter that matched nothing is refused earlier by {@link requireKnownCheck},
|
|
246
166
|
* and must stay refused even if that guard is ever widened.
|
|
247
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
|
+
*
|
|
248
174
|
* @param costs - One record per check that EXECUTED; its length is `checksRun`
|
|
249
175
|
* @param issues - What the run already found, so an existing run-integrity
|
|
250
176
|
* report is not duplicated
|
|
251
177
|
* @returns The finding, or nothing when at least one check ran
|
|
252
178
|
*/
|
|
253
179
|
function noCheckRanFinding(costs, issues) {
|
|
254
|
-
return nothingCheckedFinding(costs.length, issues, () => 'No check ran, so this document is not a verdict: a gate that checked nothing'
|
|
180
|
+
return nothingCheckedFinding(costs.length, issues, () => 'No check ran at all, so this document is not a verdict: a gate that checked nothing'
|
|
255
181
|
+ ' produces the same report as a gate that was removed, and this run cannot tell'
|
|
256
182
|
+ ' you which happened.'
|
|
257
|
-
+ '
|
|
258
|
-
+ '
|
|
259
|
-
+ '
|
|
260
|
-
+ '
|
|
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.');
|
|
261
187
|
}
|
|
262
188
|
/**
|
|
263
189
|
* Build the check report.
|
|
@@ -317,6 +243,16 @@ export function buildCheckOutputData(input) {
|
|
|
317
243
|
// lens's cost to whichever rule the reader happened to be looking at, which
|
|
318
244
|
// is the same defect `populationSecs` exists to prevent.
|
|
319
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),
|
|
320
256
|
// The denominator of rules. Derived from `checks`, never carried beside it.
|
|
321
257
|
checksRun: input.costs.length,
|
|
322
258
|
// What each rule cost, directly under the denominator it is a breakdown
|
|
@@ -336,6 +272,9 @@ export function buildCheckOutputData(input) {
|
|
|
336
272
|
durationSecs: formatDurationSecs(cost.durationMs),
|
|
337
273
|
...(cost.rows === undefined ? {} : { rows: cost.rows }),
|
|
338
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 }),
|
|
339
278
|
})),
|
|
340
279
|
},
|
|
341
280
|
});
|
|
@@ -399,7 +338,7 @@ function timed(now, run) {
|
|
|
399
338
|
*
|
|
400
339
|
* 🪤 Only `ask` reaches this. Turning the selected rows INTO findings is not a
|
|
401
340
|
* statement failure, and reporting it as one told the operator a working rule
|
|
402
|
-
* was broken — see the append in {@link
|
|
341
|
+
* was broken — see the append in {@link runUnits}.
|
|
403
342
|
*
|
|
404
343
|
* @param name - The check's key in `resources.checks`
|
|
405
344
|
* @param error - Whatever the statement threw
|
|
@@ -410,48 +349,85 @@ function brokenCheckFinding(name, error) {
|
|
|
410
349
|
+ (error instanceof Error ? error.message : String(error)));
|
|
411
350
|
}
|
|
412
351
|
/**
|
|
413
|
-
*
|
|
414
|
-
*
|
|
415
|
-
* ## What the clock measures, and what it must not
|
|
416
|
-
*
|
|
417
|
-
* The span is `ask(check.sql)` and nothing else — structurally, because it is
|
|
418
|
-
* the thunk handed to {@link timed}, which exists for that reason and documents
|
|
419
|
-
* the mutation that proved an inline stopwatch unguardable. Everything before
|
|
420
|
-
* the loop — the git tracker, `buildResourceProjection`, loading the ephemeral
|
|
421
|
-
* database — is paid ONCE for all of them and reaches the document as
|
|
422
|
-
* `populationSecs`; see {@link CheckCost} for why charging it here would be a
|
|
423
|
-
* lie in N places.
|
|
424
|
-
*
|
|
425
|
-
* ⚠️ `performance.now()`, never `Date.now()`. A rule over a small projection is
|
|
426
|
-
* routinely sub-millisecond, and a millisecond-granularity clock reports every
|
|
427
|
-
* one of them as `0` — which reads as "not measured" and makes the whole
|
|
428
|
-
* attribution worthless exactly where it is cheapest to get right.
|
|
429
|
-
*
|
|
430
|
-
* 🪤 Exactly one cost record per executed check, on both paths, because
|
|
431
|
-
* `checksRun` is this list's length. The record is filed before the rows become
|
|
432
|
-
* findings, so nothing between the two can leave a check unpriced.
|
|
433
|
-
*
|
|
434
|
-
* ## 🚨 Why the loop announces itself before it can be stopped
|
|
435
|
-
*
|
|
436
|
-
* {@link ProgressSink} is told a check is about to run, and told again when it
|
|
437
|
-
* is priced. That ordering is not decoration: this verb runs adopter-authored
|
|
438
|
-
* SQL unattended, an accidental cross join or an unterminated `WITH RECURSIVE`
|
|
439
|
-
* runs forever, and NOTHING in-process can interrupt it — the query is
|
|
440
|
-
* synchronous, it holds the event loop, and `node:sqlite` exposes no interrupt.
|
|
441
|
-
* The supervisor's only lever is an external `SIGKILL`, which publishes nothing
|
|
442
|
-
* of the killed process's memory. So the name of the rule that hangs has to be
|
|
443
|
-
* on disk BEFORE it is entered, or it is not recoverable at all.
|
|
352
|
+
* One declared statement, as a unit.
|
|
444
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
|
|
445
401
|
* @param checks - The project's `resources.checks`
|
|
446
|
-
* @param only - A single check key to run, or undefined for all of them
|
|
447
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
|
|
448
424
|
* @param now - The clock, in milliseconds; injected so a test can assert an
|
|
449
425
|
* exact duration rather than a range that passes on a timer that never started
|
|
450
426
|
* @param onProgress - Told what is about to run and what it cost, or undefined
|
|
451
427
|
* when nothing is watching (the in-process lane, and `--budget 0`)
|
|
452
428
|
* @returns The findings, and one cost record per check that ran
|
|
453
429
|
*/
|
|
454
|
-
function
|
|
430
|
+
function runUnits(units, only, now, onProgress) {
|
|
455
431
|
const issues = [];
|
|
456
432
|
const costs = [];
|
|
457
433
|
/** File the cost with the observer as well as with the caller — one place. */
|
|
@@ -459,23 +435,28 @@ function runChecks(checks, only, ask, now, onProgress) {
|
|
|
459
435
|
costs.push(cost);
|
|
460
436
|
onProgress?.({ kind: 'check', ...cost });
|
|
461
437
|
};
|
|
462
|
-
for (const
|
|
438
|
+
for (const unit of units) {
|
|
439
|
+
const { name } = unit;
|
|
463
440
|
if (only !== undefined && name !== only)
|
|
464
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 };
|
|
465
445
|
// 🪤 BEFORE `timed`, and not one line later. `ask` is where a runaway
|
|
466
446
|
// statement never returns from, so an announcement below it is filed by
|
|
467
447
|
// every check except the one whose name the operator needs.
|
|
468
448
|
onProgress?.({ kind: 'start', name });
|
|
469
|
-
const outcome = timed(now,
|
|
449
|
+
const outcome = timed(now, unit.select);
|
|
470
450
|
if (!outcome.ok) {
|
|
471
|
-
// Priced up to the throw, and with NO row count: a
|
|
451
|
+
// Priced up to the throw, and with NO row count: a check that did not
|
|
472
452
|
// complete selected nothing, and `rows: 0` would say it selected nothing
|
|
473
|
-
// and passed.
|
|
474
|
-
|
|
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 });
|
|
475
456
|
issues.push(brokenCheckFinding(name, outcome.error));
|
|
476
457
|
continue;
|
|
477
458
|
}
|
|
478
|
-
price({ name, durationMs: outcome.durationMs, rows: outcome.value.
|
|
459
|
+
price({ name, durationMs: outcome.durationMs, rows: outcome.value.rows, ...origin });
|
|
479
460
|
// 🪤 Appended one at a time, NEVER `issues.push(...findings)`. A spread
|
|
480
461
|
// becomes an ARGUMENT LIST, which throws `RangeError: Maximum call stack
|
|
481
462
|
// size exceeded` past roughly 125,000 elements on the main thread — and a
|
|
@@ -485,44 +466,49 @@ function runChecks(checks, only, ask, now, onProgress) {
|
|
|
485
466
|
// the throw landed in the arm above: a rule that ran perfectly was reported
|
|
486
467
|
// as one that "could not run, so it is asserting nothing". A loop does not
|
|
487
468
|
// put the argument limit in play at all.
|
|
488
|
-
for (const issue of
|
|
469
|
+
for (const issue of outcome.value.report())
|
|
489
470
|
issues.push(issue);
|
|
490
471
|
}
|
|
491
472
|
return { issues, costs };
|
|
492
473
|
}
|
|
493
474
|
/**
|
|
494
|
-
* Refuse a `--check` name
|
|
475
|
+
* Refuse a `--check` name that names neither a built-in nor a declared check.
|
|
495
476
|
*
|
|
496
|
-
* 🔑 **An unknown name
|
|
497
|
-
*
|
|
498
|
-
*
|
|
499
|
-
*
|
|
500
|
-
*
|
|
501
|
-
*
|
|
502
|
-
*
|
|
503
|
-
* That is not a hypothetical typo: `vat resources check --check orphan-skills`
|
|
504
|
-
* is the example in this command's own help text. Rename or delete that check
|
|
505
|
-
* and the CI step keeps passing forever while asserting nothing.
|
|
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.
|
|
506
484
|
*
|
|
507
485
|
* Thrown rather than reported as a finding, because it is an OPERATOR error and
|
|
508
486
|
* not a content violation — the two have different exit codes in this command's
|
|
509
487
|
* contract (2 versus 1) and different audiences. Thrown before the projection is
|
|
510
488
|
* populated, so a mistyped flag costs no crawl.
|
|
511
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
|
|
512
497
|
* @param checks - The project's `resources.checks`
|
|
513
498
|
* @param only - The `--check` value, or undefined
|
|
514
|
-
* @throws When `only` names no
|
|
499
|
+
* @throws When `only` names no check this run would attempt
|
|
515
500
|
*/
|
|
516
|
-
export function
|
|
501
|
+
export function requireKnownCheck(builtinNames, checks, only) {
|
|
517
502
|
// 🪤 `Object.hasOwn`, not `only in checks`. `in` walks the prototype chain, so
|
|
518
503
|
// `--check toString` would have looked declared, matched nothing, and restored
|
|
519
504
|
// the exact silent green this guard closes.
|
|
520
|
-
if (only === undefined || Object.hasOwn(checks, only))
|
|
505
|
+
if (only === undefined || builtinNames.includes(only) || Object.hasOwn(checks, only))
|
|
521
506
|
return;
|
|
522
507
|
const declared = Object.keys(checks);
|
|
523
|
-
throw new Error(`No check named "${only}"
|
|
508
|
+
throw new Error(`No check named "${only}" exists. `
|
|
509
|
+
+ `Built in: ${builtinNames.join(', ')}. `
|
|
524
510
|
+ (declared.length === 0
|
|
525
|
-
? 'This project declares none — add them under `resources.checks` in'
|
|
511
|
+
? 'This project declares none of its own — add them under `resources.checks` in'
|
|
526
512
|
+ ' vibe-agent-toolkit.config.yaml.'
|
|
527
513
|
: `Declared under \`resources.checks\`: ${declared.join(', ')}.`));
|
|
528
514
|
}
|
|
@@ -530,7 +516,7 @@ export function requireDeclaredCheck(checks, only) {
|
|
|
530
516
|
* Name every `CUSTOM:` severity override that overrides nothing.
|
|
531
517
|
*
|
|
532
518
|
* 🔑 **The same silent-green shape as an unknown `--check` name, one surface
|
|
533
|
-
* over.** {@link
|
|
519
|
+
* over.** {@link requireKnownCheck} refuses `--check nope` because a filter
|
|
534
520
|
* that matched nothing ran nothing and exited 0. A `validation.severity` entry
|
|
535
521
|
* keyed `CUSTOM:<name>` for a check that is not declared is the mirror image: it
|
|
536
522
|
* parses (the schema validates the KEY's shape and cannot see
|
|
@@ -563,7 +549,7 @@ export function requireDeclaredCheck(checks, only) {
|
|
|
563
549
|
export function warnUndeclaredOverrides(checks, validation, logger) {
|
|
564
550
|
const stale = Object.keys(validation?.severity ?? {})
|
|
565
551
|
.filter((code) => isCustomCheckCode(code))
|
|
566
|
-
// 🪤 `Object.hasOwn`, not `in` — same reason {@link
|
|
552
|
+
// 🪤 `Object.hasOwn`, not `in` — same reason {@link requireKnownCheck}
|
|
567
553
|
// uses it: `in` walks the prototype chain, so an override keyed
|
|
568
554
|
// `CUSTOM:toString` would look declared and stay silent.
|
|
569
555
|
.filter((code) => !Object.hasOwn(checks, code.slice(CUSTOM_CHECK_CODE_PREFIX.length)));
|
|
@@ -1007,6 +993,7 @@ export function buildInterruptedCheckInput(options) {
|
|
|
1007
993
|
population: population.population,
|
|
1008
994
|
populationMs: population.populationMs,
|
|
1009
995
|
lensMs: population.lensMs,
|
|
996
|
+
lensesEvaluated: population.lensesEvaluated,
|
|
1010
997
|
membersEnumerated: population.membersEnumerated,
|
|
1011
998
|
issues: [interruptedRunFinding(unitInFlight(entries), ending)],
|
|
1012
999
|
// 🪤 Rebuilt field by field rather than passed through. The log's check
|
|
@@ -1019,12 +1006,19 @@ export function buildInterruptedCheckInput(options) {
|
|
|
1019
1006
|
durationMs: entry.durationMs,
|
|
1020
1007
|
...(entry.rows === undefined ? {} : { rows: entry.rows }),
|
|
1021
1008
|
...(entry.broken === undefined ? {} : { broken: entry.broken }),
|
|
1009
|
+
...(entry.builtin === undefined ? {} : { builtin: entry.builtin }),
|
|
1022
1010
|
})),
|
|
1023
1011
|
};
|
|
1024
1012
|
}
|
|
1025
1013
|
/**
|
|
1026
|
-
* Run
|
|
1027
|
-
* verb's logic, with
|
|
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.
|
|
1028
1022
|
*
|
|
1029
1023
|
* Exported and pure so the loop is provable without a spawn. It was not: every
|
|
1030
1024
|
* spawned case declared ONE check (the `--check` case declares two and filters
|
|
@@ -1037,8 +1031,11 @@ export function buildInterruptedCheckInput(options) {
|
|
|
1037
1031
|
* trust.
|
|
1038
1032
|
*
|
|
1039
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
|
|
1040
1037
|
* @param options.checks - The declared checks
|
|
1041
|
-
* @param options.only - A single check
|
|
1038
|
+
* @param options.only - A single check name, or undefined for all
|
|
1042
1039
|
* @param options.ask - Runs one statement against the populated projection
|
|
1043
1040
|
* @param options.validation - The project's severity overrides, or undefined
|
|
1044
1041
|
* @param options.membersEnumerated - How many members the population enumerated.
|
|
@@ -1053,9 +1050,9 @@ export function buildInterruptedCheckInput(options) {
|
|
|
1053
1050
|
* unit case run with nobody watching
|
|
1054
1051
|
* @returns The resolved findings, and one cost record per check that ran
|
|
1055
1052
|
*/
|
|
1056
|
-
export function
|
|
1053
|
+
export function runProjectChecks(options) {
|
|
1057
1054
|
const now = options.now ?? (() => performance.now());
|
|
1058
|
-
const { issues, costs } =
|
|
1055
|
+
const { issues, costs } = runUnits(checkUnits(options.builtins, options.checks, options.ask), options.only, now, options.onProgress);
|
|
1059
1056
|
// 🚨 HERE, and this line moved to get here. `checks-complete` buys the phase
|
|
1060
1057
|
// after the last statement a fresh watchdog window, and three places
|
|
1061
1058
|
// documented that phase as "resolving severities and serialising". It was
|
|
@@ -1066,7 +1063,7 @@ export function runDeclaredChecks(options) {
|
|
|
1066
1063
|
// which is the useful direction: the alternative was to weaken three docs.
|
|
1067
1064
|
//
|
|
1068
1065
|
// ⚠️ It does NOT cover the last check's row-to-issue conversion, which
|
|
1069
|
-
// `
|
|
1066
|
+
// `runUnits` does after filing that check's cost — see {@link inFlightPhrase}
|
|
1070
1067
|
// for why the `idle` sentence still has to hedge for that window.
|
|
1071
1068
|
options.onProgress?.({ kind: 'checks-complete' });
|
|
1072
1069
|
// The run-integrity report leads. An aggregate check selects a row whatever
|
|
@@ -1083,6 +1080,12 @@ export function runDeclaredChecks(options) {
|
|
|
1083
1080
|
// It reaches a check's VIOLATIONS only. `RESOURCE_CHECK_BROKEN` is not an
|
|
1084
1081
|
// overridable code, so the news that a check stopped checking survives every
|
|
1085
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.
|
|
1086
1089
|
issues: resolveIssueSeverity(reported, options.validation),
|
|
1087
1090
|
costs,
|
|
1088
1091
|
};
|
|
@@ -1252,18 +1255,20 @@ export async function checkCommand(pathArg, options) {
|
|
|
1252
1255
|
const config = loadConfigCached(projectRoot);
|
|
1253
1256
|
const checks = config?.resources?.checks;
|
|
1254
1257
|
if (checks === undefined || Object.keys(checks).length === 0) {
|
|
1255
|
-
// 🪤 Stderr only, and it decides NOTHING
|
|
1256
|
-
//
|
|
1257
|
-
//
|
|
1258
|
-
// the
|
|
1259
|
-
//
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
+ ' vibe-agent-toolkit.config.yaml;
|
|
1263
|
-
+ ' statement selecting the rows that VIOLATE it.'
|
|
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.');
|
|
1264
1269
|
}
|
|
1265
1270
|
// Before the crawl: a mistyped flag is an operator error and pays nothing.
|
|
1266
|
-
|
|
1271
|
+
requireKnownCheck(BUILTIN_CHECK_NAMES, checks ?? {}, options.check);
|
|
1267
1272
|
// Also before the crawl, and for the same reason: an override that overrides
|
|
1268
1273
|
// nothing is knowable from the config alone, and the operator should read it
|
|
1269
1274
|
// beside the other config news rather than after a full population.
|
|
@@ -1315,7 +1320,13 @@ async function runOutcome(options) {
|
|
|
1315
1320
|
// bare throw, and the finding exists precisely so a renamed column cannot end a
|
|
1316
1321
|
// gate quietly. The population is the price of an honest report, and it is
|
|
1317
1322
|
// charged once for every check rather than per broken one.
|
|
1318
|
-
|
|
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) => {
|
|
1319
1330
|
// 🔑 Emitted HERE, inside the callback, because this is the one place where
|
|
1320
1331
|
// the provenance and the extent are both exactly in hand — and emitted the
|
|
1321
1332
|
// INSTANT population completes, because its arrival is what tells a
|
|
@@ -1327,14 +1338,27 @@ async function runOutcome(options) {
|
|
|
1327
1338
|
population: provenance.population,
|
|
1328
1339
|
populationMs: provenance.populationMs,
|
|
1329
1340
|
lensMs: provenance.lensMs,
|
|
1341
|
+
lensesEvaluated: [...provenance.lensesEvaluated],
|
|
1330
1342
|
membersEnumerated: extent.membersEnumerated,
|
|
1331
1343
|
});
|
|
1332
1344
|
// 🪤 `checks-complete` is NOT emitted here. It used to be, and that put
|
|
1333
|
-
// `resolveIssueSeverity` — which runs inside `
|
|
1345
|
+
// `resolveIssueSeverity` — which runs inside `runProjectChecks` — on the
|
|
1334
1346
|
// wrong side of the line, still charged to the last check's budget window
|
|
1335
1347
|
// while three separate docs said otherwise. It is emitted by
|
|
1336
|
-
// `
|
|
1337
|
-
|
|
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
|
+
});
|
|
1338
1362
|
return { ...ran, ...provenance, ...extent };
|
|
1339
1363
|
});
|
|
1340
1364
|
}
|