TileFoundry Spec — Target¶
A Target is the immutable capability context compilation and the compiler
algorithms read. Architecture describes compilation identity and instruction
structure. Device describes fixed product resources. A target is a value that
answers questions about hardware; it does not own the operations that ask, and it
answers only by projecting the facts an asking algorithm declared.
1. Target¶
TargetT = TypeVar("TargetT", bound="Target")
FactsT = TypeVar("FactsT")
class Target:
"""Identify a compilation backend."""
name: ClassVar[str]
topology_levels: ClassVar[tuple[str, ...]]
@property
def identity(self) -> str: ...
@classmethod
def available(cls) -> tuple[Target, ...]: ...
def get_analyzer(self, selector: str) -> Analyzer: ...
def get_code_generator(self) -> CodeGenerator: ...
def validate_program_topology(self, topology: Topology) -> None: ...
def get_facts(
self,
facts_type: type[FactsT],
query: object | None = None,
) -> FactsT: ...
def register_target(cls: type[TargetT]) -> type[TargetT]: ...
def registered_targets() -> Mapping[str, type[Target]]: ...
- constraints:
- A custom Target MUST use one of two modes. A document-backed product adds
complete Architecture and Device documents to an existing Target class; it
MUST NOT add a product-specific Target subclass. A new backend implements
the Target interface directly and answers its hardware through
get_facts; it need not expose Architecture, Device, or hardware documents. - Inheriting bare Architecture or Device values and injecting them into an
existing document-backed Target is not a supported custom Target mode. It
bypasses both document schema validation and the
facts_resultprojection boundary. Target.get_analyzerMUST select the standard compute-cost, memory, roofline, and performance analyzers for every Target. Those algorithms consume only requested Facts, so a backend reuses them by answeringget_facts, not by inheriting a backend-specific analysis base class. A missing Facts projection MUST fail when the selected analyzer requests it.facts_result,TargetFactsError,TopologyLimitFacts,MemoryHierarchyFacts,ThroughputFacts,PerformanceServiceFacts, andParallelCapacityFactsMUST be importable fromtilefoundry.targetfor provider implementations.nameMUST be a non-empty class variable declared directly by every concrete registered Target class. It is the stable class registration identity, not a backend-family selector and not an instance value field.@register_targetMUST be the only Target registration form. It takes the class directly, accepts no decorator arguments, never constructs the class, and returns it unchanged.- Re-registering a class with the same module, qualified name, and registered name MUST be idempotent, including after a provider reload. A different provider claiming the same name MUST fail rather than replace the owner.
registered_targets()MUST expose one read-onlyname -> classview. The view MAY be used for inspection but MUST NOT construct a Target.identityMUST name one concrete Target value. It MUST be the device document ID for a document-backed Target and the registered classnamefor a Target with no device document.available()MUST return every value of that registered class which can be constructed in the current environment. A class with no hardware documents MUST return its one parameterless value by default.- Authored Target parameters MUST accept a constructed Target instance or their documented omitted state. A string MUST fail and MUST NOT be resolved through registration.
- Target values MUST remain immutable hardware values. Their service getters select immutable descriptors and Facts; normal Python inheritance carries those selections to a subclass unless it overrides or refuses them.
validate_program_topologyMUST reject a level outsidetopology_levelsand any resolved static extent that is not positive or exceeds the finiteTopologyLimitFactsbound for that level. The shared program check MUST use this method rather than reproduce a backend's topology limits.- A provider MAY import
Analyzerfromtilefoundry.targetto construct getter results. That package MUST NOT exposeCodeGeneratororLinkableModuleas provider API. - A missing getter capability MUST fail and name the concrete Target class, its registration name, and the requested selector, topology, or Facts type.
- Target values MUST NOT own code emission, linking, loading, or the public Analyze, compile, build, or jit orchestration.
1.1 Architecture¶
class Architecture:
"""Describe compilation architecture identity and structural facts."""
name: str
max_threads_per_cta: int
- constraints:
Architectureis a Target-level marker for the architecture-side value passed into a Facts projection and for its Python reconstruction support; it is not a provider extension point.- A document-backed Target MUST accept direct values only through its
backend-specific concrete Architecture type. A further product adds a
document of that backend's schema; a further backend implements
Targetandget_factsdirectly. - Concrete backend architecture values MUST be immutable.
nameMUST be the stable architecture identity used by compilation.max_threads_per_ctaMUST describe the architecture's static CTA thread limit when the architecture has a CTA thread level.
1.2 Device¶
- constraints:
Deviceis a Target-level marker for the device-side value passed into a Facts projection and for its Python reconstruction support; it is not a provider extension point.- A document-backed Target MUST accept direct values only through its
backend-specific concrete Device type. A further product adds a document
of that backend's schema; a further backend implements
Targetandget_factsdirectly. - Concrete backend device values MUST be immutable and describe one device.
nameMUST be the stable product identity.- Device-specific capacity, bandwidth, and compute-throughput facts belong to concrete subclasses.
- The split between the two is by what the fact is a property of, not by which consumer reads it. An Architecture owns instruction legality and the per-parallel-unit structural limits, which every product built on it shares. A Device owns how many such units the product has, its memory system, and its measured or published throughput. A fact MUST be recorded on exactly one side, and the other side MUST NOT restate it.
4. CudaTarget¶
class CudaTarget(Target):
"""CUDA target composed from one architecture and one device."""
name: ClassVar[str] = "cuda"
architecture: Architecture
device: Device
architecture_id: str | None
device_id: str | None
architecture_digest: str | None
device_digest: str | None
arch: str
topology_levels: tuple[str, ...]
def __init__(
self,
device: Device | str | Path,
architecture: Architecture | str | Path | None = None,
*,
arch: str | None = None,
) -> None: ...
def validate_program_topology(self, topology: Topology) -> None: ...
def topology_limit(self, name: str) -> int: ...
def get_analyzer(self, selector: str) -> Analyzer: ...
def get_code_generator(self) -> CodeGenerator: ...
def get_facts(self, facts_type: type[FactsT], query=None) -> FactsT: ...
def __repr__(self) -> str: ...
- constraints:
deviceandarchitectureMUST each accept an installed document ID or a concrete value. An ID MUST resolve immediately to the typed value, and the resolved ID and content digest MUST be retained (§10.2).arch, when provided, MUST equal the resolved architecture'sname; it is a consistency check and MUST NOT select or override an architecture.deviceMUST be required. The constructor MUST NOT select hardware for a caller who named none: a target nobody stated would answer about a machine nobody has.- An omitted
architectureMUST be read from the device document's declared compatibility, and MUST fail unless that document names exactly one. ADevicesupplied directly carries no document, so it MUST be given an architecture as well. archMUST equalarchitecture.name.- A pair selected by ID MUST be checked for declared compatibility. A value supplied directly carries no document, so it has no ID or digest and is exempt from that check: it is a distinct hardware value rather than a revision of an installed one.
CudaTargetMUST compose any installed architecture and device pair that declares compatibility. A further CUDA product is its two documents (§10.2) and nothing else: no Target subclass, and no architecture or device type of its own. The services and Facts are selected by the value already, and the numbers are in the documents, so either addition would carry nothing.CudaTarget.available()MUST contain one value per device document whose sole compatible architecture document is available. ItsidentityMUST be that device document's ID.- CUDA MUST select its standard Analyzer, Facts, and CodeGenerator services
through the corresponding getters. These selections MUST NOT use
Target.name, an exact-concrete-type table, or a second extension registration. - CUDA's
ThroughputFactsMUST publish the whole-device compute and HBM-bandwidth rates and nothing per unit; what one CTA gets through isPerformanceServiceFacts. - CUDA's
PerformanceServiceFactsMUST deriveunit_flopsandunit_bandwidthby dividing each whole-device rate bydevice.sm_count, MUST takeunit_opsfrom the device document unchanged, and MUST name its unitcta; compiler policy such asParallelCapacityFactsMUST NOT enter this derivation.unit_opsis not a division of a device peak, because no vendor publishes a device-wide integer, predicate, select, special or local-move rate to divide. topology_limit("cta")MUST equaldevice.sm_count, andtopology_limit("thread")MUST equal the architecture's corresponding structural limit. An unsupported name MUST be refused.__repr__MUST use the concrete class name and return a constructor expression that rebuilds an equal Target. A subclass retaining the CUDA constructor shape inherits it; a subclass with another constructor MUST override__repr__.- The store the threads of one CTA cooperate in MUST be projected as
architecture.shared_memory_per_cta_bytes, and MUST be reported as belonging to thectascope even when the level being asked about isthread. - The tensor-memory level MUST be projected with
architecture.tensor_memory_per_cta_bytesonly where the architecture states a capacity, and MUST be absent fromexplicit_levelswhere it statesNone. A level on hardware that has no such store would offer a plan somewhere to hold accumulators that does not exist.
Topology levels¶
A target's topology levels define the names a program may declare. The program hierarchy stops at those levels; warp, lane, and warpgroup structure belongs in thread mesh layouts.
- constraints:
CudaTarget.topology_levelsMUST be("cta", "thread")for this single-device target.- A declared program topology name MUST be one of its target's
topology_levels. A name outside that set MUST be refused naming the levels the target declares. get_facts(TopologyLimitFacts, "cta").max_static_extentMUST beNone: the CUDA grid is a launch shape rather than an SM allocation, so its static extent is unbounded here. The"thread"Facts projection MUST equalarchitecture.max_threads_per_cta.Topology.sizeMUST be an explicitShapeDim; construction withNoneMUST fail for every topology level.- Static declared topology extents MUST be positive integers within their target resource limits.
- Unsupported topology levels MUST fail at the generic lowering boundary.
4.1 CudaArchitecture¶
class CudaArchitecture(Architecture):
"""What one CUDA architecture states about itself."""
name: str
supported_compute_dtypes: tuple[DType, ...]
instruction_capabilities: tuple[str, ...]
max_threads_per_cta: int
max_threads_per_warp: int
max_warps_per_cta: int
max_resident_ctas_per_sm: int
shared_memory_per_sm_bytes: int
shared_memory_per_cta_bytes: int
smem_owner: str
unified_l1_shared_per_sm_bytes: int
registers_per_sm_32bit: int
rmem_owner: str
tensor_memory_per_cta_bytes: int | None
tmem_owner: str
def supports_compute_dtype(self, dtype: DType) -> bool: ...
def topology_limit(self, name: str) -> int: ...
- constraints:
- One value type MUST answer for every CUDA architecture, and a concrete value MUST be immutable. What separates one architecture from another is what its installed document records (§4.1.1, §4.1.2), so an architecture MUST NOT be given a type of its own: a class holding no number would restate an identity the value already carries.
nameMUST be the architecture identity CUDA compilation uses.- A CUDA architecture MUST own supported compute DTypes, instruction capabilities, and the thread/CTA structural limits.
- It MUST own the per-SM resource limits: resident CTAs, shared-memory capacity per SM and per CTA, and register-file capacity per SM. These are properties of the microarchitecture, so every product built on it shares them, and a device MUST NOT restate them.
- It MUST NOT carry a compute-throughput rate. A FLOP/s figure depends on the clock of one product, so it is a device fact (§4.2) even though the instruction it rates is the architecture's.
unified_l1_shared_per_sm_bytesMUST be the size of the one physical block the shared-memory carveout and the L1 data cache are both taken from, and MUST be at leastshared_memory_per_sm_bytes. The architecture MUST NOT state an L1 capacity: how much L1 remains depends on how much shared memory a program asked for, which is not a property of the hardware.tensor_memory_per_cta_bytesMUST be the whole tensor-memory store on an architecture whose MMA accumulates in one, because that store is allocated in columns spanning every lane and one CTA may hold all of them. It MUST beNonewhere the architecture has no such store, which is the same statement the document makes by recording that leaf unavailable.- CUDA explicit memory ownership MUST be read from the installed hardware
values as
smem -> cta,rmem -> thread, andtmem -> cta.scopeMUST NOT be used as an ownership map: register capacity is stated per SM while each thread owns its register values. - Every architecture document MUST declare the leaf behind every field, so a value the architecture does not have is recorded as absent rather than left out (§10.2).
- No field MAY carry a default: every value comes from the installed document (§10), so the type declares shape and never content.
4.1.1 SM90¶
The installed nvidia.sm90 architecture document.
- constraints:
- Its recorded identity MUST be
sm_90. - Storage and scale DTypes
f4e2m1andf8e8m0MUST NOT be recorded as compute DTypes. - It MUST record no tensor-memory capacity: the SM90 MMA accumulates in the register file, so there is no separate store to size.
4.1.2 SM100¶
The installed nvidia.sm100 architecture document.
- constraints:
- Its recorded identity MUST be
sm_100. - It MUST record
f4e2m1as a compute DType, because the SM100 MMA takes 4-bit operands directly.f8e8m0MUST NOT be recorded as one: it scales those operands rather than being multiplied. - It MUST record a tensor-memory capacity, and its instruction capabilities MUST name the MMA family that accumulates there rather than the SM90 family, which this architecture does not run.
4.2 CudaDevice¶
class CudaDevice(Device):
"""One CUDA device: how many SMs, and the memory and compute rates."""
name: str
sm_count: int
hbm_capacity_bytes: int
gmem_owner: str
hbm_bandwidth_bytes_per_second: int
l2_capacity_bytes: int | None
_dense_flops: tuple[tuple[DType, int], ...]
_service_ops: tuple[tuple[str, int], ...]
def peak_for(self, dtype: DType) -> int: ...
@property
def service_ops_per_second(self) -> dict[str, int]: ...
- constraints:
- One value type MUST answer for every CUDA device, on the same terms as the architecture (§4.1): a product is its document (§4.2.1, §4.2.2), not a type.
- A concrete value MUST be immutable, MUST describe one device, and MUST NOT carry a GPU count.
- It MUST describe how many SMs the product has and how its memory system and compute units perform. Per-SM structural limits belong to the architecture (§4.1).
peak_forMUST answer from the installed document for every compute DType the product's tensor cores have a mode for, and MUST raise an actionable error for any other DType. A product with no such mode records that leaf unavailable, so asking for its rate fails instead of reading as a rate nobody published._dense_flopsMUST hold a dense integer FLOP/s entry per DType. A published sparse peak MUST be halved and the division stated as the document's evidence, so no plan is priced against a rate structured sparsity is required to reach._service_opsMUST hold one integer results/s entry per service kind the installed document records, stated per CTA rather than per device. A kind the document does not record MUST be absent rather than zero, so a consumer that needs it refuses instead of pricing the work at nothing.l2_capacity_bytesMUST beNonewhen the installed document records no value for it. A recorded absence and a number are both statements about the product; a substituted figure would not be.gmem_ownerMUST betarget: all execution units of this single-device Target share one HBM allocation.- No field MAY carry a default, and no resource value MAY be written as a Python literal: the installed document is the single source (§10). Selecting a different installed document by ID is not an override; supplying a partial or edited number without a document behind it is, and is not admitted.
4.2.1 H200SXM¶
The installed nvidia.h200_sxm device document.
- constraints:
- Its recorded identity MUST be
h200_sxm, and it MUST declarenvidia.sm90as the architecture it composes with. - It MUST record a dense peak for each of
f32,f16,bf16, andfp8e4m3. throughput.f4e2m1MUST be recorded unavailable rather than omitted: the Hopper tensor cores have no FP4 mode, so the product has no such rate, which is a fact about it and not a number nobody published.- It MUST record a per-CTA service rate for each of
integer,predicate,select, andspecial, because a program that compares, selects or indexes asks for work no FLOP/s figure prices. Each MUST state its derivation inconditions: the instruction throughput in results/clock/SM from the vendor's arithmetic-instruction table, times the clock the publishedf32peak implies (67e12 / (132 SM * 128 results/clock * 2 FLOP/result)), stated per CTA. These are peak-style analytical envelopes rather than measured calibrations, andconditionsMUST say so, along with the proxy each one stands for. A service rate MUST NOT stand in for a bandwidth: movement at a level with no published one is stated as traffic and left untimed.
4.2.2 B200SXM¶
The installed nvidia.b200_sxm device document.
- constraints:
- Its recorded identity MUST be
b200_sxm, and it MUST declarenvidia.sm100as the architecture it composes with. - It MUST record a dense peak for each of
f32,f16,bf16,fp8e4m3, andf4e2m1. - Resource facts the vendor does not publish for this product, its SM count and its L2 capacity among them, MUST be recorded as measured on the described host rather than estimated or borrowed from a related part (§10.1).
5. CpuTarget¶
- constraints:
nameMUST be"cpu".- CPU host Functions MAY coexist with CUDA Functions in one module and are exempt from CUDA hardware-fact equality checks.
6. Target ownership and compile resolution¶
tilefoundry.targetMUST be the sole Target implementation package. The IR package MUST NOT own Target classes or Target imports.default_target()MUST return a newCudaTargeton the installednvidia.h200_sxmdevice. It is a compiler-owned omitted-target policy, not a string lookup and not a default offered by theCudaTargetconstructor.- There MUST be no global string-to-Target resolver. A registered name returns
class identity only through
registered_targets()and never constructs a value. - A
Targetbelongs to aModulerather than an authored HIRFunction. Target inheritance and its declaration rules are defined by core-irtarget-inheritance. - Analyze MUST obtain the Target from
Module.resolve_target()and from nowhere else. It does not accept a bareFunction, and it does not resolve an undeclared Target to a default: it reports hardware-dependent results, so measuring against a device the author never declared is a silent wrong answer. In particular it does not read a Target out ofModule.metadata; themetadata["target"]the compile pipeline carries is the codegen boundary's own record (passes §6), not a Target source for Analyze. - The compile boundary MAY resolve an omitted Module Target to
default_target()for lowering, becausejit(fn)on a plain Function is a documented entry point (runtime §1.3). It MUST attach that exact value to the normalized Module before lowering. - A lowered TIR
PrimFunctionretains its owntarget: after lowering it MUST be the exact Target instance resolved from its Module. It selects the CodeGenerator service that emits it. A synthesized host entry carries aCpuTarget(). - CUDA Functions are grouped by equal Target values in source order. More than one unequal CUDA Target group MUST fail before any generator runs.
7. AppleAmx¶
class AppleAmx:
"""Describe AMX compilation identity and structural capabilities."""
name: str
supported_compute_dtypes: tuple[DType, ...]
instruction_capabilities: tuple[str, ...]
amx_units_per_core: int
staging_bytes: int
accumulator_bytes: int
rmem_owner: str
def supports_compute_dtype(self, dtype: DType) -> bool: ...
def topology_limit(self, name: str) -> int: ...
- constraints:
nameMUST be the architecture identity used by AMX compilation.- AppleAmx MUST own the supported compute DTypes and the per-core AMX unit count. The modelled atom catalogue MAY be narrower than the supported compute DTypes.
- AppleAmx MUST own the X/Y staging and Z accumulator register files. They are
ISA geometry, so every part carrying this coprocessor shares them and a
device MUST NOT restate them.
staging_bytesMUST be the size of one staging file, the X and Y files being equal. rmem_ownerMUST beamx: each AMX unit owns its register-file values.- Product- and frequency-dependent throughput values MUST NOT be stored on AppleAmx.
- AMX has no CTA thread level, so AppleAmx MUST carry no CTA thread limit.
- No field MAY carry a default: every value comes from the installed document (§10).
8. AppleM2Pro¶
class AppleM2Pro:
"""Describe the apple_m2_pro package's fixed hard resource limits."""
name: str
sm_count: int
performance_core_count: int
efficiency_core_count: int
l1d_bytes_per_performance_core: int
l1d_bytes_per_efficiency_core: int
l2_bytes_per_performance_cluster: int
l2_bytes_per_efficiency_cluster: int
cache_line_bytes: int
unified_memory_capacity_bytes: int
unified_memory_owner: str
unified_memory_bandwidth_bytes_per_second: int
_unit_flops: tuple[tuple[str, tuple[tuple[DType, int], ...]], ...]
def throughput_for(self, unit: str, dtype: DType) -> int: ...
- constraints:
- AppleM2Pro MUST describe one package and MUST NOT carry a machine count.
sm_countMUST be the number of independent AMX units, which is the parallel-unit count a makespan divides work over. It MUST NOT be read as a core count: the performance cores outnumber the units and share them, so it MUST NOT exceedperformance_core_count.- Cache and core facts MUST distinguish the performance core from the efficiency core, and every value MUST come from the installed document (§10). No field MAY carry a default.
- A core-level tile's resident footprint MUST be bounded by
l1d_bytes_per_performance_core. The AMX register files bound one atom instance instead, which the storage filter enforces rather than a per-tile capacity, so the two MUST NOT be conflated. unified_memory_ownerMUST betarget; both thehostandgmemexplicit levels project that one target-wide ownership fact.throughput_forMUST be keyed by execution unit as well as DType, because the AMX coprocessor and the core's NEON pipes have separate measured rates.- A tile's traffic MUST be charged against
unified_memory_bandwidth_bytes_per_second, which unified memory backs. throughput_forMUST return a measured per-unit throughput recorded in the installed document, and MUST raise an actionable error for a unit or compute DType with no measured entry rather than return an estimate.
9. AmxTarget¶
class AmxTarget(Target):
"""Compose one AMX target from one architecture and one device."""
name: ClassVar[str] = "amx"
architecture: Architecture
device: Device
architecture_id: str | None
device_id: str | None
architecture_digest: str | None
device_digest: str | None
arch: str
topology_levels: tuple[str, ...]
def __init__(
self,
device: Device | str | Path | None = None,
architecture: Architecture | str | Path | None = None,
) -> None: ...
def topology_limit(self, name: str) -> int: ...
def validate_program_topology(self, topology: Topology) -> None: ...
def get_analyzer(self, selector: str) -> Analyzer: ...
def get_facts(self, facts_type: type[FactsT], query=None) -> FactsT: ...
- constraints:
deviceandarchitectureMUST accept an installed document ID, a document path, or a concrete value, on the same terms as §4. An omitted architecture MUST be read from the device document's sole compatibility declaration.AmxTarget()MUST select the installedapple.amxandapple.m2_prodocuments, andarchMUST equalarchitecture.name.AmxTarget.available()MUST contain one value per device document whose sole compatible architecture document is available. ItsidentityMUST be that device document's ID.topology_levelsMUST be("core", "amx"): the performance core one tile stream runs on, and the AMX unit inside that core which issues one atom.topology_limit("core")MUST equaldevice.performance_core_countandtopology_limit("amx")MUST equalarchitecture.amx_units_per_core.- Declared topology extents MUST be positive static integers within their level's limit. AMX has no launch shape, so a deferred or symbolic extent MUST NOT be admitted at either level.
- Unsupported topology levels MUST raise an actionable error naming the supported levels, from both the limit lookup and topology validation.
10. Installed hardware resources¶
Architecture and Device documents are the canonical authored hardware database, and the only place a hardware number is written. Each is a complete document in its own right; a target is the pair composed through a declared compatibility, never a single combined record.
Each document belongs to the Target class that understands its versioned
schema. The class declares one HardwareSpec containing its immutable schema
builders and the package holding its built-in documents. There is no
cross-backend hardware registry.
10.1 Document envelope¶
[spec]
schema = "tilefoundry.cuda.device/v3"
kind = "device"
id = "nvidia.h200_sxm"
[compatibility]
architectures = ["nvidia.sm90"]
[facts.memory.hbm.bandwidth]
value = 4800000000000
unit = "byte/s"
origin = "vendor"
source = "https://www.nvidia.com/en-us/data-center/h200/"
conditions = "4.8 TB/s peak HBM3e bandwidth, decimal"
[facts.memory.l2.bandwidth]
status = "unavailable"
conditions = "No validated number."
- constraints:
- The envelope MUST carry exactly
schema,kind, andid.kindMUST bearchitectureordevice. An unknown envelope key MUST fail. - An architecture document MUST declare compatibility under
devicesand a device document underarchitectures. A pair MUST compose only when at least one side names the other; neither MUST be inferred. - Tables under
factsare freely nestable namespaces owned by the target package named byschema. A leaf is identified by carryingvalueor an explicitstatus. - An available leaf MUST carry
valueandorigin. An unavailable leaf MUST omitvalue, recordstatus = "unavailable", and state the reason inconditions. The string"unavailable"MUST NOT be used as a value, so no caller can read a placeholder as a number. originMUST name how the value was obtained:vendorfrom the vendor's published figure,measuredon the described host,referencefrom a cited third party,derivedfrom other facts, orestimatedwhere it is a reading that no source states. A value not measured on the described host MUST NOT be recorded asmeasured; a reading rather than a citation MUST be recorded asestimated.derivedandestimatedMUST state how inconditions.- Compiler policy and a program's Topology extents MUST NOT appear in a
hardware document. They are compiler inputs, not immutable hardware truth:
a fixed-wave parallel capacity is a policy even when its current value
equals a device count. An explicit memory level's
owneris different: it MUST name one topology from the Target's hardware vocabulary, or the reserved wordtargetfor an allocation shared by the whole device. The document states that ownership directly and MUST NOT encode it as an absent value. - Only explicit memory levels MUST record an owner. Implicit caches receive no program residency and MUST NOT add an owner leaf merely to make their capacity scope look like a Target topology.
10.2 Registry and resolution¶
- constraints:
- A Target class that consumes hardware documents MUST declare one
HardwareSpec. Its package MUST contain that Target's built-in documents, and its schema mapping MUST state every document format the Target accepts. - A
HardwareSpecMUST resolve its documents by exact ID. There MUST be no cross-backend document table, search path, overlay, or partial document. Built-in package documents MAY be scanned lazily, but resolution MUST NOT depend on a previously constructed Target instance. - A schema name carries a version. Requiring a leaf a previous version did not MUST take a new version, because a document written against the old one no longer loads and the failure is otherwise a missing fact rather than a contract that moved.
- A Target's schema mapping is fixed by its class. A complete document MAY be adopted as another product only when its schema is in that mapping; adding another schema is adding a backend, not adding a hardware product.
- Explicitly registered documents MUST be routed to the one registered Target
class that declares their schema in its own
HardwareSpec. An inherited reference to another Target'sHardwareSpecMUST NOT claim that schema. Removing such a document MUST discard its resolved cache entry as well as its ID, without changing the Target's built-in package data. - An adopted device document MUST NOT become available until its declared
architecture document is present in the same
HardwareSpec. Explicit registration MUST reject that incomplete pair and name the architecture document required first. - Device document IDs and every provider Target identity share one uniqueness boundary. A collision MUST fail before persistent registration and name the existing Target value; it MUST NOT choose one source by load order.
- A schema MAY validate the documents of several products when they state the same fact paths, and MUST build one value type from all of them. A product is what its document records, so a schema MUST NOT select a type by the identity a document declares.
- A typed schema MUST validate exact fact paths, value types, units, required fields, and cross-field invariants, and MUST reject any leaf the document carries that the schema does not model, so a misspelled key cannot become an unused fact.
- Units MUST be normalized while constructing the typed value: algorithms see canonical integers such as bytes and bytes per second, never source strings or unit conversion.
- A schema MAY model a leaf as optional, which yields the recorded number or
Nonefor a leaf recorded unavailable. The leaf MUST still be declared: a document says either what the value is or that there is none, and a missing key MUST remain an error rather than becoming an absent value. - Resolution MUST retain each document's ID and content digest on the composed value, so a compiled artifact can name the exact resources it was built against. Editing any recorded value or its evidence MUST change the digest.
- A Target constructor MUST accept a custom document through a filesystem path. The document MUST be complete, retain its declared ID and digest, and MUST NOT enter that Target's available-ID namespace, so it can neither shadow nor replace an available resource.
- Unknown IDs, unsupported schemas, unmodelled or malformed facts, malformed envelopes, duplicate document IDs, and incompatible pairs MUST each raise their own actionable diagnostic rather than one shared parse failure.
- Reporting the resources behind a target MUST name both documents and their digests. A target composed from a directly supplied value has no document to report and MUST say so rather than name the installed resource it resembles.
11. Target Facts projection¶
A target-aware algorithm declares the immutable aggregate of facts it needs;
the concrete Target's get_facts method builds that aggregate. This is the one
boundary between a hardware specification and an algorithm's own view of it.
class Target:
def get_facts(
self,
facts_type: type[FactsT],
query: object | None = None,
) -> FactsT: ...
- constraints:
- A subclass MUST inherit its base Target's projections through normal Python
inheritance. It MAY override
get_factsfor hardware that differs and delegate unknown requests tosuper(). - A missing projection MUST fail immediately and MUST NOT fall back to a built-in Target or substitute built-in hardware values.
- A Facts aggregate MUST be a frozen dataclass. Aggregates MUST NOT inherit one universal Facts base.
queryis owned by the requesting algorithm. A hardware-only projection MUST require it to be absent, while a program-dependent one MAY validate its own private query type. There is no common query base or mandatory public program-view type.- A returned value that is not an instance of the requested Facts type MUST fail at the projection boundary, not inside the consuming algorithm.
- Projection MUST be a read. It MUST NOT analyze IR, build a constraint model, solve, export a plan, or mutate the Target, the IR, or runtime state. It only converts what the specification already records.
- There MUST be no public Facts registration step or global Target Facts table. A custom Target provider registers only its Target class.