HOVEL // chapter 22
Chapter 22 Part 04 / Module Development

Chain Key-Value Handoffs

Hovel provides a throw-owned string-key/string-value store for passing small findings between ordered modules in one chain execution. Every throw starts with an empty store. A survey can stage writes during execution; Hovel commits the complete mutation batch only when that run succeeds. A later exploit reads the resulting chain snapshot or resolves an input from explicit configuration first and the store second. Existing modules remain compatible because the wire fields, SDK interfaces, and schema contract are additive and optional.

Scope and lifetime

PropertyContract
ScopeOne throw, identified within its operation and chain. No other throw can see the live values.
LifetimeMutable in daemon memory only while the throw is active. Hovel seals the final snapshot as chain-kv.json when the throw ends, removes the live store, and never seeds a later throw from it.
TypesUTF-8 string keys and string values.
AtomicityAll mutations from a successful module run commit together against its input revision. Failed runs commit none.
VisibilityLive RPC calls require the throw ID. After sealing, values are available only through the read-only throw artifact.

Declare the handoff

A module may advertise chainKV.produces and chainKV.requires in its schema. Each binding names a key and may name the configuration key that overrides it. Use {target} in a key template for per-target state; Hovel replaces it with the percent-encoded target string.

{
  "chainKV": {
    "produces": [
      {"key": "survey/{target}/port", "description": "Discovered service port"}
    ],
    "requires": [
      {"key": "survey/{target}/port", "configKey": "target.port", "required": true}
    ]
  }
}

Validation accepts a required binding when explicit target or chain configuration is present or when an earlier module declares that output. A declared earlier producer creates a dynamic binding: execution still blocks the consumer unless that producer actually commits the key during the current throw. Because every throw starts empty, stale findings cannot masquerade as fresh survey output. Undeclared writes are allowed for discovery and forward compatibility, but they do not satisfy declared ordering during planning.

Resolve configuration predictably

SDK helpers resolve a value in this order: invocation inputs, target configuration, chain configuration, chain KV, then the supplied default. This keeps operator intent authoritative while allowing a survey to fill values the operator did not provide.

resolution := ctx.ResolveInput(
    "target.port",
    "survey/{target}/port",
    "445",
)

if err := ctx.ChainKV().Set("survey/{target}/port", "445"); err != nil {
    return hovel.Result{}, err
}
port, source, found = ctx.resolve_input(
    "target.port",
    "survey/{target}/port",
    "445",
)
ctx.chain_kv.set("survey/{target}/port", "445")
let resolution = ctx
    .resolve_input("target.port", "survey/{target}/port")
    .unwrap_or(("445".into(), "default"));
ctx.chain_kv.set("survey/{target}/port", "445")?;

Operate and inspect the store

BeginChainKV(throwId, operation, chain)
GetChainKV(throwId, key)
ListChainKV(throwId, prefix, includeValues)
SetChainKV(throwId, key, value, expectedRevision?)
DeleteChainKV(throwId, key, expectedRevision?)
ApplyChainKV(throwId, expectedRevision, mutations)
SealChainKV(throwId) → chain-kv.json artifact

Listing returns keys only by default; add --include-values when disclosure is intentional. Expected revisions provide optimistic concurrency and reject a stale writer. Begin and seal are coordinator operations; modules normally use only the SDK context supplied for their current run. A sealed store cannot be reopened or mutated.

Limits and artifact shape

Keys are limited to 256 bytes, values to 64 KiB, one mutation batch to 256 operations, and the complete live store to 8 MiB. Keys and values must be valid UTF-8. Use a normal artifact for larger or binary content.

{
  "schemaVersion": 1,
  "throwId": "throw-uuid",
  "operation": "demo",
  "chain": "survey-then-exploit",
  "revision": 1,
  "entries": {
    "survey/mock%3A%2F%2Frouter-01/port": "8443"
  }
}

Hovel records this snapshot as chain-kv.json with media type application/vnd.hovel.chain-kv+json. It is evidence of that throw, not input to a later throw.

Planning and safety

A throw plan records the resolved binding source for each target. Its KV revision is initially zero because plans never consult artifacts from earlier throws. These fields participate in the plan hash, so changing the plan invalidates its prior confirmation. Plans warn when execution depends on dynamic survey output, but use the normal review and confirmation flow; KV support never bypasses dangerous-module policy, peer approval, or the persisted plan requirement.

Keep values small and purpose-specific. Use artifacts for files and large evidence, sessions for interactive access, and typed capabilities for richer provider contracts. The KV store is intentionally a coordination primitive, not a secret manager or an unbounded document database.