Tags: interlang, dx, policy, non-protocol
Interlang is Lace’s friendly policy authoring surface for record-set convergence and exposure. It compiles coordinate-shaped clauses to one side-agnostic 030/040 Datalog module contribution. Finalized contributions may export:
SelectStoredRecord(Record)
SelectAdvertisedCandidate(Record)
ExposeRecord(Record)
TxDisabled()
RxDisabled()
Interlang is source syntax for humans and tools, not a wire protocol. Canonical 030 Datalog bytes, not Interlang text, identify a finalized operand and its exchange plan.
Portable standard Interlang is the language defined by this specification. Local policy-frame authoring additionally accepts the 030 extended policy-module surface, including linked relations. That extension uses the same parser and lowerer but is rejected by strict standard-Interlang validation. Every finalized operand is ordinary self-contained 030 Datalog with no unresolved authoring links.
This grammar uses = for definition, [...]
for optional text, {...} for repetition, and |
for alternatives; those marks are notation, not source characters.
source = hash-source | item-source ;
hash-source = outer-spacing, hash-address, outer-spacing ;
item-source = spacing, { item }, spacing ;
item = helper-definition | converge-block | expose-block
| datalog-block | selection | bare-coordinate-selection ;
helper-definition = helper-name, "(", [parameter-list], ")", ":=", pipeline ;
parameter-list = parameter, {",", parameter} ;
parameter = record-variable | text-variable | "_" ;
converge-block = "converge", "{", {selection}, "}" ;
expose-block = "expose", "{", {expose-entry}, "}" ;
expose-entry = selection | converge-block ;
selection = pipeline, "=>", "include", record-variable, ["by", "hash"] ;
bare-coordinate-selection
= coordinate ;
datalog-block = "datalog", "{", {datalog-rule-line}, "}" ;
pipeline = clause, {"|", clause} ;
clause = coordinate-source | field-equality | field-inequality
| link-equality | helper-call | negated-call
| text-shape-test | lex-order-test | integer-test
| cardinality-test | admission-order-test | top-k-clause ;
coordinate-source = ["from"], record-variable, "in", coordinate-expression ;
coordinate-expression = coordinate | quoted-coordinate ;
quoted-coordinate = string whose decoded text parses completely as coordinate ;
field-equality = term, "==", term ;
field-inequality = term, "!=", term ;
link-equality = link-access, "==", record-term ;
helper-call = helper-atom | "peer_confidential", "(", term, ")" ;
helper-atom = helper-name, "(", [argument-list], ")" ;
negated-call = "not", helper-call ;
text-shape-test = term, "starts", "with", term
| term, "ends", "with", term
| term, "has", "shape", "(", term, ",", string,
",", term, ")" ;
lex-order-test = term, "sort", ("after" | "at-or-after"), term ;
integer-test = data-length-access, comparison, unsigned-decimal ;
cardinality-test = "count", helper-atom, comparison, unsigned-decimal ;
admission-order-test = record-term, "admit", "after", record-term ;
top-k-clause = top-k-direction, unsigned-decimal,
["per", "(", partition-variable-list, ")"],
"from", top-k-candidate, "by", top-k-order ;
top-k-direction = "greatest" | "least" ;
partition-variable-list
= variable, {",", variable} ;
top-k-candidate = helper-name, "(", [top-k-argument-list], ")" ;
top-k-argument-list = top-k-argument, {",", top-k-argument} ;
top-k-argument = record-variable | text-variable | "_" | string ;
top-k-order = "lex", top-k-order-term
| "int", top-k-order-term
| "admit", record-term ;
top-k-order-term = record-variable | text-variable | string ;
comparison = "<" | "<=" | ">" | ">=" ;
variable = record-variable | text-variable ;
argument-list = term, {",", term} ;
term = record-variable | text-variable | "_" | string
| field-access ;
record-term = record-variable | string ;
field-access = record-variable, (".", field-identifier,
["[", occurrence-index, "]"]
| "[", string, "]") ;
data-length-access = record-variable, "[", "'Data-Length'", "]" ;
link-access = record-variable, "[", string, "]", "(", term, ")" ;
occurrence-index = "_" | unsigned-decimal ;
coordinate = "//", path-pattern, "//", path-pattern, "//",
path-pattern ;
path-pattern = exact-path | "{", text-variable, "}" | "{*}"
| path-prefix, "/{*}"
| [path-prefix, "/"], "{_}/", path-suffix
| [path-prefix, "/"], literal-set,
["/", path-suffix] ;
literal-set = "{", spacing, string, spacing, ",", spacing, string,
{spacing, ",", spacing, string}, spacing, "}" ;
hash-address = "////", record-hash ;
helper-name = ? [a-z][a-z0-9_]* except peer_confidential, true, and not ? ;
record-variable = ? [A-Z][A-Za-z0-9_]* ? ;
text-variable = ? [a-z][a-z0-9_]* except true and not ? ;
field-identifier = ? [A-Za-z][A-Za-z0-9_]* ? ;
unsigned-decimal = ? 0 or [1-9][0-9]* ? ;
string = ? 030 single-quoted constant ? ;
record-hash = ? locally accepted typed record hash ? ;
exact-path = ? exact 010 Group, App, or Name value for its field ? ;
path-prefix = ? one or more exact 010 path segments for its field ? ;
path-suffix = ? one or more exact 010 path segments for its field ? ;
datalog-rule-line = ? one 030 rule on one logical source line ? ;
spacing = ? spaces, tabs, LF, blank lines, and comments ? ;
outer-spacing = ? spaces, tabs, and LF, but not comments ? ;
A hash address is the entire trimmed source and cannot be combined
with comments or declarations. A top-level selection has ordinary
convergence effect. A bare unquoted coordinate is a complete top-level
convergence selection. It binds implicit Record and lowers
identically to:
Record in //group//app//name => include Record
The shorthand cannot have pipeline clauses or an
=> include suffix and is not valid inside
converge or expose. Once a selection names a
variable, adds a clause, appears in a block, or uses a quoted
coordinate, it uses the explicit form and names its included record:
Record in '<coordinate>' => include Record
//group//app//name => include Record, an arrowless
top-level or block entry Record in //group//app//name, and
a bare coordinate followed by | are invalid. An explicit
coordinate source without => include remains valid
inside a helper-definition pipeline, whose helper head supplies its
effect.
In non-hash source, # starts a comment when only spaces
or tabs precede it on the physical line. This permits comments to follow
the indentation of their surrounding block. A # after any
other source character is expression data and may be valid or invalid
according to its surrounding syntax. Comment content is excluded before
lowering.
Whitespace separates tokens but carries no indentation meaning.
Single-line and multiline blocks use the same grammar and semantics. A
complete bare coordinate ends its top-level shorthand item;
=> include <record-variable> ends an explicit
selection, } ends a block, and | separates
conjunctive clauses at its lowest precedence. Operators inside
parentheses, brackets, literal-set braces, or strings do not terminate a
clause. Multiple definitions of one helper are ordinary Datalog
union.
Interlang uses the 030 variable model: uppercase variables are
record-reference shaped, lowercase variables are text, and
_ is anonymous and non-binding. Helper names use exactly
the ordinary 030 lowercase helper syntax; - and
~ are invalid. Capitalized profile or runtime calls are
invalid outside datalog. Standard Interlang provides only
lowercase peer_confidential(peer_key), which lowers to
PeerConfidential(peer_key). A field equality contains at
least one field access, and every operator test is non-binding and
subject to 030 safety after lowering.
The implementation-shaped long form
link Record 'Field+Link' data data target Target is not
Interlang. Use the field-call syntax defined below.
Coordinates are syntax over record facts, not a lookup authority:
hash-address = "////" record-hash
coordinate = "//" path-pattern "//" path-pattern "//" path-pattern
Hash addresses are parsed before coordinates because
////<hash> also starts with //. Exact
coordinates lower to Have and Field atoms. A
coordinate selects every match, not an index-preferred row or a hidden
latest version.
Coordinate paths follow 010. Group, App, and Name use the same pattern vocabulary. Whole-field lowercase captures bind the complete field value. In a pipeline, the coordinate source is explicit:
Task in //{group}//{app}//{name}
An unbraced * is an exact literal segment.
{*} matches any complete field when it is the whole
pattern. A final complete segment {*} matches an arbitrary
non-empty path tail:
Task in //{*}//todo/{*}//task/{*}
task/{*} includes deeper descendants and is not an
exactly-one-segment wildcard. {*} MUST occupy a complete
segment and MUST be the complete field or its final segment. Trailing
/ is not wildcard sugar.
{_} matches exactly one non-empty interior segment and
requires a non-empty literal suffix:
Task in //org/{_}/team//todo//task/{_}/item
{_} MUST occupy a complete segment. Whole-field and
final {_} are invalid. A field contains at most one
wildcard operator.
A finite literal segment set contains at least two distinct 030 single-quoted strings:
Task in //org/{'east','west'}//todo//task/{'open','closed','*'}
Set members are literal complete segments; '*' is a
literal star, not a wildcard. Members MUST be non-empty, MUST NOT
contain /, and every expanded complete field path MUST
validate under 010. Duplicate decoded members collapse before the
two-member minimum is checked. A field contains at most one set and a
set does not combine with {*} or {_}. Sets in
different fields are independent and lower without Cartesian
expansion.
Exact paths and captures lower to
Field/AdvertisedField. Wildcards also use
TextShape. Sets lower through one generated static relation
over their exact expanded field values. Identical static row sets with
the same argument-kind shape share one generated relation within a
contribution.
Raw source cannot unambiguously spell every valid coordinate. A Name
containing =>, or coordinate data with significant edge
spaces, uses a quoted coordinate on the right of
Record in:
converge {
Record in '//g//a//x=>y' => include Record
}
The quote uses the 030 single-quoted constant decoder. Its decoded text:
//;Quote characters never become coordinate bytes. Invalid escaping,
trailing decoded text, invalid patterns, or invalid 010 coordinate
values are source errors. {*}, {_}, literal
sets, and lowercase captures retain pattern meaning after string
decoding; 010 forbids literal braces in coordinate values, so no valid
literal coordinate is lost. Literal-set strings inside a quoted
coordinate escape their quotes through the outer 030 string:
Record in '//g//a//item/{\'open\',\'closed\'}'
A helper definition is:
helper(arg, ...) := clause | clause
Helpers lower to ordinary lowercase Datalog predicates. Argument kinds are inferred across definitions and calls. Friendly helpers cannot call predicates defined only by inline Datalog, and inline Datalog cannot name compiler-generated Interlang helpers. This opaque boundary avoids guessing candidate flow through arbitrary Datalog; advanced rules define facets directly.
A TopK clause is a binding pipeline clause over one friendly helper call:
current_resource(Resource, name) :=
greatest 1 per (name)
from resource_candidate(Resource, name, tai)
by lex tai
It maps exactly to Lace-030 TopK: greatest
is direction '>', least is
'<', omitted per is
PartitionBy(), per (name,...) preserves the
written variable order, and lex, int, and
admit map to Lex, Int, and
AdmitOrder. The unquoted limit becomes the canonical quoted
Datalog bound. The example lowers schematically to:
il_current_resource(Resource,name) :- TopK(il_resource_candidate(Resource,name,tai),PartitionBy(name),Lex(tai),'>','1').
The clause binds variables from selected complete candidate facts; it
is not a non-binding operator test. The candidate reaches its
lower-stratum fixed point before ranking. Omitted per
creates one global partition. Every explicit partition is independent.
The primary key is followed by every complete candidate component in
argument order, and greatest/least applies to that whole ordering. TopK
selects a set; it promises no list, iteration, advertisement, transfer,
event, or display order.
Put every restriction that must apply before ranking in the candidate helper:
eligible_candidate(Record, name, tai) :=
resource_candidate(Record, name, tai)
| allowed_name(name)
current_resource(Record, name) :=
greatest 1 per (name)
from eligible_candidate(Record, name, tai)
by lex tai
A separate pipeline clause joined with TopK runs after the aggregate and does not constrain or repartition its candidate relation.
Candidate and ordering terms deliberately use the restricted
Datalog-shaped forms in the grammar, never field accesses. Project a
field into a candidate helper variable and rank that variable. The
candidate must be a friendly helper, not a linked, runtime, profile,
facet, builtin, or inline-Datalog call. A nonempty per
contains distinct variables that occur in the candidate. The ordering
term also occurs literally in the candidate and is not _;
admit takes a record-reference variable or supported
record-hash constant. Ordinary complete-module validation remains
authoritative for helper kinds, safety, stratification, limits, and
aggregate validity. Recursion through TopK is therefore rejected as
unstratified. greatest, least,
per, from, by, lex,
int, and admit are contextual here and remain
valid ordinary helper names in other call positions.
Local frame authoring additionally accepts a raw linked call as an explicit extension node:
allowed(Record) :=
Record in //group//app//{*}
| @configured_key(authority)
| Record.By == authority
Linked names and arguments follow the 030 extended policy-module
contract. Linked arguments are text-only, so the missing candidate
record variable itself cannot be passed, while text read from one of its
advertised fields may be. Strict standard-Interlang validation rejects
every raw @ node. Frame validation resolves links by name
and arity and finalization removes all raw @ syntax.
Field equality uses an uppercase record receiver:
Record.By == by_value
Record.Role == 'writer'
Record['Content-Type'] == 'text/plain'
Record.Tag[0] == 'urgent'
Record.Tag[_] == 'urgent'
After ., the token is the exact field name. Brackets
spell non-identifier names. Field-to-field equality introduces a shared
text variable. The 030 standard-field kind checks still apply; use
lowercase tai and by_value for text-valued
TAI and By fields.
The field, inclusion modifier, and Datalog partition expression remain distinct:
marked_candidate(Record, room, tai) :=
candidate(Record, room, tai)
| Record.By == by_value
chunk(Chunk) := manifest(Manifest) | Manifest['Chunk+Link'](range) == Chunk
converge {
greatest 1 per (room) from marked_candidate(Record, room, tai) by lex tai
=> include Record
chunk(Chunk) => include Chunk by hash
}
The field access is Record.By, lowercase
by hash is inclusion syntax, and the lowered aggregate uses
TopK(...,PartitionBy(room),...). By(...) is
not an 030 partition expression.
The only Interlang record-link syntax is:
Manifest['Chunk+Link'](range) == Chunk
It lowers to:
RecordLink(Manifest,'Chunk+Link',_,range,Chunk)
A top-level converge contributes selected records to
active exchange and also exposes them:
converge {
expression => include Record
expression => include Record by hash
}
Each inclusion names one record variable. It emits checked stored selection and exposure. Request discovery follows its declared inclusion form.
An expose block contributes whole-record exposure
without selecting direct entries for transfer. It may contain direct
selections and, intentionally, one restricted nested convergence
grouping:
expose {
visible_member(Member) => include Member
converge {
public_task(Task) => include Task
public_attachment(Attachment) => include Attachment by hash
}
}
Direct entries emit only exposure. Entries in the nested
converge emit the same stored selection, request discovery,
and one exposure rule as a top-level convergence selection. The outer
block does not duplicate exposure.
Only selections and converge blocks are valid directly
inside expose. Nested expose, helper
definitions, inline datalog, and other facets inside the
block are invalid. A nested converge contains selections
only. Empty blocks are syntactically valid and have no effect.
includeBare include Record marks that record as the candidate.
Checked lowering uses one ordinary helper per source helper. Advertised
lowering generates only helper specializations reachable from exported
field-discovered arms. A specialization is identified by:
(helper name, arity, candidate argument-position set)
In an advertised specialization, candidate Have and
Field facts become Advertised and
AdvertisedField. Facts over unmarked support records remain
checked local facts. A text value obtained from a candidate field
remains text; it does not make another record a candidate and may join
checked support.
Example:
task(Task, member) :=
Task in //g//task//{*}
| Task.By == member
visible_task(Task) := task(Task, member) | recognized_member(member)
converge {
visible_task(Task) => include Task
}
Schematic lowering includes:
il_task(Task,member) :- Have(Task), Field(Task,'Group',_,'g'), Field(Task,'App',_,'task'), Field(Task,'By',_,member).
il_task_advertised_p0(Task,member) :- Advertised(Task), AdvertisedField(Task,'Group',_,'g'), AdvertisedField(Task,'App',_,'task'), AdvertisedField(Task,'By',_,member).
il_visible_task(Task) :- il_task(Task,member), il_recognized_member(member).
il_visible_task_advertised_p0(Task) :- il_task_advertised_p0(Task,member), il_recognized_member(member).
SelectStoredRecord(Task) :- il_visible_task(Task).
SelectAdvertisedCandidate(Task) :- il_visible_task_advertised_p0(Task).
ExposeRecord(Task) :- il_visible_task(Task).
Candidate marks propagate by record-variable identity through helper argument positions. Repeated or aliased arguments mark every matching position. Union, positive and mutual recursion, negated helper calls, cardinality, and TopK candidate calls preserve the corresponding specialization and operator. Each reachable finite signature is emitted once; unreachable combinations are not generated. Final 030 safety, kind, stratification, and resource checks remain authoritative.
Candidate-dependent projectability is exact:
| Construct | Advertised specialization |
|---|---|
candidate coordinate source or Have(candidate) |
Advertised(candidate) and advertised coordinate
fields |
candidate.Field |
AdvertisedField(candidate,...) |
unmarked Have(Support), Support.Field, or
other support record facts |
retain checked fact |
| text read from a candidate field | retain text binding; it may join checked support |
inequality, TextShape, Lex, or
Int over bound values |
retain builtin |
| helper receiving candidate in one or more positions | call exact candidate-position specialization |
| union, positive or mutual recursion | specialize reachable rules and preserve recursion |
| negated helper or cardinality receiving candidate positions | specialize callee and preserve operator |
RecordLink(candidate,...,Target) |
reject; advertisements have no parsed link relation |
RecordLink(Support,...,candidate) |
retain checked fact |
AdmitOrder(candidate) |
reject; a missing candidate has no local admission order |
| admission order over unmarked support | retain checked operation |
| TopK candidate helper with marked positions | call the exact advertised specialization and preserve argument/partition order, ordering, direction, and limit |
| TopK candidate helper with no marked position | retain checked aggregate support |
TopK AdmitOrder over a marked candidate, including
through repeated helper-head aliases |
reject; a missing candidate has no local admission order |
| linked call | retain frame-resolved text support |
| friendly call into inline Datalog | reject as opaque; write explicit facets |
A rejected path is an invalid Interlang discovery shape. Diagnostics identify the blocked helper and construct and suggest explicit facet rules. Compiler limits stop specialization before generated output exceeds the active 030 rule/resource limit.
include by hashinclude Record by hash is for a hash already bound by
checked local support, such as a manifest link:
manifest(Manifest) := Manifest in //files//manifest//{*}
chunk(Chunk) := manifest(Manifest) | Manifest['Chunk+Link'](range) == Chunk
converge {
manifest(Manifest) => include Manifest
chunk(Chunk) => include Chunk by hash
}
It lowers conceptually to:
SelectStoredRecord(Chunk) :- Have(Chunk), chunk(Chunk).
ExposeRecord(Chunk) :- Have(Chunk), chunk(Chunk).
SelectAdvertisedCandidate(Chunk) :- Advertised(Chunk), chunk(Chunk).
There is no advertised projection of the target’s own facts. The
support body may use checked facts of other records and bind the target
as a RecordLink target or record-reference-shaped field
value. It is invalid for that body to require Have(Record),
Field(Record,...), RecordLink(Record,...), or
AdmitOrder(Record) for the included target before transfer.
It also cannot use advertisement facts, facets, or opaque inline-Datalog
helpers.
TopK may bind a by-hash target only when its candidate helper
recursively proves that same checked-support contract. The marked target
must come from an unmarked support record’s RecordLink
target or record-reference-shaped field. The aggregate may rank those
hashes but cannot inspect the target’s own facts or admission order.
Safety binding by TopK is separate from this recursive hash binding
proof and from field-discovery grounding.
A direct exposure-only entry may also use by hash:
expose {
linked_attachment(Attachment) => include Attachment by hash
}
That form emits only checked support plus
Have(Attachment) and ExposeRecord(Attachment).
It does not select, advertise, request, or transfer the target.
Independent local admission is required before its facts are
exposed.
The standalone ////<hash> remains direct
exact-hash selection sugar, not a support-body binding form. Advanced
containment or custom discovery uses explicit facets in
datalog.
A compiler may factor an exact finite union of structurally identical arms into ordinary private static helper rows. Factoring preserves the request-discovery form, variable kinds, correlations, and the convergence versus exposure-only boundary. It never turns advertised claims into checked facts. Generated rows and rules are canonical operand content.
peer_confidential(peer_key) is the only friendly runtime
call and lowers to PeerConfidential(peer_key). On connected
routes it exists only after the live peer proves control of the matching
By-secret value under enabled confidential channel assurance; direct
composition may supply explicit final authority. Source text cannot
create or replace it. A friendly policy may therefore require a
confidential proof-derived peer while selecting records:
trusted_task(Task) :=
Task in //group//task//{*}
| peer_confidential('V.EXAMPLE_REQUIRED_PEER_KEY_B64A.H3')
converge {
trusted_task(Task) => include Task
}
Direct PeerConfidential, Here, and
Transport calls are invalid in friendly clauses; advanced
policy may use profile predicates in inline Datalog. Interlang
introduces no current-time or setup-time predicate. Record
TAI remains an ordinary checked field for version ordering,
and policy needing application time uses explicit checked application
records or static configured values.
A datalog block is a rule fragment in the surrounding
authoring contribution, not a nested module or separate provider
boundary. In strict standard Interlang, its rules are ordinary 030
source. In local frame authoring, it may use the complete 030 extended
policy-module grammar, including linked calls and provider heads:
datalog {
@configured_group('public') :- true.
SelectStoredRecord(R) :- Have(R), Field(R,'Group',_,'public').
}
The fragment is not canonicalized independently. Generated friendly rules and inline rules are assembled first; frame validation owns linked syntax, and the ordinary post-link 030/040 validator is the sole canonical serialization authority. Raw linked syntax is invalid in strict standard validation and never reaches an operand.
Builtins are non-binding; their variables are first bound by positive clauses. Interlang lowers:
name starts with 'links/'
name ends with '.json'
name has shape ('links/', './', 'msg')
new_tai sort after old_tai
new_tai sort at-or-after lower_bound
count endorsement(Record, _) >= 3
Record['Data-Length'] <= 1024
Later admit after Earlier
respectively to 030 TextShape,
Compare(Lex(...)), Cardinality,
Compare(Int(...)), and
Compare(AdmitOrder(...)). TopK provides bounded selection
over a friendly candidate helper; other advanced profile use remains
explicit Datalog.
Accepted bounded forms include global greatest Lex, partitioned least Int, and checked-only admission order:
recent(Post) := greatest 50 from post_candidate(Post, tai) by lex tai
lowest(Item, room) := least 10 per (room) from score(Item, room, points) by int points
recently_admitted(Message) := greatest 200 from room_message(Message) by admit Message
Rejected forms include an empty or duplicate partition, a partition
or ordering variable absent from the candidate, _ as an
ordering term, a field access in the candidate or ordering expression, a
linked or inline-Datalog candidate, noncanonical limits such as
01, and admission-order ranking of a marked
field-discovered advertised candidate.
One local policy frame is an order-insensitive set of Interlang or Datalog source contributions. Each contribution is one private-helper and linked-provider ownership boundary. Source labels and paths are diagnostics only.
Operations distinguish:
An incomplete frame is not claimed to pass checks that require provider substitution or the complete transformed rule graph; those checks are explicitly deferred to finalization. All checks available at the partial stage remain errors rather than additional validation-state variants.
Empty blocks and no-effect, helper-only, or provider-only sources are valid frame inputs but do not by themselves produce a facet result. Every contribution is validated even when it does not contribute facets. A provider-only contribution may contain its linked providers and permitted private static helper closure; unrelated ordinary dead rules are invalid.
Frame finalization is independent of caller ordering. Identical normalized contributions collapse before provider ownership checks. Linked providers match by name and arity; one distinct contribution owns each provider relation, while its tuples have semantic set behavior. Private helper scopes derive deterministically from normalized contribution bytes. Final output ordering and deduplication are exclusively ordinary 030 canonicalization. Permuting an equivalent contribution set or provider tuple spelling produces byte-identical canonical policy text.
A conforming compiler and frame finalizer:
SelectAdvertisedCandidate/1;