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
[]
= "openlatch-client"
= "0.6.21"
= "2021"
= "1.88"
= "Apache-2.0"
= "OpenLatch runtime enforcement node — the capture-and-enforce adapter that evaluates every covered action against a coding agent's Autonomy Zone before it runs"
= "https://github.com/OpenLatch/openlatch-client"
= "https://openlatch.ai"
= "https://docs.openlatch.ai"
= "README.md"
= "openlatch"
= ["ai-agents", "agent-security", "policy-enforcement", "hooks", "cli"]
= ["command-line-utilities", "development-tools"]
= ["tools/", "tests/", ".github/", ".claude/", ".planning/", ".brainstorming/", "npm/", "schemas/conformance/", "docker-compose*.yml"]
[]
= "openlatch_client"
= "src/lib.rs"
[[]]
= "openlatch"
= "src/app/openlatch/main.rs"
= ["full-cli"]
[[]]
= "openlatch-hook"
= "src/app/openlatch_hook/main.rs"
# Cline I-5 (D-06): the Windows plugin-sandbox runtime launcher. std + windows-sys only, links
# nothing from the lib. `full-cli` puts the optional windows-sys in scope and keeps it out of every
# `--no-default-features` (hook) build. Built everywhere so it is compiled and clippy-checked
# everywhere; staged only on Windows.
[[]]
= "openlatch-cline-runtime"
= "src/app/openlatch_cline_runtime/main.rs"
= ["full-cli"]
# The zone evaluator's performance fixtures. `harness = false` because criterion
# supplies its own `main()`; with the default harness cargo would hand the binary
# libtest's argument parser and `--save-baseline` would not parse.
#
# No `required-features`: `policy` rides `full-cli`, which is on by default, and
# nothing in CI runs `--all-targets` with default features off. Add it the day
# something does.
[[]]
= "zone_eval"
= false
# The update-spike suite spawns the `openlatch __spike-update` binary as a
# separate process, so `#[cfg(test)]` inside src/core/update.rs never applies
# to it — only the `insecure-test-keys` feature opens the
# `OPENLATCH_TRUSTED_KEYS` override the spawned binary needs. Declaring that
# here (rather than leaving the mismatch to fail at runtime) makes a plain
# `cargo test --locked` skip this target cleanly (nothing built, nothing run)
# instead of four red "no trusted public key matched" failures, and makes an
# explicit `cargo test --locked --test spike_update` print cargo's own
# required-features message instead of a confusing signature-verify panic;
# `cargo test --all-features` still runs the suite for real. See issue #444.
[[]]
= "spike_update"
= "tests/spike_update.rs"
= ["full-cli", "insecure-test-keys"]
[]
# Default builds include the full CLI and the network telemetry path.
# To compile a binary with zero PostHog client code (invariant I6 from the
# telemetry brainstorm), build with:
# cargo build --no-default-features --features full-cli-no-telemetry
= ["full-cli", "telemetry-network", "crash-report"]
# Convenience alias for "full CLI without the network telemetry path".
# Equivalent to building with just `full-cli` — `telemetry-network` is a
# strictly additive feature that wires up the PostHog HTTP client (Task 3).
#
# NOTE, since crash reporting became PostHog-backed: this build still carries
# `crash-report`, and crash reports now go to PostHog too. The two consent gates
# are independent by design — a user who opted out of product analytics has not
# asked to stop sending panics — so they stay two features rather than one. A
# binary with no PostHog code of any kind is
# `--no-default-features --features full-cli`.
= ["full-cli", "crash-report"]
# Optional gate for the network telemetry path. Phase A has no posthog-rs dep
# yet — Task 3 adds it under this feature.
= []
# Model-relay listener (Model Relay Enforcement Layer, plan 01).
# reqwest became optional in Proxy Support I-1 (the hook no longer links it), so
# this feature must activate the dependency itself. `futures-core` supplies the
# `Stream` trait for the zero-buffer response-body guard. `model-relay` relies on
# axum + the rustls TLS backend that only `full-cli` pulls in, so it is added to
# the `full-cli` list below and is NEVER built alone —
# `--no-default-features --features model-relay` intentionally lacks TLS. The lean
# `openlatch-hook` build never enables `full-cli`, so it never compiles a byte of
# the model relay.
# I-2 (the intercepting relay) adds the TLS-server half HERE and nowhere else: the per-install CA
# (rcgen, read back with x509-parser/time), TLS termination inside a CONNECT tunnel (tokio-rustls),
# and hyper's server half for HTTP/1.1 on the decrypted stream. Still never built alone: the
# `ring` provider comes from full-cli's `dep:rustls`.
# Cursor I-2 (plan 01) adds HTTP/2 HERE and nowhere else: hyper's h2 server for a tunnel whose
# host a binding flags h2-capable, and reqwest's h2 client for its forward. Every other reqwest
# client stays HTTP/1.1 — `egress::client_builder` pins it.
= [
"dep:reqwest", "reqwest/stream", "reqwest/http2", "dep:futures-core",
"dep:rcgen", "dep:time", "dep:x509-parser", "dep:tokio-rustls",
"dep:hyper", "hyper/server", "hyper/http2", "dep:hyper-util", "hyper-util/server",
"hyper-util/service", "hyper-util/http2",
# Cursor I-2 plan 03: the Connect decoder's gzip/deflate (per HTTP Content-Encoding and per
# compressed frame) and the hex-encoded `BidiAppend` payload. Both already optional deps of
# `full-cli`, so no new crate; the hook links neither.
"dep:flate2", "dep:hex",
# Cline.app's hub restart reads Cline's session store (`cline_app::process::open_sessions`).
"dep:rusqlite",
# AGT-6 P2 (D-6): the Bedrock `converse-stream` decoder's prelude and message CRC32s
# (`model_relay::bedrock_decode`). Already in the lockfile transitively; the hook links none.
"dep:crc32fast",
# The SHA-1 thumbprint Windows' root-store dialog prints (`ca::windows_thumbprint`), told to
# the user before certutil raises it. 0.10 is the version already in the lock via zbus.
"dep:sha1",
]
# Per-rule policy-bundle validation (Unified Rule Schema, plan 01). Exists to
# control the DEPENDENCY, not to gate the module: `core::policy` is already
# `#[cfg(feature = "full-cli")]`, and `full-cli` turns this on below. Keeping it
# separate is what lets `--no-default-features --bin openlatch-hook` prove, in
# CI, that the hot-path binary links no JSON Schema validator.
# Behind the `policy` feature, which only `full-cli` turns on, so the lean
# `--no-default-features --bin openlatch-hook` build never links it and
# ci/baselines/hook-deps.txt stays unchanged.
#
# `policy` also gates `src/zone_eval/` — the Autonomy Zone evaluator (D8: a
# MODULE of this crate, never a crate of its own) — and `src/generated`, whose
# typify output the evaluator reads its wire types from. Everything the whole
# engine will ever need is activated HERE, in one place, so the five slices that
# fill the module in never have to touch this manifest and collide with each
# other. `sha2`, `serde_json_canonicalizer` and `unicode-normalization` were
# `full-cli`-only and are named here too: `ground_key` / `action_hash` are
# sha256 over RFC 8785 canonical JSON, and the shell classifier normalises NFKC
# before it parses.
= [
"dep:jsonschema",
# `src/generated/types.rs` is gated on `policy` because the evaluator reads its
# wire types from it. `EventEnvelope.time` is a `chrono::DateTime<Utc>`, so the
# generated module does not compile without it. A type, not a clock — the
# purity scanner is what forbids calling `::now`, and it covers src/zone_eval/.
"dep:chrono",
"dep:aho-corasick",
"dep:regex",
"dep:regex-automata",
"dep:url",
"dep:psl",
"dep:tree-sitter",
"dep:tree-sitter-bash",
"dep:sha2",
"dep:serde_json_canonicalizer",
"dep:unicode-normalization",
"dep:caseless",
]
# Rust panic reporting. Scrubbed by src/core/privacy/ before send, and sent
# through core::egress like every other outbound request. Linked only into
# `openlatch` (never `openlatch-hook`), so disabling it drops the whole crash
# payload builder from the graph and keeps ci/baselines/hook-deps.txt meaningful.
= ["dep:backtrace", "dep:findshlibs", "dep:uuid"]
= [
"dep:axum",
"dep:tracing-subscriber",
"dep:tracing-appender",
"dep:indicatif",
"dep:console",
"dep:jsonc-parser",
"dep:open",
"dep:clap",
"dep:toml",
"dep:toml_edit",
"dep:regex",
"dep:thiserror",
"dep:anyhow",
"dep:uuid",
"dep:dirs",
"dep:dashmap",
"dep:chrono",
"dep:subtle",
"dep:getrandom",
"dep:ctrlc",
"dep:secrecy",
"dep:aes-gcm",
"dep:hkdf",
"dep:sha2",
"dep:keyring",
"dep:rpassword",
"dep:terminal_size",
"dep:local-ip-address",
"dep:zip",
"dep:sha2",
"dep:hmac",
"dep:serde_json_canonicalizer",
"dep:base64",
"dep:notify",
"dep:notify-debouncer-full",
"dep:unicode-normalization",
"dep:glob",
"dep:hex",
"dep:self-replace",
"dep:minisign-verify",
"dep:tempfile",
"dep:flate2",
"dep:tar",
"dep:semver",
"dep:arc-swap",
"dep:windows-sys",
# macOS only (declared under `[target.'cfg(target_os = "macos")'.dependencies]`):
# naming it here activates it on macOS alone, as `dep:windows-sys` does on Windows.
"dep:plist",
"policy",
"tokio/full",
"dep:reqwest",
# 0.13 renamed `rustls-tls`. `rustls-no-provider` brings rustls-platform-verifier
# (the OS trust store — Proxy Support D-3) WITHOUT pinning aws-lc-rs as the crypto
# provider; plain `rustls` would drag aws-lc-sys and its C/asm toolchain back in.
# `core::egress::init_crypto()` installs `ring` as the process default instead.
"reqwest/rustls-no-provider",
"reqwest/blocking",
"reqwest/socks",
"dep:rustls",
# The `auth = "auto"` probe parses the proxy's CONNECT response head, and the probe is
# part of the base egress path -- it decides between Basic and none on a build with no
# Negotiate transport at all. So the parser belongs here, not only under
# `proxy-negotiate`.
"dep:httparse",
"model-relay",
]
# Kerberos/Negotiate proxy authentication. NON-DEFAULT and empty until I-1 plan 03
# implements the transport: the GSSAPI/SSPI path is the only place a C dependency could
# enter the graph, so it stays behind a gate that CI builds explicitly rather than by
# default. Declared here now so `cfg!(feature = "proxy-negotiate")` resolves instead of
# warning as an unknown feature -- an undeclared cfg is a constant `false`, which would
# silently make the "this build cannot speak Negotiate" refusal unreachable.
# Every crate the connector names has to be a DIRECT optional dependency: Rust code
# cannot name a transitive one, so "reqwest already pulls hyper in" is not enough.
# `libloading` is declared under `[target.'cfg(unix)'.dependencies]` and `windows-sys`
# under `[target.'cfg(windows)'.dependencies]`, so naming both here activates each only
# on the target that has it -- and `--no-default-features` stays bit-identical, because
# nothing in this list is reachable without `full-cli` (which is what gates
# `core::egress` itself).
= [
"dep:hyper",
"dep:hyper-util",
"dep:http",
"dep:http-body-util",
"dep:httparse",
"dep:tower-service",
"dep:tokio-rustls",
"dep:rustls",
"dep:rustls-platform-verifier",
"dep:rustls-pki-types",
"dep:base64",
"dep:libloading",
"dep:windows-sys",
]
# Test-only override of the baked trusted-key list via OPENLATCH_TRUSTED_KEYS.
# NEVER enabled in release builds — gated this way so a release-build
# signing-bypass via env var is impossible (`#[cfg(any(test, feature =
# "insecure-test-keys"))]` in src/core/update.rs).
= []
[]
# Always present (needed by both binaries)
= { = "1", = ["derive"] }
= "1"
= "0.1"
# Optional since the hook stopped using it (Proxy Support I-1 plan 01): the ONLY
# ungated consumer was openlatch-hook's loopback POST, now on `ureq`. Everything
# else lives behind `full-cli`/`model-relay`, so `dep:reqwest` gates cleanly and the
# hot-path dependency graph loses the whole hyper/tower subtree.
= { = "0.13", = ["json"], = false, = true }
# Process-default rustls CryptoProvider (`core::egress::init_crypto`). reqwest's
# `rustls-no-provider` deliberately installs none, so this crate must: `ring` is
# pure Rust and keeps the C/asm toolchain (aws-lc-sys) out of every CI lane.
= { = "0.23", = false, = ["ring", "std"], = true }
# openlatch-hook's loopback POST to the daemon. Blocking, no TLS, no features:
# the hook makes exactly one plaintext 127.0.0.1 request per invocation.
= { = "3", = false }
= { = "1", = ["rt", "macros"] }
= "1"
# Model-relay listener only (feature = "model-relay", rides on full-cli). Supplies
# the `Stream` trait for the zero-buffer response-body guard that holds the
# in-flight semaphore permit until the upstream stream drains. Tiny, already in
# the tree transitively via reqwest — gating it keeps the openlatch-hook build lean.
= { = "0.3", = true }
# The intercepting relay's CA (I-2). `default-features = false` and `ring` said out loud: rcgen's
# `aws_lc_rs` feature is one default away from pulling aws-lc-sys past the CI ban
# (.github/workflows/pr-checks.yml, "Assert no OpenSSL or libdbus in dep graph").
# `x509-parser` is what gives `Issuer::from_ca_cert_pem` (a persisted CA reloads) — without it the
# "generated once, reused" criterion is unbuildable.
= { = "0.14", = false, = ["ring", "pem", "x509-parser"], = true }
# Both already resolve in Cargo.lock through rcgen (time 0.3.47, x509-parser 0.18.1). Declared so
# `ca.rs` can NAME them: `LocalCa::not_after` returns `time::OffsetDateTime` (INDEX contract), and
# rcgen keeps its own "read not_after back" (`CertificateParams::from_ca_cert_der`) crate-private.
= { = "0.3", = true }
= { = "0.18", = true }
# Full CLI only (gated behind full-cli feature)
= { = "0.8", = true }
= { = "0.3", = ["json", "env-filter"], = true }
= { = "0.2", = true }
= { = "4.6", = ["derive"], = true }
= { = "1.1", = true }
# Format-preserving TOML edits for Codex CLI's `config.toml` (I-3 §1). A customer's
# config.toml is theirs: comments, key order and spacing outside our own provider table
# survive a write-then-remove round trip byte for byte, which `toml`'s
# serialize/deserialize pair cannot do. `full-cli`-gated like `toml`, so the hook binary
# never links it (`ci/baselines/hook-deps.txt`).
= { = "0.25", = true }
= { = "1", = true }
= { = "0.34", = ["cst", "serde", "serde_json"], = true }
= { = "0.18", = true }
= { = "5", = true }
= { = "2", = true }
= { = "1", = true }
= { = "1", = ["v4", "v7"], = true }
= { = "7", = true }
= { = "6", = true }
= { = "0.4", = ["serde"], = true }
= { = "2", = true }
# The browser sign-in's PKCE verifier and `state` (PLT-7 C-1): 32 bytes each from the
# OS CSPRNG. RFC 7636 §7.1 asks for 256 bits, and two UUIDv4s carry 244. Already
# resolved in Cargo.lock through `uuid`, so no new copy; `full-cli` only, so the hook
# never links it.
= { = "0.4", = true }
= { = "3", = true }
= { = "0.10", = true }
= { = "0.11.0", = true }
= { = "0.13.0", = true }
= { = "0.11", = true }
# Only for the thumbprint Windows shows in its CA trust dialog (`model-relay`). An
# identifier the user compares, never an integrity check.
= { = "0.10", = true }
# Linux Secret Service through `async-secret-service` (zbus, pure Rust), never
# `sync-secret-service`: that one links libdbus-1.so.3, which slim and minimal
# images do not ship, and the loader then refuses the whole binary before `main`
# — so the encrypted-file fallback never gets its turn. zbus runs on its default
# async-io executor. NEVER add keyring's `tokio` feature: its blocking calls then
# panic inside our runtime ("Cannot start a runtime from within a runtime"),
# which `test_sync_keychain_read_returns_inside_a_tokio_runtime` traps. The two
# secret-service features cannot be combined. ci/check-linux-binary.py fails a
# release that links libdbus again.
= { = "~3.6", = ["apple-native", "windows-native", "async-secret-service", "crypto-rust"], = true }
# No-echo terminal reads for the proxy-credential prompt (Proxy Support I-2). The one
# thing it must do is stop a corporate password appearing in the operator's scrollback
# and, on a recorded session, in the recording. Everything else about the prompt flow is
# ours: the copy, the retry cap, and the abort path are behind the `Prompter` seam, and
# this crate is only ever reached through its production implementation.
= { = "7.5", = true }
# Terminal width for the CLI header rule. Tiny, zero-dep beyond libc/windows-sys;
# already present transitively via `indicatif → console`, promoting it to a
# direct dep makes the usage explicit without adding weight.
= { = "0.4", = true }
# Terminal primitives for the `init` rail (`src/cli/ui/`): key reads for the two-option
# select, `clear_last_lines` for the active block, ANSI-aware width for the result
# card. Already present transitively via `indicatif → console` with the same
# default features, so promoting it to a direct dep adds no crate — the
# `terminal_size` precedent above. `full-cli` only, like `indicatif`.
= { = "0.16", = true }
# Local (LAN) IP detection via getifaddrs wrapper. Cross-platform (macOS/Linux/Windows), zero-dep beyond libc/winapi.
= { = "0.6", = true }
# `doctor --rescue` ZIP archive writer. Pure-Rust deflate; no C deps.
# Gated to `full-cli` so `openlatch-hook` (<20MB binary, no zip support
# needed) never pulls it in. Verified via `cargo bloat --release --bin
# openlatch-hook --no-default-features` after the dep was added.
= { = "8", = false, = ["deflate"], = true }
# Filesystem watcher for reactive tamper detection. Cross-platform: inotify
# (Linux), kqueue (macOS), ReadDirectoryChangesW (Windows).
= { = "8.0", = true }
# Shell-style glob expansion for the configuration-plane manifest's
# `paths_glob` / `paths_glob_relative` entries. ~25 KB stripped, zero
# runtime deps, MIT/Apache-2.0; no transitive crate footprint to audit.
= { = "0.3", = true }
# Hex encoding for path/content hash extension attributes on outbound
# `ai.openlatch.config.*` CloudEvents. Already pulled in via dev-deps;
# promoting to a direct gated dep makes the usage explicit.
= { = "0.4", = true }
# Debounce wrapper with file-id correlation (FileIdMap on Win/macOS,
# NoCache on Linux). The file-id correlation collapses Vim/JetBrains/VS Code
# atomic-save patterns into one logical event — which both the tamper plane
# (500ms window) and the upcoming config-plane monitor depend on.
= { = "0.7", = true }
# Auto-update Phase 1 spike — see .local/brainstorms/auto-update/PHASE-1-spike.md.
# Both crates are gated behind `full-cli` so `openlatch-hook` (<20MB,
# no update logic) never pulls them in. `cargo bloat --release --bin
# openlatch-hook` must continue to show ZERO update-related symbols.
= { = "1.5", = true }
= { = "0.3", = true }
# Promoted from dev-deps: the spike subcommand stages the new binary in a
# tempdir before swapping; production update flow (P2/P3) needs the same.
= { = "3", = true }
# Auto-update Phase 2 — tarball decode + extract + semver compare. All three
# are gated behind `full-cli` so `openlatch-hook` never pulls them in. The
# flate2 default features include zlib-ng — disable defaults and use
# `rust_backend` to keep the supply chain pure-Rust + reproducible.
= { = "1", = false, = ["rust_backend"], = true }
= { = "0.4", = false, = true }
= { = "1", = true }
# Read-only look at Cline's `sessions.db`: whether its hub is running a session before the daemon
# restarts it (model-relay only; `bundled` so no system SQLite is needed on any OS).
= { = "0.40", = ["bundled"], = true }
# CRC32 for the Bedrock event-stream frames (AGT-6 P2, D-6). MIT OR Apache-2.0, already in the
# lockfile through flate2; direct and optional so only the `model-relay` feature enables it and
# ci/baselines/hook-deps.txt does not change.
= { = "1.5.0", = true }
# Lock-free atomic swap of the resident policy bundle. The verdict path reads
# the rule set on every hook event while the poller replaces it wholesale every
# few minutes — a read-mostly, write-rare pattern that `ArcSwap` serves with a
# wait-free load and no reader-side contention (an `RwLock` would put a shared
# lock on the hot path for no benefit). Its only dependency is `rustversion`,
# a proc-macro already in the tree, so the rustls-only guard (pr-checks.yml)
# and the `openlatch-hook` dependency baseline (ci/baselines/hook-deps.txt)
# are both unaffected — the baseline came back byte-identical when arc-swap
# landed, alongside an unchanged 1141856-byte hook binary. (The regeneration
# command lives in the `Check hook dependency budget` step and has changed
# since; the invariant it proves has not.) Gated behind
# `full-cli` so `openlatch-hook` never links it — the same treatment `zip`,
# `self-replace` and `flate2` get.
= { = "1", = true }
# Per-rule validation of the policy bundle against the embedded rule subschema
# (D-U13/D-U15). The gate lives in schemas/policy-bundle.schema.json and is
# evaluated here rather than reimplemented in Rust, so client and platform can
# never drift. Runs once per bundle swap, never on the hook hot path.
#
# Behind the `policy` feature, which only `full-cli` turns on, so the lean
# `--no-default-features --bin openlatch-hook` build never links it and
# ci/baselines/hook-deps.txt stays unchanged. Version-aligned with the
# [dev-dependencies] entry below, which is NOT a duplicate and MUST stay: the
# integration tests use jsonschema unconditionally, including on the
# `--no-default-features` CI run where this optional dep is unlinked.
= { = "0.58", = false, = true }
# --- Zone evaluator (feature = "policy") -----------------------------------
#
# Every dependency `src/zone_eval/` will need, added once so the slices that fill
# the module in never edit this file. All optional and named only by `policy`, so
# `--no-default-features` stays bit-identical and ci/baselines/hook-deps.txt does
# not move: the hook links none of them.
#
# ONE shared automaton per bundle, not one per atom: every `keyword`/`prefix` leaf
# across every artifact compiles into a single aho-corasick automaton at bundle
# load, and every `regex_lite` leaf into a single regex-automata pattern set. Each
# predicate tree then reads booleans out of that shared result.
= { = "1.1", = true }
# `regex_lite` is the name of an AUTHORED PREDICATE, not a crate. It is implemented
# with `regex`'s underlying engine in BYTES `(?-u)` mode — the `regex-lite`
# crate has neither a bytes API nor pattern sets. The direct dependency exposes
# explicit cache-pool sizing, so evaluator construction never probes the host's
# CPU/filesystem. Keep the features already supplied by `regex`, without adding
# regex-automata's broader defaults or linking it into the hook.
= { = "0.4.18", = false, = ["meta"], = true }
# `url.{host,tld,scheme,boundary}` leaves: `url` parses, `psl` supplies the public
# suffix so `tld_in` means the registrable suffix and not the last dot-separated label.
= { = "2.5", = true }
= { = "2.1", = true }
# The shell effect classifier walks a real bash AST rather than tokenising, which is
# what lets shape 12 (the CVE-2025-54795 quote-escape) be seen as separate commands.
#
# PINNED TO 0.26 ON PURPOSE: tree-sitter 0.27 declares `rust-version = 1.90` and this
# crate declares 1.88. Raising the repo MSRV to satisfy a parser dependency is
# governed by .claude/rules/release-lines.md and affects every consumer of the
# published crate — it is not a silent bump. Revisit only for a bash-grammar fix.
= { = "0.27", = true }
= { = "0.25", = true }
# Tamper-evidence: HMAC-SHA-256 over RFC 8785 JCS-canonicalized hook entries.
= { = "0.13", = true }
# RFC 8785 JSON Canonicalization Scheme — deterministic key ordering for HMAC
# input AND for the configuration-plane content hash. One canonicalizer for
# the whole tree avoids the dual-implementation footgun and the open u64
# non-conformance bug in serde_jcs.
= { = "0.3.2", = true }
# Unicode NFC normalization — applied before JCS canonicalization for the
# configuration plane content hash so visually identical strings produce
# identical hashes regardless of how the editor normalised them.
= { = "0.1.25", = true }
# Full Unicode case folding for the zone evaluator's `keyword` predicate. NOT
# `to_lowercase`, which is a different operation: folding maps `ß` to `ss`, the
# `fi` ligature to `fi` and Greek final sigma to sigma, and the reference oracle
# spells that predicate `k.casefold() in value.casefold()`. Its only dependency
# is `unicode-normalization`, directly above — a leaf on a subtree the classifier
# already carries for its NFKC pass, not a new subtree.
= { = "0.2", = true }
# base64url-urlsafe-nopad encoding for HMAC output in marker + state file.
# Already transitive via keyring; promoting to direct dep makes usage explicit.
= { = "0.23", = true }
# --- Negotiate/Kerberos proxy transport (feature = "proxy-negotiate") ------------
#
# reqwest cannot host this: its connector seam is sealed, so a SPNEGO CONNECT exchange
# has to be its own client. These are the pieces of that client. All optional and named
# only by `proxy-negotiate`, so a build without the feature links none of them --
# which is the point of the gate.
= { = "1", = false, = ["client", "http1"], = true }
= { = "0.1", = false, = ["client-legacy", "tokio", "http1"], = true }
= { = "1", = true }
= { = "0.1", = true }
# Response-head parsing for the CONNECT legs. The same parser hyper uses, so the two
# cannot disagree about what a malformed status line is.
= { = "1", = true }
# The `Service<Uri>` trait hyper-util's legacy client requires of a connector.
= { = "0.3", = true }
# TLS to the proxy (an https:// proxy hop) and TLS to the target inside the tunnel.
# `ring` matches `egress::init_crypto`'s process-default provider; a second provider
# here would be a second crypto supply chain to audit.
= { = "0.26", = false, = ["ring"], = true }
# The OS trust store, the same source reqwest's platform verifier reads. Two trust
# stores in one product is the dominant complaint class against tools in this space.
= { = "0.7", = true }
= { = "1", = true }
# Unix GSSAPI, loaded with dlopen rather than linked: a hard link puts libgssapi in
# DT_NEEDED, which the loader resolves eagerly, so a krb5-less host could not start the
# binary at all -- not even to run `openlatch doctor` and be told why.
# (Declared under [target.'cfg(unix)'.dependencies] below.)
# Crash reporting — panics only, scrubbed by src/core/privacy/ before transport,
# sent through core::egress like every other outbound request.
#
# These are the two crates posthog-rs's own `error-tracking` feature gates. We use
# them directly rather than the crate itself: posthog-rs declares reqwest with the
# plain `rustls` feature OUTSIDE any cargo feature, which pins aws-lc-rs and fails
# the "Assert no OpenSSL in dep graph" gate with no opt-out. That is the same trap
# one dependency bump later, so re-check it before adopting any error-tracking SDK.
#
# Neither carries TLS, which is why dropping the previous crash reporter made the
# dep-graph gate easier to hold rather than harder: its own feature set existed to
# route around exactly that aws-lc pull.
= { = "0.3", = true }
= { = "0.10", = true }
[]
= "0.2"
# See the comment in the main dependency block: GSSAPI is dlopen'd at first use, never
# linked. Unix-only, and gated behind `proxy-negotiate`.
= { = "0.9", = true }
[]
# OpenCode's managed preferences (`/Library/Managed Preferences/[<user>/]ai.opencode.managed.plist`,
# AGT-6 C-4, D-14): MDM delivers binary plists, parsed in-process rather than through a
# `plutil` subprocess on every scan. Optional and listed in `full-cli`, so the
# `--no-default-features` openlatch-hook build never links it and ci/baselines/hook-deps.txt
# is unchanged. MIT. No default features: the `Value` reader is all the config monitor uses.
= { = "1.10", = false, = true }
[]
# `wincon` provides FreeConsole + GetConsoleProcessList and `processenv` provides
# SetStdHandle: together they let the supervised daemon close the console Task
# Scheduler allocated for it at logon, so a console-subsystem binary costs no
# console window on the auto-start path (`release_unattended_console` in
# `src/app/openlatch/main.rs`, which documents why the binary is console-subsystem
# on every platform and must stay that way). `winbase` carries the STD_*_HANDLE
# constants that call needs.
= { = "0.3", = ["processthreadsapi", "winnt", "handleapi", "minwinbase", "wincon", "processenv", "winbase", "consoleapi", "fileapi", "minwindef"] }
# Windows identity resolution (`src/daemon/identity/`): `GetUserNameW` for the
# logged-in OS user and `RegGetValueW` for the HKLM git-install lookup. Optional
# and listed in `full-cli`, so the `--no-default-features` openlatch-hook build
# never links it and ci/baselines/hook-deps.txt stays byte-identical — the
# structural proof that identity capture cost the hook nothing. Version 0.61 is
# already the newest windows-sys in the lockfile (via transitive deps), so this
# adds a feature set, not a crate.
# The two SSPI feature groups (`..._Authentication_Identity`, `..._Credentials`) are
# what `core::egress::negotiate::sspi` needs; they are listed unconditionally rather
# than only under `proxy-negotiate` because cargo features are per-dependency, not
# per-feature, and windows-sys bindings are generated source with no runtime cost.
#
# `Win32_Networking_WinHttp` and `Win32_System_Services` are the proxy discovery
# ladder's (`src/core/egress/discovery/windows.rs`): the WinINet/WinHTTP settings
# readers, the PAC evaluator, and the `WinHttpAutoProxySvc` half of the WPAD gate.
# Both ride the same full-cli-gated optional dependency, so the hook baseline is
# untouched.
#
# `Win32_System_Threading` is the Cline runtime launcher's (`src/app/openlatch_cline_runtime`):
# GetStartupInfoW + CreateProcessW relay Node's CRT fd table (the IPC pipe), which
# std::process::Command cannot pass. Same optional dependency, same untouched hook baseline.
#
# `Win32_UI_WindowsAndMessaging` is the Cline login environment's (`src/core/login_env/windows.rs`):
# SendMessageTimeoutW broadcasts WM_SETTINGCHANGE after an HKCU\Environment write.
#
# `Win32_System_Console` is the CLI launcher shim's (`src/hooks/cli_shim.rs`): SetConsoleCtrlHandler
# lets the child take Ctrl+C while the shim waits for it.
#
# `Win32_System_Diagnostics_ToolHelp` is the OpenCode Desktop service manager's
# (`src/daemon/opencode_desktop_service/process.rs`, AGT-6 P2 D-19): CreateToolhelp32Snapshot
# enumerates processes by executable name to tell whether an OpenCode process is attached.
= { = "0.61", = ["Win32_Foundation", "Win32_System_Console", "Win32_System_Diagnostics_ToolHelp", "Win32_Networking_WinHttp", "Win32_Security", "Win32_Security_Authentication_Identity", "Win32_Security_Credentials", "Win32_System_Registry", "Win32_System_Services", "Win32_System_Threading", "Win32_System_WindowsProgramming", "Win32_UI_WindowsAndMessaging"], = true }
[]
= "2"
= "3"
= "3"
= "1"
# Test-only: `StreamExt::next` for consuming reqwest `bytes_stream()` in the
# model relay zero-buffer streaming assertions. Never linked into a shipped binary.
= "0.3"
= { = "1", = ["full", "test-util"] }
= { = "0.13", = ["json"], = false }
# Dev-only: hermetic TLS fixtures. `rcgen` mints a self-signed leaf and
# `tokio-rustls` serves it, which is what proves `egress::init_crypto()` installed a
# provider AND that the platform verifier is the active trust source (a handshake
# that fails with a *certificate* error, not a provider panic). Never shipped.
= "0.14"
= { = "0.26", = false, = ["ring"] }
# Dev-only: validates openlatch-hook's translator output against the vendored
# upstream agent hook-output schemas in schemas/vendor/. Never linked into any
# shipped binary.
= { = "0.58", = false }
# Dev-only: smoke tests open the rescue archive to verify MANIFEST.json
# presence + ZIP validity. The runtime `zip` dep is feature-gated to
# `full-cli`; tests need it unconditionally.
= { = "8", = false, = ["deflate"] }
# Dev-only: the zone evaluator's bench harness (benches/zone_eval.rs) and the p99
# the `bench-gate` CI job fails on. Default features off drops plotters, rayon and
# the HTML report — `ci/check-bench-gate.py` reads `sample.json`, which criterion
# writes either way, and the dropped graph stack is pure CI compile time.
= { = "0.8", = false, = ["cargo_bench_support"] }
# Dev-only, Unix-only: one pty smoke test proving the production prompter is really wired
# to a terminal (Proxy Support I-2, F-07). Every prompt *decision* is covered on all OSes
# through the injected `Prompter` seam; this covers the wiring the seam cannot. Not
# cross-platform on purpose — driving a Windows ConPTY needs a crate thin enough that the
# test would measure the harness rather than the product.
[]
= "0.7"
[]
# `corpus_digest` in the `openlatch evaluate` handshake is a sha256 over
# `schemas/conformance/`, computed HERE because the subcommand may not read a
# file at runtime (ci/check-engine-purity.py forbids even `include_str!`). Same
# major line as the runtime `sha2` so the digest is the same implementation the
# engine hashes with.
= "0.11"
= "0.8"
# `preserve_order` is pinned ON for the BUILD graph, and it is not a preference.
# `tree-sitter` (a normal dependency under `policy`) carries a build-dependency on
# serde_json with this feature, and cargo unifies features across the build graph —
# so without the pin, build.rs emits schema examples in declaration order when
# `policy` is on and in alphabetical order when it is off. The committed
# src/generated/types.rs would then drift on every feature-varied build and the
# codegen-drift gate would red on whichever configuration ran last. Pinning it makes
# the output the same in both. Doc comments only: no generated type changes shape.
= { = "1", = ["preserve_order"] }
# Pinned to 0.8 because typify 0.7.x still re-exports `schemars::schema::RootSchema`
# from schemars 0.8 and `TypeSpace::add_root_schema` accepts that exact type.
# schemars 1.x removed `RootSchema` and made `mod schema` private. Bump only
# when typify ships a release that targets schemars 1.x.
= "0.8"
= "3"
= "0.3"
[]
# `debug` is deliberately unset (= 0): this profile is what `cargo install`
# and local `cargo build --release` use, and building from source should not
# cost a 4x larger binary (openlatch: 12.9MB stripped vs 49.5MB with debug info).
#
# Tagged releases need the opposite — a panic cannot be symbolicated without
# debug info — so .github/workflows/publish.yml overrides `debug` / `strip` /
# `split-debuginfo` per platform via CARGO_PROFILE_RELEASE_* env vars, uploads
# the symbols, then strips the artifact. Cargo has no per-platform profile keys,
# which is why those cannot live here; see the table in that workflow's `env:`.
#
# Do NOT add `split-debuginfo = "packed"` back: on Linux it emits a .dwp with an
# all-zero debug id, which matches no uploaded symbol set and so silently never
# symbolicates. Nothing checks for this any more — the two symbol-verification
# steps were removed with the PostHog migration, by decision — so this comment is
# the only guard left. Read it before touching the line below.
= true
= "z"
= true
= 1
# Benches must measure what ships in the DAEMON, not what ships in the hook.
# `cargo bench` inherits `[profile.release]` unless this table exists, and that
# profile is tuned for `cargo install`: `opt-level = "z"` plus full LTO buys a
# small binary and costs both a slow, unrepresentative p99 and a very long CI
# compile. `lto = "thin"` keeps cross-crate inlining without the fat-LTO link.
[]
= 3
= "thin"