Descriptors Design Spec (v1.3)¶
This spec defines the core runtime descriptor model for MAP. This model introduces a significant set of capabilities into the MAP core that move us much closer to the MAP vision of open-ended self-describing, active holons and holon relationships.
We have implemented holons and holon relationships, but these holons are neither self-describing nor active. Dances, as currently implemented, are decoupled from holon types and are centrally dispatched. We've added the ability to load descriptors via the Holon Data Loader, but these loaded descriptors are not linked to any MAP core behaviors -- inverse relationships are not being populated, holon keys are not being composed and, most importantly, there is no descriptor-driven validation.
Highlights¶
The capabilities introduced via this spec include the following:
- Re-allocation of standalone behavior to descriptors. Until now, dances and commands have been implemented as standalone functions. Moving these behaviors to descriptors gives us a modular, extensible foundation.
- Decentralized (but static) dance/command dispatch. The current approach to dispatching dances is centralized and static. We have a single, flat, hard-coded dispatch table. Adding support for new dances requires extending this table and a rebuild. To support the MAP's architectural goals, we need: (1) dances structured around holons types,(2) decentralized dispatch and (3) dynamic loading of dance implementations. This spec introduces (1) and (2), but not yet (3).
- Validation operator support. MAP ValueType descriptors specify various constraints on values of that type (e.g., min/max lengths, min/max values, required). This spec introduces simple is_valid operators that encapsulate the logic for enforcing these constraints.
- Query operator support. OpenCypher and GQL support WHERE clauses that filter results on the basis of value-type specific operators. This spec introduces support for such operators.
- Convenience lookup methods. This spec prescribes various lookup methods (e.g., property and relationship and inverse relationship descriptor lookups by name).
- Automatic compression of Extends Inheritance. The MAP's type definition language allows a type descriptor to inherit instance properties, instance relationships and instance behaviors (dances and commands) from other types via (at most one)
Extendsrelationship. This spec encapsulates navigation upwards through the chain of Extends so that all such inheritance is effectively flattened -- callers don't have to worry about navigating Extends relationships and implementing inheritance logic. - JSON Core Schema definitions drive descriptors definition. Changes to the MAP Core Schema almost always have a ripple effect on the code base, test cases, and documentation. This spec references a companion document: Schema Ripple Design Spec that describes an approach to ensuring proper ripple-effects are handled with minimal effort.
Status¶
Proposed
Note: MAP design docs are normally published from the separate dev-docs repository. This file is a working draft captured alongside code for iteration and should be moved or mirrored there when the design stabilizes.
Change Log¶
v1.3¶
- added an explicit relationship persistence policy requiring inverse materialization to be treated as part of the semantic relationship commitment
- clarified that the runtime must not silently commit only the forward side when inverse materialization is required by descriptor semantics
- specified that unresolved non-local inverse materialization must become durable deferred-repair work with detection and remediation rather than an undiagnosed one-sided steady state
v1.2¶
- introduced the v1.2 descriptor runtime model and its TypeKind-organized descriptor surface
Scope¶
It is intentionally narrow and normative. Background material, rollout process, behavior allocation, and schema ripple process belong in the companion specs.
The organizing principle here is TypeKind: the main contract is what descriptor wrapper exists for each kind, what operations it exposes, and what schema-backed accessors it should provide.
Instead of leaving key operations as freestanding helpers or central registries, the design moves those operations onto descriptor wrappers themselves. That includes convenience lookup methods, inheritance flattening, validation-oriented behavior, and the static dispatch points needed for later dance/command and query/operator support.
Domain-Definability Policy¶
Domain-definability is a MAP Core policy attached to TypeKind, not a schema-declared InstanceProperty that every type descriptor must carry.
The core schema records persistent type definitions. MAP Core Rust code
interprets each type definition's declared type_kind through the Rust
TypeKind model. That Rust model is the authoritative source for whether a
non-core domain author may define new types of that kind.
This policy is evaluated when a caller attempts to define a new type.
Initial posture:
Holon,Property,Relationship,Value,ValueArray,EnumVariant,Collection, andDanceare domain-definable where otherwise permitted by schema validation and security policy.CommandandOperatorare core-defined only, not domain-definable.- Command and operator inventories evolve with MAP Core versions and their Rust implementations.
The descriptor layer may expose command and operator discovery through schema-backed descriptors, but it must not treat command or operator type creation as a domain-definable extension point.
TypeKind-Specific Names¶
Every descriptor exposes the shared descriptor header, including type_name. When a runtime API identifies a descriptor by name, it should use a TypeKind-specific Rust name wrapper rather than raw MapString.
TypeKind-specific name wrappers provide two guarantees:
- they prevent accidental cross-family name usage, such as passing a property name where a command name is required
- they centralize the naming convention for that TypeKind
For TypeKinds that already have schema-backed name properties, such as PropertyName, RelationshipName, and DanceName, the wrapper should continue to expose that schema-backed name. For command and operator descriptors, no separate schema-backed name property is introduced in this phase. Instead:
CommandNameis a Rust wrapper over the concreteCommandTypedescriptor's sharedtype_nameOperatorNameis a Rust wrapper over the concreteOperatorTypedescriptor's sharedtype_nameCommandDescriptor::command_name()should deriveCommandNamefrom the shared descriptor headerOperatorDescriptor::operator_name()should deriveOperatorNamefrom the shared descriptor header
Because commands and operators are core-defined only, their stable name inventories should be represented in MAP Core Rust code through TypeKind-specific core name enums such as CoreCommandTypeName and CoreOperatorTypeName. These enums are implementation aids for known core names; they do not replace the schema-backed descriptor holons.
Descriptor lookup APIs should match the ergonomics already provided by the reference layer. Name-based descriptor accessors should accept conversion traits such as ToPropertyName, ToRelationshipName, ToDanceName, ToCommandName, and ToOperatorName rather than forcing callers to manually construct wrapper values at every call site.
Authoritative Source¶
The authoritative source of truth for descriptor structure is the core schema JSON under:
host/import_files/map-schema/core-schema/
Structural accessors should derive from those definitions, not from handwritten Rust mirrors. In this phase, that derivation is expected to be AI-assisted rather than produced by a permanent hard-coded generator. The important constraint is that the resulting descriptor surface remains schema-backed, reviewable, and aligned with the current authoritative JSON definitions.
This does not prevent the spec from prescribing new or updated descriptor definitions in the core schema. The constraint is on implementation: runtime behavior should be tied back to schema definitions, not float free of them.
Common Contract¶
These rules apply to every descriptor wrapper, regardless of TypeKind.
Taken together, these rules define the common MAP descriptor posture: thin wrappers, schema-backed structure, no duplicated ontology state, and descriptor-local behavior rather than centralized descriptor registries.
Representation¶
- Every descriptor is itself a holon.
- Every runtime descriptor wrapper is a thinly typed view over a backing
HolonReference. - Every wrapper stores a single private
holon: HolonReference. - Descriptor wrappers do not duplicate ontology state as Rust fields.
Representative shape:
pub struct HolonDescriptor {
holon: HolonReference,
}
Shared runtime abstractions¶
ReadableHolon should expose:
pub trait ReadableHolon {
fn holon_descriptor(&self) -> Result<HolonDescriptor, HolonError>;
}
Semantics:
- follow the source holon's single
DescribedByrelationship - error if none exists
- error if more than one exists
- wrap the resulting
HolonReferenceinHolonDescriptor
All descriptor wrappers should implement:
pub trait Descriptor {
fn holon(&self) -> &HolonReference;
}
Shared descriptor/header access should be centralized through a runtime TypeHeader helper. Descriptor wrappers should expose direct convenience methods through shared code rather than storing header fields directly.
This shared layer is also the place where behavior that is common across descriptor kinds should live. The goal is not a monolithic dispatcher, but a small amount of shared runtime support that lets each concrete descriptor kind own its own behavior cleanly.
Inheritance¶
- Descriptor inheritance is single inheritance through
Extends. Extendsconfers type inheritance: structural obligations and affordance declarations may be flattened across the inheritance chain.Extendsdoes not confer value inheritance for descriptor-instance property values such as shared header fields or governance markers.- Callers should experience a flattened effective descriptor view for inherited structure and affordances.
- Normal descriptor use must not require caller-side
Extendswalking. - Descriptor wrappers should not store a constructor-time cached
extends_chain.
Flattening the Extends hierarchy is one of the core ergonomic promises of the descriptor layer. Callers should ask for the effective property, relationship, affordance, or rule they need and get a descriptor-level answer, not a chain they must interpret themselves. This flattening applies to declared type structure, not to the values populated on individual descriptor holons.
Traversal rules:
- include
selffirst - walk parent-ward until no
Extendstarget exists - error on multiple
Extends - error on cycles
- for lookups, chase only far enough to find a match unless a full chain is truly needed
Overrides¶
- Child descriptor redeclaration of an inherited property name is invalid.
- Child descriptor redeclaration of an inherited relationship name is invalid.
- These invalid structures should be prevented at descriptor/schema commit time.
- Runtime descriptor lookup should still fail loudly if such an invalid saved or staged structure is encountered.
Generated accessors¶
- Structural accessors should be generated or derived strictly from schema-declared
InstancePropertiesandInstanceRelationships. - If an accessor is not justified by the current authoritative schema, it should not be introduced as if it were schema-backed.
- If the intended accessor is part of the target design but not yet justified by the current schema, that is a core-schema deficiency to correct.
This is the mechanism by which the JSON core schema definitions drive the runtime descriptor surface. The schema defines the structural obligations; the runtime descriptor wrappers expose those obligations as typed convenience accessors.
Error semantics¶
Descriptor operations should fail precisely on:
- missing required
DescribedBy - multiple
DescribedBy - multiple
Extends - cycles in
Extends - missing required header field
- invalid descriptor/value kind casts
- missing
InverseOfwhen inverse lookup requires it - duplicate inherited declarations
Behavior Affordances¶
Descriptors are the semantic home for behavior affordances. In this phase, the design distinguishes three behavior families:
InstanceDance: domain-definable behavior afforded by holon typesCommand: core-defined behavior afforded by holon types, but not domain-definableOperator: core-defined semantic affordances of value types
All three families follow the same inheritance posture:
- affordances flatten across
Extends - callers should not reconstruct inherited behavior themselves
- no override
- no deletion
- duplicate redeclaration is invalid
They differ in where they are declared:
InstanceDanceis afforded byHolonTypedescriptorsCommandis afforded byHolonTypedescriptors and inherited by their descendantsOperatoris afforded byValueTypedescriptors
Dynamic execution/binding of dances and commands is deferred. The concern of this spec is descriptor structure, affordance lookup, and the static runtime surfaces on which later dispatch will attach.
Behavior Ownership Matrix¶
The table below makes explicit where the behavior promised by this spec lives.
| Behavior | Owning Runtime Descriptor |
|---|---|
| Property lookup by name | HolonDescriptor |
| Relationship lookup by name | HolonDescriptor |
| Inverse relationship lookup by name | HolonDescriptor |
| Instance dance lookup | HolonDescriptor |
| Property-to-value semantic bridge | PropertyDescriptor |
| Value validation | ValueDescriptor |
| Operator discovery | ValueDescriptor |
| Operator support check | ValueDescriptor |
| Operator application | ValueDescriptor |
| Command lookup | descriptor wrappers via shared behavior affordance support |
| Relationship structural semantics | RelationshipDescriptor |
Interpretation rules:
- descriptor wrappers own externally meaningful behavior
- shared runtime helpers may support those behaviors, but should not become a second semantic layer
- behavior should be attached as close as possible to the descriptor kind that semantically owns it
Static Dispatch Model¶
This spec introduces decentralized but static dispatch for dances, commands, and operators.
Decentralized means:
- behavior is discovered from descriptors and their affordances, not from a single global dispatch table
- inheritance-aware behavior lookup is local to the relevant descriptor wrapper
- different descriptor kinds may own different dispatch surfaces
Static means:
- once a descriptor or affordance has been resolved, behavior is dispatched to handwritten Rust implementations associated with the relevant descriptor kind
- there is no dynamic module loading in this phase
- there is no runtime plugin registry in this phase
Dispatch shape in this phase:
- resolve the relevant descriptor
- resolve the effective inherited affordance set through flattened
Extends - select the requested dance, command, or operator by name or identity
- dispatch through descriptor-local static Rust code
This is the mechanism by which standalone behavior is re-allocated onto descriptors without yet committing to dynamic implementation loading.
Operator Model¶
Operators are part of the descriptor foundation because query construction, collection filtering, relationship-navigation filtering, and value validation all need a discoverable comparator surface.
This spec distinguishes:
OperatorType: a descriptor holon that defines an available operator- operator invocation: a runtime method call on a descriptor, not a holon instance
Initial operator posture:
- operators are core-defined, not domain-definable
- operator affordances are declared by
ValueTypedescriptors - value descriptors expose the effective inherited operator surface
- operator application is descriptor-local static Rust dispatch
Minimum metadata promised by OperatorDescriptor:
- stable operator identity
- human-readable display metadata
- arity
- operator category
- applicability to value types
Initial runtime promises:
- descriptors can enumerate available operators for filter/query construction
- descriptors can answer whether a given operator is supported
- descriptors can apply a supported operator to concrete operands
The initial operator family should at least cover the comparator use cases required for:
- value validation support where appropriate
- OpenCypher and GQL-style value comparison
- collection filtering
- relationship-navigation filtering driven by value comparison
Shared Descriptor Surface¶
All descriptor wrappers should expose the shared structural accessors implied by
the v2.0 descriptor model: TypeHeader plus the common descriptor
relationships rooted at DescriptorRoot.
This shared surface is what makes descriptor wrappers feel coherent as a family. It gives every descriptor the same basic type-introspection vocabulary while leaving kind-specific behavior to the sections below.
Shared properties¶
type_name()type_name_plural()display_name()display_name_plural()description()type_kind()is_abstract_type()
Shared relationships¶
component_of()extends()uses_key_rule()
Shared handwritten helpers¶
header()holon()- inheritance traversal helpers
Descriptor Kinds¶
Holon¶
HolonDescriptor is the primary structural descriptor for holon types. It carries the most important convenience API in this phase because callers routinely need to resolve properties and relationships by name without manually traversing inheritance or inverse links.
It is also the primary instance-facing entrypoint for inherited behavior lookup. From a caller's perspective, the same descriptor used to inspect a holon type should also answer what dances and commands that type effectively affords.
Wrapper:
HolonDescriptor
Required handwritten operations:
fn get_property_by_name<N: ToPropertyName>(
&self,
property_name: N,
) -> Result<PropertyDescriptor, HolonError>;
fn get_relationship_by_name<N: ToRelationshipName>(
&self,
relationship_name: N,
) -> Result<RelationshipDescriptor, HolonError>;
fn get_inverse_relationship_by_name<N: ToRelationshipName>(
&self,
declared_relationship_name: N,
) -> Result<RelationshipDescriptor, HolonError>;
fn afforded_instance_dances(&self) -> Result<Vec<DanceDescriptor>, HolonError>;
fn get_instance_dance_by_name<N: ToDanceName>(
&self,
dance_name: N,
) -> Result<DanceDescriptor, HolonError>;
fn afforded_commands(&self) -> Result<Vec<CommandDescriptor>, HolonError>;
fn get_command_by_name<N: ToCommandName>(
&self,
command_name: N,
) -> Result<CommandDescriptor, HolonError>;
Lookup semantics:
- search
selffirst, then ancestors inExtendsorder - return the first matching declaration found
- relationship lookup should match both declared and inverse relationship descriptors
- inverse lookup should resolve the declared relationship first, then follow
InverseOf - dance lookup should resolve the effective inherited dance affordance set
- command lookup should resolve the effective inherited command affordance set
These lookup methods are intentionally convenience-heavy. They are part of the value of introducing descriptors as first-class runtime wrappers: descriptor consumers should be able to ask for a property, relationship, or inverse relationship by name and receive the effective descriptor directly.
The same convenience principle applies to behavior. Callers should be able to ask a holon descriptor which dances and commands it affords without separately reconstructing inheritance or consulting a central dispatch table.
Schema-backed additional properties:
allows_additional_properties()allows_additional_relationships()
Schema-backed additional relationships:
properties()described_by()owned_by()
Property¶
PropertyDescriptor is the bridge from holon structure to value semantics. Its main job in this phase is to make the property's value type directly reachable as a typed descriptor.
Wrapper:
PropertyDescriptor
Required handwritten/runtime operation:
fn value_type(&self) -> Result<ValueDescriptor, HolonError>;
Schema-backed additional properties:
is_required()property_name()
Schema-backed additional relationships:
value_type()
Relationship¶
Relationship descriptors carry the structural semantics of graph edges. In this phase they remain mostly structural, but they are important because they centralize relationship metadata that other systems should stop treating as freestanding configuration.
Wrappers:
RelationshipDescriptorDeclaredRelationshipDescriptorInverseRelationshipDescriptor
RelationshipDescriptor requires no additional handwritten behavior in this phase beyond shared descriptor access, inheritance participation, and inverse-related lookup support.
This is also the area where later decentralized dance/command dispatch should attach. The direction is static and descriptor-local: relationship-aware behavior should live with relationship descriptors and their close collaborators rather than in a central god-dispatcher.
Relationship Persistence Policy¶
Inverse relationships are part of the semantic meaning of a committed relationship occurrence, not an optional local cache effect.
When a declared relationship has an inverse, the runtime should treat the forward occurrence and the inverse occurrence as one logical relationship commitment.
Normative policy:
- the system must not silently commit only the forward relationship occurrence when the inverse occurrence is required by descriptor semantics
- if both relationship endpoints are locally resolvable, commit-time persistence should materialize both directions in the same semantic operation
- inability to resolve a non-local target for inverse materialization must be surfaced as unresolved semantic work, not treated as successful completion of a one-sided relationship write
- unresolved inverse materialization for a non-local target should create a durable deferred-repair item that can be retried when the relevant trust channel, agent, or identifier-resolution path becomes available
- if identifier resolution such as
ExternalIdsupport exists for establishing the relationship, it should be applicable symmetrically to both directions rather than being treated as a forward-only exception path - runtimes should provide detection and remediation of deferred inverse materialization work rather than leaving one-sided relationship state as an undiagnosed steady state
This policy allows eventual consistency across membranes without redefining semantic consistency as "forward-only writes succeed and inverse writes are skipped."
Schema-backed properties on RelationshipDescriptor:
deletion_semantic()is_definitional()is_ordered()allows_duplicates()min_cardinality()max_cardinality()property_name()allows_additional_properties()allows_additional_relationships()
Schema-backed relationships on RelationshipDescriptor:
source_type()target_type()
Schema-backed relationships on DeclaredRelationshipDescriptor:
has_inverse()
Schema-backed relationships on InverseRelationshipDescriptor:
inverse_of()
Dance¶
DanceDescriptor defines an affordable instance behavior. It is the descriptor-level foundation for later dance dispatch, but this spec stops at descriptor structure and lookup rather than dynamic execution or module binding.
Wrappers:
DanceDescriptor
Prescribed core-schema role:
DanceTypeis the schema type for dance descriptors- Rust should expose
DanceTypeholons through theDanceDescriptorwrapper HolonTypedescriptors should be able to afford dances through a schema-declared relationship such asAffordsInstanceDance
Primary instance-facing lookup surface on HolonDescriptor:
fn afforded_instance_dances(&self) -> Result<Vec<DanceDescriptor>, HolonError>;
fn get_instance_dance_by_name<N: ToDanceName>(
&self,
dance_name: N,
) -> Result<DanceDescriptor, HolonError>;
Inheritance semantics:
- instance dances inherit exactly like properties and relationships
- domain-specific
HolonTypes may add dances - overrides and deletions are not allowed
- lookup should return the effective flattened dance affordance set, not only local declarations
Static dispatch note:
- once a dance has been resolved through descriptor lookup, execution should dispatch through descriptor-local static Rust code in this phase
- dynamic implementation loading is deferred
Current schema note:
- this behavior family is prescribed by this design and requires corresponding core-schema additions if they do not yet exist in the authoritative schema
Command¶
CommandType defines a core command affordance. Commands are part of the descriptor foundation because they provide the stable cross-language execution surface, but unlike dances they are not domain-definable in this phase.
Wrappers:
CommandDescriptor
Prescribed core-schema role:
CommandTypeshould be introduced as the schema type for command descriptors- Rust should expose
CommandTypeholons through theCommandDescriptorwrapper - the existing
map_commands_contract::CommandDescriptorlifecycle metadata type should be namedCommandLifecyclePolicysoCommandDescriptoris reserved for the schema-backed descriptor wrapper - stable MAP command identities should be represented as thin concrete
CommandTypeholons using the sharedTypeHeadersurface unless a later phase introduces richer metadata - concrete
CommandTypeholons should be defined at the stable leaf command identity, not at a collapsed command-family or handler-label level - command-envelope identities that carry richer request semantics, such as dance or query execution commands, are still
CommandTypes; the command type describes the MAP Commands API entrypoint, while dance and query descriptor families own the invoked behavior semantics - relevant core
HolonTypedescriptors should afford commands through a schema-declared relationship such asAffordsCommand - schema command inventory and command affordance anchoring are separate obligations: stable leaf MAP Commands should have
CommandTypeholons even when their runtime target does not yet have a schema-backedHolonTypeaffordance anchor - the intended relationship shape is:
(HolonType) -[AffordsCommand]-> (CommandType)(CommandType) -[AffordedBy]-> (HolonType)- the declared relationship name includes
CommandbecauseHolonTypedescriptors may afford multiple behavior families; the inverse may useAffordedBybecause relationship identity is the fully qualified source/name/target shape
Primary instance-facing lookup surface on HolonDescriptor:
fn afforded_commands(&self) -> Result<Vec<CommandDescriptor>, HolonError>;
fn get_command_by_name<N: ToCommandName>(
&self,
command_name: N,
) -> Result<CommandDescriptor, HolonError>;
Command rules:
- commands are defined by holons core, not by domain schemas
- command lookup by name matches the concrete
CommandType's sharedtype_name - command lookup should normalize internally to the same canonical value used by the command descriptor's
type_name CommandDescriptor::command_name()should deriveCommandNamefrom the shared descriptor header- no separate
CommandNameschema property is prescribed in this phase - concrete command
type_namevalues should use stable UpperCamel leaf command identities rather than runtime handler labels - command affordances may be inherited through
Extends - overrides and deletions are not allowed
- lookup should return the effective flattened command affordance set, not only local declarations
Descriptor-surface note:
HolonDescriptoris the main caller-facing command lookup surface for holon instances- command affordance lookup is intentionally scoped to holon-type descriptors in this phase
TransactionTypeis the coreHolonTypeanchor for transaction-scoped command affordances
Static dispatch note:
- once a command has been resolved through descriptor lookup, execution should dispatch through descriptor-local static Rust code in this phase
- dynamic implementation loading is deferred
Current schema note:
- this behavior family is prescribed by this design and requires corresponding core-schema additions if they do not yet exist in the authoritative schema
Transaction¶
TransactionType defines the core transaction model: the descriptor-backed API surface for transaction-scoped command affordances. It is a core HolonType, not a new TypeKind, and not the schema type of live transaction contexts.
TransactionType exists so transaction-scoped MAP Commands can be discovered through the same descriptor-local affordance surface as other commands. It is the schema type of the transaction command scope, not the instance shape of TransactionContext.
Runtime TransactionContext values, SDK MapTransaction handles, wire-layer transaction identity, nursery state, staging state, transient state, lifecycle state, and execution guards remain runtime/execution concerns. The fields and methods of live runtime transaction structs are not inferred into TransactionType, and should not be reflected as TransactionDescriptor accessors merely because they exist on runtime execution objects.
Persistent transaction records are a distinct design concept. This descriptor design reserves TransactionType for the core transaction model: the descriptor-backed command-scope API surface afforded by HolonSpaceType. It does not currently define persisted transaction-record instances. A future transaction-record/audit design may either introduce a separate TransactionRecordType or explicitly revise the meaning of TransactionType; that choice is not implied by this descriptor model.
Wrappers:
HolonSpaceDescriptorTransactionDescriptor
HolonSpaceDescriptor is the Rust descriptor wrapper over the core HolonSpaceType holon. It is the descriptor-local home for space-specific schema affordances, including discovery of the transaction model afforded by a holon space.
TransactionDescriptor is the Rust descriptor wrapper over the core TransactionType holon. It is not a wrapper over a live TransactionContext, not a persisted transaction instance, not a transaction audit/record holon, and not the SDK-facing MapTransaction.
Core-schema role:
HolonSpaceTypeis a concrete coreHolonType- Rust exposes
HolonSpaceTypeholons through theHolonSpaceDescriptorwrapper HolonSpaceDescriptorremains a thin typed view overHolonReferenceTransactionTypeis a coreHolonType- Rust exposes
TransactionTypeholons through theTransactionDescriptorwrapper TransactionDescriptorremains a thin typed view overHolonReferenceHolonSpaceTypeaffords exactly one transaction model through:(HolonSpaceType) -[AffordsTransactionModel]-> (TransactionType)- the inverse relationship is:
(TransactionType) -[TransactionModelAffordedBy]-> (HolonSpaceType)AffordsTransactionModelidentifies the transaction command-scope API surface available from a holon spaceTransactionModelAffordedByidentifies the holon-space type that affords a transaction model without implying lifecycle containment or ownership ofTransactionTypeAffordsTransactionModeluses deletion semanticAllow; deleting or replacing the sourceHolonSpaceTypedoes not delete, block on, or cascade to the targetTransactionTypeTransactionModelAffordedByalso uses deletion semanticAllow; deleting or replacing the sourceTransactionTypedoes not delete, block on, or cascade to the targetHolonSpaceType- the relationship declares the model afforded by a space, not ownership or lifecycle containment of the model type
TransactionTypeaffords transaction-scoped command descriptors through the existing command affordance relationship shape:(HolonType) -[AffordsCommand]-> (CommandType)(CommandType) -[AffordedBy]-> (HolonType)TransactionTypeaffords every stable transaction-scoped MAP Command in the current MAP Core command inventory:CommitUndoLastRedoLastUndoToMarkerRedoToMarkerLoadHolonsDanceQueryGetAllHolonsGetStagedHolonByBaseKeyGetStagedHolonsByBaseKeyGetStagedHolonByVersionedKeyGetTransientHolonByBaseKeyGetTransientHolonByVersionedKeyGetStagedCountGetTransientCountNewHolonStageNewHolonStageNewFromCloneStageNewVersionStageNewVersionFromIdDeleteHolonHolonSpaceTypecontinues to affordBeginTransaction;TransactionTypeaffords commands that require an open transaction context
Primary transaction-facing lookup surface:
impl HolonSpaceDescriptor {
fn transaction_model(&self) -> Result<TransactionDescriptor, HolonError>;
}
impl TransactionDescriptor {
fn afforded_commands(&self) -> Result<Vec<CommandDescriptor>, HolonError>;
fn get_command_by_name<N: ToCommandName>(
&self,
command_name: N,
) -> Result<CommandDescriptor, HolonError>;
}
Transaction rules:
HolonSpaceDescriptoris the descriptor-local owner for holon-space-specific schema affordancesTransactionTypeis defined by MAP Core, not by domain schemasTransactionTypeis not domain-definable even though it is a concreteHolonType- domain schemas may not define alternate transaction-scope types or domain-specific subtypes of
TransactionType - transaction-scoped command affordances are core-defined and evolve with MAP Core command inventory
- the transaction model afforded by
HolonSpaceTypeis core-defined and not domain-definable TransactionTypecommand discovery is descriptor-local and schema-backed- runtime
TransactionContextinstances are not instances ofTransactionType TransactionDescriptorexposes descriptor-local command discovery, not live transaction state- transaction-record/audit holons, if introduced, require an explicit transaction-record design decision rather than being implied by the command-scope
TransactionTypemodel TransactionDescriptoris the descriptor-local owner for transaction-scoped command discovery and the future static dispatch attachment point for transaction-scoped commands
Runtime discovery path:
- a
HolonSpaceinstance resolves its descriptor throughDescribedBy - the resulting
HolonDescriptormay be narrowed toHolonSpaceDescriptorwhen the descriptor holon isHolonSpaceType HolonSpaceDescriptor::transaction_model()resolves the exactly-one affordedTransactionType- the resolved
TransactionTypeholon is wrapped asTransactionDescriptor TransactionContextmay expose convenience access to the same descriptor by delegating through its bound holon space, but that convenience does not makeTransactionContextan instance ofTransactionType
Descriptor contract invariants:
TransactionTypeis a coreHolonTypeHolonSpaceDescriptoris constructible from theHolonSpaceTypeholonTransactionDescriptoris constructible from theTransactionTypeholonHolonSpaceDescriptor::transaction_model()resolves the exactly-oneTransactionTypeafforded byHolonSpaceTypeTransactionDescriptor::afforded_commands()returns every current transaction-scoped MAP CommandTransactionDescriptor::get_command_by_name(...)resolves commands by canonicalCommandNameBeginTransactionis not afforded byTransactionTypeBeginTransactionis afforded byHolonSpaceType- the command affordance relationship shape is schema-backed through
(HolonType)-[AffordsCommand]->(CommandType)and(CommandType)-[AffordedBy]->(HolonType)
Operator¶
OperatorType defines an introspectable operator available to a value type. Operators are first-class descriptor holons for discovery and UI/query introspection, but operator invocation is not modeled as a holon instance in this phase. Instead, value descriptors dispatch operator application to static Rust implementations.
Wrappers:
OperatorDescriptor
Prescribed core-schema role:
OperatorTypeis the schema type for operator descriptors- Rust should expose
OperatorTypeholons through theOperatorDescriptorwrapper ValueTypedescriptors should afford operators through a schema-declared relationship such asAffordsOperator- the intended shape is:
(ValueType) -[AffordsOperator]-> (OperatorType)
Minimal prescribed schema-backed accessors:
operator_name()for stable operator identity, derived from the shared descriptor header'stype_namedisplay_name()description()arity()operator_category()
Minimal prescribed schema-backed relationships:
afforded_by()
Required handwritten/runtime behavior on ValueDescriptor:
fn supported_operators(&self) -> Result<Vec<OperatorDescriptor>, HolonError>;
fn get_operator_by_name<N: ToOperatorName>(
&self,
operator_name: N,
) -> Result<OperatorDescriptor, HolonError>;
fn supports_operator_by_name<N: ToOperatorName>(
&self,
operator_name: N,
) -> Result<bool, HolonError>;
fn supports_operator(
&self,
operator: &OperatorDescriptor,
) -> Result<bool, HolonError>;
fn apply_operator(
&self,
operator: &OperatorDescriptor,
lhs: &BaseValue,
rhs: &BaseValue,
) -> Result<bool, HolonError>;
Operator rules:
- operators are core-defined, not domain-definable
OperatorDescriptor::operator_name()should deriveOperatorNamefrom the shared descriptor header- no separate
OperatorNameschema property is prescribed in this phase - operator affordances inherit through the value-type
Extendschain supported_operators()is schema-driven and flattened across inheritance- name-based operator lookup and support checks should accept
ToOperatorName apply_operator(...)is descriptor-local static Rust dispatch- there is no global operator registry
- there are no operator-instance holons in this phase
Current schema note:
- this behavior family is prescribed by this design and requires corresponding core-schema additions if they do not yet exist in the authoritative schema
Value¶
ValueDescriptor is the first place where the descriptor layer takes on substantial runtime semantics, not just structure. Value descriptors are the intended home for validation behavior and for the operator support needed by query and filtering logic.
Base wrapper:
ValueDescriptor
Likely narrower wrappers where behavior materially differs:
StringValueDescriptorIntegerValueDescriptorBooleanValueDescriptorBytesValueDescriptorEnumValueDescriptorValueArrayDescriptor
Required handwritten/runtime behavior on ValueDescriptor:
fn is_valid(&self, value: &BaseValue) -> Result<(), HolonError>;
fn supported_operators(&self) -> Result<Vec<OperatorDescriptor>, HolonError>;
fn get_operator_by_name<N: ToOperatorName>(
&self,
operator_name: N,
) -> Result<OperatorDescriptor, HolonError>;
fn supports_operator_by_name<N: ToOperatorName>(
&self,
operator_name: N,
) -> Result<bool, HolonError>;
fn supports_operator(
&self,
operator: &OperatorDescriptor,
) -> Result<bool, HolonError>;
fn apply_operator(
&self,
operator: &OperatorDescriptor,
lhs: &BaseValue,
rhs: &BaseValue,
) -> Result<bool, HolonError>;
Semantics:
- validation is descriptor-owned and implemented in Rust
- dispatch should be value-kind-specific
- invalid values should produce validation-oriented
HolonError - operator discovery should expose the effective inherited operator affordance set
- name-based operator lookup should resolve through the same effective inherited operator affordance set
- operator application should dispatch through descriptor-local static Rust code
This is a reallocation of behavior that would otherwise tend to sprawl into validators, query code, or other standalone helpers. The design intent is that validation operators and query operators are descriptor-owned, statically implemented, and dispatched through value descriptors rather than through a central registry.
Schema-backed additional accessors from the current core schema:
- none beyond the shared descriptor surface
Current schema deficiencies that should be corrected in follow-on core-schema work:
- integer constraint properties such as
min_value/max_value - enum variant access from
EnumValueType - element value type access from
ValueArrayValueType - operator affordance declarations from
ValueTypedescriptors toOperatorTypeholons
Enum Variant¶
EnumVariantDescriptor is narrower, but still important because enum semantics ultimately need both the enum value descriptor view and the variant view. The current schema is still incomplete here, so this section is intentionally thin.
Wrapper:
EnumVariantDescriptor
Schema-backed additional accessors from the current core schema:
- none beyond the shared descriptor surface
Current schema deficiency:
- the intended
variant_of()accessor is not yet justified by the current authoritative schema and should become schema-backed through a core-schema update
Collection¶
CollectionDescriptor is included for completeness of the TypeKind-organized model, even though the current authoritative core schema does not yet give it a richer descriptor-specific surface.
Wrapper:
CollectionDescriptor
Schema-backed additional accessors from the current core schema:
- none
Current schema note:
- the authoritative core schema does not currently define collection-specific descriptor obligations beyond the shared descriptor surface
Module Shape¶
Suggested Rust organization:
descriptors/mod.rsdescriptors/descriptor.rsdescriptors/type_header.rsdescriptors/inheritance.rsdescriptors/holon_descriptor.rsdescriptors/property_descriptor.rsdescriptors/relationship_descriptor.rsdescriptors/value_descriptor.rsdescriptors/generated/...
Generated output should remain clearly separated from handwritten traversal and validation logic.
Out of Scope¶
- schema evolution strategy
- dance-specific descriptor APIs
- command surface details
- TypeScript-side behavior allocation
- schema ripple workflow