docs/hive-graph-contract.mdpinned to impactium@637886d

Hive-Knowledge Graph Contract

What the chain emits, and how downstream consumers project & filter it.

The chain is the single durable input to the Hive-Knowledge Graph (impactium-query-layer.md). Every state-changing Msg emits events sufficient for imp-ledger's indexer to build graph nodes/edges without re-executing the state machine (ARCHITECTURE.md invariant #4). This file is the contract imp-ledger and imp-wallet implement against.

Consumers regenerate typed clients from proto/ via buf generate — one command yields Rust (sdk/proto) and TypeScript (proto/gen/ts). Never hand-write protocol code.

Filters every consumer must support

Filter Values Source field
verification_tier SELF_LOGGED | NOTARIZED common.v1.VerificationTier on Capsule/MintRecord/mint.claim_resolved
scope INWARD | OUTWARD common.v1.Scope on claims, personal logs, capsules, mint records, truecost records
  • Self-logged impact is visible by default, labeled, never hidden — the UI distinguishes it from notarized, but does not suppress it.
  • The ledger's inward | outward feed toggle is the scope filter; the wallet's personal-ledger view uses it to look "inward" (impact on self) vs "outward" (impact on society).

Events → graph (indexer mapping)

Event kind Node/edge to build
identity.entity_registered Entity node (class, lineage, pubkey → current_pubkey; later rotations replace the current binding)
identity.product_registered Product node (product_id, governed class key, hex-encoded pubkey, owner_entity_id) + CREATED edge (Entity→Product)
spore.spawned SPAWNED edge (parent→child), partition root — carries class (HUMAN or ORGANIZATION org wallets, §18)
spore.spawn_rate_accounted merge the referrer's current bounded window, count, limit, and parameter version onto its Entity node so approaching the governed cap is public
org.registered merge legal registration fields onto the organization Entity node
org.member_added / org.member_role_set / org.member_removed upsert MEMBER_OF edge (member→organization) with role and active state
org.formation_proposed Formation node (proposer, required partners, terms-document ref, proposed legal name) + PROPOSED edge (proposer→Formation) + FORMATION_PARTNER edges (Formation→each required partner)
org.formation_accepted ACCEPTED_FORMATION edge (accepting partner→Formation) — one durable edge per acceptance; remaining partners are derived from required-minus-accepted, never stored as a mutable list
org.formation_completed merge Formation completed=true + link the created organization Entity — the formation record stays permanent
org.partner_linked PARTNER_OF edge (founding partner→formed organization)
bliss.job_enqueued PipelineJob node (job_id, kind, payload_ref, enqueued_at_height, run_at_height, status=PENDING) + ENQUEUED edge (actor Entity→PipelineJob) — the wallet queries jobs by their enqueuing actor
bliss.job_processed merge PipelineJob status=DONE + processed height
bliss.job_failed merge PipelineJob status=FAILED + reason + height — the failed job is retained, never dropped
document.created Document node owned by an Entity
document.version_added DocumentVersion node with the full content manifest + SUPERSEDES edge to its parent layer
document.version_signed SIGNED edge (signer Entity→DocumentVersion); version remains pending until all required signers land
document.version_effective merge DocumentVersion status=EFFECTIVE and effective height
document.version_deprecated merge DocumentVersion deprecated=true + reason — the layer remains permanent
upgrade.scheduled UpgradePlan node carrying halt height + certified node-bundle content root
upgrade.canceled merge cancellation flag/reason onto UpgradePlan — never delete the plan
upgrade.applied UpgradeMigration node recording module, from/to consensus versions, binary name, and height
mint.claim_submitted Claim node (pending) carrying ordered per-domain disclosure, 32-byte evidence anchor, and versioned anchor scheme; legacy claims may omit these additive fields
mint.claim_resolved Capsule node (MINTED edge) or failed-claim node — carries verification_tier + scope; evidence_hash is an opaque commitment whose scheme is declared in anchor_scheme (SHA256-V1 or SALTED-SHA256-V1, §26) — read the scheme, never assume it. A bare SHA256-V1 anchor over a low-entropy plaintext is recoverable by enumeration, which is precisely what the salted scheme exists to prevent
domain.policy_applied append/version an ImpactDomain policy node (name, disclosure, recoverability default, domain_policy_version); genesis v1 and every governed change remain queryable
domain.policy_proposed / domain.vote_cast append the public governance proposal/vote record for an ImpactDomain add or policy change
domain.openness_bonus_proposed / domain.openness_bonus_vote_cast append the public governance proposal/vote record for MAX_OPENNESS_BONUS
domain.openness_bonus_applied append the governed maximum in millis and its openness_param_version; genesis v1 is 250 millis
mint.claim_resolved domain fields Capsule/MintRecord carries the complete impact_domains set, per-domain disclosure/anchor/scheme data, and the domain_policy_version in force; existence and NET remain public regardless of policy. Openness bonus is computed at 0LOOKUP, never folded into mint-time bonus_millis — but it is absolute, identical for every viewer (§28). Only the upstream_coi_bonus component is perspectival.

Identity bindings use the public key, not a hash-derived guess at the identity ID. identity.entity_registered.pubkey and identity.product_registered.pubkey are hex-encoded public keys bound to the event's assigned Entity or Product ID. Product class is the classification registry key, such as agent, rather than an implementation-specific debug representation. A Product's owner is the Entity named by owner_entity_id.

