Template and template version
templates
JSON:API types: template and template_version. One page covers both: a template is the
project-scoped definition (name, type, git remote), and each of its versions
pins a git revision assets are templated from.
The v2 OpenAPI document declares this resource's schema; the resource-field coverage gate holds this page to it.
Endpoints
Reads dispatch through the node-path router under the project:
| Verb | Path | Behaviour |
|---|---|---|
GET | /api/projects/{p}/templates | Index — each template gated by read on its own rule path (<project path>/templates/<code>); meta.authorisations reports update/delete on the project's …/templates path. |
GET | /api/projects/{p}/templates/{id} | Show (404 on a miss, an out-of-project template, or a read denial). |
POST | /api/projects/{p}/templates | Create — project readable plus update on …/templates, 404-on-deny. 201, or 422. |
GET | /api/projects/{p}/templates/{id}/versions | Version index (ordered by updated_at); readability inherits the template's. Filterable with filter[<attribute>_eq]=<value> over the serialized attributes plus filter[created_at_gt]=<ts> — the asset screen's new-template probe sends created_at_gt + state_eq + archived_eq — and sortable with filter[sorts][]=<attr> asc|desc over the serialized attributes (the versions screen sends created_at desc); a sort attribute outside the wire shape is a 400. |
GET | /api/projects/{p}/templates/{id}/versions/{version} | Version show, addressed by the version label, not a row id. |
PATCH/PUT | /api/projects/{p}/templates/{template_id}/versions/{version} | Upsert: creates the version when the label is new, updates it otherwise. Requires update on the template's rule path, 404-on-deny. Request meta flags: fetch_revision (resolve the revision now) and ignore_in_use (override the in-use guards). |
POST | …/versions/{version}/assign_to_assets | Repoints the named assets (asset_paths) at this version — requires update on the template's path plus update on every named asset path. A broken version, or one without a resolved git commit, refuses ("cannot be assigned as it is broken or has not resolved a Git commit"). 200 with the version re-serialized. |
PATCH/PUT | /api/projects/{p}/templates/{id} | Update — permitted attributes name, description, git_remote_name (re-resolved within the project), archived, disabled; a JSON null archived is ignored. Requires update on the template's own rule path, 404-on-deny. Archiving or disabling refuses with 422 (Can't archive/disable templates that are in use) while any unarchived node uses the template; a supplied archived also refuses when it matches the stored state (Template is already archived/active) or while the template's commits carry active changes. 200 with the updated template. |
DELETE | /api/projects/{p}/templates/{id} | Delete — only a template with no versions and no pinned assets (the templates create --version rollback lane); a referenced template refuses by name with 422. Requires project read plus delete on the project's …/templates path. 204. |
Every template and template version is created with its empty owned
properties and settings documents (the upstream
PropertiesOwner/SettingsOwner persistence) — the serializer's
properties/settings relationship ids are therefore always populated. A
one-shot idempotent boot backfill heals owners created before that port.
template attributes
| Attribute | Type | Meaning |
|---|---|---|
code | string | Unique (case-insensitively) among the project's unarchived templates; ^[a-z0-9_]+$, max 50. Also the tail of the template's RBAC rule path. |
name | string | Display name, unique per (project, template_type) among unarchived. |
description | string, nullable | Null when never set. |
archived | boolean | Archive flag. |
disabled | boolean | Disable flag. |
template_type | string | The template's kind. Create on this server accepts only asset (the default when blank); any other supplied value answers "Template type is not included in the list". The has_mintmodel version attribute is conditional on this type. |
created_at / updated_at | string (ISO8601, 6 fractional digits) | Row timestamps. |
Relationships: properties, settings (to-one documents with related
links), git_remote (to-one — the project remote the versions fetch from,
required at create and resolved by name within the project),
template_versions (to-many), nodes (to-many — the assets templated from
it, filtered to nodes the principal can read), project (to-one).
Links: self, template_versions (= self + /versions), project.
Every relationship is includable on the index and the show with the standard
?include= parameter. In particular include=git_remote sideloads the
remote serialized exactly as its own show document, masked credentials
included (previously the serializer exposed the relationship but the
include engine could not resolve it, so the CLI re-fetched the git_remotes
index to map the id to a name).
template_version attributes
| Attribute | Type | Meaning |
|---|---|---|
version | string | The version label (the URL segment), max length enforced on upsert. |
description | string, nullable | Null when never set; an omitted key preserves the stored value. |
state | string | The version's readiness. This server writes ready (set at create and whenever a revision resolves) and treats broken as the unassignable state; the full upstream state vocabulary is not declared in this repository, so no exhaustive list is documented here. |
created_by | string | Username of the creating user. |
archived | boolean | Archive flag. Cannot be set on create ("archived on create" refusal); archiving a version that assets still use refuses unless ignore_in_use. |
initialization_error | JSON, nullable | The stored initialisation failure, served verbatim; null when none. |
locked | boolean | Version lock: a locked version refuses modification ("Template version cannot be modified as the version is currently locked/archived"-shaped 422s) and float_git_rev cannot be enabled on it. Changing git_rev on an in-use version likewise refuses unless ignore_in_use. |
float_git_rev | boolean | Whether the version tracks its git_rev's moving tip instead of a fixed commit. |
float_refresh_error | JSON, nullable | The last float-refresh failure, served verbatim; null when none. |
has_mintmodel | boolean, conditional | Rendered only for asset-type templates (the key is absent otherwise): whether the version's repository carries a MintModel. |
created_at / updated_at | string (ISO8601, 6 fractional digits) | Row timestamps. |
git_rev | string, nullable | The requested revision (branch/tag/sha). On upsert, an omitted or "" git_rev falls back to the version's current revision. |
commit_sha | string, nullable | The resolved commit, null until resolution. |
git_remote_name | string, nullable | The owning template's git remote name. |
sealed_packs | array of {name, version} | The installed pack versions this version's pinned set sealed when it resolved (installed: declarations in .opschain/packs.yaml), pack name ascending — name is the pack store name, version the exact installed version. Always an array, empty when the version mounts no installed pack; git-sourced and bundled packs are not listed. The pack store shows the other direction as referenced_by (opschain docs resources/pack), and a re-resolve cuts a new version whose list moves — this one's never changes. |
invalid_action_files | array of {file, action, error} | The declarative action files at the version's pinned commit that failed validation or compilation at the version's last catalog resolution — file the repository path (.opschain/templates/<template>/actions/…, .opschain/actions/…, or a mounted pack's actions/… path), action the identity the file would have listed under, error the validation error verbatim. Each listed action appears in the asset catalog as an invalid entry (invalid on the action resource), a change created from it is refused with the same error, and a workflow step that reaches it ends system_error with that error as its last_error_message. The resolution that records the list also emits warn:template:action_file_invalid once per file (opschain docs events). Always an array, empty when every file compiles or the version was never resolved. |
Relationships: properties, settings, project, template, and
pending_commit (to-one commit while a resolution is pending; data: null
otherwise). Links: self
(…/templates/<template_id>/versions/<version>), template.
Assets and versions
An asset pins exactly one template version (template_version /
template relationships on the asset, see
opschain docs resources/node). Repointing happens from either end: the
asset update's template_version attribute, or this page's
assign_to_assets — both close the active template-version-history window,
open the new one, and carry the previous successful generated actions
forward.
MintModel versions
A version whose pinned commit carries the template's MintModel document
(MintModel/templates/assets/<template code>.json.erb, stored as
has_mintmodel at revision resolution) renders that document on every
generation request as the first phase of the derive job itself:
- The server converges the asset's properties (the repository-committed
document, overlaid by the converged
mintpress.mintmodel_overrides, then by themintpresssubtree with those overrides removed) and the derive job stages the upstream render payload in its workspace —mintmodel.tarholding the commit'sMintModel/folder (already materialised by the snapshot fetch) plusproperties.jsonandmp_context.json, with themintmodel_request.jsondocument (erb_file, parent codes) — underOPSCHAIN_MINTMODEL_DATA_PATH, the data-directory contract of theopschain-mintmodel-renderCLI. - The CLI is co-packaged in the mintpress-runner image (beside the :7001
transformer, which serves only the MintModel diff lane) and runs before
opschain-template-actionsin the same job — no daemon dependency, so a render works even while the transformer is down, and a runner image without the CLI fails the generation naming the missing binary. This server never renders ERB or derives step trees itself; the result document rides the job-result channel back. - A successful render stores the concrete model in
mintmodels, deduplicated by the SHA-256 of its canonical JSON (the upstreamfind_or_create_by_shasemantics — re-rendering an identical model reuses the row), and the result's actions — full-path enriched exactly as upstream (name→step_name, duplicate paths uniquified case-insensitively) — inasset_mintmodel_actions, keyed on the asset, the pinned commit and the SHA-256 of the staged properties. A later generation against an unchanged key reuses the stored row without re-rendering (no render phase is dispatched at all); a changed commit or properties re-renders. - The asset's action catalog merges those stored step trees with the
actions.rb and declarative-action-file sources (actions.rb is only
applicable to existing MintPress customers and those using the
MintPress SDK or the
packs:mintpressfeature), and the runner discovery workspace receives the formatted trees as~/.opschain/mintmodel_actions.jsonfor actions.rb'sOpsChain.mintmodel_actions— staged through the job's prepare spec with no size limit on current runners (a runner build predating protocol 3 receives them base64-encoded inside the job command instead, where a catalog over the 96 KiB budget degrades to an empty list with awarn:mintmodel:actions_unavailableevent) — and an actions.rb that consumes them after a fresh render is re-exported once against the stored result. - A render failure fails the generation request with
MintModel actions could not be derived: <cause>on the task'serror_message(the actions history serves it), never runs the template-actions phase, and caches the cause as the row'sgeneration_error; the next generation request re-renders.
API v2
TemplateService on /api/v2 (opschain docs api-v2) serves the
same catalog with the same authorisation split and refusals, audited with
the same api:templates:* / api:template_versions:* events:
ListTemplates(project_code)(typedarchived/template_typefilter, per-row read on each template's rule path) andGetTemplate(id). Every template get carries itsgit_remoterelation — the equivalence: the same fields the v1 git_remotes show serializes, the secret-bearing ones (user,ssh_key_data,password,passphrase) masked as{{hidden}}when stored and empty when not, never a value.TEMPLATE_VIEW_FULLadds the version rows (newest first; also on list rows) and the pinned nodes on the get.CreateTemplate/UpdateTemplate/DeleteTemplate— the v1 permit lists as typed optional fields, the same in-use and referenced-template refusals.ListTemplateVersions(template_id)(newest first; typedarchived/state/created_after),GetTemplateVersionby label, andUpsertTemplateVersionwith the v1 payload plusignore_in_use/fetch_revisionas typed flags.DeleteTemplateVersion(template_id, version)(the ruling — upstreamDestroyTemplateVersionCommandsemantics): an actively assigned version refuses ("currently in use"); one assigned in the past — or still referenced by another record — is archived instead, answered asarchived: truewith the row (the v1 206); otherwise the version and its owned documents are destroyed. Requires delete on the template's rule path; auditedapi:template_versions:destroy.CancelTemplateVersionRefresh(template_id, version)(upstreamCancelTemplateVersionRefreshCommandsemantics): refuses unless the version isinitializing("No SHA refresh is in progress…"); a cancelled version drops its pending commit and returns toreadyon its resolved commit — or becomesbrokenwhen none resolved — recording theinfo|error:template_version:refresh_cancelledevent. Requires update on the template's rule path; auditedapi:template_versions:cancel_refresh. Revision resolution is synchronous in this server, so only a row adopted mid-refresh can be cancellable.
The v1 facade still serves neither route (the route-parity waivers
stand for v1); both verbs live on the v2 surface only. The catalog read
model is PostgreSQL-only: the embedded store answers the typed
ERROR_REASON_STORE_POSTGRES_REQUIRED posture.
Related pages
opschain docs resources/node— the asset side of the pin.opschain docs resources/git_remote— where versions fetch from.