SSSR-URL
Asset/URL Link Registry
(Addendum to SSSR – Smart Searchable Signal Registry)
1. Introduction
The Smart Searchable Signal Registry (SSSR) is the authoritative, cross-module control plane for all semantic references in ZAYAZ. While its core purpose is to govern data signals (metrics, codes, formulas), the same principles apply to digital assets — such as hazard pictograms, logos, templates, documents and other URL-addressable resources.
The Asset/URL Link extension of SSSR centralizes the definition, classification, and resolution of all external and internal file references. Instead of embedding brittle, hard-coded URLs in data tables, ZAYAZ stores semantic asset identifiers (sssr:asset.<class>.<asset_id>[.<ext>]) which are dynamically resolved into valid URLs at runtime.
This approach delivers four strategic benefits:
- Durability – Asset references remain valid even if file locations, CDN providers, or folder structures change.
- Governance & Auditability – Every asset is registered with metadata, versioning rules, usage scope, and compliance tags, ensuring traceability across all consuming modules.
- Dynamic Optimization – The resolver can select the most appropriate asset variant (e.g., SVG for web, CMYK TIF for print) based on runtime context, without changing stored references.
- Cross-Module Consistency – All engines (FOGE, EXPORT, DAIM, APIs) resolve assets through the same policy layer, guaranteeing uniform output.
In practice, the asset subsystem introduces two dedicated registry tables alongside the core SSSR:
sssr_asset_class_registry– Defines asset classes (e.g.,hazard_pictograms), their path templates, default folders, fallback order, and resolution policies.sssr_assets_registry– Defines individual assets, their available variants, template overrides, and the source table/column where they are referenced.
Together with sssr_global_config (for system-wide defaults like cdn_base), these tables enable a dedicated assets-URL-resolver service to translate [SSSR_REF: …] markers into correct, policy-compliant URLs in real time.
This design ensures that asset linking in ZAYAZ is as robust, auditable, and future-proof as its core data signal referencing, while also enabling direct integration into internal modules, partner APIs, and client-facing endpoints.
2. Always go through the SSSR instead of hardcoding direct links to libraries of assets.
Here’s why:
- Traceable Asset Management
- If we reference
[SSSR_REF: sssr:hazard_pictograms.gif_url @ HZP-0003](example), we can later change the storage location, CDN provider, or file format without breaking any references. - The SSSR keeps the logical link (“hazard pictogram – GIF variant”) separate from the physical URL, so only the registry entry needs updating.
- Multi-Format Routing
- A single pictogram may exist in GIF, EPS, TIFF, SVG, etc.
- By registering all variants in the signal metadata (symbol_url_gif, symbol_url_eps, symbol_url_label_tiff), we can let engines (FOGE, DAIM, exporters) pick the right one for the output context (web dashboard = GIF/SVG, print = EPS/TIFF) without hardcoding it.
- Compliance & Auditability
- As an example, some hazard pictograms are legally regulated (GHS, CLP) — linking them via SSSR ensures we can log which exact official asset was served and from which source version. This is important for traceability in SDS or chemical safety reports.
- CDN & Localization Support
- The SSSR entry can route to a different URL based on:
- Geo (EU/US CDNs)
- Language (localized hazard labels)
- Theme (dark/light UI)
- Direct linking in the symbol_url column skips all that intelligence.
- Future-proofing for Theming/Branding
- If Viroway or a client wants custom hazard icon styling, only the SSSR entry changes.
- Direct links in our tables would require bulk updates and risk breaking historical data.
Implementation
- In
code_registry.symbol_url, store theSSSR_REFpointing to the canonical pictogram entry (e.g., GIF version for default use). - Keep the
hazard_pictogramstable as the asset source table in SSSR with columns for each format. - Let FOGE or your export logic resolve the right format at runtime via preferred_format or output_context.
3. SSSR Links
3.1. SSSR Signal Registry
The signal registry (signal_registry) provides a universal way to reference any row in any table.
Each signal_id has three parts:
- "sssr:" (prefix marking it as a system reference)
- The source table
- The source column
The table and column are separated by a dot ".".
Example:
sssr:label_elements.label_element_id
Here:
label_elements= table namelabel_element_id= column name
To reference a specific row, append " @ <row_id>".
Example:
sssr:label_elements.label_element_id @ LEID-0001
Finally, wrap it with [SSSR_REF: … ] to create the full inline reference:
[SSSR_REF: sssr:label_elements.label_element_id @ LEID-0001]
Rules:
- Always put a space after "
SSSR_REF:". - Always put spaces around "@".
- All links in all tables (except inside the signal registry itself) must use this format.
3.2. SSSR Asset Registry
Assets are managed in a dedicated registry: sssr_asset_registry.
Assets are different from signals because they are independent objects (files, images, pictograms, logos, etc.) that are referenced by many rows across the system.
3.2.1. Basic asset reference
To link to an asset, the format is:
sssr:asset.<class>.<asset_id>[.<ext>]
Examples:
sssr:asset.hazard_pictograms.GHS01.gif
sssr:asset.client-profiles.css.css
sssr:asset.client-profiles.main-logo.gif
Where:
asset= registry type<class>= asset class (defined in thesssr_asset_class_registrytable)<asset_id>= the logical asset identifier within that class<ext>= the file extension or variant selector when explicitly pinned
Assets may be resolved in two modes:
-
Unscoped asset reference
Used for globally shared assets that do not require a client or tenant scope.Example:
sssr:asset.hazard_pictograms.GHS01.gif -
Scoped asset reference
Used when the asset must be resolved within a governed scope such as a client.Example:
sssr:asset.client-profiles.css.css @ eco-173-123-456-789sssr:asset.client-profiles.main-logo.gif @ eco-173-123-456-789
For scoped assets, the suffix:
@ <client_id>
is part of the canonical lookup key used by the resolver. It is not provenance metadata.
This means the generalized asset-reference form is:
sssr:asset.<class>.<asset_id>[.<ext>] @ <scope_id>
In the current ZAYAZ branding model, <scope_id> is normally the client_id.
3.2.2. Extended Inline Form
When we want to store or export a full inline reference with traceability, we use the extended form:
[SSSR_REF: sssr:asset.<class>.<asset_id>[.<ext>] [ @ <scope_id> ] [ @ sssr:<table>.<column> @ <row_id> ]]
This form supports three layers:
-
Target asset
The canonical asset reference. -
Optional resolution scope
Used when the asset must be resolved in a specific governed scope, such as a client. -
Optional provenance metadata
Used to record which table/column/row emitted or stored the reference.
Examples:
Global asset, no provenance
[SSSR_REF: sssr:asset.hazard_pictograms.GHS01.gif]
Client-scoped asset, no provenance
[SSSR_REF: sssr:asset.client-profiles.css.css @ eco-173-123-456-789]
Client-scoped asset with provenance
[SSSR_REF: sssr:asset.client-profiles.css.css @ eco-173-123-456-789 @ sssr:tenant_branding.css_sssr_ref @ eco-173-123-456-789]
Global asset with provenance
[SSSR_REF: sssr:asset.hazard_pictograms.GHS01.gif @ sssr:label_elements.symbol_sssr @ LEID-0029]
This tells us:
- the asset target to resolve
- optionally the scope in which it must be resolved
- optionally the source table/column and row that emitted the reference
Important distinction:
- The first
@ ...suffix, when present immediately after the asset target, is the resolution scope. - The later
@ sssr:<table>.<column> @ <row_id>segment is provenance.
So for scoped assets:
@ <client_id>= part of lookup@ sssr:<table>.<column> @ <row_id>= audit/export traceability
Summary:
- Signals (
sssr:<table>.<column> @ row_id) = references to data rows. - Global assets (
sssr:asset.<class>.<asset_id>[.<ext>]) = references to globally shared files/resources. - Scoped assets (
sssr:asset.<class>.<asset_id>[.<ext>] @ <client_id>) = references to assets resolved within a specific client scope. - Extended inline form = adds provenance metadata for auditing, exports, portability, and AI self-healing.
3.3. Extended Inline Form for Links to Assets
In tables, the canonical inline string form is:
[SSSR_REF: sssr:asset.<class>.<asset_id>[.<ext>] [ @ <scope_id> ] [ @ sssr:<table>.<column> @ <row_id> ]]
Everything before the provenance segment is the target to resolve as a URL.
That means:
sssr:asset...= the target asset- optional
@ <scope_id>= the resolution scope - optional
@ sssr:<table>.<column> @ <row_id>= provenance only
Examples:
[SSSR_REF: sssr:asset.hazard_pictograms.GHS01]
[SSSR_REF: sssr:asset.hazard_pictograms.GHS01.gif]
[SSSR_REF: sssr:asset.client-profiles.css.css @ eco-173-123-456-789]
[SSSR_REF: sssr:asset.client-profiles.main-logo.gif @ eco-173-123-456-789]
[SSSR_REF: sssr:asset.client-profiles.css.css @ eco-173-123-456-789 @ sssr:tenant_branding.css_sssr_ref @ eco-173-123-456-789]
[SSSR_REF: sssr:asset.hazard_pictograms.GHS01.gif @ sssr:label_elements.symbol_sssr @ LEID-0029]
Precise grammar (so it’s easy to parse)!
EBNF:
SSSR_REF := "[SSSR_REF: " TARGET ( " @ " SCOPE_ID )? ( " @ " SRC_REG ( " @ " ROW_ID )? )? "]"
TARGET := "sssr:" 1*( CHAR - WHITESPACE - "]" - "@" )
SCOPE_ID := 1*( CHAR - WHITESPACE - "@" - "]" )
SRC_REG := "sssr:" 1*( CHAR - WHITESPACE - "@" - "]" )
ROW_ID := 1*( CHAR - "]" )
Rules:
- Exactly one space after the colon and around each @.
- No trailing spaces.
- TARGET MUST start with sssr: and contain no ] or @.
SCOPE_IDis the resolution scope and, in the current client-branding model, is normally theclient_id.SRC_REGMUST besssr:<table>.<column>.ROW_IDshould be the row’s PK (e.g., CR-00213).
Converter → JSON (TypeScript)
type Enriched =
| {
target: string;
scope_id?: string;
source?: { registry: string; row_id?: string };
};
export function parseSSSRRef(ref: string): Enriched | null {
const m = ref.match(
/^\[SSSR_REF:\s*([^\]@]+?)\s*(?:@\s*([^@\]]+?)\s*)?(?:@\s*(sssr:[\w.]+)\s*(?:@\s*([^\]]+))?)?\]$/
);
if (!m) return null;
const [, target, scopeId, srcReg, rowId] = m;
const out: Enriched = { target: target.trim() };
if (scopeId && !scopeId.trim().startsWith("sssr:")) {
out.scope_id = scopeId.trim();
}
if (srcReg) {
out.source = {
registry: srcReg.trim(),
...(rowId ? { row_id: rowId.trim() } : {})
};
}
return out;
}
Converter → JSON (Postgres SQL)
CREATE OR REPLACE FUNCTION sssr_to_json(ref text)
RETURNS jsonb
LANGUAGE plpgsql
IMMUTABLE
AS $$
DECLARE
m text[];
BEGIN
-- regex groups:
-- 1 = target
-- 2 = scope_id
-- 3 = source registry
-- 4 = row_id
SELECT regexp_match(
ref,
'^\[SSSR_REF:\s*([^\]@]+?)\s*(?:@\s*([^@\]]+?)\s*)?(?:@\s*(sssr:[\w.]+)\s*(?:@\s*([^\]]+))?)?\]$'
)
INTO m;
IF m IS NULL THEN
RETURN NULL;
END IF;
RETURN jsonb_strip_nulls(
jsonb_build_object(
'target', trim(m[1]),
'scope_id', CASE
WHEN m[2] IS NOT NULL AND m[2] !~ '^sssr:'
THEN trim(m[2])
ELSE NULL
END,
'source', CASE
WHEN m[3] IS NULL THEN NULL
ELSE jsonb_strip_nulls(
jsonb_build_object(
'registry', trim(m[3]),
'row_id', CASE WHEN m[4] IS NULL THEN NULL ELSE trim(m[4]) END
)
)
END
)
);
END $$;
Direct Link Behavior
- The resolver must treat:
- TARGET alone as sufficient for global asset resolution
- TARGET +
SCOPE_IDas sufficient for scoped asset resolution
- The provenance segment:
@ sssr:<table>.<column> @ <row_id>is ignored by URL resolution and used only for audit, export, logging, and AI autonomy.
SSSR Link Examples: Signals vs Assets
| Type | Basic Form | Extended Inline Form (with provenance) | Meaning |
|---|---|---|---|
| Signal (row in a table) | [SSSR_REF: sssr:label_elements.label_element_id @ LEID-0001] | [SSSR_REF: sssr:label_elements.label_element_id @ LEID-0001] | Points to the label_element_id in the label_elements table, row LEID-0001. |
| Global asset | [SSSR_REF: sssr:asset.hazard_pictograms.GHS01.gif] | [SSSR_REF: sssr:asset.hazard_pictograms.GHS01.gif @ sssr:label_elements.symbol_sssr @ LEID-0029] | Points to the global asset GHS01.gif in the hazard_pictograms class. |
| Scoped asset | [SSSR_REF: sssr:asset.client-profiles.css.css @ eco-173-123-456-789] | [SSSR_REF: sssr:asset.client-profiles.css.css @ eco-173-123-456-789 @ sssr:tenant_branding.css_sssr_ref @ eco-173-123-456-789] | Points to the client-scoped CSS asset for client eco-173-123-456-789, with optional provenance for audit/export. |
Array-of-Refs Pattern Example:
[
"[SSSR_REF: sssr:asset.hazard_pictograms.explos @ sssr:label_elements.symbol_sssr @ LEID-0029]",
"[SSSR_REF: sssr:asset.hazard_pictograms.flamme @ sssr:label_elements.symbol_sssr @ LEID-0029]"
]
- Each string is a full
SSSR_REFwith:- the asset target (
hazard_pictograms.explos,hazard_pictograms.flamme), - the source table + column (
label_elements.symbol_sssr), - and the row ID (
LEID-0029).
- the asset target (
- The array tells the resolver that multiple assets are bound to the same label element.
- When enriched/exported, this can either remain as a JSON array of refs, or be expanded into atomic enriched JSON objects for each pictogram.
This is consistent with the “multiple target references” pattern in the SSSR design: use an array of valid SSSR_REF strings when more than one reference applies .
Recommendation:
- Use this form only in human-facing or interchange contexts.
- For machine-native DB fields, prefer structured JSON.
- At export/API time, the resolver can emit them either as a JSON array of enriched objects or flatten them into multiple rows depending on consumer needs.
3.4. Key Takeaways
- Signals always need @
row_id, because they are row-level references. - Global assets do not require a scope suffix.
- Scoped assets use @
<client_id>(or another future scope identifier) as part of the canonical lookup. - The extended inline form may additionally include provenance for traceability, audits, and AI self-healing.
4. SSSR “Asset/URL Link”
We have a dedicated SSSR “Asset/URL Link” section and route every file/link through it. That keeps links portable, auditable, and swappable without touching data tables or code. It’s fully aligned with how SSSR is meant to centralize column-level references and cross-table links (incl. the [SSSR_REF: <signal_id> @ <scope_id>] pattern).
We use
- A registry area:
assets_registry(as part of SSSR). - Resolver microservice: assets-url-resolver that expands an sssr: asset ID into the correct URL at runtime (UI, exports, APIs).
- Tokenized URL templates: so we can flip base domains/folders once and everything updates.
sssr_asset_registry schema (SSSR → assets_registry)
| Field | Purpose |
|---|---|
| asset_id (PK) | Canonical signal, e.g. sssr:assets.hazard_pictograms.GHS01 |
| asset_class | hazard_pictogram, logo, policy_pdf, etc. |
| variants[] | Array of objects: {format, mime, dpi, width_px, usage_hint} (e.g., gif, svg, eps, tiff_label) |
| template_vars | Defaults: cdn_base, version, locale, theme, folder |
| fallback_order[] | e.g., ["svg","gif","tiff_label"] |
| hash / etag | Integrity/audit (and cache-busting) |
| license/source | Legal metadata (e.g., GHS/CLP source) |
| visibility_scope | admin/user/system/public (matches SSSR visibility) |
| availability_status | active/draft/deprecated |
| created_at/updated_at | Governance/audit trail |
sssr_asset_class_registry schema
| Column | Type | Description | Example / Rules |
|---|---|---|---|
| asset_class | text (PK) | Canonical class name for a family of assets. | hazard_pictograms, logos, templates_pdf |
| path_template | text | URL “recipe” with placeholders resolved by the resolver. Must include {{cdn_base}} and {{code}}. {{ext}} strongly recommended. | {{cdn_base}}/{{folder}}/{{code}}.{{ext}} |
| defaults | jsonb | Class‑level default variables used to fill the template and drive selection logic. Required keys: folder, fallback_order. | {"folder":"library/images/pictograms/hazard_pictograms","fallback_order":["svg","gif","tif"]} |
| availability_status | text (enum) | Lifecycle: active, draft, deprecated. | active |
| created_at / updated_at | timestamptz | Governance/audit timestamps. | auto‑managed triggers |
| description | text | Human‑readable purpose of the class. | “GHS/CLP hazard pictograms for SDS & labeling.” |
| integrity_rules | jsonb | Class‑level integrity constraints. | {"allowed_ext":["svg","gif","tif","eps"],"path_regex":"^library/images/pictograms/.\*"} |
| resolver_policy | jsonb | How to pick variants at runtime (context/locale/version). | {"variant_selection":{"print":["tif","eps"],"web":["svg","gif"]},"collapse_empty_segments":true} |
| cdn_policy | jsonb | CDN behaviors. | {"cache_control":"public,max-age=604800","signing_required":false,"ttl_seconds":604800} |
| visibility_scope | text (enum) | Access semantics aligned with SSSR. | admin,user,system,public |
| owner_module | text | Owning engine/team for ops. | ZHIF, SIS |
| maintainer_contact | text | Email/Slack/Group for break‑glass. | cto@viroway.com |
| versioning_mode | text | pinned, latest, or inherit. Affects {{version}} handling. | latest |
| localization_mode | text | none, optional, required. Drives {{locale}}. | optional |
| tenancy_mode | text | global, tenant_overrides_allowed. | tenant_overrides_allowed |
| override_rules | jsonb | Declarative per‑tenant/per‑region overrides. | {"tenant:eco-196-...":{"folder":"tenants/acme/hazards"}} |
| compliance_tags | text[] | Legal or standard tags for audit. | {"GHS","CLP"} |
| license_info | jsonb | License/source metadata. | {"source":"UNECE","license":"permitted-use"} |
| telemetry_flags | jsonb | Emit resolver telemetry, sample rates, alerting. | {"emit_resolution_events":true} |
| deprecation_policy | jsonb | Sunset plan for class. | {"requires_migration_ticket":true,"replacement_class":"hazard_pictograms_v2"} |
Validation & constraints (practical guardrails)
- Template requirements: reject rows where path_template lacks
{{cdn_base}}or{{code}}. Prefer{{ext}}; if omitted, engine must supply it. - Defaults integrity: defaults.folder must be non‑empty;
defaults.fallback_ordermust be a non‑empty array whose values appear inintegrity_rules.allowed_ext(if provided). - Enum checks: constrain
availability_status,visibility_scope,tenancy_mode, etc. - Indexing: PK on asset_class; add BTREE index on
availability_statusand GIN index ondefaults/integrity_rules(jsonb_path_ops) for ops queries.
Example row (hazard pictograms)
{
"asset_class": "hazard_pictograms",
"description": "GHS/CLP hazard pictograms for SDS printing and UI.",
"path_template": "{{cdn_base}}/{{folder}}/{{code}}.{{ext}}",
"defaults": {
"folder": "library/images/pictograms/hazard_pictograms",
"fallback_order": ["svg","gif","tif"],
"preferred_format_by_context": {
"web": ["svg","gif"],
"print": ["tif","eps"]
}
},
"integrity_rules": {
"allowed_ext": ["svg","gif","tif","eps"],
"path_regex": "^library/images/pictograms/hazard_pictograms/.*"
},
"resolver_policy": {
"collapse_empty_segments": true,
"variant_selection": {"print":["tif","eps"],"web":["svg","gif"]},
"locale_inheritance": "optional",
"versioning": "latest"
},
"cdn_policy": {"cache_control":"public,max-age=604800","signing_required":false},
"visibility_scope": "public",
"owner_module": "ZHIF",
"maintainer_contact": "cto@zayaz.io",
"availability_status": "active"
}
sssr_global_config schema (for system‑wide defaults inherited by many asset classes)
| Column | Type | Purpose / Notes | Example |
|---|---|---|---|
| key (PK) | text | Config item name | cdn_base |
| value | jsonb | Arbitrary JSON to allow rich settings | {"url":"https://lib.zayaz.io"} |
| scope | text enum | global | tenant | region | env (prod/stage) — lets you override per context | global |
| selector | jsonb | When scope != global, the selector describing “who” this applies to | {"tenant":"ECO-196-000-000-000"} |
| description | text | Human description so ops knows why it exists | “Primary CDN for public assets” |
| availability_status | text enum | active | draft | deprecated | active |
| updated_by | text | Change accountability | [cto@viroway.com](mailto:cto@viroway.com) |
| updated_at | timestamptz | Governance | auto |
| effective_from / effective_to | timestamptz | Time‑boxed rollouts, blue/green | optional |
| integrity_rules | jsonb | Optional checks (regexes, URL allowlists) | \`{“url_regex”:”^https://(lib |
Common keys used day‑to‑day
cdn_base:{"url":"https://lib.zayaz.io"}default_locale:{"value":"en"}default_version:{"value":"latest"}theme_defaults:{"value":"light"}asset_resolution_policy: e.g.{"web":["svg","gif"],"print":["tif","eps"]}region_overrides: e.g.{"EU":{"cdn_base":"https://cdn-eu.zayaz.net"}}
This preserves the SSSR pattern: global → class → instance; engines read from the registry rather than hardcoding.
How consumers should store references
- In tables like
code_registry.symbol_url, store only the SSSR reference, not a URL:- "
[SSSR_REF: sssr:asset.hazard_pictograms.GHS01.gif @ sssr:code_registry.code_type @ CR-00213]".
- "
- Engines (FOGE/SEM/DAIM/exports) call assets-url-resolver with:
asset_id, optional hints{context:"print" | "web", preferred_format:"gif", locale:"en", theme:"light"}, and it returns the best URL.
- This matches the doc’s cross-table link idiom and lets engines auto-pick formats without hardcoding.
URL resolution logic (tiny, robust)
- Fetch asset_id → registry entry.
- Apply path_template with precedence: request hints → tenant overrides → registry defaults.
- Choose variant by rules:
preferred_formatorusage_hint(web/print) → fallback_order. - Emit URL + integrity metadata; log to ALTD for traceability.
- If missing, trigger SEM/ops fallback and raise a registry notification (keeps auditability intact).
Ops conveniences
- One-click base switch:
cdn_baseis kept in a global SSSR config row (sssr_global_config); changing it flips every link. - Folder refactors: do them by editing path_template once.
- Version pinning: support version="latest" vs. explicit v2025.08 per tenant/report.
- Localization/theming: drive
{{locale}}/{{theme}}from user/session—no schema churn.
5. Migration plan
- Register assets in
assets_registrywith variants (e.g. pictograms with GIF/EPS/TIFF/SVG variants). - Add cdn_base + default version to
sssr_global_config. - Replace direct URLs in tables with
[SSSR_REF: …]strings (Excel CONCAT works great). - Point UI/export code to assets-url-resolver.
- Turn on telemetry: log every resolution event for audits.
This keeps ZAYAZ consistent with SSSR’s purpose—semantic, traceable indirection for anything other modules consume—so links gain the same durability and auditability as data signals.
5.1. The path_template - {{cdn_base}}/{{folder}}/{{version}}/{{locale}}/{{code}}.{{ext}}
The path_template is just a parameterized blueprint for generating the actual file URL at runtime.
It works like a URL “recipe” where placeholders (inside {{...}}) get replaced with values from the SSSR entry, the global config, or runtime context.
Example:
https://lib.zayaz.io/library/images/pictograms/hazard_pictograms/1-1.tif
5.1.1. Breaking it into components
Using the SSSR asset registry pattern, we’d map it to:
| Placeholder | Value in this case | Where it comes from |
|---|---|---|
| cdn_base | https://lib.zayaz.io | Global SSSR config (sssr_global_config) so we can change CDN easily. |
| folder | library/images/pictograms/hazard_pictograms | Asset registry entry for this asset class. |
| version | omitted in this case (could be v1, 2025.08 etc.) | Optional — can be blank if no versioning is in the URL. |
| locale | none here (could be en, fr, etc.) | Runtime context (user/report locale). |
| code | 1-1 | The asset’s logical code inside the hazard pictograms category. |
| ext | tif | Variant/format selection (chosen by engine: print/web/etc.). |
5.1.2. For this specific URL, a working template could be:
{{cdn_base}}/{{folder}}/{{code}}.{{ext}}
To support versioning and localization later without breaking current URLs, we can plan ahead with:
{{cdn_base}}/{{folder}}/{{version}}/{{locale}}/{{code}}.{{ext}}
And set version="" and locale="" when not in use.
5.1.3. Resolution process
When a consumer (UI, export engine, API) requests the asset:
- Look up SSSR asset entry — e.g.,
sssr:asset.hazard_pictograms.1-1.tiff_label. - Get template:
{{cdn_base}}/{{folder}}/{{code}}.{{ext}}. - Pull defaults from:
- Global config (sssr_global_config): cdn_base = https://lib.zayaz.io
- Asset class config (sssr_asset_class_registry): folder = library/images/pictograms/hazard_pictograms
- Asset record (sssr_asset_registry): code = 1-1
- Format/variant mapping: ext = tif
- Substitute placeholders into the template →
https://lib.zayaz.io/library/images/pictograms/hazard_pictograms/1-1.tif
- Serve the file, with optional integrity hash for audit.
5.1.4. Why this matters
- Base URL change? → Update
cdn_basein one SSSR config row. - Folder restructure? → Update folder for the asset class in one place.
- Format change? → Update ext mapping; all consuming modules get the right variant.
- Localization/versioning in future? → Fill in locale / version without rewriting URLs in the data tables.
Summary
- Three-layer fill-in process
-
Layer 1 – Global defaults (
sssr_global_config)- Values that apply to all assets, stored in global config rows in SSSR.
- Examples:
cdn_base= https://lib.zayaz.iodefault_locale= endefault_theme= lightversion= latest
- These can be swapped in one place to move all assets to a new CDN or switch a default locale.
-
Layer 2 – Asset class defaults
- Stored in the sssr_asset_class_registry entry for the asset class.
- Examples:
- folder = library/images/pictograms/hazard_pictograms
- fallback_order = ["svg", "gif", "tiff_label"]
- This ensures all hazard pictograms share the same base folder and format fallback rules.
-
Layer 3 – Asset instance values
- tored in the sssr_asset_registry entry for that specific asset.
- Examples:
- code = 1-1
- variants = gif, svg, eps, tiff
- primary_format = tif
- These define what makes that specific pictogram unique.
- Example resolution step-by-step
-
To resolve:
[SSSR_REF: sssr:asset.hazard_pictograms.1-1.tif] -
Step 1 – Get path_template
- From sssr_asset_class_registry for hazard_pictograms:
path_template = {{cdn_base}}/{{folder}}/{{code}}.{{ext}}
- From sssr_asset_class_registry for hazard_pictograms:
-
Step 2 – Pull values
- cdn_base → https://lib.zayaz.io (global config)
- folder → library/images/pictograms/hazard_pictograms (class default)
- code → 1-1 (asset instance)
- ext → tif (from request or asset default)
-
Step 3 - Substitute placeholders
https://lib.zayaz.io/library/images/pictograms/hazard_pictograms/1-1.tif
- Why this is powerful
- Moving the hazard pictogram library to /assets/icons/hazards? → Update 1 row in the asset class config.
- Switching CDN from lib.zayaz.io to cdn.zayaz.net? → Update 1 global config row.
- Adding a localization? → Add
{{locale}}in template and populate it from user session.
5.2. assets-url-resolver
The assets-url-resolver is the “URL kitchen” that cooks up the final link using the “recipe” (path_template) and the “ingredients” (metadata from global config, asset class defaults, and asset instance values). It’s a:
- A microservice (or internal module) in the ZAYAZ backend.
- Could be implemented as:
- A REST endpoint →
/resolve_asset_url?sssr_id=sssr:asset.hazard_pictograms.1-1.tif - A library function inside the platform →
resolveAssetURL(sssr_id, options).
- A REST endpoint →
- Uses the SSSR registry tables as its data source — it doesn’t store URLs itself.
5.3.SSSR Assets URL Resolver — Architecture & Flow
The following shows how the assets-url-resolver service (logic) works with the SSSR assets registry tables (data). It includes a component map, data model, API contract, and the step-by-step URL resolution flow.
5.3.1. Components (High-Level)
Key idea: Tables hold metadata; the resolver composes the final URL at runtime.
5.3.2. Minimal Data Model
- Global Config —
sssr_global_config
- key:
cdn_base,default_locale,default_version,default_theme - value
- Asset Class Registry — sssr_asset_class_registry
asset_class(PK): e.g., hazard_pictograms, docs, logos, templatespath_template: e.g.,{{cdn_base}}/{{folder}}/{{version}}/{{locale}}/{{code}}.{{ext}}defaults: JSON (e.g.,{ "folder": "library/images/pictograms/hazard_pictograms", "fallback_order": [“svg","gif","tif"] })
- Assets Registry —
sssr_assets_registry
asset_id(PK): e.g., GHS01, 1-1,company_logoasset_class: FK →sssr_asset_class_registry.asset_classvariants: JSON array of objects, e.g.,[ {"ext":"svg"}, {"ext":"gif"}, {"ext":"tif","usage_hint":"label_print"} ]template_vars: JSON (overrides), e.g.,{ "code": "1-1" }license,hash,availability_status,created_at,updated_at
5.3.3. Resolver API Contract (Service)
HTTP (REST) option
GET /api/assets/resolve?sssr_id=sssr:asset.hazard_pictograms
Library (in-process) option
/**
* Types
*/
export type AssetContext = 'web' | 'print' | 'pdf';
export type AssetTheme = 'light' | 'dark';
export interface ResolveAssetOptions {
preferredFormat?: string; // e.g. 'svg' | 'gif' | 'tif'
locale?: string; // e.g. 'en', 'no'
version?: string; // e.g. 'latest', 'v2025.08'
context?: AssetContext; // influences variant choice
theme?: AssetTheme;
}
export interface ResolvedAsset {
url: string;
format: string;
locale?: string;
version?: string;
context?: AssetContext;
theme?: AssetTheme;
}
/**
* Example implementation
*/
export function resolveAssetURL(
sssrId: string, // e.g. 'sssr:asset.hazard_pictograms.1-1.tif'
opts: ResolveAssetOptions = {}
): ResolvedAsset {
const {
preferredFormat = 'svg',
locale = 'en',
version = 'latest',
context = 'web',
theme = 'light'
} = opts;
// Example base CDN path (adjust to your system)
const baseURL = 'https://cdn.example.com/assets';
const url = `${baseURL}/${version}/${locale}/${context}/${theme}/${sssrId}.${preferredFormat}`;
return {
url,
format: preferredFormat,
locale,
version,
context,
theme
};
}
Response
{
"url": "https://lib.zayaz.io/library/images/pictograms/hazard_pictograms/1-1.tif",
"resolved": {
"cdn_base": "https://lib.zayaz.io",
"folder": "library/images/pictograms/hazard_pictograms",
"code": "1-1",
"ext": "tif",
"version": "",
"locale": ""
},
"variant": {"ext":"tif","usage_hint":"label_print"},
"integrity": {"hash":"sha256:…"},
"meta": {"asset_class":"hazard_pictograms","asset_id":"1-1"}
}
5.3.4. URL Resolution Flow (Step-by-Step)
The resolution process works as follows:
- The consumer (UI, FOGE, Exporter, etc.) calls the resolver with an
sssr:assetID and optional options. - The resolver parses the ID into its parts:
asset_classasset_id- optional
ext - optional
scope_id
- It determines whether the target asset class is:
- global/unscoped, or
- scope-bound (for example, client-scoped branding assets)
- If the asset is scope-bound:
- the resolver requires a valid scope, normally
client_id - the asset is resolved using
(asset_class, asset_id, scope_id)
- the resolver requires a valid scope, normally
- If the asset is global/unscoped:
- the asset is resolved using
(asset_class, asset_id)
- the asset is resolved using
- The resolver retrieves global defaults (such as
cdn_base,default_locale,default_version, andtheme). - It retrieves the class template and defaults for the given asset class.
- It loads the asset record (variants, template variables, integrity metadata, etc.).
- It decides the best variant to serve based on:
- explicit extension in the target
- caller options
- context
- resolver policy
- fallback order
- It composes the URL by substituting variables into the path template.
- It returns the resolved URL and metadata to the consumer, which then fetches the asset from the CDN or asset host.
Important distinction
- For scoped assets, the scope suffix (currently usually
@ <client_id>) is part of the canonical resolution input. - Provenance suffixes are not part of URL resolution.
Variant choice logic (simplified):
- If target includes
.<ext>→ use it. - Else if
opts.preferredFormatis available → use it. - Else if
context=print→ prefer tif / eps if available. - Else → prefer svg / gif.
- Else → follow
fallback_orderfrom class defaults.
5.3.5. Path Template Examples
Example of simple flat structure:
{{cdn_base}}/{{folder}}/{{code}}.{{ext}}
Works for:
Future-ready with version & locale:
{{cdn_base}}/{{folder}}/{{version}}/{{locale}}/{{code}}.{{ext}}
- With version="latest" or v2025.08, and locale="en"|"no".
- If empty, resolver collapses empty segments to avoid double slashes.
Tenant-branding override:
{{cdn_base}}/{{tenant}}/{{folder}}/{{code}}.{{ext}}
tenant comes from auth context; lets us theme per client.
5.3.6. Example SQL Sketches
Illustrative only — adapt types/indexes to the DB.
-- Global config
CREATE TABLE sssr_global_config (
key text PRIMARY KEY,
value jsonb NOT NULL,
updated_at timestamptz DEFAULT now()
);
-- Asset classes
CREATE TABLE sssr_asset_class_registry (
asset_class text PRIMARY KEY,
path_template text NOT NULL,
defaults jsonb NOT NULL,
updated_at timestamptz DEFAULT now()
);
-- Assets
CREATE TABLE sssr_assets_registry (
asset_id text PRIMARY KEY,
asset_class text NOT NULL REFERENCES sssr_asset_class_registry(asset_class),
variants jsonb NOT NULL,
template_vars jsonb,
license text,
hash text,
availability_status text DEFAULT 'active',
created_at timestamptz DEFAULT now(),
updated_at timestamptz DEFAULT now()
);
CREATE INDEX ON sssr_assets_registry(asset_class);
5.3.7. Caching & Performance
- In-memory cache for class templates and global config (TTL 5–15 min).
- Per-asset cache for resolved URLs keyed by (
sssr_id,locale,version,preferredFormat,theme,context). - Cache busting when
updated_atchanges on relevant rows.
5.3.8. Error Handling & Telemetry
- If asset/variant missing → return structured error with fallback_attempted=true and candidates list.
- Emit telemetry event:
asset_resolve_failedwithasset_id,asset_class,requested_ext,fallback_path,reason. - Optionally route a task to “Registry Ops” queue to fix broken entries; link to ALTD audit trail.
5.3.9. Governance & Audit (Why this is SSSR-aligned)
- Traceability: Every URL comes from registry metadata → auditable.
- Swap safety: Change cdn_base or folder once → all consumers updated.
- Version pinning: Reports can pin version for reproducibility.
- Locale-aware: Localized assets can be served without altering data tables
5.3.10. Quick Usage Cheat Sheet
Store in tables:
-
Signal reference
[SSSR_REF: sssr:<table>.<column> @ <row_id>] -
Global asset reference
[SSSR_REF: sssr:asset.<class>.<asset_id>[.<ext>]] -
Scoped asset reference
[SSSR_REF: sssr:asset.<class>.<asset_id>[.<ext>] @ <client_id>] -
Extended asset reference with provenance
[SSSR_REF: sssr:asset.<class>.<asset_id>[.<ext>] @ <client_id> @ sssr:<table>.<column> @ <row_id>]
Examples:
[SSSR_REF: sssr:label_elements.label_element_id @ LEID-0001][SSSR_REF: sssr:asset.hazard_pictograms.GHS01.gif][SSSR_REF: sssr:asset.client-profiles.css.css @ eco-173-123-456-789][SSSR_REF: sssr:asset.client-profiles.main-logo.gif @ eco-173-123-456-789][SSSR_REF: sssr:asset.client-profiles.css.css @ eco-173-123-456-789 @ sssr:tenant_branding.css_sssr_ref @ eco-173-123-456-789]
Resolver behavior
- For global assets, the resolver uses the asset target only.
- For scoped assets, the resolver uses the asset target plus the scope suffix.
- Provenance is ignored by URL resolution and used only for:
- exports
- audits
- logs
- AI self-healing
- traceable interchange
5.4. Resolver variable mapping (DB → template vars → URL)
The resolver variable-mapping table shows exactly how DB columns → template vars → final URLs.
| Source | Field (DB / runtime) | Template var | Notes / Precedence |
|---|---|---|---|
| sssr_global_config | cdn_base.url | cdn_base | Global default; can be overridden by region/tenant config at runtime. |
| sssr_asset_class_registry.defaults | folder | folder | Class-level folder; typically the only place you change folder structure. |
| sssr_asset_class_registry.defaults | version (optional) | version | Class default; can be empty. Runtime can override (e.g., v2025.08). |
| sssr_asset_class_registry.defaults | locale (optional) | locale | Class default; runtime/user session can override (e.g., no, en). |
| sssr_assets_registry | asset_code | code | Asset code → mapped to {{code}} (keep DB name as asset_code). |
| sssr_assets_registry.template_vars | arbitrary keys | same key name | Per-asset overrides for any var (e.g., folder, version, locale, code). |
| Runtime (resolver input) | preferredFormat or ext | ext | Highest precedence for format if provided explicitly. |
| Policy (class defaults / resolver_policy) | fallback_order | ext (chosen) | Used when ext not explicit; picks first available variant. |
| sssr_assets_registry.variants[] | ext, color_space, dpi, … | n/a | Source of available formats and technical constraints. |
Example: vars bag assembly Inputs
- Template (class):
{{cdn_base}}/{{folder}}/{{version}}/{{locale}}/{{code}}.{{ext}} - Global: cdn_base = https://lib.zayaz.io
- Class defaults: folder = library/images/pictograms/hazard_pictograms, fallback_order =
["svg","gif","tif"] - Asset row: asset_code = "1-1", variants =
[svg, gif, tif] - template_vars (asset):
{ "locale": "no" } - Runtime: context = "web" (no explicit ext, no version)
Resolver chooses ext
- From policy (web): prefer svg, else fallback order.
- svg exists → pick ext = “svg".
Final variable bag
{
"cdn_base": "https://lib.zayaz.io",
"folder": "library/images/pictograms/hazard_pictograms",
"version": "",
"locale": "no",
"code": "1-1",
"ext": "svg"
}
Rendered URL
https://lib.zayaz.io/library/images/pictograms/hazard_pictograms/no/1-1.svg
(Empty version collapses cleanly if the renderer removes empty path segments.)
Pseudocode (the tiny mapper)
def build_vars(sssr_id, runtime):
cls = load_class(sssr_id.asset_class) # path_template, defaults, policy
asset = load_asset(sssr_id.asset_class, sssr_id.asset_code, runtime.eco_number)
vars = {}
# 1) global
vars["cdn_base"] = get_global("cdn_base.url")
# 2) class defaults
vars.update(cls.defaults) # folder, version?, locale?, etc.
# 3) asset specifics
vars["code"] = asset.asset_code
if asset.template_vars:
vars.update(asset.template_vars)
# 4) ext selection
ext = sssr_id.ext or runtime.preferredFormat or choose_ext(cls.policy, asset.variants, runtime.context)
vars["ext"] = ext
# 5) runtime overrides (last write wins)
for k in ("version","locale"):
if getattr(runtime, k, None):
vars[k] = getattr(runtime, k)
return vars, cls.path_template
5.4. Quick validator checks (nice guardrails)
- Template must reference required vars:
{{cdn_base}},{{code}},{{ext}}(reject row otherwise). - Folder non-empty: class.defaults.folder must be present.
- Variant sanity: chosen ext must exist in asset.variants[] (or after fallback selection).
- Collapse empties: strip double slashes when version/locale are empty.
5.5. How variants[] is chosen (EPS/TIFF for print, GIF/SVG for web)
Use a deterministic policy the resolver applies; AI is optional seasoning, not the core rule.
Where the choice lives
- Explicit in the ID (highest priority): sssr:asset.hazard_pictograms.1-1.tif → use TIF, no questions asked.
- Runtime options from the caller (UI/export): preferredFormat="svg" or context="print" / context="web".
- Class policy (in sssr_asset_class_registry.resolver_policy or defaults):
{"variant_selection":{"web":["svg","gif"],"print":["tif","eps"]}} - Fallback order (class defaults):
{"fallback_order":["svg","gif","tif","eps"]}
variants[] shape (per asset instance)
{
"variants": [
{"ext":"svg", "usage_hint":"web", "color_space":"RGB"},
{"ext":"gif", "usage_hint":"web", "color_space":"RGB"},
{"ext":"tif", "usage_hint":"print","color_space":"CMYK","dpi":300},
{"ext":"eps", "usage_hint":"print"}
]
}
5.6. Selection algorithm (resolver)
- If ID includes
.<ext>→ pick that. - Else if preferredFormat supplied and present in variants → pick it.
- Else if context supplied → consult class
variant_selection[context], pick first available. - Else → use class fallback_order.
- If multiple candidates remain, match technical constraints (e.g.,
color_space="CMYK"anddpi>=300for print). If none match, fall back and log a structured warning.
This keeps the decision declarative and auditable (aligned with SSSR’s design), while still allowing an AI helper to suggest alternatives when constraints aren’t met. The canonical link remains the [SSSR_REF: …] form so lineage and routing are preserved across modules.
5.7. Concrete URL conversion (sanity check)
Current:
https://lib.zayaz.io/library/images/pictograms/hazard_pictograms/1-1.gif
After populating:
cdn_base(global) =https://lib.zayaz.io- Class defaults: folder =
library/images/pictograms/hazard_pictograms - Template:
{{cdn_base}}/{{folder}}/{{code}}.{{ext}}
Use either:
- Explicit:
[SSSR_REF: sssr:asset.hazard_pictograms.1-1.gif]→ exact same GIF URL resolves. - Auto:
[SSSR_REF: sssr:asset.hazard_pictograms.1-1]→ web might resolve to SVG, print to TIF, depending on policy.
5.8. API additions
- Implement an endpoint over the existing resolver logic, e.g.
*GET /api/assets/resolve?sssr_id=sssr:asset.hazard_pictograms.1-1.tif&locale=en&context=web - Return
{ url, variant, meta, integrity }plus HTTP cache headers (ETag, Cache-Control, Last-Modified). - Enforce auth (JWT/OAuth) and scope checks in the service, using visibility_scope from class and asset (logical AND).
Schema Tweaks
sssr_asset_class_registry
| Column Name | Type | Description |
|---|---|---|
| api_enabled | bool, default true | Allow/deny class via API. |
| api_slug | text | Friendly path fragment (e.g., hazards). |
| cors_policy | jsonb | { "origins": ["\*"] } or stricter. |
| rate_limit_bucket | text | Link to gateway settings. |
| signing_required | bool | Require pre‑signed or tokenized URLs. |
| content_disposition | text | Inline | attachment (default per class). |
| filename_template | text | E.g., {{asset_class}}-{{code}}.{{ext}}. |
sssr_assets_registry (asset-level)
| Column Name | Type | Description |
|---|---|---|
| api_enabled | bool | Per‑asset override (default null = inherit). |
| legal_restrictions | jsonb | Export constraints (regions, licenses). |
| download_filename | text | Override for Content-Disposition. |
| integrity | jsonb | { "hash":"sha256:…", "size":12345 }. |
sssr_global_config (global)
| Column Name | Type | Description |
|---|---|---|
| asset_api_defaults | jsonb | Global API behaviors. {“cache_control":"public,max-age=604800","etag_strategy":"hash"} |
| token_signing | jsonb | Key ids, TTL for pre‑signed URLs. |
5.9. API shapes (minimal and clean)
5.9.1. Resolution API (metadata forwarder)
GET /api/assets/resolve
?sssr_id=sssr:asset.hazard_pictograms.1-1
&context=print&locale=no&preferredFormat=tif
5.9.2. 200 JSON
{
"url": "https://lib.zayaz.io/library/images/pictograms/hazard_pictograms/1-1.tif",
"variant": {"ext":"tif","usage_hint":"print","color_space":"CMYK","dpi":300},
"meta": {"asset_class":"hazard_pictograms","asset_id":"1-1","locale":"no"},
"integrity": {"hash":"sha256:...","size": 481233}
}
Headers: ETag, Cache-Control, Last-Modified, optional x-sssr-id.
5.9.3. Direct fetch API (proxy with policy)
GET /api/assets/fetch/hazards/1-1
?context=web&locale=en
Authorization: Bearer …
Service chooses variant, applies signing if required, sets Content-Type and Content-Disposition from class/asset rules, streams the bytes (or 302 to CDN).
5.9.3. Selection logic (where “EPS/TIFF vs GIF/SVG” is decided)
Priority order:
- Explicit ext in ID → use it.
- Caller hint → preferredFormat or context (web/print).
- Class policy →
resolver_policy.variant_selection. - Fallback →
defaults.fallback_order. - Constraints → choose variant matching color_space, dpi (from variants).
This keeps decisions declarative in tables, not in opaque code or “AI magic”. AI helper can be added to suggest alternatives, but the authoritative source remains the registry.
5.9.4. Security & governance touches
- Enforce
visibility_scope(public/user/admin/system) andapi_enabledat both class and asset; deny if either blocks. - Optional tenancy/region guards using scope/selector in global config.
- Log every API resolution with SSSR ID, variant chosen, and caller scope for audits.
- Use pre‑signed URLs if
signing_required=trueat class/asset.
5.9.5. Migration checklist
- Populate resolver_policy, fallback_order, and rich variants.
- Stand up /api/assets/resolve (metadata) and optionally /api/assets/fetch/:class/:id (proxy).
- Wire gateway: auth scopes, rate limits, CORS, caching.
- Record structured telemetry for audits and SLA.
6. Keeping Assets with the Same Name (code) Unique
6.1. Keys & uniqueness (safe schema)
- Primary key (surrogate): asset_uid UUID→ Opaque, never exposed to users or in SSSR IDs.
- Natural key (human/semantic): asset_code TEXT (e.g., "1-1", "logo")
- Tenant scoping: eco_number UUID NULL (NULL = global/shared asset)
- Class scoping: class_id UUID (FK → sssr_asset_class_registry)
- Uniqueness constraint:UNIQUE(eco_number, class_id, asset_code)
→ Allows each tenant to have e.g. a "logo" in client_logos without clashing; also allows a global "logo" when tenant_id IS NULL.
6.2. What the SSSR ID looks like (no collisions)
Keep the SSSR ID stable and simple:
sssr:asset.<asset_class>.<asset_code>[.<ext>]
Examples:
- Global hazard pictogram: sssr:asset.hazard_pictograms.1-1
- Client logo: sssr:asset.client_logos.logo
How tenant is handled: the resolver takes the tenant from auth/context (session, API token) and first tries:
(eco_number = caller.eco_number, class_id = <class>, asset_code = <code>)
If not found, it falls back to global:
(eco_number IS NULL, class_id = <class>, asset_code = <code>)
This gives each tenant its own "logo" while preserving a global default.
6.3. Version pinning (if e.g. multiple dated logos is needed)
If multiple versions per tenant/class/code (e.g., a rebrand) is needed/wanted:
- Add version TEXT to the unique key or create a separate table for versions.
- Recommended uniqueness when enabling versions:UNIQUE(
tenant_id,class_id,asset_code, COALESCE(version,'latest')) - Resolution order:
-
- If SSSR ID (or request) specifies version, use it.
-
- Else use class policy
default_version(often "latest").
- Else use class policy
-
- Fall back to the newest active version.
-
6.4. Concrete examples
Two clients, both want client_logos.logo:
- Row A: (
eco_number = ECO-197-123-456-789, class = client_logos, asset_code = 'logo', variants = […]) - Row B: (
eco_number = ECO-197-987-654-321, class = client_logos, asset_code = 'logo', variants = […]) - Global fallback (optional): (
eco_number = NULL, class = client_logos, asset_code = 'logo', variants = […])
Resolution:
- ECO-197-123-456-789 (ACME) calling
sssr:asset.client_logos.logo→ gets ACME’s logo. - ECO-197-987-654-321 (BRAVO) calling the same ID → gets BRAVO’s logo. A public/anon context → gets the global logo (if present).
Hazard pictograms (global library):
Keep tenant_id = NULL, class = hazard_pictograms, asset_code = '1-1'.
Tenants can override specific pictograms by inserting tenant‑scoped rows; otherwise they inherit the global set.
6.5. Why not encode tenant into the SSSR ID?
We could do sssr:asset.client_logos. ECO-197-123-456-789.logo, but we don’t need to:
- Tenant (
eco_number) is already in auth context; keeping IDs tenant‑agnostic makes references portable across sandboxes and easier to move/merge tenants. - If you ever need to resolve “as another tenant”, you can pass tenantOverride to the resolver (ops/admin use).
6.6. Migration checklist
- For global items, set
eco_number = NULLor ECO-197-000-000-000 (Viroway Ltd). - For client items, set
eco_numberfrom the tenant/org table. - Create the unique index on (
eco_number,class_id,asset_code). - Keep SSSR IDs unchanged (
sssr:asset.<class>.<code>[.<ext>]). - Ensure resolver reads tenant from auth and applies the two‑step lookup with global fallback.
7. fallback_order[]
We keep fallback_order[] in both sssr_asset_registry and sssr_asset_class_registry because they operate at different scopes — one is the class-level default policy, and one is the per-asset override.
7.1. Class-level (sssr_asset_class_registry.defaults.fallback_order)
- Purpose: define the standard order for selecting variants when no asset-specific rule exists.
- Scope: applies to all assets in that class unless overridden.
- **Example:**In
hazard_pictograms, most assets might be small vector or raster icons — so it might default to:
{ "folder": "library/images/pictograms/hazard_pictograms",
"fallback_order": ["svg","gif","tif"] }
- Usage: Resolver checks class.defaults.fallback_order only if the asset doesn’t have its own fallback_order.
7.2. Asset-level (sssr_assets_registry.fallback_order)
- Purpose: override the class policy for this specific asset.
- Scope: only affects that one row in
sssr_assets_registry. - Example: One pictogram is a high-detail photo, not a vector. For that asset, we might say:
json
["tif","eps","gif"]
so the resolver tries high-resolution print formats first.
7.3. Resolution order in practice
When the resolver needs to pick a format (no explicit ext in the SSSR_REF):
- Check asset-level fallback_order[]
- If present → use it directly.
- If not present → go to class level.
- If class has fallback_order[] → use it.
- If neither is present → use system/global default from
sssr_global_config.asset_resolution_policy.
7.4. Why not only keep it in the asset table?
Because most assets don’t need their own override — 90%+ will follow the class default.
If we only stored it in the asset table:
- We’d have to copy the same list into hundreds of rows.
- Changing the default for a class would require bulk updates instead of a single class row edit.
By having both:
- We set once at class level.
- We override only when needed at asset level.
- The resolver has a clear hierarchy:
asset-level override → class default → global default
8. AI autonomy
Below is the schema design that lets us move from AI-assisted to AI-autonomous asset management without losing control.
8.1. Clear Override Hierarchy
Because we’ve separated:
- Global default (in
sssr_global_config) - Class default (in
sssr_asset_class_registry.defaults) - Per-asset override (in
sssr_assets_registry)
…an AI agent can:
- Detect patterns in overrides (e.g., “80% of assets in
hazard_pictogramshave['tif','eps']instead of the default”). - Suggest updating the class default rather than touching 100 individual rows.
- Roll back a bad change easily — just restore the default at the right scope.
8.2. Self-healing on broken links
If a variant is missing (404 or checksum mismatch):
- AI can try the next item in fallback_order[] without human intervention.
- It can log the failure, suggest updating variants[] or the fallback order in the right place.
- It can also spot global issues (e.g., “All .gif variants in hazard_pictograms are failing — check CDN folder path in defaults.folder”).
8.3. Autonomous optimization
Given usage telemetry:
- AI can see which variant is actually being served most often for a context.
- It can propose re-ordering fallback_order[] to match real-world demand.
- For example:“In web context, svg load times are consistently better — moved svg to top of fallback list for
hazard_pictograms.”
8.4. Controlled learning scope
The AI doesn’t have to “guess” where to change things:
- Global change → update
sssr_global_config. - Class change → update
sssr_asset_class_registry.defaults. - One-off fix → update
sssr_assets_registryfor that asset.
This scoping means autonomous changes can be reviewed, sandboxed, and rolled back in a predictable way — no silent drift.
8.5. Easy audit trail
Because all overrides are explicit fields, not hidden in code:
- Every AI “healing” action becomes a data mutation with timestamp, user/agent ID, and diff.
- We can run a periodic SSSR diff to see AI-driven changes vs. human changes.
9. Self-Healing Asset Resolver – AI Autonomy Sketch
This sketch shows how the resolver + AI agent collaborate to detect issues, auto-fallback, propose or apply fixes at the right scope (global → class → asset), and keep the whole loop auditable and safe.
9.1. High-Level Flow
9.2. Resolution + Self-Heal Sequence
9.3. AI Autonomy Loop (Detect → Plan → Act → Verify → Rollback)
9.4. Decision Tree (Where to Change?)
- Is the problem systemic? (e.g., CDN base 5xx globally) → change
sssr_global_config.cdn_base. - Is it class-wide? (e.g., all
hazard_pictograms.gif404) → updatesssr_asset_class_registry.defaults.folderorfallback_order. - Is it isolated to one asset? → update
sssr_assets_registry.variantsor per-assetfallback_order. - Only context-specific regression? → adjust
resolver_policy.variant_selectionfor that class/context.
Always prefer the highest applicable scope to minimize per-row churn and maximize consistency.
9.5. Policy & Constraints (Declarative)
- Variant selection (class):
{
"variant_selection": { "web": ["svg","gif"], "print": ["tif","eps"] },
"fallback_order": ["svg","gif","tif","eps"],
"constraints": {
"print": { "color_space": "CMYK", "min_dpi": 300 }
}
}
Per-asset variants:
{
"variants": [
{"ext":"svg","usage_hint":"web","size":18345},
{"ext":"gif","usage_hint":"web"},
{"ext":"tif","usage_hint":"print","color_space":"CMYK","dpi":300}
]
}
Resolver applies: explicit ext → caller hint → class policy → asset fallback → global default.
9.6. Telemetry Events (for Learning & Audits)
- asset_resolution_success
{ sssr_id, eco_number, class, asset_code, ext, latency_ms, cache_hit, etag } - asset_resolution_fallback
{ from_ext, to_ext, reason, frequency } - asset_resolution_failed
{ class, asset_code, tried_exts[], error_codes[], last_seen } - asset_policy_change_proposed
{ scope, diff } - asset_policy_change_applied
{ scope, diff, actor, ticket } - asset_policy_change_rolled_back
{ scope, reason, rollback_id }
Stream to the MON/DET components. Use thresholds (e.g., >2% failure over 15 min) to trigger autonomous plans.
9.7. Pseudocode (Resolver w/ Self-Heal Hooks)
result = try_resolve(sssr_id, opts)
if result.ok:
emit('asset_resolution_success', result.meta)
return result
# First failure: try policy fallback
for ext in policy.next_variants():
result = try_resolve(sssr_id.with_ext(ext), opts)
if result.ok:
emit('asset_resolution_fallback', {"from": opts.ext, "to": ext})
return result
# Total failure: emit and exit
emit('asset_resolution_failed', failure_meta)
return failure(404, "Asset not available”)
9.8. Change Safety & Rollback
- Dry-run: run synthetic resolves against the proposed change; ensure ≥95% success and no size/latency regressions.
- Time-box: apply change with effective_from/effective_to for phased rollouts.
- Shadow mode: evaluate new policy in parallel, compare KPIs before switching.
- Rollback: keep previous policy/version in history; one-click revert.
9.9. What the AI Actually Edits
- sssr_global_config:
cdn_base, globalasset_resolution_policy. - sssr_asset_class_registry: defaults.folder, resolver_policy.variant_selection, defaults
.fallback_order. - sssr_assets_registry: add/remove variants, set per-asset fallback_order, tweak
template_vars.
All edits generate audit records and can be constrained behind approval gates per environment (dev/stage/prod).
9.10. Quick “Why This Works”
- Declarative → AI edits data, not code.
- Scoped → Fixes land at the smallest surface that solves the problem.
- Auditable → Every change is a registry diff with metadata.
- Reversible → Rollback is a data write, not a redeploy.
This is the foundation for trustworthy AI autonomy in asset delivery.
10. IMPORTANT LAST NOTE ON LINKS TO THE SSSR REGISTRIES
For machine-only fields inside the DB use a pure JSON form because it’s safer, faster to process, and easier to evolve than storing an array of human-formatted strings.
The preferred format is:
{ "registry": "sssr:code_registry.code_registry_id", "ids": ["CR-00213","CR-00214"] }
10.1. Benefits
- Zero string parsing. We avoid brittle regex/splitting. ETL/API/AI can read registry once and iterate ids directly.
- Schema evolution. We can add more keys without breaking anything (filters, constraints, notes, eco_number, etc.).
- Validation & constraints. Easy to enforce with DB checks (e.g., keys must exist, ids must be array of text).
- Indexing & querying. Postgres JSONB operators work cleanly:
jsonb_array_elements_text(payload->'ids') to unnest- GIN indexes (
jsonb_path_ops) for fast membership queries.
- Lower storage & duplication. One copy of the registry string; many IDs. (The stringy form repeats the registry per item.)
- Safer AI/autonomy edits. Agents can append/remove IDs without worrying about bracket/spacing typos.
- Cleaner provenance. We can enrich to the extended form only when exporting, instead of storing verbose strings everywhere.
10.2. Single reference column (one target)
- If humans edit this cell (e.g., in Excel): store the short string
[SSSR_REF: …]. - If it is machine-only: store as structured JSON.
Recommended JSON shape for a single target:
{
"target": "sssr:asset.client-profiles.css.css",
"scope_id": "eco-173-123-456-789"
}
For global assets, scope_id may be omitted:
{
"target": "sssr:asset.hazard_pictograms.GHS01.gif"
}
For signals:
{
"target": "sssr:label_elements.label_element_id",
"row_id": "LEID-0001"
}
This keeps the DB representation explicit and avoids string parsing.
Rule of thumb
- Use scope_id when the reference is a scope-bound asset lookup.
- Use row_id when the reference is a row-level signal lookup.
- Only generate extended provenance forms when exporting or enriching the reference for auditability.
10.3. Multiple references from the same registry (common cases)
For multiple references from the same registry, use structured JSON instead of storing arrays of string-based SSSR_REF values.
This avoids parsing complexity and makes scope, identity, and lookup behavior explicit.
10.3.1. Multiple signal references (row-based registries)
Recommended structure:
{
"registry": "sssr:code_registry.code_registry_id",
"ids": ["CR-00213", "CR-00214", "CR-00215"]
}
- registry = the signal registry (
sssr:<table>.<column>) - ids = list of row identifiers
Query example (Postgres):
SELECT row_id, j.elem AS code_id
FROM your_table t
CROSS JOIN LATERAL jsonb_array_elements_text(t.refs->'ids') AS j(elem)
WHERE t.refs->>'registry' = 'sssr:code_registry.code_registry_id';
10.3.2. Multiple global asset references (unscoped)
For globally shared assets:
{
"target_registry": "sssr:asset.hazard_pictograms",
"ids": ["GHS01.gif", "GHS02.gif", "GHS03.gif"]
}
- No scope_id required
- Resolver treats each id as:
sssr:asset.hazard_pictograms.<id>
10.3.3. Multiple scoped asset references (client-bound)
For client-specific or tenant-scoped assets:
{
"target_registry": "sssr:asset.client-profiles",
"scope_id": "eco-173-123-456-789",
"ids": ["css.css", "main-logo.gif", "favicon.ico"]
}
- target_registry = asset class namespace
- scope_id = required for scoped resolution (typically client_id)
- ids = asset identifiers within that class
Each entry resolves as:
sssr:asset.client-profiles.<id> @ eco-173-123-456-789
10.3.4. Mixed registry references (advanced / multi-domain)
If a column must reference multiple registries, use a list of structured objects:
[
{
"registry": "sssr:code_registry.code_registry_id",
"ids": ["CR-00213", "CR-00214"]
},
{
"target_registry": "sssr:asset.client-profiles",
"scope_id": "eco-173-123-456-789",
"ids": ["main-logo.gif"]
}
]
10.3.5. Design principles
- Do not store arrays of [SSSR_REF: ...] strings in machine-native fields.
- Always separate:
- registry (what system to query)
- ids (what items to resolve)
- scope_id (if required for resolution)
- Keep scope_id at the object level (not repeated per item).
- Use structured JSON for:
- validation
- indexing
- API transformation
- AI-safe mutation
10.3.6. When to still use [SSSR_REF: ...]
Use string-based references only for:
- Excel / CSV workflows
- human-readable documentation
- external exports
- debugging / logs
Internally (DB, APIs, pipelines), always prefer structured JSON.
10.3.7. Resolver behavior
Given structured input:
- If registry → treat as signal lookup
- If target_registry → treat as asset lookup
- If scope_id present → resolve as scoped asset
- If no scope_id → resolve as global asset
This ensures deterministic and schema-driven resolution across all ZAYAZ modules.
10.4. Multiple references across different registries (mixed set)
- Use a list of objects:
[
{"registry":"sssr:code_registry.code_registry_id","ids":["CR-00213","CR-00214"]},
{"registry":"sssr:nace_registry.nace_code","ids":["C10.1","C10.2"]}
]
- This avoids mixing everything into one string soup.
10.5. Validation & indexes (Postgres examples)
- Checks
ALTER TABLE your_table
ADD CONSTRAINT refs_has_keys
CHECK (refs ? 'registry' AND refs ? 'ids' AND jsonb_typeof(refs->'ids') = 'array');
- Index (fast membership by ID)
CREATE INDEX your_table_refs_gin
ON your_table USING GIN ((refs->'ids') jsonb_path_ops);
- Generated column to expose first registry
ALTER TABLE your_table
ADD COLUMN registry_txt TEXT GENERATED ALWAYS AS (refs->>'registry') STORED;
CREATE INDEX your_table_registry_idx ON your_table(registry_txt);
10.6. Human-friendly views
- Keep the DB clean, but provide a view that renders the bracketed form for readability:
CREATE VIEW your_table_human AS
SELECT
row_id,
format(
'[SSSR_REF: %s @ {%s}]',
refs->>'registry',
string_agg(q.id, ', ')
) AS refs_compact
FROM your_table
CROSS JOIN LATERAL (
SELECT jsonb_array_elements_text(refs->'ids') AS id
) q
GROUP BY row_id, refs;
10.7. Provenance (extended form)
- Don’t store provenance redundantly on every cell. Generate it on export:
{
"target_registry": "sssr:code_registry.code_registry_id",
"targets": ["CR-00213","CR-00214"],
"source": {"registry":"sssr:label_elements.symbol_sssr","row_id":"LEID-0029"}
}
We already have table, column, and row_id in the DB context—attach them only when needed (API/output/AI).
Simple rule of thumb
Inside DB (machine fields): use structured JSON ({"registry": "...","ids": [...]}) or a join table; avoid parsing strings.
At the edges (docs, Excel, emails): use the human string form [SSSR_REF: …].
On export/APIs: enrich to the extended JSON with provenance.