Outcome
When you finish this guide, you have a minimal static external system and business entity — validated, uploaded, and ready to supply dimension or join data for ABAC and records search.
Static datasources hold reference rows your CRM or ERP does not provide — country mappings, department codes, or cross-system keys. They use the same recordStorage contract as vendor-backed datasources so dimensions, protection, and records search treat static rows like any other governed records.
Prerequisites
- Builder CLI login (
aifabrix auth status) - Static data for dimensions understood
- Dimension catalog keys planned (create catalog entries before cross-system ABAC if new keys are required)
- Integration folder writable under your workstation layout
Where it lives
| Artifact | Location |
|---|---|
| Application manifest | integration/<systemKey>/application.yaml |
| Static system JSON | integration/<systemKey>/<systemKey>-system.json |
| Static datasource JSON | integration/<systemKey>/<systemKey>-datasource-<name>.json |
| Deploy manifest | Generated via aifabrix json <systemKey> |
Use type: custom on the external system for static reference data not backed by a vendor OpenAPI spec.
How to set
- Create external integration scaffold:
aifabrix create <systemKey> --type external
-
Author system JSON — custom system for static mappings (api key or platform auth per your environment template).
-
Add datasource JSON — minimal valid shape:
{
"key": "case-region-mapping",
"displayName": "Case Region Mapping",
"systemKey": "static-reference",
"entityType": "recordStorage",
"resourceType": "region-mapping",
"primaryKey": ["externalId"],
"labelKey": ["caseId"],
"enabled": true,
"metadataSchema": {
"type": "object",
"properties": {
"externalId": { "type": "string", "index": true },
"caseId": { "type": "string", "index": true, "filter": true },
"country": { "type": "string", "index": true, "filter": true }
}
},
"fieldMappings": {
"attributes": {
"externalId": { "expression": "{{raw.externalId}}", "description": "Stable row key" },
"caseId": { "expression": "{{raw.caseId}}", "description": "CRM case identifier" },
"country": { "expression": "{{raw.country}}", "description": "Country for ABAC dimension" }
}
},
"dimensions": {
"country": { "type": "local", "field": "country" }
}
}
- List business entity in application manifest — run repair if file lists drift:
aifabrix repair <systemKey> --dry-run
aifabrix repair <systemKey>
- Validate structure:
aifabrix validate <systemKey>
- Upload when ready:
aifabrix upload <systemKey> --probe
aifabrix upload <systemKey>
- Load rows with
datasource load— after the business entity is published on the dataplane, import JSON or NDJSON fixtures through bulk record sync (aifabrix datasource load <datasourceKey> --app <systemKey>; use--dry-runfirst,--fileto override the default path):
aifabrix datasource upload reference-countries --app <systemKey>
aifabrix datasource load reference-countries --app <systemKey> --dry-run
aifabrix datasource load reference-countries --app <systemKey> -v
By default, fixture files live under integration/.data/ as {systemKey}-data-{entitySuffix}.json (for example static-reference-data-countries.json for business entity key reference-countries). Override with --file ./path/to/rows.json.
-
Declare cross-system joins — set
foreignKeyson CRM or ERP datasources socountryCode(or your normalized field) references the static country catalog. See Cross-system relationships. -
Reference in ABAC on protected datasources — cross-system expressions join vendor records to static rows and filter on shared dimension keys. See Dimensions and access model.
-
Re-run governance certification when static mappings change visibility for subject-scoped test users — dimension outcomes shift immediately after row updates.
Defaults and examples
ISO country catalog (join key across CRM, ERP, and document systems)
SAP, Salesforce, HubSpot, and other vendors represent country differently — full names, numeric codes, picklist IDs, or optional fields. For ABAC and records search, normalize on a single static catalog keyed by ISO 3166-1 alpha-2 (two-letter codes such as FI, DE, US).
| Source for codes | Notes |
|---|---|
| ISO 3166-1 | Authoritative standard — use alpha-2 as the canonical join key |
| Your master data team | Often already maintains an approved country list for ERP/CRM |
| Dev fixtures | Start with a small subset; expand to the full catalog under change control |
Do: normalize vendor country fields to ISO alpha-2 in CRM field mappings (or a mapping static datasource) before joining to the country catalog.
Don't: join CRM picklist IDs directly to static ISO rows without a translation table — codes will not match across systems.
Country catalog datasource (minimal manifest excerpt):
{
"key": "reference-countries",
"displayName": "ISO country catalog",
"systemKey": "static-reference",
"entityType": "recordStorage",
"resourceType": "country",
"primaryKey": ["countryCode"],
"labelKey": ["name"],
"enabled": true,
"metadataSchema": {
"type": "object",
"properties": {
"countryCode": {
"type": "string",
"index": true,
"filter": true,
"description": "ISO 3166-1 alpha-2 code"
},
"name": { "type": "string", "index": true, "filter": true }
}
},
"fieldMappings": {
"attributes": {
"countryCode": { "expression": "{{raw.countryCode}} | toUpper | trim" },
"name": { "expression": "{{raw.name}} | trim" }
}
},
"dimensions": {
"country": { "type": "local", "field": "countryCode" }
}
}
Fixture file (integration/.data/static-reference-data-countries.json):
[
{ "countryCode": "FI", "name": "Finland" },
{ "countryCode": "DE", "name": "Germany" },
{ "countryCode": "SE", "name": "Sweden" },
{ "countryCode": "US", "name": "United States" }
]
Load after publish:
aifabrix datasource load reference-countries --app static-reference --dry-run
aifabrix datasource load reference-countries --app static-reference -v
Then declare a foreign key on your CRM customer datasource so metadata.countryCode references reference-countries.countryCode. Records search and ABAC can join customers, documents, and the shared country dimension through the same code.
Export governed rows for diff or backup:
aifabrix datasource export reference-countries --app static-reference --limit 500 -v
Use export after load to confirm rows landed — see Validate loaded data in the dataplane.
Case-to-country mapping (when CRM has no country field)
| Field | Typical value |
|---|---|
| entityType | recordStorage for row-oriented mappings |
| resourceType | Catalog key describing mapping purpose (country, region-mapping, …) |
| primaryKey | Stable row key (countryCode for ISO catalog, externalId for mappings) |
| dimensions | Bind static metadata paths to catalog keys |
Use the case-region JSON in step 3 when the vendor system lacks country entirely and you map business keys to countries in a separate static datasource.
When to repair
After adding or renaming business entity files:
aifabrix repair <systemKey> --expose --rbac
aifabrix json <systemKey>
Validate loaded data in the dataplane
After datasource load, confirm what the platform stores by exporting through governed records search — not a database admin dump:
aifabrix datasource export reference-countries --app <systemKey> --limit 500 -v
aifabrix datasource export reference-countries --app <systemKey> --format ndjson --filter '{"countryCode":{"eq":"FI"}}'
Compare export output to your fixture file (row counts, keys, dimension fields). For subject-scoped proof, run export with the same Bearer token you use for verify-governance or records-search checks — not only the integrator token used to load rows.
Do: treat datasource export as “what Records Search returns for your principal.” Use -v to see ABAC exclusion counts when rows exist in storage but not in the export file.
Don't: assume export lists every physical row. Export uses the governed search path (record:search permission) with ABAC and dimension projection applied — the same visibility rules as the dataplane UI and records search APIs. This is not a platform-admin or direct-database service.
How access rights apply (load vs export)
Both commands run with your deployment token (Bearer/API key or application token from client credentials). Neither bypasses platform RBAC.
| Command | Permission | What your token controls |
|---|---|---|
datasource load |
external-data-source:sync |
Whether you may write fixture rows through bulk sync. Without sync scope, the command is denied. |
datasource export |
record:search |
Which rows you may read back through governed search. With protection and dimension grants active, you may receive only the subset your principal is allowed to see — even if load inserted more rows under a broader sync token. |
Load vs read scope: An integrator token that can sync all countries may still fail to export all countries when logged in as a subject-scoped test user. Validate governance with the subject token, or compare verify-governance / records-search results — not export alone under a privileged sync account.
Validate
aifabrix validate <systemKey>
aifabrix datasource validate reference-countries --app <systemKey>
aifabrix datasource load reference-countries --app <systemKey> --dry-run
aifabrix datasource load reference-countries --app <systemKey> -v
aifabrix datasource export reference-countries --app <systemKey> --limit 500 -v
aifabrix datasource validate case-region-mapping --app <systemKey>
Run subject-scoped verify-governance when static rows participate in protection-backed datasources. Confirm records search returns expected scope for a test subject after mapping loads.
Common mistakes
| Mistake | Fix |
|---|---|
| Business Entity not in application.yaml | repair syncs file lists |
| Missing dimension bindings | Add dimensions object on static datasource |
| Wrong load file path | Default: integration/.data/{systemKey}-data-{suffix}.json; use --file to override |
| Export shows fewer rows than loaded | Expected under subject-scoped tokens — export is governed read, not admin dump |
| Stale mapping data | Operational process for bulk reloads via datasource load |
| Skipping upload | Static rows exist only after publish |
| Huge files in static JSON | Use bulk load — do not embed all rows in manifest |
Limits
Bulk load uses the external-data-source:sync permission (bulk sync API). Export uses record:search (governed records search with ABAC). Row limits and batch sizes vary by deployment — confirm with your operator before large loads.
Changing static mapping rows changes ABAC outcomes immediately after sync. Treat updates as production access changes — not informal spreadsheet edits. datasource export under a restricted principal may show zero rows while storage still holds data excluded by ABAC — use -v and subject-scoped tokens when validating protection.