Ikanos — Guide — Importing¶
Table of Contents¶
- Overview
- The Import Directive
- Standalone Source Files
- Consumes
- Exposes
- Aggregates
- Binds
- How Resolution Works
- Cross-File References
- Alias Disambiguation
- Validation & Linting
- Error Catalog
- Worked Example — Full Four-Section Import
- FAQ
Overview¶
Ikanos supports importing entries from external source files into a capability's consumes, exposes, aggregates, and binds sections. This enables:
- Modularization — split a large capability into smaller, focused files
- Reuse — share adapter definitions, domain functions, or binding catalogs across multiple capabilities
- Separation of concerns — review and lint each section independently
The import mechanism uses a single, uniform directive — from / import / as — that works identically across all four sections.
Key principle: A capability is the only assembly point. Source files are leaves — they cannot import other files. This keeps the model simple, predictable, and cycle-free.
The Import Directive¶
Every imported entry uses the same three fields:
- from: <path to source file>
import: <namespace of the entry in that file>
as: <optional alias>
description: <optional documentation>
| Field | Required | Description |
|---|---|---|
from |
yes | Relative or absolute path to a source file. Relative paths resolve against the directory of the importing file. |
import |
yes | The namespace of the entry to take from the source file. |
as |
no | Renames the imported entry's namespace in the importing capability. Useful when two sources use the same namespace. |
description |
no | Free-form documentation for the import. |
Example¶
capability:
consumes:
- from: "./shared/registry.consumes.yml"
import: registry
- from: "./shared/legacy.consumes.yml"
import: legacy
as: legacy-dockyard
description: "Legacy Dockyard adapter, imported with alias"
Standalone Source Files¶
Each section can live in its own YAML file. The file has a root-level ikanos version and the section array at the root level (not nested under capability).
Consumes¶
Convention: *.consumes.yml
ikanos: "1.0.0-alpha3"
consumes:
- type: "http"
namespace: "registry"
baseUri: "https://api.registry.example.com"
resources: # alpha3 — keyed map
ships:
path: "/ships"
operations: # alpha3 — keyed map
list-ships:
method: GET
Exposes¶
Convention: *.exposes.yml
ikanos: "1.0.0-alpha3"
exposes:
- type: "rest"
namespace: "weather-rest"
address: "localhost"
port: 8080
resources: # alpha3 — keyed map
forecasts:
path: "/forecasts"
operations: # alpha3 — keyed map
get-forecast:
method: GET
call: weather-api.get-forecast
Aggregates¶
Convention: *.aggregates.yml
Standalone aggregates documents use the root-level array form (with explicit namespace: on each entry):
ikanos: "1.0.0-alpha3"
aggregates:
- namespace: "forecast"
display: "Forecast Domain"
flows: # alpha3 — keyed map (was `functions`)
get-forecast:
description: "Retrieves weather forecast for a location"
call: "weather-api.get-forecast"
Binds¶
Convention: *.binds.yml
ikanos: "1.0.0-alpha3"
binds:
- namespace: "api-secrets"
description: "API credentials"
location: "./secrets.env"
keys:
required:
- API_KEY
Note:
BindingSpec.locationis the runtime variable source (where to fetch secrets). It is not related to the import directive'sfromfield, which is the parse-time source path.
How Resolution Works¶
The engine resolves imports eagerly at capability load time, before any adapter or aggregate wiring. After resolution, the rest of the engine sees only fully-materialized inline entries — no import directive escapes the constructor.
Resolution pass order¶
- consumes — all consumes imports are loaded
- aggregates — all aggregates imports are loaded
- exposes — all exposes imports are loaded
- binds — all binds imports are loaded
- cross-file references —
callandreftargets are validated against the now-complete section lists
What happens during resolution¶
For each import entry:
- Read the
fromandimportfields - Resolve the file path relative to the importing file's directory
- Load and parse the source file (cached across sections)
- Find the matching
namespacein the source file's section array - Deep-copy the entry (to prevent cross-capability mutation)
- Apply the
asalias if present (replace the entry's namespace) - Replace the import directive with the materialized inline entry
Caching¶
A shared SourceFileLoader caches parsed source files across the four section resolvers. If two sections import from the same file, it is parsed only once.
Cross-File References¶
Imported entries may contain call and ref references. These references are resolved against the importing capability's sections, not the source file.
For example, an imported exposes adapter with call: weather-api.get-forecast will look for a weather-api namespace in the importing capability's consumes — not in the source file. This means:
- The source file declares the contract shape (what tools exist, what calls they make)
- The importing capability provides the wiring (the consumed APIs those calls target)
This separation is intentional: the same exposed adapter can be reused across capabilities that wire it to different consumed APIs.
Alias Disambiguation¶
When two source files export entries with the same namespace, use as to give each a unique name in the importing capability:
capability:
consumes:
- from: "./adapters/weather-us.consumes.yml"
import: weather-api
as: weather-us
- from: "./adapters/weather-eu.consumes.yml"
import: weather-api
as: weather-eu
Without aliases, the two entries would collide and the engine would throw an ImportException with a "Duplicate namespace" message.
Validation & Linting¶
Import directives are validated at three levels:
| Level | Tool | What it checks |
|---|---|---|
| JSON Schema | ikanos-schema.json |
Structural validity of import entries; root-level oneOf prevents imports in standalone files |
| Spectral Rules | ikanos-rules.yml |
Directive completeness, alias uniqueness, leaf enforcement, namespace requirements |
| Engine Resolver | ImportResolver |
File existence, namespace lookup, cross-file reference integrity, deep-copy isolation |
Key Spectral rules for imports¶
| Rule | Severity | What it checks |
|---|---|---|
ikanos-import-from-required |
error | from entries must also have import |
ikanos-import-import-required |
error | import entries (without type) must also have from |
ikanos-import-unique-alias |
error | Effective namespaces are unique per section |
ikanos-standalone-no-imports |
error | Standalone files must not contain import entries |
ikanos-exposes-namespace-required |
error | Source exposes entries must have namespace |
ikanos-aggregates-namespace-required |
error | Source aggregates entries must have namespace |
See the Specification - Rules page for the full rule reference.
Error Catalog¶
All import errors share a uniform shape: [sectionName] message.
| Error message | Cause | Fix |
|---|---|---|
[consumes] Import 'from' is required |
from field is missing or empty |
Add the from field pointing to the source file |
[consumes] Import 'import' is required |
import field is missing or empty |
Add the import field with the namespace to import |
[consumes] Import source file not found: /path/to/file |
The from path does not resolve to an existing file |
Check the relative path and file name |
[consumes] Failed to load source file: /path |
The source file is malformed YAML | Fix the YAML syntax in the source file |
[consumes] No consumes entries found in source file: /path |
The source file has no entries in the expected section | Verify the file has a root-level section array |
[consumes] Namespace 'foo' not found in source consumes file: /path |
The requested namespace does not exist in the source file | Check the spelling and verify the source file's namespaces |
[consumes] Duplicate namespace 'foo' after import resolution |
Two entries resolve to the same namespace | Use as aliases to disambiguate |
Replace [consumes] with any section name ([exposes], [aggregates], [binds]) — the format is identical.
Worked Example — Full Four-Section Import¶
A capability that imports all four sections from shared files:
ikanos: "1.0.0-alpha3"
binds:
- from: "./shared/secrets.binds.yml"
import: api-secrets
capability:
consumes:
- from: "./shared/registry.consumes.yml"
import: registry
- from: "./shared/legacy.consumes.yml"
import: legacy
aggregates:
- from: "./shared/fleet.aggregates.yml"
import: fleet-domain
exposes:
- from: "./shared/fleet.exposes.yml"
import: fleet-rest
- type: mcp
namespace: fleet-mcp
port: 3001
tools:
- name: list-ships
ref: fleet-domain.list-ships
This capability:
- Imports two consumed adapters (registry, legacy)
- Imports a shared aggregate domain (fleet-domain)
- Imports a REST adapter and declares an inline MCP adapter
- Imports a binding catalog for secrets
After resolution, all entries are materialized inline — the engine sees no import directives.
FAQ¶
Why was location renamed to from for consumes imports?¶
The binds section already uses location to mean "runtime variable source" (e.g., ./secrets.env, vault://app/prod). Reusing location for "import source file path" would create semantic ambiguity. from is unambiguous, short, and idiomatic (inspired by ES module syntax: import x from './foo').
Can standalone files import other files?¶
No. Standalone files are leaves in the import DAG. Only capability documents may import. This is enforced by both the JSON Schema (oneOf) and the Spectral rule ikanos-standalone-no-imports.
Can I import individual functions instead of a whole aggregate?¶
Not in 1.0. You import an entire namespaced entry. Selective (field-level) import is a candidate for a future evolution. If you need only a subset of functions, declare a local aggregate.