On this page

SpellIndex๏ƒ

Use md.SpellIndex from the public package namespace.

Implementation source

class SpellIndex(initial_id: str)[source]๏ƒ

A stable SpellIndex identity that points to a mutable selected-spell id and holds the set of member spell ids that belong to this index.

Design: This class solves the "mutable dictionary key" problem. It provides a stable, hashable SpellIndex identity via an immutable ULID, while tracking both a mutable selected-spell pointer (the SHA256 id of the active spell) and the set of member spell ids the index contains.

  • Hashing and equality are based only on the immutable ULID.

  • The selected-spell pointer can be safely updated (mutated) in a thread-safe manner without breaking its location in a dictionary.

  • The index can hold multiple member spell ids; selected_spell_id is the one currently active. The index organizes its member ids; the spell_id resolves. Lookup-map propagation to owning/contracted Spellbooks is the Spellbook's responsibility (the notch seam), not the index's.

Contract:

  • Hash and equality derive ONLY from the immutable ULID.

  • selected_spell_id is mutable and thread-safe to update.

  • The index owns its member spell ids; it does NOT own lookup-map propagation to owning or contracted Spellbooks - that is the Spellbook's notch seam.

Threading:

The selected-spell pointer is updated under lock, so a repoint is safe while the index sits in a dictionary.

Registration:

MELDER KERNEL - guarded. Created by Bind; users receive indexes rather than constructing them.

Subsystem Context:

The stable identity spells are organized under. Spell is one version; the index is the lineage. Version HISTORY belongs to MutationResearch, not here.

System Context:

The design note names the real problem this solves: the MUTABLE DICTIONARY KEY. Identity must stay stable so the object keeps its place in every map that holds it, while the thing it POINTS AT must be free to change. Hashing on the immutable ULID and mutating only the pointer is what makes a notch possible at all - repointing an index cannot corrupt the maps it lives in. That split ripples outward. Contracts carry BOTH a Detail (a captured spell_id, the answer at grant time) and an IndexDetail (a subscription to this index, following the head), so a notch updates every borrower without renegotiating a single contract. The crystallizer records index MEMBERSHIP as its own twin (SpellIndexCrystal) for the same reason - membership is lineage truth, distinct from any one spell's custody. The explicit non-ownership of lookup-map propagation is the boundary that keeps this class small: the index organizes ids, the spell_id resolves, and the Spellbook is what republishes lookups on a notch.

AGENT_ACCESS: public

AGENT_PURPOSE:

access: public. The stable lineage identity (immutable ULID) pointing at a mutable selected spell. Hash/equality use only the ULID, so a notch can repoint the active member without breaking any map. Version history belongs to MutationResearch.

cleanup() None[source]๏ƒ

Release the member set and pointer and mark this index as cleaned.

Contract:

  • Idempotent and lock-guarded.

  • Clears and drops the member set and the selected-spell pointer.

  • Leaves future callers to fail through check_cleaned().

Returns:

None.

property selected_spell_id: str๏ƒ

Return the active spell's id for this SpellIndex.

Returns:

The active spell's id.

Return type:

str

Contract:

  • Returns the live selected-spell pointer, not a historical value.

  • Lock-free read: the pointer is one attribute holding one string reference, so a reader observes either the previous or the new id, never a torn value. update(...) still serializes writers under the instance lock.

Threading:

  • Safe on free-threaded builds; attribute reference loads are atomic. This property is the hottest fingerprint read in the compiler phases (thousands of reads per conjure), and the per-read RLock acquire/release dominated its cost on nogil.

update(new_id: str) None[source]๏ƒ

Set the active spell id and add it to the index's member set.

This is a pure index-side repoint: it changes which member is active and records new_id in the member set, and nothing else. Owning and contracted Spellbook lookup-map propagation is the Spellbook's responsibility (the notch seam), not the index's. Thread-safe; does not affect the object's hash or its dictionary location.

Parameters:

new_id (str) -- The new SHA256 spell id to select and add as a member.

Contract:

  • SELECTS a new current spell id AND adds it to the member set, so selection always implies membership.

  • DOES NOT RETIRE THE PREVIOUS ID. The superseded version stays a MEMBER, which is exactly why Spellbook.find_spell_by_id still resolves a parked id to the live spell. The member set is the lineage; the selected id is only the current head.

Threading:

Reads and writes under self._lock, so the result is a coherent snapshot rather than a torn read.

