The family's furnace: the gates, the discovery, the guarded write, the refresh, the continuation, the effects and the schema come from it.
hook-dsh-governor-package
Hook @ DSH @ Governor @ Package • _The DeepSeek Harness Plugin Family for PlayForm._ The silent package.json governor of the DeepSeek Harness - a plugin on the fs/observed event (the cordis event every harness file write dispatches; the tool layer is the only dispatcher) that governs every package.json written by any agent, anywhere: a pure chain pass canonicalizes chain-governed dependency pins, then an update stage lets npm-check-updates bump the public ones - and the author never learns. A factory flavor: the family's furnace does the plumbing; this package is the model logic.
$ pnpm add @playform/hook-dsh-governor-package 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 @ Package): a hook child of the plugin-dsh-factory service and the hook-dsh-core helpers; it has no hook children of its own. One of three governance hooks sharing the fs/observed seam, each gating on its own basename and writing its own ledger:
| Plugin | Basename gate | Ledger | Pass |
|---|---|---|---|
| hook-dsh-governor-package (this bundle) | package.json | governor.log | chain pass (→ ^resolved) + update stage (ncu) |
| hook-dsh-pinner-package | package.json | pinner.log | pin pass (^0.3.4 → 0.3.4; keep-list wins) |
| hook-dsh-governor-cargo | Cargo.toml | cargo-governor.log | chain pass (bare caret / =exact) + cargo upgrade |
Composition semantics when several are activated: pin → bump-exact (a fully pinned manifest leaves ncu nothing to do; the chain pass may still re-canonicalize chain pins), chain > strip > normalize (the cargo module's precedence), keep-list wins (the pinner's pin-policy.json keeps ranges as authored). The ledgers are separate; activating one never implies another. Machinery-wise it is a factory flavor: inject: ["fs", "pluginFactory"] - the gates, the discovery, the guarded write, the refresh, the continuation, the effects and the schema come from plugin-dsh-factory; the pure helpers (Suppress, the policy loader) come from hook-dsh-core. This bundle keeps only its own vocabulary: the Config extension, the chain pass, the update engine and every ledger string. Besides the fs/observed event path, its two steps are registered with the factory's direct-govern registry at apply (Factory.RegisterGovern("package.json", "canonicalize" | "update", ...)), so the raw-write tool's per-call govern selection can drive the same chain 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 governor's one listener hook: fs/observed is the cordis event every harness file write dispatches, fired by the tool layer only - so it runs after content is on disk, on every write path (full writes, single-line edits, str_replace_editor patches alike). | The fix happens where the write happens, without the author's tool result changing by a single byte. |
| ctx.fs - the filesystem service (dsh-fs) | All rewrites go through the ctx.fs service (which dispatches no fs/* events - only the tool layer does), carrying replaceIfVersion at the observed version and an explicit sandbox policy because the plugin's own exclude list IS its fence. | No recursion, no clobber; a racing author write fails safely. |
| ctx.jobs - the jobs facility | The update stage runs inside the jobs envelope (kind governor-update, unowned) when a controller serves the context - the factory's Attach registers it from the root - with the detached contained continuation as the probed fallback. | ncu runs off the author's critical path and never blocks the session. |
| ctx.subprocess (dsh-subprocess, bin mode) | In updateMode bin, the ncu binary is driven through the subprocess seam with a fully-specified argv (ncuBin is absolute - host PATH != shell PATH). | The external-tool path without shell PATH drift. |
| The ledger / session | One global log records activation, exclusions, chain-pass results and every update-stage dispatch; the activation line is written by apply() so "did it activate" is answerable from the ledger alone. | The governed state is discoverable only by a subsequent read - or in the ledger. |
The Problem
Agents edit package.json files constantly - and every edit can leave stale pins, stray version ranges, or deps that should track a governed registry. The fix must happen where the write happens, on every write path (full writes, single-line edits, str_replace_editor patches alike), without the author's tool result changing by a single byte. fs/observed is the only hook that runs after content is on disk - the fs/write-intent waterfall carries a version guard but never the content.
How It Works
Key properties, all enforced by construction. Anywhere mode, exclusion-first - no roots allowlist: any agent (main, subagent, workflow child) writing a package.json via the harness fs tools is governed, unless the path contains an excluded segment. No recursion - the governor's rewrites go through the ctx.fs service (which dispatches no fs/* events - only the tool layer does) and the update stage is a continuation, not an author tool call. Version coherence - the rewrite carries replaceIfVersion at the observed version; a racing author write makes it fail safely (no clobber). The cordis-trap guard - ncu's reject list is always policy.reject ∪ the chain-governed dep names; ncu must never bump a chain pin to public npm latest. Silence - the model-facing write result is built from the author's own content; a listener throw is contained logger-only (the core's Suppress composer); the ledger is best-effort.
The G4 update-stage envelope - the Dispatch/Settle pair of the diagram above (the gates, the jobs envelope, the breaker update, the U2 refresh) - lives in the core (@playform/hook-dsh-core); this module is a thin delegate that injects its own collections, child stage and ledger strings, so the flow is not forked per governance module.
The Config
The exported Config schema (the factory's Schema helper extended with the module's own fields) validates and fills every default at load - invalid configuration fails loudly. New patch entries are declared with insert:. Full example:
| Field | Type | Default | Meaning |
|---|---|---|---|
| log | boolean | true | write the durable ledger file |
| logFile | string | ~/.dsh/hook-dsh-governor-package.log | the governor's global ledger |
| updateCooldownMs | number | 3000 | cooldown between update-stage dispatches per directory |
| strict | boolean | false | strip unknown deps - explicit only; never default-delete unknown deps |
| mutationTools | string[] | [write, edit, str_replace_editor] | the actor tools whose writes count as triggers |
| maxUpdateFailures | number | 3 | circuit breaker: pause a dir's update stage |
| ncuBin | string | /usr/local/bin/ncu | absolute - host PATH != shell PATH |
| updateMode | "programmatic" | "bin" | programmatic | ncu as a library (no external binary) or via the ncu binary |
| policyFile | string | optional global update-policy.json (else discovery, else built-in) | |
| exclude | string[] | [node_modules, .git, .dsh, .pnpm, .store, DeepSeek Harness.app] | the exclusion segments (the core's Default) |
In Action
One governed write. The author writes a package.json anywhere with the write tool - one dep that should track the governed registry, one public dep left on a range:
Before - the manifest as the author wrote it
The chain pass finds @acme/chain-core in the nearest registry.json (effective 0.4.5) and canonicalizes it to ^0.4.5; the public dep stays a range until the update stage's ncu run bumps it. The transcript shows only what the author wrote; the ledger shows what actually happened:
After - the ledger line for the same pass
Read the file back and @acme/chain-core is ^0.4.5 (or already bumped by ncu if the policy allowed it); write it again immediately and nothing complains - no FS_STALE_VERSION, the re-emit made the second pass a no-op. Write inside node_modules/.git/ .dsh and the ledger gains one line, skipped (excluded) ... , while the file stays untouched.
The Ledger
One global log (logFile, default ~/.dsh/hook-dsh-governor-package.log) records activation, exclusions, chain-pass results, and every update-stage dispatch. The strings this module composes (the hook-dsh-governor-package: 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/FAILED lines are update: DONE + em dash + reason and update: FAILED + 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 sibling pin pass on the same seam: pins ^0.3.4 → 0.3.4; a fully pinned manifest leaves this module's update stage nothing to do.
The Rust-sided sibling: same architecture for Cargo.toml, with the update stage driven by cargo upgrade.
License: MIT. Full method contract: SCHEME.md. TypeScript-first, built with @playform build (ESBuild + tsc type-check); the published artifact contains only the built output.