The storage provider
Storage is a provider behind one ABC. The contract owns theStorage ABC and its
root-delete guard; the skeleton adds a registry that holds the one active provider.
A concrete provider — a local filesystem or object store — ships as a separate
plugin and registers through tai42_app when the
manifest names its storage_module.
The ResourceManager
The ResourceManager layers on the active provider and resolves a resource from one of three sources:- a storage id — content stored under the provider,
- a url — content fetched from an address, or
- a raw file — content read directly.
Templates are stored resources
A template is one kind of stored resource. The ResourceManager renders Jinja templates fetched through the provider, with a compiled-template cache in front of the render step. Render mixins addrendered_* methods to the contract’s field
models, so a stored template resolves in place wherever a field expects rendered
content.
A template write, a template delete, a directory delete, or a manual
tai templates clear-cache evicts that compiled-template cache on every worker
over the worker bus, not only on the process that made the
change — so no worker keeps serving a template compiled from stale content.
Locale variants
A template renders against the subject’slocale
when the render carries one — the template author never selects a language. A locale-scoped
variant lives at "{id}@{subtags}" (for example greeting@he-IL, greeting@he), and a render
for a locale walks a fallback chain from the most specific subtag form to the least, then to
the bare default id: he-IL resolves greeting@he-IL → greeting@he → greeting, returning
the first stored variant. When none of the chain is stored — no variant and no bare default —
the render refuses loudly, naming the template and locale, rather than silently rendering the
wrong language. A None locale (the locale-agnostic callers — hooks, authz, access control)
renders the bare id unchanged.
The locale also drives the list_format filter: a template writes {{ names | list_format }}
(with an optional CLDR list style, standard by default) and the separator and final connective
come from the resolved locale’s CLDR list patterns. It reads the locale the render resolved, never a
template argument; a render that resolved no locale, or a locale with no CLDR data, raises loudly.
Managing stored content
Two CLI groups reach stored content.tai templates is the higher-level,
template-aware surface (render, cache), while tai storage is the raw
provider-resource surface — thin wrappers over the authed /api/storage* routes:
tai storage info reports the empty state (present: false, a 200
so a UI renders it cleanly) while every CRUD door answers an honest 501 rather
than a fabricated default. A resource id must be a relative path with no ..
segment; an absolute path or a .. traversal is rejected with a 400 before it
reaches the filesystem-backed provider. Upload takes exactly one of --text (verbatim)
or --base64 (decoded bytes), and an existing id is overwritten.
The presence answer (GET /api/storage / tai storage info) is a core,
always-mounted read: it is answerable in every deployment, including one that mounts
no storage management surface, so a consumer can always tell “no provider” apart from
“cannot ask”.
The template-aware surface is the same content viewed as renderable resources:
A specific storage provider’s configuration — its bucket, credentials, or paths —
lives in that provider package’s own repository, reachable from the
Plugins section. The platform documents the storage
contract, not any one provider.
Storage ABC and the ResourceManager.
