Primitive spec
Radiant
Git-backed, YAML-defined configuration assets. Radiant is where every other primitive looks up the rules, schemas, templates, and policies it operates against.
What it owns
Radiant centralises four jobs: registering Git repositories the tenant trusts as a source of configuration, registering individual YAML assets out of those repositories, validating those assets against schemas, and resolving stable references to a specific effective version at runtime. It is the only primitive that talks to Git.
Everything declarative on the platform — pricing rules, cancellation policies, stage flow definitions, message templates, vendor configs, frontend view definitions — lives in Radiant. Other primitives carry an opaque radiantAssetId and call resolve when they need the parsed payload.
Concepts
- Repository
- A registered Git remote the tenant can read assets from. Tracks credentials, default branch, and sync state. Soft-deletable; soft-delete leaves dependent assets unresolvable rather than cascading.
- Asset
- A logical YAML document inside a repository — identified by path plus schema kind. The asset is the stable identity; its content changes through versions.
- Version
- An immutable snapshot of an asset's parsed payload at a particular Git commit. Versions are append-only. Each version carries its source ref, the schema it was validated against, and the parsed payload.
- Alias
- A stable, human-readable name that resolves to either a pinned version (
tagbehaviour) or the latest version on a channel (branchbehaviour). Aliases are how runtime code refers to "the production pricing rules" without baking in a version id. - Channel
- A named track of an asset (
main,staging,preview). Aliases follow a channel; resolve returns the head of the channel at request time. - Resolution
- The single runtime entry point. Given an
assetId + channelor analiasName, return the effective version's id and parsed payload. Cached, validated, and tenant-scoped. - VersionProposal
- A pull-request-style review on an asset version before it lands. Carries the proposed payload, the base version it's diffing from, the target channel, an author (employee or AI agent — same surface), and the count of approvals required. Status moves
DRAFT → OPEN → APPROVED → MERGED, withCLOSEDandSUPERSEDEDbranches. - ProposalComment
- Threaded review comments on a proposal. Anchored by JSON Pointer (
linePath) so a comment can point at a specific field inside the proposed payload; replies are threaded throughparentId; resolution flag tracks whether the conversation is closed. - ProposalApproval
- One operator's vote on a proposal. Unique per (proposal, approver); re-voting updates in place. When approvals required is met, the proposal auto-promotes to
APPROVED; withdrawing or requesting changes flips it back toOPEN. - Merge
- The atomic write that lands an approved proposal as a new
AssetVersionon the target channel. Gated by a conflict check (the channel's current head must still equal the proposal's base version) and a sibling-sweep that flips any competing proposals toSUPERSEDED. Conflict returns 409 with the proposal flipped to SUPERSEDED. - Authorship & AI proposals
- Every proposal records an
authorKind—HUMAN,AI, orSYSTEM. An AI agent opens one through the same Layer-1 API viadraft-with-ai: a prompt plus a stableagentIdgo to the model, the returned payload is schema-validated, and aDRAFTproposal lands on the same review surface a human's would. The author is recorded; the approval gate is identical. - Audience & fork
- Assets carry an
audience:tenant_private(the owner-only default),tenant_public(the tenant's customer-facing surfaces), orplatform(a LatticeKit-published reference asset every tenant can read). Audience is immutable after creation — to take a platform asset and make it yours,forkcopies it into your tenant as a freshtenant_privaterow. - Git bridge
- Layer-2 mirrors proposals to a tenant's GitHub repository — materialize a proposal as a branch + pull request, ingest PR edits back, merge from either side — behind a
GitHubClientInterfacewith a signature-verified webhook receiver at/radiant/v1/git-webhooks. Creating a proposal now enqueues a Messenger job that materializes it to git out-of-band, so the API never blocks on GitHub; it is gated per tenant by a provisionedGitSyncStaterow, and the real HTTP client supersedes the original in-memory one. - Bandit handoff
- A proposal can carry a
banditExperimentId. When it does, approving it does not merge — it starts a Branno experiment with the proposed payload as one arm. The proposal staysAPPROVEDuntil the experiment converges; if the variant wins, Branno calls back and the merge completes, and if control wins the proposal closes as "bandit lost". - ui_view templates & instances
- A
ui_viewasset can be a template with a declaredvariables:block; aui_view_instancebinds values to it per tenant. Resolution is at render time — author once, instantiate per tenant: the resolve endpoint loads the instance, pins or follows its template via an Alias, fills the variables (typed and validated: string, integer, boolean, color, enum, ref), and returns a concreteui_viewfor the render-runtime to draw. - Layouts, themes & the template library
- A
view_refnode embeds anotherui_view(or instance) by id, so a layout is just a stack ofview_refs composed from picked widgets. A theme asset binds a palette that resolves the render catalog's colour tokens, layerable per view. LatticeKit publishes a library of forkableplatformtemplates — Home hero, About, Hours & location, Contact, Gallery, FAQ, Menu (which self-fetches the tenant's live Hardin menu), and the booking landing — that a tenant binds with its own variables (no fork) or forks to restructure. Platform-audience asset versions are readable cross-tenant, which is what makes "use a template without forking" resolve. - Live preview & CEL conditions
- The
ui_vieweditor's preview pane renders through the real render-runtime (the resolve endpoint takes an inline-template path for an unpublished draft), so it is WYSIWYG by construction and data-bound widgets show live, tenant-scoped data while you edit. The renderifnode now evaluates a real CEL expression —slots.size() > 0,context.user.authenticated,'vip' in context.tags— rather than bare scope-ref truthiness; a broken condition hides its children and warns rather than failing the page.
API surface
All endpoints are versioned under /radiant/v1/, return RFC 7807 problem details on error, and read tenantId from the bearer token. Single-item by default; explicit /batch variants exist where genuine bulk demand exists.
Quick reference
| Method | Path | Purpose |
|---|---|---|
| POST / GET | /radiant/v1/repositories | Register or list Git repositories for the tenant. |
| GET / PUT / DELETE | /radiant/v1/repositories/{id} | Fetch, replace, or soft-delete a repository. |
| POST / GET | /radiant/v1/assets | Register a YAML asset out of a repository, or list assets. |
| POST | /radiant/v1/assets/{id}/versions | Publish a new immutable version of an asset. |
| POST / GET / PUT | /radiant/v1/aliases | Manage stable names that resolve to a version or channel head. |
| POST | /radiant/v1/resolve | Resolve an assetId + channel or aliasName to an effective version. |
| POST | /radiant/v1/ui-views/resolve | Resolve a ui_view template + instance bindings into a concrete view at render time. |
| POST / GET | /radiant/v1/assets/{id}/proposals | Open a pull-request-style proposal on an asset, or list a tenant's proposals. |
| POST | /radiant/v1/assets/{id}/proposals/draft-with-ai | Draft a proposal authored by an AI agent (authorKind=AI) from a prompt. |
| POST | /radiant/v1/proposals/{id}/approvals | Cast or withdraw an approval; auto-promotes to APPROVED when the count is met. |
| POST | /radiant/v1/proposals/{id}/merge | Merge an approved proposal — or hand it to Branno if it carries a banditExperimentId. |
| POST | /radiant/v1/assets/{id}/fork | Copy a platform asset into the caller's tenant as a private row. |
| POST | /radiant/v1/validate | Validate a payload against a registered schema before publication. Planned. |
Example
Resolve the production pricing rules for salon appointment bookings:
POST /radiant/v1/resolve
Content-Type: application/json
Authorization: Bearer <token>
{
"aliasName": "pricing.haircut.production"
}
Response (abbreviated):
{
"assetId": "asset_01J8K2…",
"versionId": "ver_01J9TQ…",
"schemaKind": "seldon.pricing.v1",
"sourceRef": "main@7c4a2e3",
"payload": {
"currency": "USD",
"rules": [
{ "dim": "daypart", "value": "peak", "windowAdjPct": 10 },
{ "dim": "daypart", "value": "off_peak", "windowAdjPct": 0 }
]
}
}
How it fits with the rest
flowchart LR
Git[Git / YAML] --> R(Radiant)
R --> S[Seldon]
R --> D[Daneel]
R --> Sp[Speaker]
R --> Other[other primitives]
R -- bandit handoff --> Br[Branno]
Br -- winning arm --> R
Radiant is referenced, not depended on, by every other primitive. Seldon offers carry a radiantAssetId pointing at their pricing and cancellation YAML. Speaker templates resolve through Radiant. Daneel workflow definitions live in Radiant. Frontend ui_view assets that the rendering runtime consumes are Radiant assets too.
Two newer edges close the config-as-code loop. The same review surface serves human and AI authors alike — Demerzel's performance-review agent drafts proposals as authorKind=AI. And when a proposal carries a banditExperimentId, the merge is deferred to Branno: the experiment runs, and its winning arm flows back as the merged version.
The cross-primitive contract is intentionally minimal: opaque id in, parsed payload out. No primitive needs to know how Radiant stores or syncs anything.