Entity key rotation emits identity.key_rotated with entity_id, old_pubkey and new_pubkey; consumers replace the Entity's current binding and stop resolving the old key. Product key rotation is not currently implemented. Historical Product events that omit pubkey do not establish a key binding; replaying them must leave that binding unknown rather than inventing one.

x/domain retains MAX_OPENNESS_BONUS under a versioned history key as well as the current pointer. Karma projects the applied history and exposes capsuleLookup(id, lookupParty): the public record plus a reusable bounded walk tree, a Scope-Matrix pure-folded from that tree, and a fixed-point valuation stamp (base, upstream-COI and absolute openness components in millis, their additive zeroBonusMillis, exact valueImpMillis, committed parameter version, graph height, minute-bucket timestamp, truncation, and v0 flags). A sealed path remains in the walk with its public Total and meta=SEALED; it never leaks children. Scope-Matrix cells are deterministically ordered by depth then scope_dir then domain and report traversed/sealed counts, reached Total, and an explicit depth-cap flag. The valuation is derived per lookup and is never stored on the Capsule. impactStamp is the STAMP-SIERPINSKI-V1 pure SVG projection of the same walk and carries its canonical integer encoding, fractal type, and flags. Alignment is the primary hue channel; while alignment inputs remain unwired, neutral hue is explicitly flagged ALIGNMENT_HUE_RESERVED_V0. Domain is a secondary texture. Sealed boundaries render as opaque grey sub-triangles without gated texture/Meta. | mint.downline_share | DOWNLINE_SHARE edge (minter→ancestor) — NOTARIZED only | | mint.genesis_residual | GENESIS_RESIDUAL edge (mint→founder) — NOTARIZED only | | weights.proposed | WeightProposal node | | weights.vote_cast | VOTED edge (voter→proposal), proximity weight | | weights.applied | WeightEntry node/version bump (matrix_version) | | truecost.delivered | TrueCost node + DELIVERED edge; resource_legs as properties | | truecost.split | COST_SHARE edge (record→beneficiary); traded flag | | identity.node_updated | merge onto the Entity node (mutable @Node attrs; provenance stays registration) | | identity.key_rotated | merge onto the Entity node (current_pubkey, key_rotations+1) — the identity survives the key (decisions §18) | | identity.keyholders_set | append a KeyholderSet version and link its designated Keyholder Entities to the subject | | identity.recovery_proposed | append a public RecoveryRequest node with subject, requested key, set version, proposer, threshold progress, and Substrate-verification status | | identity.recovery_approved | append/idempotently merge a distinct Keyholder approval and public n-of-3 progress | | identity.recovery_executed | mark the request executed and link it to the resulting identity.key_rotated event | | identity.recovery_cancelled / identity.recovery_invalidated | mark the append-only request terminal with height and public reason; never delete it | | constitution.article_proposed | Article node (stage=MUSE, title, proposer) | | constitution.vote_cast | VOTED edge (voter→Article), target_stage | | constitution.stage_advanced | merge Article stage = to + ADVANCED edge (actor→Article: from, to, rationale) — the deliberation record | | constitution.article_ratified | merge Article stage=HARDENED, ratified=true | | resource.release_published | Release node (keyed product:version, carrying the FULL manifest: artifact, sizes, chunk hashes, content root) + RELEASED edge (Product→Release) — the resource tier serves bytes against this | | resource.certified | CERTIFIED edge (certifier Entity→Product) — root-gated pre-genesis, I-corps later (decisions §15) | | resource.release_deprecated | merge Release deprecated=true + reason — flagged, never removed (permanence) |

notary.sealed and validators.updated are infrastructure, not graph facts — the projection ignores them (the seal/schema registry replays with the chain).

Partition root resolution

Only identity.entity_registered / spore.spawned carry root_id on the wire. Every other fact's partition root is its acting entity's root — the wallet's COA root is its partition key (spore §). Because an entity is always registered (carrying root_id) before it acts, the actor's root is already in the graph; the indexer resolves it from the acting entity (a same-block overlay covers register-then-act within one block). An unresolved root (a malformed / out-of-order stream) never drops the fact — permanence — it lands with an empty root and MAPPED mapping-confidence to flag the inference. (Implemented in imp-ledger / Karma transform.rs.)

Claim lifecycle & failed claims

mint.claim_resolved merges onto the existing Claim node: status → minted | failed. A failed claim is kept as a first-class, queryable node (the "failed-claim node") — never deleted or hidden — so a member's history shows rejected claims beside minted capsules. A minted resolution additionally creates the Capsule node + MINTED edge.

Governance-input note

Weight-Matrix votes are weighted by proximity to subject matter, and the leading proximity function is graph-derived (lineage distance + participation edges to the subject node type). That makes the graph a governance input, not only a read surface: proximity queries must be deterministic, versioned, and provable (ICS23) like everything else the chain commits to. The on-chain x/actioncatalog proximity function is a v0 placeholder (override table); the graph-derived version is the standing balance workstream.

Determinism / provenance

Every node/edge carries the four provenance properties (root, blockHash, txHash, schemaVersion). The projection's schemaVersion bumps whenever the ontology grows (a node/edge kind is added), so a consumer can tell which shape a record was written under and the graph can be rebuilt by replay at any version. Mint records additionally carry action_catalog_version — the ActionCatalog version in force at mint time — so historical impact figures never change when weights are revised.