Declaring App Dependencies
An app rarely lives in isolation. A pricing rule reads asset.product.name. An order-enrichment reactor imports a lookupPrices function from the Products app. A returns app navigates from order to custom returnRequest nodes contributed by another app.
The requires section of h_app.yaml turns those assumptions into explicit contracts. The CLI, language server, and server use the same declaration to:
- Compile your components against a realistic stub while you author.
- Reject installation if your app references graph shapes or modules that are neither declared nor part of the base graph.
- Surface
Brokenruntime state in the portal when the active tenant no longer satisfies your contract.
If your app only reads fields that exist in the base Hantera graph and only imports modules shipped inside your own app, you do not need a requires block.
Where requires lives
requires is an optional top-level field of h_app.yaml, alongside components, ingresses, registryEntries, and others.
id: my-pricing-app
name: My Pricing App
version: 1.0.0
requires:
graph:
# Nodes, fields, edges, and sets the app reads or navigates.
...
modules:
# Filtrera modules the app imports from other apps.
...
components:
- id: components/apply-pricing.hrcYou can declare only requires.graph, only requires.modules, or both.
Graph contracts (requires.graph)
A graph contract says: I depend on this graph shape. Wherever my app runs, the graph must provide at least these nodes, fields, edges, and sets.
Nodes, fields, and edges
Inside requires.graph.nodes, list every node you reference. For each node, declare only the fields and edges you actually use.
requires:
graph:
nodes:
asset.product:
fields:
name:
type: text
dimension: locale
vatClass:
type: enum
edges:
skus:
cardinality: many
relatedNode: asset.product.sku
prices:
cardinality: many
relatedNode: asset.price
asset.product.sku:
fields:
skuNumber:
type: text
quantity:
type: number
asset.price:
fields:
currentPrice:
type: numberSets
requires.graph.sets declares top-level query entry points your components address.
requires:
graph:
sets:
products: { node: asset.product }
prices: { node: asset.price }If your components never query from a top-level set, you can omit sets.
Graph field types
For requires.graph.nodes.<node>.fields.<field>.type, use graph field types:
textenumnumberinstant[text][enum]
enumDefinition is only valid for enum and [enum].
Dimensions and enums
If a field has a dimension, declare it explicitly.
fields:
name:
type: text
dimension: localeEnum fields use type: enum and optionally enumDefinition.
fields:
vatClass:
type: enum
enumDefinition: vat-classModule contracts (requires.modules)
When one app imports a module exported by another app, declare it under requires.modules.
requires:
modules:
/apps/products/price-lookup.module.hrc:
exports:
lookupPrices:
type: '(params: { productNumbers: [text], currencyCode: text, priceListKeys: [text], window: duration }) => { text -> { currentPrice: number | nothing, history: [{ at: instant, price: number | nothing }], lowestPrice: number | nothing, highestPrice: number | nothing } }'The example above is the actual contract for the Hantera Products app's Price Lookup module.
The key is the path part of the component:// URI (everything after component://).
For requires.modules.<module>.exports.<name>.type, values are Filtrera type strings. Any valid Filtrera type expression is allowed.
Full example
id: order-enrichment
name: Order Enrichment
version: 1.0.0
requires:
graph:
nodes:
asset.product:
fields:
name:
type: text
dimension: locale
edges:
skus:
cardinality: many
relatedNode: asset.product.sku
asset.product.sku:
fields:
skuNumber:
type: text
sets:
products: { node: asset.product }
modules:
/apps/products/price-lookup.module.hrc:
exports:
lookupPrices:
type: '(params: { productNumbers: [text], currencyCode: text, priceListKeys: [text], window: duration }) => { text -> { currentPrice: number | nothing, history: [{ at: instant, price: number | nothing }], lowestPrice: number | nothing, highestPrice: number | nothing } }'
components:
- id: components/enrich-order.hrc
ingresses:
- id: orders
componentId: components/enrich-order.hrc
type: http
acl:
- actors/order:create
properties:
route: order-enrichment/orders
httpMethod: POSTHow validation works
Validation runs in three phases.
1) h_ app pack (manifest-level validation)
The CLI rejects manifests if, for example:
- A
requires.moduleskey resolves to a local module in the same app. - A
requires.graphdeclaration overlaps with your ownregistryEntriescontribution. - A
requires.graphfield type is outside the supported graph field type set (text,enum,number,instant,[text],[enum]). - A
requires.modulesexporttypestring cannot be parsed as Filtrera. - An edge
relatedNodecannot be resolved in the composite context.
2) Install / isolated compile
Components compile against a stub composed of:
- Base Hantera graph
- This app's own contributions
requires.graphoverlaysrequires.modulesstub modules
3) Activation
When activated in a tenant, components are compiled against real producer apps in the activated union. Type mismatches surface as regular Filtrera diagnostics, and runtime state becomes Broken until compatibility is restored.
Portal view
In Apps → App details, you can see:
- Runtime state (
Starting,Running,Broken) - Diagnostics (for
Broken) - Declared Dependencies (
requires.graph,requires.modules) - Derived Provides (graph fields and edges from
registryEntries)