Appearance
SetHook Operations: Field Matrix
Every element of a SetHook transaction's sfHooks array is one sfHook object, and every sfHook object is interpreted as exactly one of six operations — hsoCREATE, hsoINSTALL, hsoUPDATE, hsoDELETE, hsoNSDELETE, or hsoNOOP — inferred purely from which of the object's 12 possible fields are present. This page is the authoritative per-operation map of which fields are required, optional, or forbidden.
How the operation is inferred
The operation is inferred roughly as follows:
In order: both sfHookHash and sfCreateCode present → hsoINVALID. Only sfHookHash → hsoINSTALL. Only sfCreateCode → hsoCREATE if non-empty, hsoDELETE if empty. Neither present, and every other field also absent → hsoNOOP. Otherwise: hsoNSDELETE if sfHookNamespace is present and the hsfNSDELETE bit is set in sfFlags, else hsoUPDATE.
The subtle NOOP condition. The sfHookOn term inside the NOOP check is !(hasOn || (hasOutgoing && hasIncoming)) — note this is not symmetric between the plain field and the directional pair. sfHookOn alone disqualifies NOOP the instant it's present. But sfHookOnOutgoing and sfHookOnIncoming only disqualify NOOP when both are present together — if an sfHook object carries sfHookOnOutgoing (or sfHookOnIncoming) alone, with every other field absent, the object still infers as hsoNOOP and that lone directional field is silently ignored (the apply loop's NOOP case simply copies the old hook through unchanged, never reading the field at all). This is easy to trigger by accident: submitting {HookOnOutgoing: <value>} with nothing else in the object does not update anything.
sfHooks-array-level checks that apply before any per-object inference gate the whole transaction regardless of what individual objects infer to: featureHooks must be enabled (temDISABLED otherwise); the transaction's own top-level sfFlags must be clean of non-universal bits when fixInvalidTxFlags is enabled (temINVALID_FLAG); sfHooks must be present, non-empty, and at most hook::maxHookChainLength() = 10 entries (temMALFORMED); every element must be an sfHook object; a present sfCreateCode may not exceed hook::maxHookWasmSize() = 65,535 bytes regardless of operation; every non-blank sfHook object may only contain the 12 fields listed below — anything else is temMALFORMED; and at least one sfHook object in the array must be non-blank (HOOKS_ARRAY_BLANK).
The matrix
| Field | CREATE | INSTALL | UPDATE | DELETE | NSDELETE | NOOP |
|---|---|---|---|---|---|---|
sfHookHash | F [^hash-create] | R | F [^hash-update] | F | F | F |
sfCreateCode | R (non-empty) | F [^code-other] | F [^code-other] | R (empty) [^code-delete] | F | F |
sfHookGrants | O | O | O | F | F | F |
sfHookNamespace | R | O | O [^ns-update] | F | R | F |
sfHookParameters | O | O | O | F | F | F |
sfHookOn | R/O [^onv2] | O | O | F | F | F |
sfHookOnIncoming | R/O [^onv2] | O [^onv2-gate] | O [^onv2-gate] | F | F | F |
sfHookOnOutgoing | R/O [^onv2] | O [^onv2-gate] | O [^onv2-gate] | F | F | F |
sfHookCanEmit | O [^canemit] | O [^canemit] | O [^canemit] | F | F | F |
sfHookApiVersion | R (must be 0) | F | F | F | F | F |
sfHookName | O [^name] | O [^name] | O [^name] | F | F | F |
sfFlags | O [^flags-create] | O [^flags-install] | O [^flags-update] | R [^flags-delete] | R [^flags-nsdelete] | F |
R = required, O = optional (subject to the cited validation), F = forbidden (presence is rejected, or — where noted — reclassifies the object as a different operation before the forbidding check would even run). NOOP forbids all 12 fields by construction: any field present other than a directional HookOn field presented alone (see "subtle NOOP condition" above) makes the object something other than NOOP.
[^hash-create]: Presence together with non-empty sfCreateCode infers hsoINVALID, not hsoCREATE — see inferOperation above. [^hash-update]: sfHookHash presence always infers hsoINSTALL (or hsoINVALID with sfCreateCode) — an object with sfHookHash is never classified hsoUPDATE. [^code-other]: Presence (even as an empty blob) reclassifies the object as hsoCREATE/hsoDELETE, never hsoINSTALL/hsoUPDATE. [^code-delete]: Must be present as an empty blob; a non-empty blob infers hsoCREATE instead. [^ns-update]: If both sfHookNamespace and hsfNSDELETE (in sfFlags) are present, the object infers hsoNSDELETE, not hsoUPDATE — see HookNamespace. [^onv2]: Before featureHookOnV2_1: sfHookOn alone is required. With featureHookOnV2_1: either sfHookOn alone, or both sfHookOnIncoming and sfHookOnOutgoing together (and they must differ) — never a mix of sfHookOn with either directional field. See HookOnIncoming / HookOnOutgoing. [^onv2-gate]: Directional HookOn fields are only valid when featureHookOnV2_1 is enabled and both sfHookOnIncoming and sfHookOnOutgoing are supplied together with different values. See HookOnIncoming / HookOnOutgoing. [^canemit]: Gated globally: any non-blank sfHook object carrying sfHookCanEmit without featureHookCanEmit enabled is temDISABLED, independent of which operation it is. See HookCanEmit. [^name]: Gated globally: any non-blank sfHook object carrying sfHookName without featureNamedHooks enabled is temDISABLED, and the value must pass SetHook::validateHookName (4–16 bytes or empty, valid UTF-8). See HookName. [^flags-create]: hsfOVERRIDE is required only if a hook already occupies the target chain slot (apply-time tecREQUIRES_FLAG otherwise); no other bit is restricted by validateHookSetEntry. [^flags-install]: Same hsfOVERRIDE rule as create; no other bit restricted. [^flags-update]: hsfOVERRIDE is forbidden outright; hsfNSDELETE is accepted by inferOperation/validation only in combination with sfHookNamespace — but that exact combination is always classified hsoNSDELETE, not hsoUPDATE (see [^ns-update]), so a validated hsoUPDATE can never actually carry hsfNSDELETE. See Flags. [^flags-delete]: Required; must include hsfOVERRIDE; allowed bits are limited to hsfOVERRIDE | hsfNSDELETE | hsfCOLLECT. [^flags-nsdelete]: Required; must equal exactly hsfNSDELETE, no other bit.
Per-operation detail
hsoCREATE
sfCreateCodenon-empty, ≤ 65,535 bytes; guard-validated and WasmEdge-smoke-tested (see CreateCode).sfHookNamespacerequired (NAMESPACE_MISSINGotherwise).sfHookApiVersionrequired, must equal0(API_MISSING/API_INVALID).- Exactly one of
sfHookOnalone, orsfHookOnIncoming+sfHookOnOutgoingtogether (distinct values, requiresfeatureHookOnV2_1); before the amendment,sfHookOnalone is mandatory. sfHookGrants/sfHookParameters, if present, validated byvalidateHookGrants/validateHookParamsand become the new definition's default set — not merged against anything, since there is no prior definition.sfHookName, if present, validated byvalidateHookName; requiresfeatureNamedHooks.sfHookCanEmit, if present, requiresfeatureHookCanEmit; no additional shape validation.sfFlags:hsfOVERRIDErequired only to replace an already-installed hash at the same chain slot.- If the WASM's hash already matches an existing
ltHOOK_DEFINITION, the whole operation[[fallthrough]]s intohsoINSTALL's apply logic — see HookHash.
hsoINSTALL
sfHookHashrequired;preclaimrequires the referencedltHOOK_DEFINITIONto already exist (terNO_HOOK).sfHookApiVersionforbidden (API_ILLEGAL).sfHookGrants/sfHookParameters, if present, validated the same way as create; parameters go through the three-way merge described in HookParameters (starting from an empty prior set).sfHookNamespace,sfHookOn/sfHookOnIncoming/sfHookOnOutgoing,sfHookCanEmit,sfHookNameare accepted. WhenfeatureHookOnV2_1is enabled, suppliedHookOnfields must pass the same pair, mutual-exclusion, and unequal-value checks as create; at apply time each is stored on the entry only if it differs from the target definition's resolved value (the same storage-optimization pattern ashsoUPDATE).sfFlags:hsfOVERRIDErequired only if a hook already occupies the target chain slot (tecREQUIRES_FLAGotherwise).
hsoUPDATE
- Inferred whenever none of
sfHookHash/sfCreateCodeare present, at least one other field is present, and it isn't the specific(sfHookNamespace present, hsfNSDELETE set)pair that infershsoNSDELETEinstead. sfHookApiVersionforbidden (API_ILLEGAL).hsfOVERRIDEforbidden unconditionally;hsfNSDELETEis only accepted by validation together withsfHookNamespace, but that combination is never actually classifiedhsoUPDATE(see [^flags-update] above) — so in effecthsoUPDATEcan carry neither control bit.sfHookGrants/sfHookParameters: absent → prior value carried over unchanged; explicit empty array → cleared; non-empty → replaces (grants) or three-way-merges (parameters, see HookParameters).sfHookNamespace,sfHookOn/sfHookOnIncoming/sfHookOnOutgoing,sfHookCanEmit,sfHookName: all optional, each stored on the entry only if it differs from the resolved definition value, else removed from the entry to fall back to the default. This has been confirmed by individually setting each field, then individually resetting each back to its definition default.sfFlags, if present, is stored verbatim (raw, not thehsfOVERRIDE/hsfNSDELETE-stripped value used by create/install — see Flags for why this has no observable effect here).
hsoDELETE
- Only
sfCreateCode(empty) andsfFlagsmay be present; every other field — grants, parameters,HookOn/HookOnIncoming/HookOnOutgoing,HookCanEmit,HookApiVersion,HookNamespace,HookName— is forbidden (DELETE_FIELD). This has been verified exhaustively. sfFlagsrequired, must includehsfOVERRIDE(OVERRIDE_MISSINGotherwise); allowed bits limited tohsfOVERRIDE | hsfNSDELETE | hsfCOLLECT(FLAGS_INVALIDotherwise).- At apply time: places a blank
sfHookat that chain position; if a hook previously occupied the slot, itsltHOOK_DEFINITIONreference count is decremented (and the definition erased if it reaches zero) — see HookHash. - If
hsfNSDELETEis also set, the prior occupant's namespace is queued for destruction as a side effect — see HookNamespace.
hsoNSDELETE
- Only
sfHookNamespaceandsfFlagsmay be present, and both are required; every other field is forbidden (NSDELETE_FIELD). This has been verified exhaustively. sfFlagsmust equal exactlyhsfNSDELETE— no other bit, includinghsfOVERRIDEorhsfCOLLECT(NSDELETE_FLAGS).- Does not touch the hook chain itself — the
sfHookarray slot this entry occupies is otherwise treated like NOOP for chain purposes (the prior hook at that position, if any, is carried through unchanged); only the named namespace's Hook State is affected. See HookNamespace for the full deletion mechanics, including thefixNSDelete-gated partial-delete (tesPARTIAL) and owner-reserve-refund behavior.
hsoNOOP
- All 12 fields absent (subject to the directional-
HookOnquirk described above). A wholly blanksfHookobject (getCount() == 0) is skipped even earlier, in the preflight per-array-element loop, and doesn't reachinferOperationat all. - At apply time, the chain slot is left exactly as it was (existing hook copied through unchanged, or a blank placeholder if none existed).
Amendment gates
featureHooksgates the entire transaction type.featureHookOnV2_1governs the directionalHookOnform: use plainsfHookOn, or use thesfHookOnIncoming/sfHookOnOutgoingpair with different values. See HookOnIncoming / HookOnOutgoing.featureHookCanEmitandfeatureNamedHookseach gate their field globally, on every non-blanksfHookobject regardless of inferred operation.
Related documents
- README — full field-location table and index.
- HookHash, CreateCode — the two fields that drive
inferOperation's primary branch. - HookGrants, HookParameters — the two array fields, both optional on CREATE/INSTALL/UPDATE, forbidden on DELETE/NSDELETE.
- HookNamespace — the field with opposite required/forbidden rules between DELETE and NSDELETE.
- HookApiVersion — legal in exactly one operation.
- Flags — full
hsf*bit reference. - HookOn, HookOnIncoming / HookOnOutgoing, HookCanEmit, HookName — the four fields documented before this page was added.