The npm sibling: same architecture, same API-awareness discipline, same silence - with the update stage driven by ncu.
hook-dsh-governor-cargo
Hook @ DSH @ Governor @ Cargo • _The DeepSeek Harness Plugin Family for PlayForm._ The Cargo.toml flavor of the silent package.json governor - the same architecture, the same API-awareness discipline, the same silence toward the author, but for RUST manifests, with the update stage driven by the RUST-SIDE CLI ( cargo upgrade, from cargo-edit): Rust does not co-opt into the TypeScript ecosystem, so there is no npm-library equivalent - the update stage operates at the exclusionary level through the cargo CLI. Chain canonicalization + full-version normalization + surgical TOML rewriting + cargo upgrade --exclude - comments survive. Every claim below is live-verified (2026-10-03, live).
$ pnpm add @playform/hook-dsh-governor-cargo The profile wiring for this plugin - the bundles list, the patch entry and the restart - is on the setup page.
Where It Fits
Family position (the @-sentence Hook @ DSH @ Governor @ Cargo): a hook child of the plugin-dsh-factory service and the hook-dsh-core helpers; the Rust-sided sibling of the two npm governance hooks on the same fs/observed seam. One of three governance hooks, each gating on its own basename and writing its own ledger: governor-package (package.json → governor.log), pinner-package (package.json → pinner.log), and this bundle (Cargo.toml → cargo-governor.log, chain pass bare caret /=exact + cargo upgrade).
The basenames are disjoint: the cargo module never sees a package.json event and the npm plugins never see a Cargo.toml event. Composition semantics: chain > strip > normalize (this module's precedence), keep-list wins over normalization - never over the chain. Machinery-wise it is a factory flavor: gates, discovery, the guarded write, the refresh, the continuation, the effects, the schema and the namespaced UpdateKey come from plugin-dsh-factory; the policy loader and the suppression composer come from hook-dsh-core. This bundle keeps the Cargo-specific residue: the TOML identification, the surgical pin, the full-version directive, and the cargo upgrade bridge. Besides the fs/observed event path, its two steps are registered with the factory's direct-govern registry at apply (Factory.RegisterGovern("Cargo.toml", "cargo" | "update", ...)), so the raw-write tool's per-call govern selection can drive the same chain + normalization pass and update stage directly through Factory.Govern.
In the DeepSeek Harness
| Seam | What the plugin does there | What you can observe |
|---|---|---|
| fs/observed - the file observation event | The same seam the two npm governance hooks use, gated on the Cargo.toml basename - the basenames are disjoint, so the cargo module never sees a package.json event and the npm plugins never see a Cargo.toml event. | One trigger law covers the whole machine, npm and Rust alike. |
| ctx.fs - the filesystem service (dsh-fs) | The surgical TOML rewrite goes through the factory's guarded write (replaceIfVersion + the P4 sandbox fence); smol-toml is used for identification only, never a stringify, so comments survive byte-for-byte. | Governed manifests that still read exactly like the author's file. |
| ctx.subprocess (dsh-subprocess) - the Rust side | The update stage crosses the Rust/TS boundary through the subprocess seam: cargo upgrade driven with a fully-specified argv, collect-mode output piped to the job ring and the ledger. | The only update tool Rust has, driven without shell PATH drift. |
| ctx.jobs - the jobs facility | The update stage runs in the jobs envelope (kind governor-update, unowned) when a controller serves the context, with the detached contained continuation as the probed fallback. | cargo upgrade never blocks or aborts the author's session. |
| The ledger / session | A separate ledger (cargo-governor.log) records activation, exclusions, chain-pass results, refusals and every update dispatch - including the exact cargo upgrade argv and its exit code. | The Rust-side pass is fully auditable from the ledger alone. |
The Problem
Rust manifests drift the same way npm manifests do - but Cargo.toml is a TOML document people decorate with comments, and there is no in-process library path: the only update tool is the cargo CLI. A governor for Cargo.toml must therefore rewrite surgically (never a TOML stringify) and delegate its update stage across the Rust/TS boundary - while still hooking the same fs/observed event the npm hooks use, so one trigger law covers the whole machine.
How It Works
The same G1/G2 gates and Stash seeding as the npm governor; the G3 chain pass parses with smol-toml (IDENTIFICATION ONLY) and plans: 1. CHAIN - dep in registry.effectiveLatest → the bare resolved version (Cargo's implicit caret) or =resolved with pinStyle "exact", always the FULL form (shorthand padded); 2. STRIP - strict-mode unknown deps; 3. NORMALIZE - every other simple version (full-version directive below; the keep-list wins → byte-identical). The rewrite is a surgical line-level edit on the raw text - comments, inline comments, blank lines and spacing survive byte-for-byte - then GuardedWrite → ledger governed <path> → <version> → Refresh. The G4 update stage crosses the Rust/TS boundary:
The full-version normalization directive. No shorthand like "1.0" may remain in a governed Cargo.toml; every dependency version is expanded to its MOST SPECIFIC form:
| Written | Governed |
|---|---|
| 1.0 | 1.0.0 |
| 1 | 1.0.0 |
| 0.1 | 0.1.0 |
| =1.0 | =1.0.0 (the exact form keeps its = prefix) |
| 1.2-rc.1 | 1.2.0-rc.1 (padded BEFORE the prerelease) |
| 1.2.3-rc.1 | unchanged (prerelease preserved as authored) |
| 1.2.3+build | unchanged (build metadata preserved) |
| >=1.2, ~0.3, >1.0, 1.0, 2.0 | unchanged (complex ranges left as-is) |
The TOML rewrite: why comments survive. smol-toml (the ONE runtime dependency, carried by the bundle's own node_modules) is used for PARSING ONLY - identification of the dep entries in [dependencies], [dev-dependencies], [build-dependencies], [target.'cfg(...)'.dependencies] (and its dev/build variants), and [workspace.dependencies] (handled natively). Handled entry shapes: name = "0.3.4", name = { version = "0.3.4", ... } (single line or pretty-printed), [dependencies.name] table style. Never rewritten: path = "...", git = "...", and inherited entries (workspace = true). Non-dependency sections are NEVER touched; the refusal guard refuses any plan entry outside an identified dependency table, and Pin aborts (never a partial rewrite) if a planned entry cannot be located in the text.
The Rust side (live-verified). cargo 1.100.0-nightly + cargo-edit 0.13.13, resolved through cargoBin. --exclude is ONE flag per crate - the comma form is silently IGNORED, the exact inverse of ncu's one-comma-argument reject list. There is NO --exact flag in cargo-edit 0.13.13 - exact pins are enforced at the manifest level by the chain pass (pinStyle: "exact" → =resolved). cargo upgrade preserves comments and formatting (it edits via toml_edit). If cargo-edit is NOT installed, cargo upgrade exits 101 with no such command: upgrade - the module logs update: cargo-edit unavailable: ... and fails gracefully.
The Config
The exported Config schema validates and fills every default at load; new patch entries are declared with insert:. Full example:
Update-stage policy mapping (update-policy.json, same discovery as the npm governor): reject → --exclude <crate> (ONE flag per crate); allow → -p <crate> (repeatable); incompatible/pinned/compatible → --incompatible/--pinned/--compatible; pinStyle has NO CLI equivalent (manifest-level via the chain pass); verifyCommand runs after the upgrade via the subprocess seam, exit 127 non-fatal. The built-in default mirrors cargo upgrade's own semantics: no rejects, incompatible: "ignore", pinned: "ignore", pinStyle: "caret", no verifyCommand (never install implicitly).
In Action
One governed write. The author saves a Cargo.toml with comments, a shorthand version, and a chain-governed dep:
Before - the Cargo.toml as the author wrote it
With the nearest registry.json resolving tokio to 0.4.5, the surgical rewrite produces (same text, same comments, same inline spacing):
After - the same file, surgically rewritten
The transcript shows only what the author wrote; the ledger shows the pass:
The ledger lines for the same pass
Here the policy's chain deps plus rejects became one --exclude flag per crate; serde and tokio are protected from the CLI bump - tokio by the chain, serde by the normalization pass already writing its full form. Read the file back to see the governed state; write it again immediately and nothing complains - no FS_STALE_VERSION. Write inside node_modules/.git/.dsh and the ledger gains skipped (excluded) ... while the file stays untouched. Remove cargo-edit and the pass fails gracefully: update: cargo-edit unavailable: ..., the manifest untouched, the breaker counting the failure.
The Ledger
One global log (logFile, default ~/.dsh/hook-dsh-governor-cargo.log - SEPARATE from the npm ledgers) records activation, exclusions, chain-pass results, refusals, and every update-stage dispatch. The strings this module composes (the hook-dsh-governor-cargo: logger prefix is the factory's Append; each line is [<ISO>] <message> in the file):
The registry-miss line ends with an em dash followed by chain pass skipped; the DONE line is update: DONE + em dash + reason - byte-exact forms are in the In Action excerpt above. Those em dashes are part of the literal ledger strings, so they live only in the example block. The activation line is written by apply() - "did it activate" must be answerable from the ledger alone.
Related plugins
The keep-list sidecar (pin-policy.json) is shared: discovery global keepFile → the file's own directory → registry-adjacent, the union.
Gates, discovery, the guarded write, the refresh, the continuation, the effects, the schema and the namespaced UpdateKey come from the furnace.
License: MIT. Full contract: SCHEME.md. TypeScript-first, built with @playform build (ESBuild + tsc type-check); the published artifact contains only the built output.