Entry Registry
Query and modify registered entries. Access metadata, snapshots, and version history.
Loading
local registry = require("registry")
Entry Structure
{
id = "app.lib:assert", -- string: "namespace:name"
kind = "function.lua", -- string: entry type
meta = {type = "test"}, -- table: searchable metadata
data = {...} -- any: entry payload
}
Entries read back from registry.get, registry.find, snap:entries(), snap:get(), snap:namespace() and snap:find() carry only these four author-facing fields.
dependency_root is a write-side field accepted by changes:create() and changes:update(). It is a boolean that marks an ns.dependency entry as a deployment root. It is never returned by the entry APIs; registry-owned state is read through snap:state().
Get Entry
local entry, err = registry.get("app.lib:assert")
Permission: registry.get on entry ID
Find Entries
local entries, err = registry.find({[".kind"] = "function.lua"})
local entries, err = registry.find({[".kind"] = "http.endpoint", [".ns"] = "app.api"})
Keys prefixed with . match entry fields (.kind, .ns, .name, .id) and accept * globs. Keys prefixed with meta. match entry metadata; a leading ~, *, ^ or $ on a meta. key selects regex, contains, prefix or suffix matching. Keys with neither prefix are ignored.
Parse ID
local id = registry.parse_id("app.lib:assert")
-- id.ns = "app.lib", id.name = "assert"
Snapshots
Point-in-time view of the registry:
local snap, err = registry.snapshot() -- current state
local snap, err = registry.snapshot_at(5) -- at version 5
Snapshot Methods
| Method | Returns | Description |
|---|---|---|
snap:entries() |
Entry[], error |
All accessible entries |
snap:state() |
State, error |
Entries with registry-owned metadata, plus the resolved module graph |
snap:get(id) |
Entry, error |
Single entry by ID |
snap:find(filter) |
Entry[] |
Filter entries |
snap:namespace(ns) |
Entry[] |
Entries in namespace |
snap:version() |
Version |
Snapshot version |
snap:changes() |
Changes |
Create changeset |
Snapshot State
snap:state() returns the entry state together with the module graph selected for the snapshot version. Registry-owned provenance is carried on each entry rather than merged into meta, so it cannot be confused with authored metadata.
local snap, err = registry.snapshot()
local state, err = snap:state()
for _, entry in ipairs(state.entries) do
print(entry.id, entry.registry.owner, entry.registry.root)
end
if state.resolution then
print(state.resolution.digest, state.resolution.input_digest)
for _, module in ipairs(state.resolution.modules) do
print(module.name, module.version)
end
end
Each entry in state.entries has the four author-facing fields plus:
registry.owner- deployment source that supplied the entryregistry.root-truewhen the entry is a dependency declaration selected by the deployment
state.resolution describes the snapshot's module graph. Historical snapshots include the resolution recorded for that version when available. Reading it requires registry.resolution.get on resource ""; denial omits the table without failing entry reads. Overlay snapshots carry no resolution.
| Field | Type | Description |
|---|---|---|
digest |
string | Content digest of the complete immutable selection |
input_digest |
string | Digest of the declared root set |
baseline_digest |
string | Digest of the deployment baseline the graph was solved against; omitted when unbound |
roots |
array | Authored dependency declarations used as solver inputs |
references |
array | Root-shaped declarations folded into an existing root for the same component; omitted when empty |
modules |
array | Selected modules |
lock |
table | Recorded lock selection: root_module, modules, and a deterministic digest; omitted when no lock selection is recorded |
roots and references entries have id, component and version. modules entries have name and version, plus version_id, source, digest, size_bytes and protected when set.
resolution.modules describes the effective selection; resolution.lock.modules describes the pinned selection. They can differ after a live update.
Versions
local version, err = registry.current_version()
local versions, err = registry.versions()
print(version:id()) -- numeric ID
print(version:string()) -- display string
local prev = version:previous() -- previous version or nil
local next = version:next() -- next version or nil
History
local hist, err = registry.history()
local versions, err = hist:versions()
local version, err = hist:get_version(5)
local snap, err = hist:snapshot_at(version)
Changesets
Build and apply modifications:
local snap, err = registry.snapshot()
local changes = snap:changes()
changes:create({
id = "test:new_entry",
kind = "test.kind",
meta = {type = "test"},
data = {config = "value"}
})
changes:update({
id = "test:existing",
kind = "test.kind",
meta = {updated = true},
data = {new_value = true}
})
changes:delete("test:old_entry")
local new_version, err = changes:apply()
Permission: registry.apply on every changed entry ID, for both changes:plan() and changes:apply().
Planning and Concurrency
changes:plan() expands a durable changeset without applying entries or advancing history. It returns base, digest, changes, history, effects, and the permitted resolution metadata.
local plan, err = changes:plan()
if err then return nil, err end
-- Inspect plan.changes before applying the same changeset.
local version, apply_err = changes:apply()
if apply_err then return nil, apply_err end
A durable apply fails with retryable errors.CONFLICT if the registry moved since its snapshot. After planning, it also rejects changes to the reviewed inputs, such as artifacts or source trees. Reopen the snapshot and rebuild on conflict. Mutating a planned changeset drops its plan binding. Overlay changesets do not support plan().
Deleting Entries
changes:delete() accepts an ID string, a table with an id string, a table with ns and name strings, or an array of any of those. Arrays may nest, and duplicate IDs collapse into a single delete operation.
changes:delete("test:old_entry")
changes:delete({id = "test:old_entry"})
changes:delete({ns = "test", name = "old_entry"})
changes:delete({"test:a", {ns = "test", name = "b"}, {"test:c"}})
An empty list, a table that references itself, and a value that is neither a string nor a table are rejected with errors.INVALID.
Changes Methods
| Method | Description |
|---|---|
changes:create(entry) |
Add create operation |
changes:update(entry) |
Add update operation |
changes:delete(id) |
Add delete operation |
changes:ops() |
Get pending operations |
changes:plan() |
Preview a durable changeset and bind its next apply to the result |
changes:apply() |
Apply changes, returns new Version |
Apply Version
Roll back or forward to a specific version:
local prev = current_version:previous()
local ok, err = registry.apply_version(prev)
Permission: registry.apply_version
Build Delta
Compute operations to transition between states:
local from = {{id = "test:a", kind = "test", meta = {}, data = {}}}
local to = {{id = "test:b", kind = "test", meta = {}, data = {}}}
local ops, err = registry.build_delta(from, to)
for _, op in ipairs(ops) do
print(op.kind, op.entry.id) -- "entry.create", "entry.update", "entry.delete"
end
Overlays
An overlay is a process-local set of registry entries owned by a logical identity. Overlay entries take part in ordinary topology and handler transitions, so services start and stop for them exactly as for durable entries, but they never advance registry history and never appear in a version. They exist only in the running process and are empty after a cold boot, so the owning control service reconciles them on startup.
local snap, err = registry.overlay("data-sources:crm")
Returns: Snapshot, error
The snapshot exposes the owner's overlay entries through the usual methods and reports the current registry version from snap:version(). It also captures the overlay generation at the moment it is opened, which is what makes writes safe.
local snap, err = registry.overlay("data-sources:crm")
if err then return nil, err end
local changes = snap:changes()
changes:create({
id = "data.crm:connection",
kind = "registry.entry",
meta = {},
data = {endpoint = "https://crm.internal"}
})
local version, err = changes:apply()
changes:apply() on an overlay snapshot writes the overlay and returns the current registry version. No history version is created, so the returned version is unchanged unless a durable change happened concurrently.
Concurrency
Each overlay carries a generation counter that increases on every successful apply. changes:apply() succeeds only if the generation still matches the one captured when the snapshot was opened. A concurrent apply to the same overlay fails with errors.CONFLICT marked retryable: reopen the overlay and rebuild the changeset.
local last_err
for _ = 1, 3 do
local snap, err = registry.overlay("data-sources:crm")
if err then return nil, err end
local _, apply_err = snap:changes():delete("data.crm:connection"):apply()
if not apply_err then return true end
if not apply_err:retryable() then return nil, apply_err end
last_err = apply_err
end
return nil, last_err
Restrictions
- The owner string is required and must not be blank.
- A changeset must be non-empty and must not name the same entry twice.
createfails when the ID already exists in durable state or in any overlay.updateordeletemay shadow a durable entry withregistry.overlay.shadowpermission on that ID. Only one overlay may shadow it, and durable writes to it are blocked while the shadow exists. Deleting an existing shadow releases it and restores the current durable entry.- Overlay entries cannot set
dependency_rootor any other registry-owned metadata. - Overlay entries cannot use kinds owned by a registry directive, such as
ns.dependency. - A delete that removes an entry a surviving entry depends on is rejected.
- Dependencies cannot cross overlay owner boundaries, and durable entries cannot depend on newly created overlay entries. A shadowed durable entry may retain durable dependents.
The rest surface as errors.CONFLICT or errors.INVALID, and none are retryable: only the generation mismatch above is.
Permissions: registry.overlay.get on the owner to open and read, registry.overlay.apply on the owner to write, and registry.overlay.<create|update|delete>.<kind> on each entry ID in the changeset.
Permissions
| Permission | Resource | Description |
|---|---|---|
registry.get |
entry ID | Read entry (also filters find/entries results) |
registry.apply |
entry ID | Plan or apply each durable operation |
registry.resolution.get |
"" |
Read resolution and lock selection in snapshot state or plans |
registry.apply_version |
- | Apply/rollback version |
registry.overlay.get |
owner ID | Open and read an overlay snapshot |
registry.overlay.apply |
owner ID | Apply an overlay changeset |
registry.overlay.create.<kind> |
entry ID | Create an overlay entry of that kind |
registry.overlay.update.<kind> |
entry ID | Update an overlay entry of that kind |
registry.overlay.delete.<kind> |
entry ID | Delete an overlay entry of that kind |
registry.overlay.shadow |
durable entry ID | Shadow a durable entry, in addition to the per-kind action |
Errors
| Condition | Kind |
|---|---|
| Entry not found | errors.NOT_FOUND |
| Version not found | errors.NOT_FOUND |
| Permission denied | errors.PERMISSION_DENIED |
| Invalid parameter | errors.INVALID |
| No changes to apply | errors.INVALID |
| Overlay changed during apply | errors.CONFLICT (retryable) |
| Durable snapshot or reviewed plan is stale | errors.CONFLICT (retryable) |
| Overlay entry owned elsewhere or conflicts with durable state | errors.CONFLICT |
| Registry not available | errors.INTERNAL |
See Error Handling for working with errors.