Adds the second operator-pushed kind-30078 document type alongside
cassette config (#29). Wire format locked at coord-log §2026-06-01T14:25Z.
models.py:
- FeePayloadComponents — producer-mandatory `components` sub-object
with super + operator splits per direction. Consumer-optional in v1
but ships on every payload from this producer for audit + future-
promo extensibility.
- FeeConfigPayload — the wire-format envelope. Pydantic validators
enforce: cash_*_fee_fraction in [0, 0.15] (cap per direction);
|total - (super + operator)| < 1e-6 (consistency assert per the
§07:33Z lnbits advisory, mirrored on bitspire's #57 consumer side);
schema_version integer ≥ 1.
fee_transport.py:
- build_fee_payload(super_config, machine) — compose + validate in
one call; returned payload is wire-shippable. Raises ValueError
(via Pydantic) if the constructed totals violate the cap. That
shouldn't happen in practice because the API guards in
views_api._assert_machine_fee_cap_safe + _assert_super_config_cap_safe
refuse cap-violating writes; if it does, refuse-to-publish rather
than ship a malformed event.
- publish_fee_config(machine, super_config, operator_user_id) —
builds, encrypts, signs, publishes via the shared
publish_encrypted_kind_30078 helper from nostr_publish. d-tag is
`bitspire-fees:<atm_pubkey_hex>` per spec; recipient is the ATM
npub canonicalised to hex; signer is the operator.
- Soft-fail discipline matches cassette_transport.publish_to_atm —
transport-layer errors (RelayUnavailable / SignerUnavailable /
OperatorIdentityMissing) log WARN + return None so trigger callers
(api_create_machine etc.) don't break on transient transport hiccups.
Cap violations are NOT soft-fail since they indicate an API-guard
bypass and need operator attention.
Tests (18 cases, all green):
- 9 FeeConfigPayload validator cases (well-formed accept, wire round-
trip, cap violations per direction, exact-cap acceptance, sum/
components mismatch per direction, schema_version ≥ 1, zero-zero
free-charge ATM)
- 4 build_fee_payload composition cases (basic, asymmetric directions,
super-only-no-operator default, cap violation at build time)
- 5 publish_fee_config soft-fail discipline cases (relay unavailable,
signer unavailable, operator identity missing, publish success with
d-tag + recipient + payload-shape assertions, cap violation raises
before reaching publish)
182/182 tests green.
Refs: aiolabs/satmachineadmin#37 (parent), #39 (Layer 2), coord-log
§2026-06-01T14:25Z (locked wire format), §2026-06-01T07:33Z (lnbits
consistency-assert advisory).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>