Lifecycle / Cleanup:

Guarded by check_cleaned().

Raises:

RuntimeError -- If the object has been cleaned.

Returns:

None.

add_member(spell_id: str) None[source]๏ƒ

Add spell_id to this index's member set without selecting it.

Used to stage an inactive candidate (Spellbook.bind_inactive): the id becomes a member visible to spells_in_index() / has_spell(), but the active selected_spell_id is left unchanged. Promotion to active happens later via notch.

Parameters:

spell_id (str) -- The candidate member spell id to record.

Contract:

  • Adds to the lineage WITHOUT changing the selected id, so it grows the version set without promoting anything.

  • Set semantics: re-adding an existing id is a silent no-op.

Threading:

Reads and writes under self._lock, so the result is a coherent snapshot rather than a torn read.

Lifecycle / Cleanup:

Guarded by check_cleaned().

Raises:

RuntimeError -- If the object has been cleaned.

Returns:

None.

remove_member(spell_id: str) None[source]๏ƒ

Remove spell_id from this index's member set.

Idempotent; does not touch the selected pointer. Used when a member is moved out of this index (add_to_spell_index).

Parameters:

spell_id (str) -- The member spell id to remove.

Contract:

  • Uses discard, so removing an id that was never a member is a SILENT NO-OP rather than an error. Absence of an exception is not proof the id was present.

  • CAN REMOVE THE CURRENTLY SELECTED ID. Nothing here re-points the selection, so afterwards selected_spell_id may name an id that is no longer a member. Callers retiring the head must select a new one.

Threading:

Reads and writes under self._lock, so the result is a coherent snapshot rather than a torn read.

Lifecycle / Cleanup:

Guarded by check_cleaned().

Raises:

RuntimeError -- If the object has been cleaned.

Returns:

None.

spells_in_index() set[source]๏ƒ

Return a snapshot of every member spell id in this index.

Returns:

A detached copy of the member spell ids.

Return type:

set

Contract:

  • Includes the initial spell id and every id added via update(...).

has_spell(spell_id: str) bool[source]๏ƒ

Return whether spell_id is a member of this index.

Parameters:

spell_id (str) -- The spell id to check.

Contract:

  • Tests MEMBERSHIP OF THE LINEAGE, not equality with the selected id, so a superseded or parked version still answers True. This is the check spellbook lookups rely on to resolve old ids to the live spell.

Threading:

Reads and writes under self._lock, so the result is a coherent snapshot rather than a torn read.

Lifecycle / Cleanup:

Guarded by check_cleaned().

Raises:

RuntimeError -- If the object has been cleaned.

Returns:

True if spell_id is in the member set, False otherwise.

Return type:

bool

is_empty() bool[source]๏ƒ

Return whether this index has no members.

O(1): tests the live member set directly without copying it.

Contract:

  • Reports that the MEMBER SET is empty. It says nothing about the selected id, which member removal does not clear - an index can be empty and still carry a stale selected id.

Threading:

Reads and writes under self._lock, so the result is a coherent snapshot rather than a torn read.

Lifecycle / Cleanup:

Guarded by check_cleaned().

Raises:

RuntimeError -- If the object has been cleaned.

Returns:

True if the member set is empty.

Return type:

bool

is_sole_member(spell_id: str) bool[source]๏ƒ

Return whether spell_id is this index's only member.

O(1): a length test plus a membership test on the live set; no copy and no set construction (unlike spells_in_index() == {spell_id}).

Parameters:

spell_id (str) -- The spell id to test as the sole member.

Contract:

  • True only when the member set has exactly one entry AND that entry is the supplied id; a different lone member returns False rather than raising.

  • The usual "is this the last version" test before a destructive step.

Threading:

Reads and writes under self._lock, so the result is a coherent snapshot rather than a torn read.

Lifecycle / Cleanup:

Guarded by check_cleaned().

Raises:

RuntimeError -- If the object has been cleaned.

Returns:

True iff the member set is exactly {spell_id}.

Return type:

bool

property id: str๏ƒ

Return the immutable ULID that defines this index's stable identity.

Contract:

  • This value never changes for the lifetime of the index.

  • Hashing and equality are derived from this id, not from the mutable selected-spell pointer.

Returns:

The immutable ULID. Hash and equality derive from this alone, which

is what lets the selected spell move without breaking dictionary placement.

Return type:

str

Topic reference ยท Full contents