Lace policy describes selected checked record sets and whole-record exposure. Interlang is the normal human authoring surface. Lace lowers it to canonical Datalog for evaluation and exchange-plan identity; raw Datalog is the advanced surface, not the starting language.
The authoritative syntax and lowering rules are in Lace-120 · Interlang. The record-fact, Datalog, and exchange contracts remain in Lace-020, Lace-030, and Lace-040. This guide introduces the decisions an application author makes without duplicating those contracts.
A coordinate pattern constrains the Group,
App, and Name fields of checked records. It
does not identify one mutable row or bypass record validation.
converge {
Task in //community//todo/task//task/{*} => include Task
}
This selects every checked record with:
Group = community
App = todo/task
Name = task/<non-empty path tail>
Use App for the record family and Name for
its app-local names. Keep App exact when later clauses
assume one record shape; an App-prefix wildcard also matches other
contracts below that prefix.
The hash remains record identity, so several record versions may
match one coordinate. {*} is the whole-field or final-tail
wildcard; an unbraced * is literal data. Use the exact
coordinate and wildcard rules from Lace-120 rather than treating
coordinates as URL globs.
Start with one selected family and one deliberately valid non-match.
Test the policy through list or between two memory
participants before adding evidence, links, negation, ordering, or
transport authority.
converge selects and
exposesA top-level converge entry contributes records to active
exchange and exposes the same checked local records to peer-origin
rules:
converge {
Task in //community//todo/task//task/{*} => include Task
}
These are distinct effects:
Exposure alone does not transfer a record. Selection by one participant alone does not transfer it either: standard interlace uses the intersection of both policy operands.
Exposure is whole-record declassification. If peer-authored rules must not inspect a record’s facts, do not expose that record. Hiding or deselecting it later cannot erase bytes already copied.
expose for supporting inspection scopeSometimes peer-origin policy must inspect checked evidence that
should not be selected as a direct entry by that particular clause. Put
that additional scope in an explicit expose block:
expose {
Member in //community//membership//member/{*} => include Member
converge {
Task in //community//todo/task//task/{*} => include Task
}
}
The direct Member entry contributes exposure only. The
nested converge entries contribute active selection and
exposure. This is a privacy boundary, not an optimization hint: review
what a peer may infer from every exposed whole record.
Do not add a broad expose block by default. A simple
top-level converge already exposes its selected checked
records.
Evidence is ordinary validated record data. Select evidence records too when a recipient must receive and validate them before dependent records become eligible.
writer_member(Member, writer_key) :=
Member in //community//membership//member/{*}
| Member.By == 'V.EXAMPLE_AUTHORITY_BY_B64A.H3'
| Member.Role == 'writer'
| Member.Member == writer_key
writer(writer_key) := writer_member(_, writer_key)
converge {
writer_member(Member, _) => include Member
Task in //community//todo/task//task/{*}
| Task.By == writer_key
| writer(writer_key)
=> include Task
}
Here a task depends on a checked local membership record associating
its By value with the writer role. An advertisement is only
a discovery claim; it cannot stand in for checked membership evidence.
Converging the membership records allows a later round to validate the
evidence locally and unlock matching tasks.
A valid Mark proves that the matching By-secret value produced the
Mark over those exact record bytes. Application policy still decides
what the record’s By value means; a Mark alone does not
prove identity, authorship, truth, or authorization.
Use include ... by hash when checked local support
already names the target hash, such as a manifest’s record-link
field:
manifest(Manifest) :=
Manifest in //files//manifest//manifest/{*}
chunk(Chunk) :=
manifest(Manifest)
| Manifest['Chunk+Link'](range) == Chunk
converge {
manifest(Manifest) => include Manifest
chunk(Chunk) => include Chunk by hash
}
The manifest is discovered from advertised fields. Once a checked
manifest binds a chunk target hash, include Chunk by hash
can select and request that exact target without pretending the missing
chunk’s own fields are already known. Use ordinary include
for records discovered from their advertised coordinate or fields.
See the chunk-manifest-closure
scenario for a staged example.
Use a TopK binding clause over a candidate helper when policy needs a bounded set. Put every pre-ranking restriction and projected ordering field in that helper:
state_candidate(State, tai) :=
State in //community//settings//state/{*}
| State.By == 'V.EXAMPLE_AUTHORITY_BY_B64A.H3'
| State.TAI == tai
current_state(State) :=
greatest 1 from state_candidate(State, tai) by lex tai
Omitting per makes one global partition. Add explicit
partitions for one winner per application key:
current_resource(Resource, name) :=
greatest 1 per (name)
from resource_candidate(Resource, name, tai)
by lex tai
TopK selects a set; it does not order list results or
promise transfer order, pagination, or a hidden coordinate winner. Equal
primary keys use every complete candidate component in argument order as
the deterministic tie-break. Candidate argument order therefore
matters.
TAI is application-supplied ordering data, not proof of trusted wall-clock time. Choose which authority supplies it and constrain that authority in the candidate helper. A clause joined after TopK filters after ranking; restrictions that must affect the ranked population belong inside the candidate helper.
See the name-current-key-rotation
scenario for current checked state gating newly advertised content,
and latest-message-window
for a bounded tracker window plus checked-support hash closure.
The scenario workbench shows policy source, records on each side, exposure, staged additions, events, and expected results. Useful starting scenarios are:
shared-todo-list
for marked membership and tasks;trusted-members
for evidence unlocking later records; andchunk-manifest-closure
for checked-support hash closure.On the published site these source links are rewritten to the
interactive workbench. In a source checkout, the JSON and Interlang
templates are executable fixtures; follow
scenario-tools/README.md to build and run the browser or
Node harness.
For each policy, ask:
Interlang already supports coordinate patterns, helpers, field conditions, checked evidence, links, common text/order/cardinality tests, exposure, and field- or hash-discovered inclusion. Prefer it for application policy.
Move to raw Lace Datalog when the required behavior genuinely needs explicit 040 facets, custom advertisement reasoning, or another lower-level surface that Interlang cannot express. Raw Datalog requires understanding:
Do not hand-author Datalog just to spell a coordinate selector in a more verbose form.
local policy sources → one operand
one operand from each participant → exchange plan
A policy frame performs the first step. Interlace performs the second.
Both operands must select a record before it moves. Each Lace controls exposure of its own records.
The runtime bindings and C FFI expose the policy-frame step.
Keep the first policy loop local:
list against a memory Lace;The lace
CLI provides explicit check interlang, frame checking,
and finalization commands. Runtime bindings also expose
standard-Interlang validation and policy-frame operations. Validation
checks source and profile rules; executable scenarios prove that the
selected set matches the application’s intent.
Return to Runtime and application integration to choose storage and transport, or run Your first interlace for the smallest complete policy/runtime example.