1
2
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
# SAMPLE.toml — Reference for all supported TOML command definition fields.
#
# This file is not loaded by safe-chains. It documents every field the
# registry understands, when to use each one, and how they compose.
# Copy the pattern that matches your command and fill in the data.
#
# CORE PRINCIPLE: This is an allowlist. Only what you list is allowed.
# Omitted flags, subcommands, and arguments are implicitly not allowed.
# Never describe what is blocked — just list what is permitted.
#
# FIELD DEFAULTS (omit when the default is what you want):
# level = "Inert" (alternatives: "SafeRead", "SafeWrite")
# bare = true (command can run with no arguments)
# tolerate_unknown_short = false (unknown -X tokens are rejected; if true,
# accepted as positional. Use for tools
# like `pdftotext -help`, `sample -mayDie`)
# tolerate_unknown_long = false (unknown --foo tokens are rejected; if
# true, accepted as positional. AVOID
# unless the tool's long-flag surface is
# genuinely unbounded — most modern
# destructive flags are double-dash, so
# this silently passes things like --repair,
# --delete, --to-remote, etc.)
#
# `positional_style = true` is the legacy single-bool form of these two; it
# was removed because removing a flag from the allowlist did NOT deny it
# while positional_style was on (the unknown form still slipped through as
# a positional). The build panics if any TOML still sets it.
#
# CHOOSING THE RIGHT PATTERN:
#
# Is the command a simple tool with flags and positional args?
# → Use "Flat command" (grep, cat, jq, bat, etc.)
#
# Does the command have subcommands (git log, cargo build, etc.)?
# → Use "Structured command with subcommands"
#
# Does a subcommand require a specific flag to be safe (cargo fmt --check)?
# → Use "guard" on the sub
#
# Does a subcommand have its own sub-subcommands (npm config get)?
# → Use nested [[command.sub.sub]]
#
# Does a subcommand delegate to an inner command (rustup run stable echo)?
# → Use "delegate_skip" or "delegate_after"
#
# Does a subcommand accept anything (git help)?
# → Use "allow_all = true"
#
# Does a subcommand only allow specific argument values (npm run test)?
# → Use "first_arg" with patterns
#
# Does a subcommand require one of several flags (conda config --show)?
# → Use "require_any"
#
# Does a flag promote the safety level (sk --history → SafeWrite)?
# → Use "write_flags"
#
# Does the command have global flags before subcommands (jj --no-pager log)?
# → Use "wrapper" on a structured command to strip them first
#
# Does the command wrap and delegate to an inner command (timeout, nice)?
# → Use "wrapper" with skip flags and positional_skip
#
# Does the command need Rust code for validation (curl, perl, fzf)?
# → Use "handler" to reference a named Rust function
#
# Was a subcommand considered but deliberately not approved?
# → Use "candidate = true" to record the decision
#
# ─────────────────────────────────────────────────────────────────────
# FLAT COMMAND — simple tool with flags and positional args
# ─────────────────────────────────────────────────────────────────────
#
# Use for: grep, cat, jq, ls, bat, wc, and most CLI tools.
# A flat command has no subcommands — just flags and file arguments.
# [[command]]
# name = "grep"
# aliases = ["egrep", "fgrep"] # optional; registers additional names
# url = "https://www.gnu.org/software/grep/manual/grep.html"
# level = "Inert" # Inert | SafeRead | SafeWrite
# bare = false # false = requires at least one argument
# # max_positional = 2 # optional; limits positional arg count
# # tolerate_unknown_short = true # optional; treats unknown -X / -word
# # tokens as positional. Use for
# # tools like `pdftotext -help`.
# # tolerate_unknown_long = true # optional; treats unknown --foo
# # tokens as positional. DANGEROUS:
# # most destructive flags are
# # double-dash. Reserve for tools
# # like AWS CLI whose long-flag
# # surface is genuinely unbounded.
# # numeric_dash = true # optional; accepts -NUMBER shorthand
# # (e.g. head -20 ≡ head -n 20).
# # env_assignment_positionals = true # optional; this command PUTS its
# # NAME=VALUE positionals into the
# # ENVIRONMENT (`export`, `declare -x`,
# # `typeset -x`). Each assignment is
# # then classified through
# # envvars.toml, exactly as the
# # `VAR=value cmd` prefix form is, so
# # the two spellings of one capability
# # cannot disagree. Without it,
# # `export LD_PRELOAD=/tmp/evil.so`
# # auto-approved while
# # `LD_PRELOAD=/tmp/evil.so ls` denied.
# # Set this on ANY command that exports
# # a caller-named variable.
# # Only for commands with POSIX -number legacy syntax.
# standalone = [ # flags that take no value
# "--count", "--help", "--recursive", "--version",
# "-c", "-h", "-i", "-r", "-V",
# ]
# valued = [ # flags that consume the next token as value
# "--after-context", "--max-count", # also accepts --max-count=5 syntax
# "-A", "-m",
# ]
# ─────────────────────────────────────────────────────────────────────
# STRUCTURED COMMAND — has subcommands
# ─────────────────────────────────────────────────────────────────────
#
# Use for: cargo, git, docker, npm, brew, etc.
# bare_flags are accepted when the command is invoked alone (e.g. cargo --help).
# Each [[command.sub]] defines one allowed subcommand.
# [[command]]
# name = "cargo"
# url = "https://doc.rust-lang.org/cargo/commands/"
# bare_flags = ["--help", "--version", "-V", "-h"] # allowed with just the flag
#
# [[command.sub]]
# name = "test"
# level = "SafeRead"
# standalone = ["--release", "--no-fail-fast", "-h"]
# valued = ["--jobs", "--package", "-j", "-p"]
#
# [[command.sub]]
# name = "build"
# level = "SafeWrite"
# standalone = ["--release", "-h"]
# valued = ["--jobs", "--target", "-j"]
# ─────────────────────────────────────────────────────────────────────
# ARCHETYPE-CLASSIFIED SUBCOMMAND — profile + research provenance
# ─────────────────────────────────────────────────────────────────────
#
# The facet-derived successor to `candidate = true`, for subcommand tools whose subs are above the
# auto-approve line (cloud/infra/remote CLIs). Instead of hand-marking, declare which recurring
# capability profile the sub is (`archetypes.toml`); the engine DERIVES the verdict by projecting
# that profile through the levels. See docs/design/behavioral-taxonomy-archetypes.md.
#
# `profile` requires research provenance — this is a HIGHER bar than the rest of the TOML, and it is
# enforced (`registry::every_profiled_sub_has_provenance`). Three layers, so a future researcher can
# act on each precisely:
# fact — what the upstream tool DOCUMENTS (re-verify against `source` if upstream moves)
# profile — OUR inference: which archetype it maps to (this field IS the classification)
# judgment — OUR stance where the source doesn't decide it (a policy call, may be revisited) [optional]
# `source` cites the upstream doc/section. `fact` + `source` are REQUIRED whenever `profile` is set.
#
# [[command.sub]]
# name = "delete"
# profile = "remote-destroy-recoverable"
# fact = "Deletes the named app and all its services from the control plane via the HTTPS API."
# source = "https://.../docs/cli — `tool apps delete`"
# judgment = "Recoverable, not irreversible: the app is redeployable from source; app-held DATA is a separate, higher sub."
#
# Per-FLAG escalation: a `[[command.sub.flag]]` ADDS a capability when present (the level algebra
# takes the max), so a benign base + a dangerous flag lands at the flag's tier. `classifies` names an
# archetype (or "unclassified" to fail-closed), and each flag cites its OWN research. Bare flags
# (--force) escalate on presence; add `value_prefix` to escalate only when the flag's VALUE starts
# with it (so one valued flag is benign for most values, dangerous for a specific key).
#
# [[command.sub]]
# name = "push"
# profile = "vcs-sync"
# network_destination = true # classify the first positional (the send TARGET) onto
# # locus.provenance: a bare remote NAME → established, an
# # inline URL / scp-path / filesystem path → literal, a
# # $VAR → opaque; an `ext::<cmd>` transport → worst-cased
# # as RCE (it runs a local command). Requires `profile`.
# # For send commands (git push, scp, rsync, curl -d) where
# # the destination's PROVENANCE decides trust, not the
# # operation. See behavioral-taxonomy-exposure.md §4.
# fact = "…"
# source = "…"
# [[command.sub.flag]]
# name = "--force" # bare — escalates on presence
# classifies = "remote-destroy-irreversible"
# fact = "Overwrites the remote ref with no merge check, discarding remote-only commits."
# source = "https://git-scm.com/docs/git-push — `--force`"
# [[command.sub.flag]]
# name = "-c" # valued — escalates only for this key's value
# value_prefix = "core.sshCommand="
# classifies = "unclassified"
# fact = "Sets git's ssh command to an arbitrary program — command execution."
# source = "https://git-scm.com/docs/git-config — `core.sshCommand`"
#
# Bulk data export (`profile = "data-export"`): a db dump / full export is a BULK remote read —
# `scale = unbounded` records the volume, `disclosure = local-process` the destination (the two proxy
# the data's unknowable sensitivity). Its OUTPUT-FILE flags add a SECOND, path-gated local write, so a
# dump to the worktree stays local while one to a system path gates on locus. List every spelling of
# the output flag (short and long); the glued short form `-fPATH` is matched too, so it can't slip the
# gate. Absent (the export streams to stdout) → just the remote read. Requires `profile`.
#
# [[command.sub]]
# name = "dump"
# profile = "data-export"
# output_path_flags = ["-f", "--file"] # `-f ./out.sql` → worktree write (local); `-f /etc/x` → denied
# fact = "Dumps the remote database (schema and, with --data-only, table data) to stdout or -f/--file."
# source = "https://…/docs — `tool db dump`"
#
# Provenance is validated at BUILD time (build::assert_sub_provenance): a profiled sub or escalating
# flag missing fact+source, or naming an unknown archetype, PANICS at registry load — fail-closed.
#
# For a command whose disclosure logic is too conditional for these declarative primitives (openssl's
# private-key subs: `--`/`-` flag equivalence, the `-text` side channel, an `-out` whose VALUE can be
# stdout), write a per-command ENGINE RESOLVER instead (`resolve_openssl` in src/engine/resolve.rs),
# which returns a Profile from arbitrary Rust and stays yolo-reachable.
#
# TOP-LEVEL classifying flag (`[[command.flag]]`): the FLAT-command analog of `[[command.sub.flag]]`.
# A flag whose PRESENCE classifies the WHOLE invocation as an archetype — for a bimodal tool where a
# mode flag flips the operation. The bare/other form falls through to ordinary resolution (its own
# `level`/behavior); only the listed flag switches the classification. Same fields and build-time
# provenance check as a sub flag (`name`, `classifies`, optional `value_prefix`/`when_absent`,
# `fact`/`source`). List every spelling (short and long). Example — `age -d`/`--decrypt` reveals
# plaintext to the model (`decrypt-read`, yolo) while the bare/encrypt form stays SafeWrite:
#
# [[command.flag]]
# name = "-d"
# classifies = "decrypt-read"
# fact = "`age -d` decrypts the input and writes the PLAINTEXT to stdout — it enters the caller's context."
# source = "https://age-encryption.org/ (age(1) — decryption mode)"
# [[command.flag]]
# name = "--decrypt"
# classifies = "decrypt-read"
# fact = "…"
# source = "…"
# ─────────────────────────────────────────────────────────────────────
# GUARDED SUBCOMMAND — requires a specific flag to be present
# ─────────────────────────────────────────────────────────────────────
#
# Use when a subcommand is only safe with a particular flag.
# Example: "cargo fmt" is a write operation, but "cargo fmt --check" is read-only.
# Without the guard flag, the subcommand is not allowed.
#
# Internally, guard is a shorthand for require_any with a single flag.
# The difference: guard also accepts bare "help" as a positional argument
# (e.g. "cargo fmt help"), while require_any only accepts --help/-h.
# Use guard when there is exactly one required flag; use require_any
# when multiple flags are acceptable alternatives.
# [[command.sub]]
# name = "fmt"
# guard = "--check" # long flag that must be present
# # guard_short = "-c" # optional short form of the guard
# level = "Inert"
# bare = false # must have at least the guard flag
# standalone = ["--all", "--check", "-h"]
# valued = ["--package", "-p"]
# ─────────────────────────────────────────────────────────────────────
# NESTED SUBCOMMANDS — subcommand has its own subcommands
# ─────────────────────────────────────────────────────────────────────
#
# Use for: npm config get, docker compose ps, mise config ls, etc.
# The parent sub has no flags of its own — it just groups child subs.
# Bare invocation of the parent (e.g. "npm config") is rejected by default.
#
# Set nested_bare = true if bare invocation and --help/-h should be
# accepted. Use this for commands like "mise settings" that show output
# when invoked without a subcommand. This also allows --help/-h on the
# parent without routing to a child sub.
#
# If the parent sub accepts flags that can appear before the child sub
# (e.g. "git config --local --list"), add standalone/valued on the parent.
# These are skipped when locating the child sub name.
# [[command.sub]]
# name = "config"
# # nested_bare = true # set if "tool config" alone is safe
# # standalone = ["--local", "--global"] # flags allowed before the child sub
# # valued = ["--file", "-f"] # valued flags allowed before the child sub
#
# [[command.sub.sub]]
# name = "get"
# standalone = ["--help", "--json", "-h"]
#
# [[command.sub.sub]]
# name = "list"
# standalone = ["--help", "--json", "-h"]
# ─────────────────────────────────────────────────────────────────────
# FIRST ARG FILTER — restrict which argument values are accepted
# ─────────────────────────────────────────────────────────────────────
#
# Use when a subcommand accepts an argument but only specific values are
# safe. The first positional argument after the subcommand name must
# match one of the patterns. Supports trailing * for prefix matching.
# Example: "npm run test" and "npm run test:unit" are safe, but
# "npm run build" is not.
# --help/-h is always allowed without matching.
# [[command.sub]]
# name = "run"
# first_arg = ["test", "test:*"] # "test" exact or "test:..." prefix
# level = "SafeRead"
# FIRST_ARG_STANDALONE / FIRST_ARG_VALUED — the flag allowlist for a first_arg
# GLOB family. A glob decides on the first positional alone; without these it
# never examines the rest of the line, so ANY flag rides along on an admitted
# verb — including one that changes where the call goes or who it runs as
# (`aws iam list-users --endpoint-url http://evil.com`). Declare them whenever
# the glob covers a family whose flags carry security meaning.
#
# Same arity split as standalone/valued: a valued flag consumes the next token,
# and both accept the --flag=value spelling. Bare positionals always pass (a
# glob family like `kubectl get pods` names its resource positionally); use
# credential_first_arg for positionals that must deny.
#
# Empty = the family is not yet researched and stays permissive. That is a
# migration affordance for the ~250 cloud-service groups, not a default to
# reach for: `no_new_unresearched_first_arg_family` pins the remaining set, so
# a new undeclared family fails the build.
# [[command.sub]]
# name = "iam"
# first_arg = ["get-*", "list-*"]
# first_arg_standalone = ["--debug", "--no-cli-pager"]
# first_arg_valued = ["--output", "--query", "--region", "--role-name"]
# # --endpoint-url/--profile absent by research
# FIRST_ARG_LOOPBACK_VALUED — a flag admitted only when its VALUE names THIS
# machine. Some flags are safe or unsafe depending entirely on where they point:
# `--endpoint-url http://localhost:8000` is a developer talking to their own
# emulator, while the same flag pointed elsewhere redirects an authenticated
# request to a host of the caller's choosing. Listing the flag here says "the
# destination decides" instead of forcing an all-or-nothing ruling on the flag.
#
# The value is classified by netloc::is_loopback, which is a positive recognizer
# over a small vetted set — `localhost` and `*.localhost`, 127.0.0.0/8 in dotted
# quad, and `::1`. Anything it does not positively recognize is remote, including
# spellings that ARE loopback in fact (`http://2130706433`, `http://0177.0.0.1`):
# recognizing them buys little and every added spelling is a chance to disagree
# with how the tool itself parses. A missing value denies.
#
# Use it only where a local emulator is a real workflow for that tool. Prefer
# omitting the flag entirely when it isn't — an absent flag denies, which is the
# cheap and correct default.
# [[command.sub]]
# name = "dynamodb"
# first_arg = ["describe-*", "list-*"]
# first_arg_loopback_valued = ["--endpoint-url"] # DynamoDB Local
# first_arg_standalone = ["--debug", "--no-cli-pager"]
# first_arg_valued = ["--output", "--query", "--region", "--table-name"]
# LOOPBACK_VALUED / LOOPBACK_LOCALIZES (on a profiled sub) — the same endpoint
# gate, plus whether a local destination changes the CLASSIFICATION.
#
# The facets split into two kinds, and only one kind depends on where the call
# goes. `remote-mutate` describes a cloud service in exactly four places: it
# reaches a fixed remote, talks outbound, sends host data off the machine, and
# bills. All four are false for http://localhost:8000. Everything else it says
# — the operation, scale, retrieval, reversibility, persistence, disclosure —
# describes what the call DOES and is just as true against an emulator.
#
# So `loopback_localizes` clears the destination facets and leaves the rest
# alone. It does NOT swap in a "local" archetype: there would have to be a local
# twin of every remote archetype, and a twin like local-mutate-recoverable
# asserts `locus.local = worktree` and `persistence = data`, neither of which is
# true of a container. Composing means a bulk read pointed at localhost is
# automatically bulk-read-minus-the-network, with no new archetype to invent.
#
# loopback_valued alone → the flag is admitted only when local; classification
# is unchanged (right for reads, which already pass).
# + loopback_localizes → a local destination also clears those four facets.
#
# DESTROY archetypes may not set loopback_localizes and the build refuses it.
# The emulator claim is unverifiable — `ssh -L 8000:dynamodb.<region>.amazonaws
# .com:443` makes localhost mean production — and being wrong about that costs a
# stray write for a mutate and the data for a delete. Destroys may still declare
# loopback_valued: the flag stays usable, it just buys no relaxation.
# [[command.sub.sub]]
# name = "put-item"
# profile = "remote-mutate" # against the real service
# loopback_valued = ["--endpoint-url"]
# loopback_localizes = true # against DynamoDB Local
# valued = ["--table-name", "--item", "--region"]
# CREDENTIAL_FIRST_ARG — the value-dependent credential-read gate. Same glob
# syntax as first_arg (exact or trailing-* prefix), but a MATCH DENIES the
# invocation as a credential exposure instead of allowing it. Checked after
# explicit sub-subs and BEFORE the first_arg allow-glob, and it is flag-aware
# (skips leading flags to find the real first positional). Use it when a
# specific resource/key name turns an otherwise-safe read into secret
# disclosure — while every OTHER value stays a normal read via first_arg.
# It is the value-dependent complement to `profile = "credential-read"`
# (which classifies a WHOLE sub). Pairs with first_arg = ["*"] so non-secret
# values still pass.
# [[command.sub]]
# name = "get" # e.g. kubectl get
# first_arg = ["*"] # any resource is a read…
# credential_first_arg = ["secret", "secret/*"] # …except the Secret resource
# level = "SafeRead"
# ─────────────────────────────────────────────────────────────────────
# REQUIRE ANY — subcommand needs at least one of several flags
# ─────────────────────────────────────────────────────────────────────
#
# Use when a subcommand is only safe if at least one of several specific
# flags is present. Internally the same dispatch as guard, but accepts
# multiple alternatives. Use guard when there is exactly one required
# flag; use require_any when several flags are acceptable.
# Example: "conda config" is only safe with --show or --show-sources.
# Without one of those, it could be used for --set or --add operations.
# --help/-h is always allowed without the required flag.
# [[command.sub]]
# name = "config"
# bare = false
# require_any = ["--show", "--show-sources"]
# standalone = ["--help", "--json", "--show", "--show-sources", "-h"]
# valued = ["--file", "--name", "-f", "-n"]
# ─────────────────────────────────────────────────────────────────────
# ALLOW ALL — subcommand that accepts anything
# ─────────────────────────────────────────────────────────────────────
#
# Use for: help subcommands that accept any topic as an argument.
# Internally equivalent to a policy with both `tolerate_unknown_short` and
# `tolerate_unknown_long` set and no flags listed, but allow_all is clearer
# about intent.
# [[command.sub]]
# name = "help"
# allow_all = true
# level = "Inert"
# ─────────────────────────────────────────────────────────────────────
# WRITE-FLAGGED — specific flags promote the safety level
# ─────────────────────────────────────────────────────────────────────
#
# Use when a command is normally Inert but certain flags cause writes.
# Example: sk (skim) is Inert, but --history writes to a file → SafeWrite.
# The base level applies when none of the write_flags are present.
# [[command.sub]]
# name = "run"
# write_flags = ["--history"] # if any of these appear → SafeWrite
# # level = "Inert" # base level when write_flags absent
# standalone = ["--help", "-h"]
# valued = ["--history", "--query", "-q"]
# ─────────────────────────────────────────────────────────────────────
# PATH GATE — declare which flags/positionals are filesystem PATHS
# ─────────────────────────────────────────────────────────────────────
#
# The command level (SafeRead/SafeWrite/Inert) says a command reads or writes,
# but not WHICH path. `[command.path_gate]` declares the read/write role of each
# path-bearing flag value and of bare positionals, so a `--output /etc/cron.d/job`
# or `--input ~/.ssh/id_rsa` is gated by locus (a system/secret path denies; a
# workspace path allows). Without it, a path-flag command auto-approves ANY path.
#
# Declare it on the command whose OWN flags carry paths — the gate then derives
# from the command definition (you can't forget to also edit pathgates.toml).
# Same grammar as pathgates.toml's [roles.X]:
# flags = { "-o" = "write", "--input" = "read" }
# the role of each flag's VALUE. List ONLY flags whose value is a real
# filesystem path — never a format name, URL, identity, or number.
# positional = "read" | "write" role of bare positional paths (omit = ignore).
# shape = "last_write" positionals are SOURCE… DEST (last one writes);
# pair with positional = "read".
#
# [[command]]
# name = "iconutil"
# level = "SafeWrite"
# valued = ["--convert", "--output", "-c", "-o"]
#
# [command.path_gate]
# positional = "read" # the .iconset/.car input
# flags = { "-o" = "write", "--output" = "write" }
# ─────────────────────────────────────────────────────────────────────
# DELEGATION — subcommand wraps an inner command
# ─────────────────────────────────────────────────────────────────────
#
# delegate_skip: Skip N tokens after the subcommand name, then validate
# the rest as a complete command. Used for "rustup run stable echo hello"
# where skip=2 skips the toolchain name.
#
# delegate_after: Find a separator token (usually "--"), then validate
# everything after it as a complete command. Used for
# "mise exec -- git status" where the separator is "--".
# [[command.sub]]
# name = "run"
# delegate_skip = 2 # skip 2 tokens, validate the rest
#
# [[command.sub]]
# name = "exec"
# delegate_after = "--" # validate everything after "--"
# ─────────────────────────────────────────────────────────────────────
# STRUCTURED + WRAPPER — global flags stripped before sub dispatch
# ─────────────────────────────────────────────────────────────────────
#
# Use for: commands with global flags that appear before the subcommand.
# Example: "jj --no-pager log" or "xcrun --sdk macosx --find clang".
# The wrapper flags are stripped from the front, then the remaining
# tokens are dispatched to subcommands as normal.
# This reuses the wrapper concept on a structured command.
# [[command]]
# name = "jj"
# bare_flags = ["--help", "--version", "-h"]
# [command.wrapper]
# standalone = ["--no-pager", "--quiet", "--verbose"]
# valued = ["--color", "--repository", "-R"]
#
# [[command.sub]]
# name = "log"
# standalone = ["--help", "-h"]
#
# [[command.sub]]
# name = "diff"
# standalone = ["--help", "-h"]
# ─────────────────────────────────────────────────────────────────────
# WRAPPER — command that wraps an inner command
# ─────────────────────────────────────────────────────────────────────
#
# Use for: timeout, time, nice, ionice, dotenv, and similar commands
# that accept their own flags, optionally skip positional args (like a
# duration or priority), then delegate everything remaining as a
# complete inner command to be validated recursively.
#
# Fields:
# standalone — wrapper's own flags that take no value
# valued — wrapper's own flags that consume the next token
# positional_skip — number of positional args to skip before the
# inner command (e.g., 1 for timeout's duration)
# separator — stop scanning flags at this token (e.g., "--")
# bare_ok — if true, running the wrapper with no inner command
# is allowed (e.g., "env" alone lists variables)
# [[command]]
# name = "timeout"
# url = "https://www.gnu.org/software/coreutils/..."
# [command.wrapper]
# standalone = ["--preserve-status"]
# valued = ["--signal", "--kill-after", "-s", "-k"]
# positional_skip = 1 # skip the duration argument
#
# [[command]]
# name = "dotenv"
# url = "https://github.com/bkeepers/dotenv"
# [command.wrapper]
# valued = ["-c", "-e", "-f", "-v"]
# separator = "--" # stop flag scanning at "--"
# ─────────────────────────────────────────────────────────────────────
# CUSTOM HANDLER — requires Rust code for validation
# ─────────────────────────────────────────────────────────────────────
#
# Use as a last resort when the command needs logic that can't be
# expressed with the fields above. The handler name references a Rust
# function registered in custom_cmd_handlers() or custom_sub_handlers()
# in src/handlers/mod.rs. The function receives the token slice and
# returns a Verdict.
#
# Works at both levels:
# - Command level: the entire command uses a Rust handler
# - Sub level: one subcommand uses a Rust handler while siblings
# use declarative TOML fields
#
# Before reaching for this: can you express the rule as a guard, a
# nested sub, a delegation, or simply by listing the right flags?
# Most commands that seem to need custom code can be modeled with the
# declarative fields above.
# Command-level handler (entire command validated by Rust):
# [[command]]
# name = "curl"
# handler = "curl" # references Rust handler by name
# url = "https://curl.se/docs/manpage.html"
# Sub-level handler (one sub uses Rust, siblings use TOML):
# [[command.sub]]
# name = "exec"
# handler = "bundle_exec" # references Rust handler by name
# When a command (or sub) uses a handler, the auto-generated docs
# can't introspect the Rust logic, so add a doc_body field with the
# allowed surface in prose. Markdown bullet lines (lines starting
# with "- ") are kept as-is; other lines get a "- " prefix added.
# Follow the documentation style rules: list what is allowed in
# positive terms; do not say "denied", "blocked", "no flags", etc.
#
# [[command]]
# name = "php"
# handler = "php"
# doc_body = """
# - Allowed standalone flags: --help, --version
# - Subcommands: artisan, please
# """
#
# [[command.sub]]
# name = "cache:clear"
# handler = "laravel_cache_clear"
# doc_body = "requires --store with one of file, array, null"
# Handlers should hold LOGIC, not DATA. If your handler wants a static
# `WordSet` of allowed flags or sub names, that data belongs in TOML.
# A handler-using command can declare `[[command.sub]]` blocks (the
# handler dispatches them via `registry::try_sub_dispatch(name, tokens)`)
# and a `[command.fallback]` block — an alternate grammar engaged when
# no sub matches (dispatched via `registry::try_fallback_grammar(name, tokens)`).
# `commands/tools/tilt.toml` and `src/handlers/tilt.rs` are the canonical
# example: tilt's K8s subs live in TOML, the Ruby template-engine grammar
# is the fallback, and the Rust handler is a few lines of dispatch.
# ─────────────────────────────────────────────────────────────────────
# COMMAND.FALLBACK — alternate grammar for handler-using commands
# ─────────────────────────────────────────────────────────────────────
#
# Engaged when the handler calls `registry::try_fallback_grammar(name, tokens)`
# and `[[command.sub]]` dispatch found no match. Mirrors the flat-policy
# fields available on a sub block, plus `positional_shape`.
#
# [command.fallback]
# level = "Inert" # SafetyLevel for matching invocations
# bare = true # is bare invocation allowed
# max_positional = 1 # cap on non-flag arguments
# positional_shape = "path" # predicate over the first positional;
# # only "path" is supported today —
# # accepts tokens shaped like a file
# # path (contain `/`, contain `.`, or
# # are `-` for stdin) and rejects flags
# executor = "file" # the command runs code — gate the EXECUTOR
# # through the execution-origin engine instead
# # of the flat `level`: a WORKSPACE-local
# # executor allows (the dev loop), a foreign one
# # (/tmp, ~, absolute) or unpinnable one ($VAR,
# # glob) denies. Two forms:
# # "file" — the first positional is the
# # executor path (python3 ./s.py,
# # go run ./cmd). Tokens after it are
# # the script's own args (not
# # validated). No positional → the
# # flat `level` (python3 --version).
# # "project" — the current project is the
# # executor, no path operand
# # (cargo run). Also valid on a
# # [[command.sub]] block.
# executor_redirect_flag = "--manifest-path" # (project only) a valued flag whose
# # value moves the executor OUT of the project
# # (cargo run --manifest-path DIR/Cargo.toml) —
# # that value is locus-gated, so a foreign
# # manifest denies. See
# # docs/design/behavioral-taxonomy-execution-origin.md.
# standalone = ["--help", "--list", "-h", "-l"]
# valued = ["--type", "-t"]
# tolerate_unknown_short = false
# tolerate_unknown_long = false
# numeric_dash = false
# ─────────────────────────────────────────────────────────────────────
# VERB CHAIN — `CMD [main-flags] verb [args] then verb …` (e.g. mlr)
# ─────────────────────────────────────────────────────────────────────
#
# `[command.verb_chain]` makes a command fully declarative (NO handler)
# when its grammar is: a strict MAIN-flag region, then a `then`-chain of
# allowlisted VERBS. Dispatched by `dispatch::dispatch_verb_chain`.
#
# The main region is a STRICT allowlist — an unlisted flag DENIES, so a
# mutating flag (mlr's `-I`/`--in-place`), omitted, is caught by omission
# with no denylist. The region ends at `--` or the first non-flag token,
# which opens the verb chain: every `then`-segment's first token (the
# verb NAME) must be on `verbs`; verb ARGS are open-ended and uninspected
# (a pure read→stdout verb has no shell/file escape).
#
# SAFETY: every value-taking main flag MUST go in `main_valued`, never
# `main_standalone` — else the walk mistakes its value for the verb
# boundary and a later mutating flag slips past in verb-land.
#
# [command.verb_chain]
# level = "Inert" # verdict for a matching invocation
# separator = "then" # chain keyword (default "then")
# main_standalone = ["--csv", "--json"] # boolean main flags
# main_valued = ["--from", "--ifs"] # value-taking main flags (--from FILE)
# main_variadic = ["--mfrom"] # consume input files until a `--` terminator
# verbs = ["cat", "head", "sort", …] # allowlisted verb names
#
# See commands/data/mlr.toml for a complete example.
# ─────────────────────────────────────────────────────────────────────
# HANDLER POLICIES / MATRIX — data the handler walks
# ─────────────────────────────────────────────────────────────────────
#
# For handlers whose dispatch logic genuinely can't move to TOML (e.g.
# gh's sub × action matrix) but whose per-policy WordSets should still
# be data, not Rust constants.
#
# `[command.handler_policy.NAME]` — a named flag policy. The handler
# reads it via `registry::check_handler_policy(cmd, key, tokens)`.
# Same fields as [command.fallback]: standalone, valued, bare,
# max_positional, tolerate_unknown_short, tolerate_unknown_long,
# numeric_dash.
#
# `[[command.matrix]]` — parent × action → policy mappings. Each
# block says "for these parent subcommand names, this action verb
# maps to this `handler_policy` and validates at this safety level."
# Walked by `registry::try_matrix_dispatch(cmd, tokens)`. Lets a
# handler express its dispatch table as data instead of `match` arms.
#
# Shorthand form (no guard required):
# [command.matrix.actions]
# list = "list_policy"
#
# Detailed form (action requires a specific flag to be present):
# [command.matrix.actions.download]
# policy = "download_policy"
# guard = "--output"
# guard_short = "-O"
#
# `policy = "key"` on a [[command.sub]] — references a
# [command.handler_policy.KEY] by name and copies its flag list into
# this sub's effective policy. Lets a single-sub form (no action
# verb, optionally with a guard) share the same WordSets a matrix
# entry would use, without duplicating them. Mutually exclusive with
# inline `standalone` / `valued` on the sub.
#
# [[command.sub]]
# name = "browse"
# policy = "browse"
# guard = "--no-browser"
# guard_short = "-n"
# level = "Inert"
# ─────────────────────────────────────────────────────────────────────
# RESEARCHED_VERSION — record which upstream version was last vetted
# ─────────────────────────────────────────────────────────────────────
#
# Optional free-form string on [[command]]. Records the upstream
# version of the underlying tool that was last researched when this
# TOML was written or revised. Internal use only — not rendered in
# docs or used at runtime; surfaces as a tripwire when re-researching
# a tool to see what's changed since.
#
# Common forms:
# researched_version = "1.9.0" # semver tag
# researched_version = "v5.10.3 (April 2026)" # tag + release date
# researched_version = "@northflank/cli 0.10.15" # package@version
# researched_version = "github.com/foo/bar @ master, 2026-05-08" # snapshot
# researched_version = "POSIX kill(1); surface frozen" # truly stable surface
#
# When ADDING or REVISING a TOML, set this field to whatever the
# upstream's *latest* released version is — verified by checking
# the project's GitHub releases / npm / crates.io / official docs,
# NOT just `<tool> --version` on the local machine, which can be
# stale. If the local install and the online docs disagree, follow
# the online docs (the latest version) and treat any local-only
# behavior as a follow-up to revisit.
#
# When backfill is impossible (older TOMLs that pre-date this field)
# leave it absent rather than guess. Re-researching is the right
# moment to fill it in, not before.
# ─────────────────────────────────────────────────────────────────────
# EVAL_SAFE — tag invocations safe to substitute inside `eval "$(...)"`
# ─────────────────────────────────────────────────────────────────────
#
# Use for: commands whose stdout is documented shell-init code (e.g.
# `mise activate bash`, `ssh-agent`, `starship init bash`). Lets the
# check accept `eval "$(CMD ...)"` for the specific tagged invocation.
#
# Set `eval_safe = true` at the leaf dispatch node — the [[command]]
# itself for flat commands, the [[command.sub]] for subcommands, or
# the [[command.sub.sub]] for nested ones. The tag is leaf-precise:
# tagging a parent does NOT propagate to its subs.
#
# [[command.sub]]
# name = "activate"
# bare = true
# max_positional = 1
# standalone = ["--help", "-h", "--shims", "--quiet", "--no-hook-env"]
# eval_safe = true
# eval_safe_flags = ["--shims", "--quiet", "--no-hook-env"]
#
# `eval_safe_flags` is the allowlist of `-`-prefixed tokens permitted
# inside the substitution. Default empty — meaning only the bare form
# plus positionals are eval-safe. Omit flags like `--help` and
# `--version` whose output is not shell-init code.
#
# Positionals (e.g. `bash` for the shell name) are not in the flag
# list — they're vetted by the regular `max_positional` check.
# Tagging the sub asserts that every valid positional combination
# produces shell-init stdout.
#
# Flat command, bare form only:
#
# [[command]]
# name = "ssh-agent"
# bare = true
# eval_safe = true
#
# CONTRIBUTOR VETTING CHECKLIST — before tagging:
# 1. The sub's stdout is documented shell-init code (link to the
# upstream docs in the description).
# 2. The tool writes errors to stderr, never stdout (a stdout error
# message would be eval'd).
# 3. Each flag in `eval_safe_flags` has been confirmed to keep the
# stdout shape (e.g. boolean toggles are safe; valued flags whose
# value embeds in output need extra care because the eval walker
# only checks the flag NAME, not its value).
# 4. Every valid positional this sub accepts produces shell-init
# output — including unusual values, paths, etc.
#
# SHAPE REQUIREMENTS — the check enforces:
# - The eval substitution must be exactly one simple command (no
# pipelines, no `!`, no trailing `&`, no env prefix, no redirects,
# no nested substitutions).
# - Every word in the substitution must be a bare literal: every
# character drawn from `[a-zA-Z0-9_./=-]`. Words made of this
# alphabet are passed to the substituted command unchanged at
# runtime, so what the contributor sees in the eval is what the
# tool receives. Anything outside the alphabet (a `$` in `$VAR`,
# a `*` in a glob, a `{` in brace expansion, whitespace, etc.)
# would defer the value to runtime and defeat per-tag vetting.
#
#
# REQUIRED FLAGS — for tools whose bare invocation isn't shell-init.
# `fzf` is an interactive fuzzy finder by default; it produces shell-
# init code only when called with `--bash`, `--zsh`, `--fish`, or
# `--nushell`. Declare the required-from-set:
#
# [[command]]
# name = "fzf"
# eval_safe = true
# eval_safe_flags = ["--bash", "--zsh", "--fish", "--nushell"]
# eval_safe_required_flags = ["--bash", "--zsh", "--fish", "--nushell"]
#
# Semantics: AT LEAST ONE entry from `eval_safe_required_flags` must
# appear in the substituted invocation. Two-or-more of the set is
# also accepted — the contributor's tag promises "every allowed
# combination produces shell-init code," not "exactly one of these."
# In practice the tool typically takes the last (e.g. fzf picks the
# last `--bash`/`--zsh`); both the literal "two are present" form
# AND the resulting single-shell output remain eval-safe.
#
# Every entry must also appear in `eval_safe_flags` — the build
# panics otherwise. Default empty = no required flag (the bare-
# invocation form is fine, as for mise activate / ssh-agent /
# starship init).
#
# VALUED-FLAG VALUE ALLOWLIST — for flags whose value determines
# whether stdout is shell-init code. `aws --format env` produces shell
# exports; `aws --format json` produces JSON that would crash an eval.
# Declare the allowed values per flag:
#
# [[command.sub.sub]]
# name = "export-credentials"
# valued = ["--format", "--profile"]
# eval_safe = true
# eval_safe_flags = ["--format", "--profile"]
# [command.sub.sub.eval_safe_flag_values]
# --format = ["env", "env-no-export", "fish", "powershell", "windows-cmd"]
#
# A flag in eval_safe_flag_values is structurally a "valued" flag:
# the walker requires the value (via `--flag VALUE` or `--flag=VALUE`)
# and rejects the invocation if the value is missing or not in the
# allowlist. The values themselves must be drawn from the bare-literal
# alphabet — the build panics otherwise.
#
# For valued flags whose value doesn't affect stdout shape (like
# `--profile NAME` selecting which credential profile to read), the
# explicit-unrestricted form `--profile = []` declares: "I considered
# this valued flag; any bare-literal value preserves shell-init
# output." The walker still consumes the value but skips the value
# allowlist check.
#
# REQUIRED: every valued flag (i.e. every flag also listed in the
# sub's `valued`) that appears in `eval_safe_flags` MUST declare its
# value posture here — either a concrete allowlist or `[]`. Omitting
# an entry panics at build time. This forces every contributor to
# make an explicit choice and prevents the aws v0.196.0 near-miss
# pattern (where `--format` defaulted to `process` JSON and a missing
# value posture would silently substitute JSON into eval).
#
# BUILD-TIME CHECKS — the build panics if:
# - `eval_safe_flags` is set without `eval_safe = true`.
# - `eval_safe = true` is set on a command/sub that has nested
# subs (tag the specific sub-sub instead — the walker is leaf-
# precise, not greedy).
# - `eval_safe = true` is set on a handler-based or wrapper
# command (those use Rust dispatch the walker can't introspect).
# - `eval_safe = true` is set on a `delegate_after` / `delegate_skip`
# sub (those forward to an unrelated inner command).
# - Both `deny = true` and `eval_safe = true` are set.
# - Any `eval_safe = true` tag exists on the command or its subs
# but `researched_version` is not set. The tag is a snapshot
# claim — the version pins which upstream the contributor vetted
# against, so re-research can diff cleanly.
# - A flag appears in `eval_safe_flag_values` but not in
# `eval_safe_flags` (the value allowlist would never fire).
# - Any allowed value contains characters outside the bare-literal
# alphabet `[a-zA-Z0-9_./=-]`.
# - A flag appears in `eval_safe_required_flags` but not in
# `eval_safe_flags` (the requirement could never be satisfied).
# - A flag appears in `eval_safe_flags` AND in the sub's `valued`
# list, but has no entry in `eval_safe_flag_values`. Every valued
# flag tagged eval-safe must declare its value posture (concrete
# allowlist OR explicit-unrestricted `= []`) so a future contributor
# adding a short alias of the flag can't silently widen the surface.
# - Any field unrecognized by the matrix or matrix-action schema
# appears in a `[[command.matrix]]` block. The matrix schema does
# NOT support `eval_safe` — tag the underlying `[[command.sub]]`
# declarations instead.
#
# CUSTOM TOMLS: every field documented in SAMPLE.toml — including
# `eval_safe` and `eval_safe_flags` — applies in project-level
# `.safe-chains.toml` and user-level `~/.config/safe-chains.toml`.
# The user owns those files; eval-safety in a custom TOML is the
# same trust model as adding any other command override.
# ─────────────────────────────────────────────────────────────────────
# DENY — shortcut for "every invocation of this command is denied"
# ─────────────────────────────────────────────────────────────────────
#
# Use for: locking down a built-in command from a custom TOML
# (.safe-chains.toml). One field replaces a whole spec.
#
# [[command]]
# name = "gh"
# deny = true
#
# Equivalent to writing a spec with bare = false, max_positional = 0,
# and no flags or subs — just shorter.
# ─────────────────────────────────────────────────────────────────────
# CANDIDATE — record that a command was considered but not approved
# ─────────────────────────────────────────────────────────────────────
#
# Use for: subcommands or commands that were reviewed and deliberately
# left out of the allowlist. Candidates are parsed but skipped during
# registry building — they won't be dispatched or appear in docs.
# They serve as a record so future contributors don't re-evaluate
# the same commands.
#
# [[command.sub.sub]]
# name = "push"
# candidate = true
#
# [[command.sub.sub]]
# name = "pop"
# candidate = true
#
# Works on [[command]] and [[command.sub]] too:
#
# [[command]]
# name = "dangerous-tool"
# candidate = true
# url = "https://example.com"
# ─────────────────────────────────────────────────────────────────────
# SAFETY LEVELS
# ─────────────────────────────────────────────────────────────────────
#
# "Inert" — No side effects. Read-only or purely informational.
# Examples: ls, cat, grep, git log, cargo tree
#
# "SafeRead" — Runs code but doesn't produce build artifacts.
# Examples: cargo test, npm test, go vet, linters
#
# "SafeWrite" — Produces artifacts or modifies the working tree.
# Examples: cargo build, go build, cargo doc
#
# When commands are piped or chained, the highest level wins.
# Unlisted commands are always denied regardless of level.