@agenttrail/guardrails 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -12
- package/dist/{chunk-CDYD5WBH.js → chunk-C4B2NPWH.js} +2 -2
- package/dist/chunk-C4B2NPWH.js.map +1 -0
- package/dist/{chunk-DKLS2UEL.js → chunk-DBB7HO4T.js} +1071 -22
- package/dist/chunk-DBB7HO4T.js.map +1 -0
- package/dist/guardrails.cjs +1070 -21
- package/dist/guardrails.cjs.map +1 -1
- package/dist/guardrails.d.cts +56 -90
- package/dist/guardrails.d.ts +56 -90
- package/dist/guardrails.js +1 -1
- package/dist/index.cjs +1071 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/schema.cjs +1 -1
- package/dist/schema.cjs.map +1 -1
- package/dist/schema.d.cts +37 -94
- package/dist/schema.d.ts +37 -94
- package/dist/schema.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-CDYD5WBH.js.map +0 -1
- package/dist/chunk-DKLS2UEL.js.map +0 -1
package/dist/guardrails.d.cts
CHANGED
|
@@ -3,28 +3,23 @@ export { Action, FixtureInput, Fixtures, Match, RuleInput, Severity } from './sc
|
|
|
3
3
|
import 'zod';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* The
|
|
6
|
+
* The eleven packs, and the order they are presented in.
|
|
7
7
|
*
|
|
8
8
|
* These ids are authoritative and shared with the rules authored against them,
|
|
9
|
-
* and with the guard's `config.json` `enabledPacks
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* mining found production destructive-SQL at zero.
|
|
19
|
-
*
|
|
20
|
-
* The per-pack counts in spec 7.1 are a budget, not a quota: trim or extend at
|
|
21
|
-
* authoring time. They are deliberately NOT encoded here, because a rule count
|
|
22
|
-
* asserted in code becomes a reason to write a weak rule to hit a number.
|
|
9
|
+
* and with the guard's `config.json` `enabledPacks`. Renaming one of these is a
|
|
10
|
+
* breaking change to a user's config file.
|
|
11
|
+
*
|
|
12
|
+
* Working-tree destruction leads: destructive git commands are the most frequently
|
|
13
|
+
* documented coding-agent failure, with public incident reports across Claude
|
|
14
|
+
* Code, Gemini CLI and Codex.
|
|
15
|
+
*
|
|
16
|
+
* Per-pack rule counts are deliberately NOT encoded here: a rule count asserted in
|
|
17
|
+
* code becomes a reason to write a weak rule to hit a number.
|
|
23
18
|
*/
|
|
24
|
-
declare const PACKS: readonly ["working-tree", "destructive-data", "prod-infra", "secret-exposure", "rce-supply-chain", "safety-bypass", "privilege-supply-chain", "file-scope"];
|
|
25
|
-
/** One of the
|
|
19
|
+
declare const PACKS: readonly ["working-tree", "destructive-data", "prod-infra", "secret-exposure", "rce-supply-chain", "safety-bypass", "privilege-supply-chain", "file-scope", "agent-context", "test-integrity", "exfiltration"];
|
|
20
|
+
/** One of the eleven pack ids. A rule's `category` is always one of these. */
|
|
26
21
|
type Pack = (typeof PACKS)[number];
|
|
27
|
-
/** Is this string one of the
|
|
22
|
+
/** Is this string one of the eleven pack ids? */
|
|
28
23
|
declare function isPack(value: string): value is Pack;
|
|
29
24
|
|
|
30
25
|
/**
|
|
@@ -42,17 +37,9 @@ declare function isPack(value: string): value is Pack;
|
|
|
42
37
|
* echo "never run rm -rf /" -> DENY dd.rm-rf-absolute
|
|
43
38
|
* curl --data "we ran rm -rf /tmp/x" https://… -> DENY dd.rm-rf-absolute
|
|
44
39
|
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* It was NOT found by the 438 fixtures or the 307-entry quiet corpus, and could
|
|
50
|
-
* not have been: every entry in both is a command somebody wrote on purpose. It
|
|
51
|
-
* took 997 real transcripts, where an automated `curl` posting an issue-tracker comment
|
|
52
|
-
* whose BODY discussed `rm -rf` was labelled "rm -rf against an absolute path".
|
|
53
|
-
* That is why the quoted-mention family is now generated as a cross-product over
|
|
54
|
-
* the whole corpus (`guardrails-quoted-mention.test.ts`) rather than listed by
|
|
55
|
-
* hand: a hand-written list only covers the rules somebody remembered.
|
|
40
|
+
* Without an exemption, a person documenting this tool is blocked by this tool.
|
|
41
|
+
* That applies to every command-channel rule — 62 of the 74. The twelve `file_glob`
|
|
42
|
+
* rules never see a command.
|
|
56
43
|
*
|
|
57
44
|
* ── Why exemption by CARRIER VERB, and not by "the payload is quoted" ────────
|
|
58
45
|
*
|
|
@@ -73,17 +60,14 @@ declare function isPack(value: string): value is Pack;
|
|
|
73
60
|
* echo "rm -rf /" | bash -> NOT exempt: `|` is outside the quotes
|
|
74
61
|
* echo "$(rm -rf /var)" -> NOT exempt: the shell expands `$( )`
|
|
75
62
|
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
* the loose form still covers a search that is not the FIRST word — measured,
|
|
82
|
-
* `cat runbook.md | grep -n TRUNCATE` is exempt only because of it — and a fix is
|
|
83
|
-
* not allowed to regress what the rule already allowed. The other four rules with a pre-existing `none_of`
|
|
63
|
+
* `block-destructive-sql` also keeps a looser condition of its own: it exempts any
|
|
64
|
+
* command whose text NAMES a search or history tool, anywhere. That covers a search
|
|
65
|
+
* that is not the FIRST word, such as `cat runbook.md | grep -n TRUNCATE`, and it is
|
|
66
|
+
* why that rule misses a compound command that both searches and executes, as its
|
|
67
|
+
* description states. The other four rules with their own `none_of`
|
|
84
68
|
* (`se.env-print`, `require-approval-rm-rf`, `wt.clean-fdx`, `wt.restore-path`)
|
|
85
69
|
* exclude something else entirely — a dry run, a build directory, `--staged` —
|
|
86
|
-
* and
|
|
70
|
+
* and have these conditions appended, not replaced.
|
|
87
71
|
*
|
|
88
72
|
* ── The character classes, and why each character is in them ─────────────────
|
|
89
73
|
*
|
|
@@ -204,8 +188,8 @@ declare const QUOTED_MENTION: readonly MatchCondition[];
|
|
|
204
188
|
* harness reads `"command" in fixture`.
|
|
205
189
|
*
|
|
206
190
|
* Writing every fixture out longhand would work and would be unreadable, so
|
|
207
|
-
* these exist instead. They
|
|
208
|
-
*
|
|
191
|
+
* these exist instead. They also make the CHANNEL visible at every single
|
|
192
|
+
* fixture, and the channel is the thing
|
|
209
193
|
* that silently makes a fixture vacuous — a `file_glob` rule handed a command
|
|
210
194
|
* fixture matches nothing, and an `allow` fixture that matches nothing passes.
|
|
211
195
|
*/
|
|
@@ -254,35 +238,31 @@ declare function mentions(text: string): Fixture[];
|
|
|
254
238
|
* The catalog's own version and publication date.
|
|
255
239
|
*
|
|
256
240
|
* These are the two facts the offline guard needs in order to tell someone how old
|
|
257
|
-
* their protection is. The guard has no update check and no network path for rules
|
|
258
|
-
*
|
|
259
|
-
*
|
|
241
|
+
* their protection is. The guard has no update check and no network path for rules,
|
|
242
|
+
* so a user's rules are frozen on the day they installed and nothing else can tell
|
|
243
|
+
* them that.
|
|
260
244
|
*
|
|
261
245
|
* ── Why the stamp lives HERE and not in `@agenttrail/guard` ──────────────────
|
|
262
|
-
* The guard BUNDLES this package at build time
|
|
263
|
-
*
|
|
264
|
-
*
|
|
265
|
-
*
|
|
266
|
-
* carry the same rules. Spec 11.1.1 states it outright — "that is exactly why C6
|
|
267
|
-
* has `status` print the bundled catalog version rather than the guard's."
|
|
246
|
+
* The guard BUNDLES this package at build time, so it is *this* package's version
|
|
247
|
+
* that determines which rules a user is actually running. The guard's own version
|
|
248
|
+
* answers a different question and would be the wrong number: `guard@0.4.1` may ship
|
|
249
|
+
* an unchanged catalog, and two different guard versions may carry the same rules.
|
|
268
250
|
*
|
|
269
251
|
* ── This module imports nothing, and that is load-bearing ───────────────────
|
|
270
|
-
* It is exported from `rules.ts`, the zod-free entry
|
|
252
|
+
* It is exported from `rules.ts`, the zod-free entry the guard bundles into the
|
|
271
253
|
* file Claude Code runs before every tool call. Two string constants add nothing to
|
|
272
254
|
* that bundle; an import of `schema.ts` here would add zod to it. Add no imports here.
|
|
273
255
|
*
|
|
274
256
|
* ── Both constants are hand-written, and that is deliberate ──────────────────
|
|
275
|
-
* A build-time `define` would reach `dist/` only
|
|
276
|
-
*
|
|
277
|
-
*
|
|
278
|
-
*
|
|
279
|
-
* precedent, and `__tests__/stamp.test.ts` pins `CATALOG_VERSION` to `package.json`
|
|
280
|
-
* so the two cannot drift in silence.
|
|
257
|
+
* A build-time `define` would reach `dist/` only, so the value would exist in the
|
|
258
|
+
* published tarball and be absent from the source every test imports — the shipped
|
|
259
|
+
* value would be the one value never exercised. `__tests__/stamp.test.ts` pins
|
|
260
|
+
* `CATALOG_VERSION` to `package.json` so the two cannot drift in silence.
|
|
281
261
|
*
|
|
282
262
|
* ── AT RELEASE, BUMP BOTH CONSTANTS BELOW, TOGETHER ──────────────────────────
|
|
283
263
|
* Bumping `package.json`'s version alone turns `stamp.test.ts` red, and that is the
|
|
284
264
|
* point: it is what brings whoever cut the release into this file, where the date is
|
|
285
|
-
* a few lines away. Publish order is `guardrails` first, then `guard
|
|
265
|
+
* a few lines away. Publish order is `guardrails` first, then `guard`;
|
|
286
266
|
* a guard release carries whatever stamp was here when it was built.
|
|
287
267
|
*
|
|
288
268
|
* Forgetting to advance the date understates how fresh the rules are, which tells a
|
|
@@ -296,36 +276,26 @@ declare function mentions(text: string): Fixture[];
|
|
|
296
276
|
* Pinned to `package.json`'s `version` by `__tests__/stamp.test.ts` — this is a copy
|
|
297
277
|
* of that value, not an independent one, and the test is what makes the copy safe.
|
|
298
278
|
*/
|
|
299
|
-
declare const CATALOG_VERSION = "0.0
|
|
279
|
+
declare const CATALOG_VERSION = "0.1.0";
|
|
300
280
|
/**
|
|
301
|
-
* When this catalog
|
|
302
|
-
*
|
|
303
|
-
*
|
|
304
|
-
*
|
|
305
|
-
*
|
|
306
|
-
*
|
|
307
|
-
*
|
|
308
|
-
* I am running".
|
|
309
|
-
*
|
|
310
|
-
* It is a real, sourced instant rather than a plausible-looking default. If a future
|
|
311
|
-
* release cannot source one, leave it absent rather than approximate: the renderer in
|
|
312
|
-
* `guard/src/core/catalog-stamp.ts` degrades honestly, and a wrong date that looks
|
|
313
|
-
* right is worse than a missing one in a tool whose whole proposition is candour
|
|
314
|
-
* about its own coverage.
|
|
281
|
+
* When the rules in this catalog reached their current state, as an ISO-8601
|
|
282
|
+
* instant. The question it answers is not "when did this reach a registry" but "how
|
|
283
|
+
* old are the rules I am running".
|
|
284
|
+
*
|
|
285
|
+
* If a release cannot source a real date, leave it absent rather than approximate:
|
|
286
|
+
* the guard handles a missing date, and a wrong date that looks right is worse than
|
|
287
|
+
* a missing one.
|
|
315
288
|
*/
|
|
316
|
-
declare const CATALOG_PUBLISHED_AT = "2026-09-
|
|
289
|
+
declare const CATALOG_PUBLISHED_AT = "2026-09-15T07:13:18Z";
|
|
317
290
|
|
|
318
291
|
/**
|
|
319
292
|
* The corpus itself — rules only, and **deliberately free of zod**.
|
|
320
293
|
*
|
|
321
294
|
* ── Why this is a separate entry point from `index.ts` ───────────────────────
|
|
322
295
|
*
|
|
323
|
-
*
|
|
324
|
-
*
|
|
325
|
-
*
|
|
326
|
-
* `ZodError` and no `ZodType`, captioned "the whole point of not importing
|
|
327
|
-
* ./context" — because the guard "cannot afford to parse a validator it never
|
|
328
|
-
* calls" (`guard/src/core/normalize.ts`).
|
|
296
|
+
* The guard bundles this catalog into its hook script, the file Claude Code
|
|
297
|
+
* executes before EVERY tool call, in a fresh Node process, under a 10s ceiling.
|
|
298
|
+
* That bundle must not carry a validator it never calls.
|
|
329
299
|
*
|
|
330
300
|
* The package ROOT cannot satisfy that: `index.ts` re-exports `./schema.js`, which
|
|
331
301
|
* value-imports zod. So the guard imports `@agenttrail/guardrails/guardrails` — this
|
|
@@ -334,19 +304,15 @@ declare const CATALOG_PUBLISHED_AT = "2026-09-07T12:44:09Z";
|
|
|
334
304
|
* reference to `./schema.js`, which esbuild erases. Everyone else keeps using the
|
|
335
305
|
* root and gets the schema with it.
|
|
336
306
|
*
|
|
337
|
-
*
|
|
338
|
-
*
|
|
339
|
-
*
|
|
307
|
+
* A cold import of the package root, validating every rule through `defineRule`,
|
|
308
|
+
* takes about twice as long as an empty Node process. The subpath avoids that on
|
|
309
|
+
* every tool call.
|
|
340
310
|
*
|
|
341
|
-
* ── Where validation
|
|
311
|
+
* ── Where validation runs ───────────────────────────────────────────────────
|
|
342
312
|
*
|
|
343
|
-
*
|
|
344
|
-
*
|
|
345
|
-
*
|
|
346
|
-
* A test IS test time: `__tests__/corpus.test.ts` runs `parseRule()` over all 56
|
|
347
|
-
* and fails on any invalid one, and proves that sweep bites with a deliberately
|
|
348
|
-
* malformed rule. The guarantee is unchanged; only the mechanism moved off the
|
|
349
|
-
* hook's hot path. `defineRule` stays exported for contributors authoring locally.
|
|
313
|
+
* At test time: `__tests__/corpus.test.ts` runs `parseRule()` over every rule and
|
|
314
|
+
* fails on any invalid one, and proves that sweep bites with a deliberately
|
|
315
|
+
* malformed rule. `defineRule` is exported for contributors authoring locally.
|
|
350
316
|
*
|
|
351
317
|
* So every rule file exports a plain object literal typed `satisfies Rule` with a
|
|
352
318
|
* TYPE-only schema import. `corpus.test.ts` is what stops one from drifting.
|
package/dist/guardrails.d.ts
CHANGED
|
@@ -3,28 +3,23 @@ export { Action, FixtureInput, Fixtures, Match, RuleInput, Severity } from './sc
|
|
|
3
3
|
import 'zod';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* The
|
|
6
|
+
* The eleven packs, and the order they are presented in.
|
|
7
7
|
*
|
|
8
8
|
* These ids are authoritative and shared with the rules authored against them,
|
|
9
|
-
* and with the guard's `config.json` `enabledPacks
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* mining found production destructive-SQL at zero.
|
|
19
|
-
*
|
|
20
|
-
* The per-pack counts in spec 7.1 are a budget, not a quota: trim or extend at
|
|
21
|
-
* authoring time. They are deliberately NOT encoded here, because a rule count
|
|
22
|
-
* asserted in code becomes a reason to write a weak rule to hit a number.
|
|
9
|
+
* and with the guard's `config.json` `enabledPacks`. Renaming one of these is a
|
|
10
|
+
* breaking change to a user's config file.
|
|
11
|
+
*
|
|
12
|
+
* Working-tree destruction leads: destructive git commands are the most frequently
|
|
13
|
+
* documented coding-agent failure, with public incident reports across Claude
|
|
14
|
+
* Code, Gemini CLI and Codex.
|
|
15
|
+
*
|
|
16
|
+
* Per-pack rule counts are deliberately NOT encoded here: a rule count asserted in
|
|
17
|
+
* code becomes a reason to write a weak rule to hit a number.
|
|
23
18
|
*/
|
|
24
|
-
declare const PACKS: readonly ["working-tree", "destructive-data", "prod-infra", "secret-exposure", "rce-supply-chain", "safety-bypass", "privilege-supply-chain", "file-scope"];
|
|
25
|
-
/** One of the
|
|
19
|
+
declare const PACKS: readonly ["working-tree", "destructive-data", "prod-infra", "secret-exposure", "rce-supply-chain", "safety-bypass", "privilege-supply-chain", "file-scope", "agent-context", "test-integrity", "exfiltration"];
|
|
20
|
+
/** One of the eleven pack ids. A rule's `category` is always one of these. */
|
|
26
21
|
type Pack = (typeof PACKS)[number];
|
|
27
|
-
/** Is this string one of the
|
|
22
|
+
/** Is this string one of the eleven pack ids? */
|
|
28
23
|
declare function isPack(value: string): value is Pack;
|
|
29
24
|
|
|
30
25
|
/**
|
|
@@ -42,17 +37,9 @@ declare function isPack(value: string): value is Pack;
|
|
|
42
37
|
* echo "never run rm -rf /" -> DENY dd.rm-rf-absolute
|
|
43
38
|
* curl --data "we ran rm -rf /tmp/x" https://… -> DENY dd.rm-rf-absolute
|
|
44
39
|
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* It was NOT found by the 438 fixtures or the 307-entry quiet corpus, and could
|
|
50
|
-
* not have been: every entry in both is a command somebody wrote on purpose. It
|
|
51
|
-
* took 997 real transcripts, where an automated `curl` posting an issue-tracker comment
|
|
52
|
-
* whose BODY discussed `rm -rf` was labelled "rm -rf against an absolute path".
|
|
53
|
-
* That is why the quoted-mention family is now generated as a cross-product over
|
|
54
|
-
* the whole corpus (`guardrails-quoted-mention.test.ts`) rather than listed by
|
|
55
|
-
* hand: a hand-written list only covers the rules somebody remembered.
|
|
40
|
+
* Without an exemption, a person documenting this tool is blocked by this tool.
|
|
41
|
+
* That applies to every command-channel rule — 62 of the 74. The twelve `file_glob`
|
|
42
|
+
* rules never see a command.
|
|
56
43
|
*
|
|
57
44
|
* ── Why exemption by CARRIER VERB, and not by "the payload is quoted" ────────
|
|
58
45
|
*
|
|
@@ -73,17 +60,14 @@ declare function isPack(value: string): value is Pack;
|
|
|
73
60
|
* echo "rm -rf /" | bash -> NOT exempt: `|` is outside the quotes
|
|
74
61
|
* echo "$(rm -rf /var)" -> NOT exempt: the shell expands `$( )`
|
|
75
62
|
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
* the loose form still covers a search that is not the FIRST word — measured,
|
|
82
|
-
* `cat runbook.md | grep -n TRUNCATE` is exempt only because of it — and a fix is
|
|
83
|
-
* not allowed to regress what the rule already allowed. The other four rules with a pre-existing `none_of`
|
|
63
|
+
* `block-destructive-sql` also keeps a looser condition of its own: it exempts any
|
|
64
|
+
* command whose text NAMES a search or history tool, anywhere. That covers a search
|
|
65
|
+
* that is not the FIRST word, such as `cat runbook.md | grep -n TRUNCATE`, and it is
|
|
66
|
+
* why that rule misses a compound command that both searches and executes, as its
|
|
67
|
+
* description states. The other four rules with their own `none_of`
|
|
84
68
|
* (`se.env-print`, `require-approval-rm-rf`, `wt.clean-fdx`, `wt.restore-path`)
|
|
85
69
|
* exclude something else entirely — a dry run, a build directory, `--staged` —
|
|
86
|
-
* and
|
|
70
|
+
* and have these conditions appended, not replaced.
|
|
87
71
|
*
|
|
88
72
|
* ── The character classes, and why each character is in them ─────────────────
|
|
89
73
|
*
|
|
@@ -204,8 +188,8 @@ declare const QUOTED_MENTION: readonly MatchCondition[];
|
|
|
204
188
|
* harness reads `"command" in fixture`.
|
|
205
189
|
*
|
|
206
190
|
* Writing every fixture out longhand would work and would be unreadable, so
|
|
207
|
-
* these exist instead. They
|
|
208
|
-
*
|
|
191
|
+
* these exist instead. They also make the CHANNEL visible at every single
|
|
192
|
+
* fixture, and the channel is the thing
|
|
209
193
|
* that silently makes a fixture vacuous — a `file_glob` rule handed a command
|
|
210
194
|
* fixture matches nothing, and an `allow` fixture that matches nothing passes.
|
|
211
195
|
*/
|
|
@@ -254,35 +238,31 @@ declare function mentions(text: string): Fixture[];
|
|
|
254
238
|
* The catalog's own version and publication date.
|
|
255
239
|
*
|
|
256
240
|
* These are the two facts the offline guard needs in order to tell someone how old
|
|
257
|
-
* their protection is. The guard has no update check and no network path for rules
|
|
258
|
-
*
|
|
259
|
-
*
|
|
241
|
+
* their protection is. The guard has no update check and no network path for rules,
|
|
242
|
+
* so a user's rules are frozen on the day they installed and nothing else can tell
|
|
243
|
+
* them that.
|
|
260
244
|
*
|
|
261
245
|
* ── Why the stamp lives HERE and not in `@agenttrail/guard` ──────────────────
|
|
262
|
-
* The guard BUNDLES this package at build time
|
|
263
|
-
*
|
|
264
|
-
*
|
|
265
|
-
*
|
|
266
|
-
* carry the same rules. Spec 11.1.1 states it outright — "that is exactly why C6
|
|
267
|
-
* has `status` print the bundled catalog version rather than the guard's."
|
|
246
|
+
* The guard BUNDLES this package at build time, so it is *this* package's version
|
|
247
|
+
* that determines which rules a user is actually running. The guard's own version
|
|
248
|
+
* answers a different question and would be the wrong number: `guard@0.4.1` may ship
|
|
249
|
+
* an unchanged catalog, and two different guard versions may carry the same rules.
|
|
268
250
|
*
|
|
269
251
|
* ── This module imports nothing, and that is load-bearing ───────────────────
|
|
270
|
-
* It is exported from `rules.ts`, the zod-free entry
|
|
252
|
+
* It is exported from `rules.ts`, the zod-free entry the guard bundles into the
|
|
271
253
|
* file Claude Code runs before every tool call. Two string constants add nothing to
|
|
272
254
|
* that bundle; an import of `schema.ts` here would add zod to it. Add no imports here.
|
|
273
255
|
*
|
|
274
256
|
* ── Both constants are hand-written, and that is deliberate ──────────────────
|
|
275
|
-
* A build-time `define` would reach `dist/` only
|
|
276
|
-
*
|
|
277
|
-
*
|
|
278
|
-
*
|
|
279
|
-
* precedent, and `__tests__/stamp.test.ts` pins `CATALOG_VERSION` to `package.json`
|
|
280
|
-
* so the two cannot drift in silence.
|
|
257
|
+
* A build-time `define` would reach `dist/` only, so the value would exist in the
|
|
258
|
+
* published tarball and be absent from the source every test imports — the shipped
|
|
259
|
+
* value would be the one value never exercised. `__tests__/stamp.test.ts` pins
|
|
260
|
+
* `CATALOG_VERSION` to `package.json` so the two cannot drift in silence.
|
|
281
261
|
*
|
|
282
262
|
* ── AT RELEASE, BUMP BOTH CONSTANTS BELOW, TOGETHER ──────────────────────────
|
|
283
263
|
* Bumping `package.json`'s version alone turns `stamp.test.ts` red, and that is the
|
|
284
264
|
* point: it is what brings whoever cut the release into this file, where the date is
|
|
285
|
-
* a few lines away. Publish order is `guardrails` first, then `guard
|
|
265
|
+
* a few lines away. Publish order is `guardrails` first, then `guard`;
|
|
286
266
|
* a guard release carries whatever stamp was here when it was built.
|
|
287
267
|
*
|
|
288
268
|
* Forgetting to advance the date understates how fresh the rules are, which tells a
|
|
@@ -296,36 +276,26 @@ declare function mentions(text: string): Fixture[];
|
|
|
296
276
|
* Pinned to `package.json`'s `version` by `__tests__/stamp.test.ts` — this is a copy
|
|
297
277
|
* of that value, not an independent one, and the test is what makes the copy safe.
|
|
298
278
|
*/
|
|
299
|
-
declare const CATALOG_VERSION = "0.0
|
|
279
|
+
declare const CATALOG_VERSION = "0.1.0";
|
|
300
280
|
/**
|
|
301
|
-
* When this catalog
|
|
302
|
-
*
|
|
303
|
-
*
|
|
304
|
-
*
|
|
305
|
-
*
|
|
306
|
-
*
|
|
307
|
-
*
|
|
308
|
-
* I am running".
|
|
309
|
-
*
|
|
310
|
-
* It is a real, sourced instant rather than a plausible-looking default. If a future
|
|
311
|
-
* release cannot source one, leave it absent rather than approximate: the renderer in
|
|
312
|
-
* `guard/src/core/catalog-stamp.ts` degrades honestly, and a wrong date that looks
|
|
313
|
-
* right is worse than a missing one in a tool whose whole proposition is candour
|
|
314
|
-
* about its own coverage.
|
|
281
|
+
* When the rules in this catalog reached their current state, as an ISO-8601
|
|
282
|
+
* instant. The question it answers is not "when did this reach a registry" but "how
|
|
283
|
+
* old are the rules I am running".
|
|
284
|
+
*
|
|
285
|
+
* If a release cannot source a real date, leave it absent rather than approximate:
|
|
286
|
+
* the guard handles a missing date, and a wrong date that looks right is worse than
|
|
287
|
+
* a missing one.
|
|
315
288
|
*/
|
|
316
|
-
declare const CATALOG_PUBLISHED_AT = "2026-09-
|
|
289
|
+
declare const CATALOG_PUBLISHED_AT = "2026-09-15T07:13:18Z";
|
|
317
290
|
|
|
318
291
|
/**
|
|
319
292
|
* The corpus itself — rules only, and **deliberately free of zod**.
|
|
320
293
|
*
|
|
321
294
|
* ── Why this is a separate entry point from `index.ts` ───────────────────────
|
|
322
295
|
*
|
|
323
|
-
*
|
|
324
|
-
*
|
|
325
|
-
*
|
|
326
|
-
* `ZodError` and no `ZodType`, captioned "the whole point of not importing
|
|
327
|
-
* ./context" — because the guard "cannot afford to parse a validator it never
|
|
328
|
-
* calls" (`guard/src/core/normalize.ts`).
|
|
296
|
+
* The guard bundles this catalog into its hook script, the file Claude Code
|
|
297
|
+
* executes before EVERY tool call, in a fresh Node process, under a 10s ceiling.
|
|
298
|
+
* That bundle must not carry a validator it never calls.
|
|
329
299
|
*
|
|
330
300
|
* The package ROOT cannot satisfy that: `index.ts` re-exports `./schema.js`, which
|
|
331
301
|
* value-imports zod. So the guard imports `@agenttrail/guardrails/guardrails` — this
|
|
@@ -334,19 +304,15 @@ declare const CATALOG_PUBLISHED_AT = "2026-09-07T12:44:09Z";
|
|
|
334
304
|
* reference to `./schema.js`, which esbuild erases. Everyone else keeps using the
|
|
335
305
|
* root and gets the schema with it.
|
|
336
306
|
*
|
|
337
|
-
*
|
|
338
|
-
*
|
|
339
|
-
*
|
|
307
|
+
* A cold import of the package root, validating every rule through `defineRule`,
|
|
308
|
+
* takes about twice as long as an empty Node process. The subpath avoids that on
|
|
309
|
+
* every tool call.
|
|
340
310
|
*
|
|
341
|
-
* ── Where validation
|
|
311
|
+
* ── Where validation runs ───────────────────────────────────────────────────
|
|
342
312
|
*
|
|
343
|
-
*
|
|
344
|
-
*
|
|
345
|
-
*
|
|
346
|
-
* A test IS test time: `__tests__/corpus.test.ts` runs `parseRule()` over all 56
|
|
347
|
-
* and fails on any invalid one, and proves that sweep bites with a deliberately
|
|
348
|
-
* malformed rule. The guarantee is unchanged; only the mechanism moved off the
|
|
349
|
-
* hook's hot path. `defineRule` stays exported for contributors authoring locally.
|
|
313
|
+
* At test time: `__tests__/corpus.test.ts` runs `parseRule()` over every rule and
|
|
314
|
+
* fails on any invalid one, and proves that sweep bites with a deliberately
|
|
315
|
+
* malformed rule. `defineRule` is exported for contributors authoring locally.
|
|
350
316
|
*
|
|
351
317
|
* So every rule file exports a plain object literal typed `satisfies Rule` with a
|
|
352
318
|
* TYPE-only schema import. `corpus.test.ts` is what stops one from drifting.
|