Status: Recommendation (REC‑PROD)
Version: 2.1.3
Date: 2026‑02‑19
Author: Emil Rokossovskiy
Document Type: Technical Standard
Format: Markdown (UTF‑8)
- Scope
- Conformance, Profiles, Extensions
- File Normalization (UTF‑8, NFC, LF)
- Lexical Rules
- Concrete Syntax (Facet YAML‑lite)
- AST Model (Normative)
- Resolution (
@import) and Merge (Smart Merge) - Facet Type System (FTS)
- Lenses (Registry, Trust Levels, Gas, Determinism)
- Execution Model (Phases 1–5, Modes)
- Token Box Model (Context Algebra)
- Standard Facets & Semantics
- Interfaces (
@interface) - Variables (
@vars), Types (@var_types), Inputs (@input) - Testing (
@test) - Policy / Authorization Model (
@policy, Capability Classes, Runtime Guard) - Security Model
- Canonical JSON, Canonicalization, Document Hash
- Error Code Catalog (Normative)
- Reference CLI (
fct) (Recommended) - Change History
Appendix A — Standard Lens Library (Normative)
Appendix B — ABNF Grammar (Informative)
Appendix C — Cache Key & Pure Cache‑Only Contract (Normative)
Appendix D — FTS → JSON Schema Mapping (Normative)
Appendix E — Conformance Checklist (Normative)
Appendix F — Execution Artifact, Guard Decisions, Attestation (Normative)
FACET is a Neural Architecture Description Language (NADL) for defining, validating, and executing AI request construction in a deterministic, type-safe, resource-bounded manner.
FACET standardizes:
- Concrete syntax and normalized source form
- Deterministic import resolution and deterministic merge behavior
- A strict type system (FTS) for variables, lenses, interfaces, and policies
- Reactive computation of variables via a dependency graph (R‑DAG)
- Deterministic context packing via a provider-independent layout model
- Provider-agnostic Canonical JSON output
- Deterministic behavior in Pure Mode
- A security boundary for hermetic compilation and constrained runtime I/O
- A policy / authorization model with fail-closed runtime guard enforcement
- A minimal conformance testing facility
- A minimal provenance / evidence artifact for auditability and attestation
FACET does not standardize model inference behavior, provider request/response protocols, or model internals beyond the requirement that provider payloads preserve Canonical JSON semantics.
This document uses RFC 2119 keywords (MUST, MUST NOT, SHOULD, MAY).
An implementation is FACET v2.1.3 compliant if and only if it satisfies all normative requirements in this specification for its declared profile(s) and mode(s).
Core implementations MUST support:
- Phase 1 (Resolution), Phase 2 (Type Checking), Phase 5 (Render)
@meta,@import,@context,@system,@user,@assistant,@vars,@var_types,@policy- Inline list/map literals
- Parsing of the full FACET concrete syntax (§5), followed by profile enforcement
Core restrictions:
@varsvalues MUST be literals only (no$references, no lens pipelines, no@input)- No
@interface, no@test, no R‑DAG evaluation, no Token Box Model, no multimodal canonicalization, no runtime guard, no execution artifact emission @policyis permitted, but Core MUST treat enforcement points that require execution (tool_call,lens_call) as not applicable; Core MUST still type-check@policyand MUST rendermetadata.policy_hash(§18.1)
Core MUST reject any syntactically valid construct disallowed by Core using F801 (not F003).
Hypervisor implementations MUST support all features described in this specification, including:
- All phases 1–5
- Token Box Model
- Multimodal canonicalization contract
@interface,@input, R‑DAG,@test- Lens registry, gas accounting, and Pure cache-only contract
@policyenforcement, capability/effect classes, runtime guard (fail-closed), and execution artifact provenance (§16, Appendix F)
Unless explicitly stated otherwise, normative requirements target Hypervisor.
Host extensions MUST be namespaced to avoid collisions:
- Facets:
@x.<host>.<name>or@x_<host>_<name> - Lenses:
x.<host>.<lens_name>
Error codes:
- The numeric
F000–F999space is RESERVED for the FACET standard only. Hosts MUST NOT emit new numericF-prefixed codes. - Host extension diagnostics MUST use a namespaced code string:
X.<host>.<code>(example:X.acme.TIMEOUT). - A host MAY include a secondary legacy code in auxiliary diagnostics, but the primary error identifier MUST be namespaced.
Implementations MUST apply the following normalization before Phase 1 completes:
- Input MUST be valid UTF‑8.
- Text MUST be normalized to Unicode NFC.
- Line endings MUST be normalized to LF (
\n). - Tabs (
\t) MUST raiseF002.
All source spans, hashing, and canonicalization MUST refer to the normalized form.
Identifiers MUST match:
[A-Za-z_][A-Za-z0-9_]*
Non‑ASCII identifiers MUST raise F003.
Strings are double‑quoted and support escapes:
\",\\,\n,\t,\r,\uXXXX
Invalid escapes or unclosed strings MUST raise F003.
Supported scalars:
true,false,null- integers: optional leading
- - floats: decimal form with optional exponent
e/E
Invalid scalar MUST raise F003.
A map key in the concrete syntax MAY be either:
- an identifier, or
- a quoted string
Semantic restrictions on where string keys are permitted are defined in §12.1.
FACET is indentation-scoped. Indentation MUST be exactly 2 spaces.
A facet begins with @name optionally followed by attributes:
@system(model="gpt-x", when=true)
content: "You are a helpful assistant."
Facet bodies are maps (block form) and may contain nested maps and lists.
Exception: Certain facets define a specialized body subgrammar in their own sections (notably @interface (§13) and @test (§15)). Such facets remain syntactically valid FACET documents, but their internal line structure is not constrained to the generic map/list-only body in Appendix B (which is informative).
- Attribute syntax:
@facet(k=v, ...) - Attribute values MUST be atoms of the attribute-atom set:
string | number | bool | null | varref
- Lens pipelines inside attributes MUST raise
F003. @input(...)MUST NOT appear in facet attributes and MUST raiseF003.- Attribute interpolation syntax containing
{{or}}is forbidden and MUST raiseF402.
Map entry:
key: "value"
List:
items:
- "a"
- "b"
Inline list and inline map literals MUST be supported in both Core and Hypervisor:
tags: ["a", "b", $x]
cfg: { retries: 3, mode: "safe" }
Trailing commas are not permitted; violations MUST raise F003.
$name$name.path.to.field
Path semantics are defined in §14.8.
Pipeline syntax:
value: $doc |> trim() |> json(indent=2)
All profiles MUST parse the full FACET concrete syntax (including |> pipelines and $ references). Profile restrictions are enforced after parsing; therefore any construct that is syntactically valid but disallowed by the active profile/mode MUST raise F801 (not F003).
FACET defines a directive-expression:
@input(...)
Directive-expressions are expressions and may appear only where explicitly permitted by this specification (§14.3). Any directive-expression used in an invalid position MUST raise F452.
Implementations MUST produce an AST with at least:
FacetNode(name, attrs, body, span)MapNode(entries[], span)(ordered)ListNode(items[], span)(ordered)KeyValueNode(key, valueExpr, span)wherekeyisIdentifierKeyorStringKeyStringNode(value, span)ScalarNode(kind, value, span)wherekind ∈ {int,float,bool,null}VarRefNode(name, pathSegments[], span)LensPipelineNode(baseExpr, steps[], span)LensCallNode(name, args[], namedArgs{}, span)InputExprNode(attrs, span)representing@input(...)ImportDirectiveNode(pathString, span)for top-level@import
For @policy, implementations MUST represent policy rule structures using the standard MapNode/ListNode/scalar nodes and MUST preserve ordering as required by §16.2 and §7.4.
AST spans MUST reference normalized NFC+LF source coordinates.
Ordered maps MUST preserve key insertion order as defined by the merge rules in §7.4.
Top-level directive:
@import "relative/path/module.facet"
Rules:
- Imports MUST resolve relative to the importing file.
- Imports MUST be constrained by allowlisted roots (§17.2).
- Violations of import sandbox rules (absolute paths,
..traversal, URL imports, or outside allowlisted roots) MUST raiseF601. - Import not found MUST raise
F601. - Import cycles MUST raise
F602.
Imports MUST be applied in source order: imported content is expanded in-place at the point the @import appears, forming a single Resolved Source Form and a single Resolved AST.
Standard facets have fixed cardinality:
Singleton-map facets (deep-merged by key):
@meta,@context,@vars,@var_types,@policy
Repeatable-block facets (collected as ordered lists of blocks):
@interface,@system,@user,@assistant,@test
For repeatable-block facets, each occurrence represents one block instance. Merging/applying imports MUST preserve the deterministic block occurrence order from the Resolved Source Form.
If a host introduces an extension facet, the host MUST define its cardinality. If cardinality is unknown, implementations MUST raise F452.
Singleton-map facets MUST be merged as ordered maps:
- When a key appears for the first time, it is inserted at that position.
- When a key appears again, its value is overridden by the later value, but the key’s position MUST remain the position of its first insertion.
- If both old and new values are maps, they MUST be deep-merged recursively using the same ordered-map rules.
- Lists are not deep-merged unless the keyed-list rule applies (§7.4.3) or a facet-specific list merge rule is defined (see §16.2.3 for
@policy).
Repeatable-block facets MUST be merged by concatenating block instances in encounter order as they appear in the Resolved Source Form.
If a list-bearing map field declares key="field" as a facet attribute on its containing facet, list merge MUST be keyed:
- Each list item MUST be a map containing the key field; missing key →
F452 - Items match by string equality of
item[field] - Matched items deep merge as maps; later overrides earlier
- Order preserves first appearance; new keys append in encounter order
Phase 1 outputs:
- Resolved Source Form (imports expanded, normalized)
- Resolved AST with deterministic block ordering and deterministic ordered-map key ordering
FTS is used for:
@varsvalidation (@var_types)- Lens signatures and pipeline type checking
- Interface schemas (
@interface) - Policy condition and rule type checking (§16.3)
string,int,float,bool,null,any
Constraints:
- numbers:
min,max - strings:
pattern(safe subset; see §9.9) enum: list of literal values
Violations:
- type mismatch →
F451 - constraint violation →
F452
struct { field: T, ... }(fields required by default)list<T>map<string, T>T1 | T2 | ...(union)
Optional fields MUST be expressed as T | null.
imagewith constraints:format ∈ {png,jpeg,webp},max_dim(int)audiowith constraints:format ∈ {mp3,wav,ogg},max_duration(seconds, float)embedding<size=N>whereNis a positive integer
T1 is assignable to T2 iff:
T2 == any, ORT1andT2are the same primitive and satisfy constraints, OR- union:
T1assignable to at least one member ofT2, OR - list/map: element types assignable, OR
- struct: all required fields of
T2exist inT1and are assignable.
Hypervisor implementations MUST maintain a lens registry. Each lens entry MUST include:
name(string)version(string; MUST change if behavior changes)input_type(FTS)output_type(FTS)trust_level ∈ {0,1,2}- deterministic gas function:
gas_cost(input, args) -> int - determinism class:
pure | bounded | volatile
Additionally, for policy/guard support (§16.4–§16.6):
effect_class(string) fortrust_level ∈ {1,2}(see §16.5)- missing/invalid
effect_classfor Level‑1/2 MUST raiseF456at registry construction time or first use
- missing/invalid
Unknown lens MUST raise F802.
- Level 0 — Pure: deterministic, no I/O
- Level 1 — Bounded: potentially external but governed by cache contract
- Level 2 — Volatile: nondeterministic and/or unbounded external effects
In Phase 2, the compiler MUST validate that each lens step accepts the previous output type (assignability). Violations MUST raise F451.
The host MUST define GasLimit. Every lens invocation consumes gas. If gas exceeds GasLimit, execution MUST raise F902.
In Pure Mode:
- Level 2 lenses MUST be rejected:
F801 - Level 1 lenses MUST run in cache-only mode:
- cache hit: allowed
- cache miss:
F803
- Level 0 lenses: allowed
In Execution Mode, Level 1 and Level 2 lenses MAY execute if permitted by the host and permitted by @policy / guard enforcement (§16.6).
Level 1 lenses MUST be cache-addressable by the contract in Appendix C.
Any lens pipeline used as a Layout strategy MUST be:
- Level‑0 only in Pure Mode (else
F801) - deterministic
- idempotent on identical input
- total over all valid NFC+LF
stringinputs (must not throw for any valid string) - independent of locale, time, environment variables, filesystem, or network
Any lens performing regex evaluation MUST use a linear-time safe engine (RE2-class) or a proven safe subset. Otherwise, the lens MUST NOT be registered as Level‑0.
Hypervisor execution MUST follow these phases in order.
- Normalize input (§3)
- Parse to AST (§5, §6)
- Resolve imports (§7.1–§7.2)
- Apply merge rules (§7.3–§7.4)
Errors: F001–F003, F601, F602, F402
- Validate
@var_typesand@varsexpressions and pipelines - Validate
@interfaceand schema mappability - Validate placement constraints (e.g.
@inputplacement) - Validate facet attributes (
whentype, etc.) - Validate multimodal constraints
- Validate
@policyschema and policy condition typing (§16.3)
Errors: F451, F452, F456, F802
AST MUST be treated as immutable after Phase 2 success.
Inputs:
- typed Resolved AST
- runtime values for
@inputvariables (Hypervisor)
Rules:
- Build a dependency graph from
$varreferences in@vars. - Unknown variable reference MUST raise
F401. - Cycles MUST raise
F505. - Evaluate in topological order.
- Tie-break between independent nodes MUST follow the merged ordered-map insertion order of
@varsas produced by §7.4.1 (first-insertion position preserved under overrides). - Overriding a variable's value via a later merged source MUST NOT change that variable's position in the ordered map. The position of first insertion is the canonical position used for tie-break traversal. Implementations that reorder keys upon override violate determinism.
- Tie-break between independent nodes MUST follow the merged ordered-map insertion order of
- Apply lenses (respect mode policy, gas, caching, and runtime guard/policy enforcement where applicable; §16.6).
- Freeze computed variable map (immutable).
- Materialize an Effective Policy object and compute
policy_hash(§16.2.4, §18.3).
Errors: F401, F405, F453, F505, F801, F802, F803, F902, F454, F455
Forward references are allowed; only unknown references and cycles are errors.
Input:
- computed messages and section metadata
@contextbudget and defaults
Output: finalized ordered sections within budget
Errors: F901
Render MUST produce:
- Canonical JSON (§18)
- provider payload (host-defined) that preserves Canonical JSON semantics
If the host emits an Execution Artifact (Hypervisor run/test), it MUST conform to Appendix F.
Layout MUST measure content in FACET Units:
Provider token counts MAY be reported as telemetry but MUST NOT affect normative layout.
Each message block (@system, @user, @assistant) produces exactly one Layout section.
Each section has:
| Field | Type | Default |
|---|---|---|
id |
string | derived if omitted (§11.2.1) |
priority |
int | 500 |
min |
int | 0 |
grow |
float | 0 |
shrink |
float | 0 |
strategy |
lens pipeline | none |
content |
string | required |
A section is Critical iff shrink == 0. Critical sections MUST NOT be compressed, truncated, or dropped.
If a message block does not specify id, the implementation MUST derive:
- Determine the message’s canonical role rank (
system=0,user=1,assistant=2). - Within each role, count occurrences in canonical message order starting at 1.
- Set
id = "<role>#<n>"(example:user#2).
Let B be budget in FACET Units and size[i] = facet_units(content[i]).
- Critical load
FixedLoad = sum(size[i] for critical sections)- If
FixedLoad > B→F901
- If total fits
- If
sum(size[i] for all sections) <= B, keep all, preserving section order (§18.1.2).
- Compress/drop flexible
- Let
Flex = { i | shrink[i] > 0 } - Sort
Flexby stable key:priorityascendingshrinkdescending- original section order ascending
Iterate Flex in that order while total size > B:
- If
strategyis set: apply strategy tocontent[i](Pure Mode: Level‑0 only; elseF801) - Recompute
size[i]and total - If still over budget: truncate deterministically from the end down to satisfy budget but not below
min- truncation MUST NOT split UTF‑8 sequences
- If still over budget and
size[i] == min: drop the entire section (unless Critical)
Result MUST be deterministic across implementations.
@meta is optional. If present, it MUST be a map whose values are atoms only:
string | number | bool | null
@meta keys MUST be either:
- identifiers, or
- strings
If a @meta key is a string, it MUST NOT contain control characters (Unicode code points U+0000–U+001F and U+007F).
String keys are permitted only in @meta. Any string-keyed map entry outside @meta MUST raise F452.
@meta values MUST NOT contain $ references, @input, or lens pipelines.
Host extensions SHOULD use string keys with a namespaced dotted form, for example:
@meta
"x.acme.build_id": "..."
@context defines layout configuration.
Minimum schema:
@context
budget: 32000
defaults:
priority: 500
min: 0
grow: 0
shrink: 0
Rules:
budgetMUST be an integer ≥ 0 measured in FACET Units.defaultsMAY include any of:priority|min|grow|shrink.- Missing values default to §11.2 defaults.
If @context is absent, the host MUST supply a budget and MUST surface it in canonical metadata.
If @context is absent and the host supplies a budget, that host-provided budget MUST be a deterministic function of the execution configuration. At minimum it MUST be stable for a fixed tuple:
(host_profile_id, facet_version, profile, mode, target_provider_id)
Each message block MUST be a map and MAY include:
content(required)- layout fields:
id|priority|min|grow|shrink|strategy when(boolean gate)
@system MAY include:
tools: list of interface references ($InterfaceName)
when gating is applied before policy-based message gating (message_emit) if implemented (§16.6.4).
A message content MUST be either:
- a
string, or - a list of content items, each of which is one of:
{ type: "text", text: string }{ type: "image", asset: <canonical asset> }{ type: "audio", asset: <canonical asset> }
Canonical assets MUST be represented by semantic digest:
{
"kind": "image",
"format": "jpeg",
"digest": { "algo": "sha256", "value": "…" },
"shape": { "width": 1024, "height": 768 }
}or for audio:
{
"kind": "audio",
"format": "wav",
"digest": { "algo": "sha256", "value": "…" },
"shape": { "duration": 3.2 }
}Asset canonicalization is host-profile-defined. Therefore:
- Canonical JSON MUST include
metadata.host_profile_id. host_profile_idMUST be stable and versioned.- Any change that can alter semantic digests (codec pipeline, resampling, colorspace, normalization rules) MUST change
host_profile_id.
Facet attributes MAY include when=<atom> where atom is:
true|false, or$varthat evaluates tobool
If when evaluates to false, that message block MUST be omitted from layout and render.
Non-boolean when MUST raise F451.
@interface WeatherAPI
fn get_current(city: string) -> struct {
temp: float
condition: string
} (effect="read")
Rules:
- Interface name MUST be an identifier.
- Function names MUST be unique within the interface.
- Parameter names MUST be unique within the function.
- Parameter and return types MUST be FTS types.
- Each function MUST declare an
effectattribute (see §16.5) using the attribute atom set:effectMUST be a string- Lens pipelines inside function attributes MUST raise
F003 @input(...)MUST NOT appear in function attributes and MUST raiseF003
- Missing
effectMUST raiseF456.
Duplicate interface names in the Resolved AST MUST raise F452.
All interface types MUST be mappable to JSON Schema per Appendix D. If not, MUST raise F452.
@system
tools: [$WeatherAPI]
content: "..."
Unknown interface reference MUST raise F452.
@vars is a singleton ordered map of variable definitions.
Names MUST be unique after merge. If a variable name is overridden by a later merged value, it is not an error; the later value replaces the earlier value and the variable retains the order position of its first insertion (§7.4.1).
Hypervisor allows variable value expressions consisting of:
- literals, maps, lists
$references- lens pipelines
@input(...)directive-expression, subject to §14.3
Core allows literals only and MUST reject any disallowed variable construct with F801.
@var_types is a singleton ordered map from variable name to FTS type expression.
If a variable has a declared type, its computed value MUST satisfy it; violations MUST raise F451 or F452.
@input(...) is a directive-expression that denotes a runtime-supplied input value.
Placement:
@input(...)MUST appear only as the base expression of a@varsentry value, optionally followed by a lens pipeline.@input(...)MUST NOT appear inside composite literals (not nested inside a list/map/struct literal).- Any invalid placement MUST raise
F452.
Attributes:
type(REQUIRED): string containing an FTS type expressiondefault(OPTIONAL): an atom (string|number|bool|null)
Examples:
@vars
query: @input(type="string")
n: @input(type="int", default=3)
q: @input(type="string") |> trim()
Semantics:
- If
defaultis present and the host does not supply an input value, the default MUST be used. - If
defaultis absent, the host MUST supply an input value. - Supplied or defaulted values MUST validate against the
type; violations MUST raiseF453. - Invalid FTS type strings MUST raise
F452.
Variables are computed in Phase 3 using R‑DAG rules (§10.3).
Any reference to a missing variable MUST raise F401.
Any dependency cycle MUST raise F505.
@input values are materialized during Phase 3 before dependent computations.
For $x.a.b:
$xmust exist orF401- each path segment on a map/struct must exist or
F405 - list indexing is not standardized in v2.1.3; any numeric indexing MUST raise
F452unless a namespaced host extension is enabled
@test "basic"
vars:
username: "TestUser"
input:
query: "hello"
mock:
WeatherAPI.get_current: { temp: 10, condition: "Rain" }
assert:
- canonical.messages[0].role == "system"
- canonical contains "hello"
- telemetry.gas_used < 5000
- Each test runs in an isolated environment.
vars:overrides variable values in the test environment (must still type-check).input:supplies@inputvalues by variable name.mock:intercepts interface calls by fully-qualified tool function name.- Assertions are evaluated against:
canonical: the Canonical JSON object produced by Phase 5telemetry: host-defined telemetry fieldsexecution(RECOMMENDED): the Execution Artifact object (Appendix F) produced by the run/test engine, when applicable
In Pure Mode:
- Level‑1 cache misses MUST yield
F803. - Disallowed I/O MUST yield
F801. - Policy/guard failures MUST yield
F454orF455as applicable (§16.6).
FACET policy is designed to be:
- declarative and deterministic
- type-checkable in Phase 2
- enforceable with a runtime guard in Hypervisor
- fail-closed: if a dangerous operation cannot be proven allowed, it MUST be rejected
Policy evaluation MUST be independent of locale, time, environment variables, filesystem, or network.
policy_version identifies the revision of the @policy DSL and guard evaluation semantics standardized in §16 and Appendix F. It covers: condition evaluation rules, rule match algorithm, effect class semantics, guard timing, and enforcement points. Any change to these semantics that is incompatible with existing Effective Policy Objects MUST result in a new policy_version value, even if facet_version does not change (e.g. in an errata or clarification release). policy_version is emitted in metadata.policy_version (§18.1) and included in policy_hash (§16.2.4) and hash-chain seed (§F.4).
@policyis a standard singleton-map facet (§7.3) and is deep-merged (§7.4.1) with additional list-merging rules (§16.2.3).@policyMAY be absent.@policyMUST NOT use string keys outside rules where allowed by general map key rules; string keys outside@metaare still forbidden (§12.1).
@policy MUST be a map whose known top-level keys are:
defaults(OPTIONAL): mapdeny(OPTIONAL): list ofPolicyRuleallow(OPTIONAL): list ofPolicyRule
Unknown top-level keys MUST raise F452.
A PolicyRule MUST be a map with keys:
id(OPTIONAL): string identifier (see §16.2.3)op(REQUIRED): string enum (§16.4.1)name(REQUIRED fortool_*andlens_call): string matcher (§16.2.5)effect(OPTIONAL): string effect matcher (§16.5.3)when(OPTIONAL):PolicyCondunless(OPTIONAL):PolicyCond
Unknown keys inside a PolicyRule MUST raise F452.
Within @policy only, if both earlier and later merged values contain allow and/or deny lists, implementations MUST merge them as follows:
- If a rule item has an
idfield:- it participates in a keyed merge with key
id(string) - matched rules deep-merge as maps; later overrides earlier
- ordering preserves first appearance; new ids append in encounter order
- it participates in a keyed merge with key
- If a rule item does not have
id:- it MUST be appended in encounter order (no matching)
If id exists but is not a string, MUST raise F452.
The implementation MUST compute an Effective Policy Object after Phase 1 merging and after Phase 2 validation. The object is the fully merged @policy map with all lists in their effective merged order.
metadata.policy_hash MUST be:
nullif@policyis absent, else"sha256:" + hex( sha256( JCS({ policy_version: <policy_version>, policy: EffectivePolicyObject }) ) )
Where policy_version is the value emitted in metadata.policy_version (§18.1). Including policy_version in the hash ensures that semantically incompatible policy interpretations produce distinct hashes even when the source @policy text is identical.
For PolicyRule.name:
- If
namecontains no*, it MUST be an exact match string. - If
nameends with.*, it MUST match any string with that prefix before the*:PaymentAPI.*matchesPaymentAPI.charge,PaymentAPI.refund
- Any other use of
*MUST raiseF452.
The name field in a PolicyRule and in OpDesc.name MUST use a canonical identifier form:
- The canonical form is case-sensitive.
- The canonical form MUST contain no whitespace characters.
- For
tool_*operations: canonical form is"<InterfaceName>.<fn_name>"exactly as the identifiers are defined in the@interfaceAST node (§6, §13.1). - For
lens_call: canonical form is the lens registryname(§9.1), exactly as registered. - For
message_emit: canonical form is the derived or explicit sectionid(§11.2.1).
Implementations MUST normalize both the rule name and the runtime OpDesc.name to canonical form before comparison. A PolicyRule.name that contains whitespace or deviates from canonical form (e.g. wrong case) MUST raise F452 at policy parse / Phase 2 validation time.
All fields within a PolicyRule act as a conjunctive (AND) filter over the OpDesc. A rule matches an operation if and only if all present conditions are satisfied simultaneously:
opmatchesOpDesc.op(always required)namematchesOpDesc.nameper §16.2.5–§16.2.6 (required fortool_*andlens_call)effectmatchesOpDesc.effect_classper §16.5.4 (only ifeffectfield is present)whenevaluates totrue(only ifwhenfield is present)unlessevaluates tofalse(only ifunlessfield is present)
No field is an independent match. Implementations MUST NOT treat any single field as sufficient for a rule match unless all present fields are satisfied concurrently. A rule that matches on name but not on effect does not match.
PolicyCond is a deterministic boolean expression over computed variables (@vars) and MAY reference $var paths (§14.8). It MUST NOT perform I/O.
PolicyCond MUST be one of:
- boolean literal:
true/false - variable reference:
$nameor$name.path(result MUST bebool) - map form:
{ not: <PolicyCond> }{ all: [<PolicyCond>, ...] }{ any: [<PolicyCond>, ...] }
Rules:
all/anylists MUST be non-empty; empty list MUST raiseF452.- Any
VarRefNodeused inPolicyCondMUST resolve to an existing variable, elseF401. - Any invalid path segment MUST raise
F405. - The resulting type of every
PolicyCondMUST bebool; violations MUST raiseF451. PolicyCondMUST NOT contain lens pipelines or@input(...); occurrences MUST raiseF452.- Note: policy MAY still depend on runtime inputs indirectly via variables that use
@inputin@vars.
- Note: policy MAY still depend on runtime inputs indirectly via variables that use
all and any MUST use short-circuit (lazy) evaluation:
all: evaluates arguments left-to-right; stops and returnsfalseat the first argument that evaluates tofalse. Arguments after the stopping point MUST NOT be evaluated and MUST NOT generate errors (includingF401,F405, orF455).any: evaluates arguments left-to-right; stops and returnstrueat the first argument that evaluates totrue. Arguments after the stopping point MUST NOT be evaluated and MUST NOT generate errors.
This guarantees deterministic short-circuit behavior across all implementations. An implementation that evaluates arguments unconditionally and raises an error on an unevaluated branch violates this requirement.
The policy/guard model standardizes the following operation kinds:
tool_expose— exposing a tool schema incanonical.toolstool_call— invoking an interface function at runtimelens_call— invoking a lens step at runtime (or during Phase 3 compute)message_emit— emitting an included message block intocanonical.messages
A PolicyRule.op MUST be one of the strings above; otherwise F452.
For every policy decision, the implementation MUST construct an OpDesc containing at least:
op(string): operation kindname(string): fully qualified nametool_*:"<InterfaceName>.<fn_name>"lens_call:"<lens_name>"message_emit:"<role>#<n>"(derived id; §11.2.1) or explicit id if present
effect_class(string | null): see §16.5mode("pure"or"exec")profile("core"or"hypervisor")
OpDesc is an internal conceptual entity; its externally testable representation is GuardDecision (Appendix F).
A side-effecting operation is any operation whose OpDesc.effect_class is not "read".
- An operation with
effect_class == nullMUST be treated as side-effecting and unsafe for fail-closed purposes. The guard MUST NOT assume a non-mutating default for null-class operations; absence of an effect class is not equivalent toread. - An operation with
effect_class == "read"is the only class that MAY be treated as non-mutating for guard classification purposes. All other non-null classes MUST be treated as potentially state-mutating.
This definition is a classification aid for host logic and informative descriptions. Normative guard timing requirements in §16.6.1a are defined by operation kind, trust_level, and mode — not by this classification alone. The classification is useful for reasoning about which operations require extra scrutiny, but guard timing is controlled by the explicit enumeration in §16.6.1a, not by this definition.
FACET standardizes the following effect classes with normative semantics:
read— observational only; MUST NOT mutate any persistent state observable outside the current execution. An operation declaredreadthat actually mutates state is a host configuration error and does not reduce the host's security obligations.write— mutates persistent state; MUST be treated as at least as sensitive aspaymentfor default-deny purposes.external— crosses the hermetic boundary to interact with an external system; may be read or write in nature; subsumesnetworkunless a more specific class is declared.payment— initiates or authorizes a financial transaction; MUST be treated as the highest-sensitivity class by default policy. Implementations MUST NOT inferpaymentis equivalent towrite; they are distinct and non-substitutable.filesystem— reads or writes filesystem paths outside the import sandbox; does not implynetwork.network— performs network I/O; does not implypaymentorfilesystem.
Implementations MUST NOT treat read as interchangeable with any mutating class (write, payment, filesystem, network). Effect classes carry disjoint semantic obligations and MUST be matched precisely.
Hosts MAY introduce additional effect classes but MUST namespace them:
x.<host>.<effect>
Every @interface fn MUST declare effect as a string in the standard or namespaced effect classes (§13.1, §16.5.1). Missing or invalid effect MUST raise F456.
The tool’s effect_class in OpDesc MUST be the declared effect.
Every lens registry entry with trust_level ∈ {1,2} MUST declare effect_class (§9.1). Missing or invalid effect MUST raise F456.
The lens’s effect_class in OpDesc MUST be the registry effect_class.
If a PolicyRule.effect is present:
- it MUST be a string effect matcher:
- exact match with no
*, OR - suffix
.*prefix match as in §16.2.5
- exact match with no
- it MUST be matched against
OpDesc.effect_class - If
OpDesc.effect_classisnulland a rule requires an effect match, the rule MUST NOT match.
Hypervisor implementations MUST enforce a Runtime Guard over dangerous operations.
The guard MUST be invoked for:
- every
tool_call - every
lens_callwheretrust_level == 2 - every
lens_callwheretrust_level == 1inexecmode
Additionally:
- the host MAY guard
tool_exposeand/ormessage_emit; if implemented, it MUST be consistent with this section and MUST emit guard decisions (Appendix F).
The guard decision MUST be evaluated before any external initiation associated with the guarded operation.
Permitted before ALLOW — deterministic, Level-0, hermetic local computation needed to construct the OpDesc and InputObject (including input_hash per §F.3). This preparation MUST NOT perform any I/O, invoke any lens with trust_level ≥ 1, or initiate any external call.
Prohibited before ALLOW:
- external invocation of any
tool_call, regardless of declaredeffect_class; - lens execution for any
lens_callwithtrust_level ∈ {1, 2}inexecmode; - lens execution for any
lens_callwithtrust_level == 2inpuremode; - inclusion of a tool schema in
canonical.toolsfor anytool_expose; - inclusion of message content in the canonical message list for any
message_emit, if the host guards that operation.
An operation with effect_class == null MUST require a guard decision before any prohibited action above.
An implementation that initiates any prohibited action before obtaining an explicit guard ALLOW violates fail-closed semantics and MUST raise F455.
For a given OpDesc, define active(rule):
whenis absent ORwhenevaluates totrue- AND
unlessis absent ORunlessevaluates tofalse
Match order:
- Evaluate
denyrules in encounter order; first matching active deny → DENY (policy violation). - Else evaluate
allowrules in encounter order; first matching active allow → ALLOW. - Else apply defaults (§16.6.3).
If evaluation of a condition fails (e.g. type mismatch, missing variable) at runtime, the guard MUST treat this as undecidable and MUST deny with F455 (fail-closed), unless the implementation can prove a deterministic, safe deny reason F454 applies.
Defaults apply when no allow/deny rule matches.
- For
tool_call:- If
effect_class ∈ {write, payment, filesystem}, default MUST be DENY. - Otherwise default MUST be DENY unless the host explicitly configures a deterministic, documented, and testable default allow for a subset of non-state-changing effect classes.
- If
- For
lens_callof Level‑1/2:- default MUST be DENY.
- For
tool_expose:- default SHOULD be DENY.
- For
message_emit:- default MAY be ALLOW (subject to
when), unless configured otherwise.
- default MAY be ALLOW (subject to
Any host-defined default allow MUST be a deterministic function of execution configuration and MUST be testable via @test.
when=falseMUST omit the block/tool reference before any policymessage_emitortool_exposedecision is considered.when=truedoes not imply policy allow.
For every guarded operation attempt (including denied), Hypervisor MUST record a GuardDecision event as specified in Appendix F.
F454 and F455 are strictly distinct and MUST NOT be substituted for each other:
-
F454— Policy deny: the guard evaluated the Effective Policy successfully and reached a deterministic DENY decision via: a matching active deny rule, or a deterministic default deny (§16.6.3) with no matching allow rule.F454represents a reproducible, well-defined policy rejection. Implementations MUST emitF454whenever the deny outcome is deterministic. -
F455— Guard undecidable: the guard encountered an evaluation failure that prevented a deterministic outcome. Causes include:- a variable required by a
PolicyCondis missing at runtime (F401condition) - a type error occurs during condition evaluation
effect_classis missing or invalid where required by the matched rule- the guard or rule-evaluation logic encounters an internal failure
- a variable required by a
F455 MUST NOT be used if a deterministic deny decision (F454) can be made. Implementations MUST distinguish between the two cases: if the policy can be fully evaluated and the result is DENY, it is F454; if evaluation cannot complete, it is F455.
Phases 1–2 MUST be hermetic:
- no network access
- no filesystem access except allowed import roots
- no environment variable leakage
- deterministic execution
@import MUST be restricted:
- allowlisted roots
- no absolute paths
- no
.. - no URLs
Violations MUST raise F601.
Attempts to perform runtime I/O outside @input or registered Level‑1/2 lenses MUST raise F801.
Additionally, Hypervisor MUST apply runtime guard enforcement per §16.6. Any attempt to execute a guarded operation without a guard decision MUST be treated as fail-closed and MUST raise F455.
Phase 5 MUST produce a provider-agnostic Canonical JSON object with:
metadata:facet_version:"2.1.3"profile:"core"or"hypervisor"mode:"pure"or"exec"host_profile_id: stringdocument_hash: stringpolicy_hash: string | nullpolicy_version: string — version identifier for the@policyDSL and guard evaluation semantics in use, independent offacet_version. MUST be"1"for v2.1.3. MUST be incremented by the FACET standard if policy semantics change in a future version.budget_units: int (effective budget)target_provider_id: string
tools: list of canonical tool schemas derived from interfacesmessages: ordered list of:{ "role": "system"|"user"|"assistant", "content": string | list }
Canonical JSON represents the request context. It does not include the model’s runtime output.
tools list order MUST follow the Resolved Source Form order of @interface declarations.
Canonical message order MUST be deterministic:
- All
@systemmessage blocks in their Resolved Source Form order - Then all
@usermessage blocks in their Resolved Source Form order - Then all
@assistantmessage blocks in their Resolved Source Form order
Within each role, relative order MUST be preserved.
Layout (§11) MUST preserve this canonical message ordering when truncating/dropping sections; it MUST NOT reorder messages.
If @system.tools is present in any included @system blocks, canonical.tools MUST be the union of referenced interfaces, ordered by §18.1.1.
Hypervisor MUST apply policy tool_expose filtering before emitting canonical.tools. Denied tools MUST be omitted rather than emitted with a marker, and a GuardDecision with decision: "denied" MUST be recorded (Appendix F, §16.6.5).
Core MAY apply tool_expose policy filtering; if implemented, it MUST follow the same omission semantics.
Canonical JSON MUST be serialized using JSON Canonicalization Scheme (RFC 8785) or equivalent:
- UTF‑8
- stable object key ordering
- stable number formatting
metadata.document_hash MUST be:
sha256of the Resolved Source Form (imports expanded, NFC+LF normalized)
The host MAY additionally publish a hash of Canonical JSON for debugging, but the normative document hash is the Resolved Source Form hash.
F001invalid indentation (must be 2 spaces)F002tabs forbiddenF003malformed syntax / invalid token / invalid escape / unclosed structureF402attribute interpolation forbidden
F401variable not foundF405invalid variable path (missing field)F451type mismatchF452constraint violation / unsupported construct / invalid placement / invalid signatureF453runtime input validation failed (@input)F454policy violation (denied by policy or default policy deny where decision is well-defined)F455guard undecidable / missing evidence (fail-closed)F456effect not declared / invalid effect class
F505cyclic dependency detected (R‑DAG)
F601import not found / disallowed pathF602import cycle
F801I/O prohibited / lens disallowed in mode/profileF802unknown lensF803Pure cache miss (Level‑1 lens attempted without cache hit)F901critical overflow (Token Box Model)F902compute gas exhausted
Host extension diagnostics MUST use the X.<host>.<code> namespace (§2.2).
A standard implementation SHOULD provide:
fct build file.facet(Phases 1–2)fct run file.facet --input input.json --pure|--execfct test file.facetfct inspect file.facet --ast ast.json --dag dag.json --layout layout.json --policy policy.jsonfct codegen file.facet --lang python|ts
If an Execution Artifact is produced during run or test, it SHOULD be emitted as execution.json and MUST conform to Appendix F if present.
Normative additions within v2.1.3 to close formal gaps identified post-publication:
- §16.2.6 — Added canonical identifier form requirement for
PolicyRule.nameandOpDesc.name: case-sensitive, no whitespace,<InterfaceName>.<fn>exactly as in AST; non-canonical form MUST raiseF452at Phase 2. - §16.2.7 — Formalized that all
PolicyRulefields are a conjunctive (AND) filter; a rule matches only when all present fields are satisfied simultaneously. - §16.3 — Added short-circuit evaluation semantics for
all/any: left-to-right, stops at first false/true; arguments after stopping point MUST NOT be evaluated and MUST NOT raise errors. - §16.5.1 — Added normative semantic constraints to standard effect classes:
readMUST NOT mutate state;paymentis distinct fromwrite; classes are non-interchangeable and non-substitutable. - §16.6.1a — Added guard evaluation timing: guard decision MUST be made before any associated side-effecting or non-Level-0 computation begins.
- §16.6.6 — Sharpened
F454vsF455distinction:F454= deterministic deny (policy evaluated successfully);F455= evaluation failure preventing a deterministic outcome.F455MUST NOT be used whenF454is applicable. - §18.1.3 — Promoted Hypervisor
tool_exposepolicy filtering from SHOULD to MUST; denied tools MUST be omitted and a deniedGuardDecisionMUST be recorded. - Appendix F.4 — Added
profileandmodeto the H0 hash-chain seed to prevent collisions across execution configurations. - Appendix E — Updated conformance checklist items 15, 17, 18, 19 to reflect above additions.
- §10.3 — Clarified R-DAG tie-break: independent nodes MUST follow merged ordered-map insertion order; override MUST NOT change a variable's position.
- §16.4.3 (new) — Formal definition of "side-effecting operation": any operation with
effect_class ≠ "read";effect_class == nulltreated as unsafe/side-effecting. - §16.6.1a — Replaced ambiguous "side-effecting or non-Level-0" with a precise enumeration of guarded operations and explicit reference to §16.4.3.
- §16.2.4 —
policy_hashcomputation now wraps{ policy_version, policy: EffectivePolicyObject }to bind hash to DSL semantics version. - §18.1 — Added
policy_versionfield to Canonical JSON metadata (MUST be"1"for v2.1.3). - §F.2 — Added
policy_versionto Execution Artifact metadata. - §F.4 — Added
policy_versionto H0 hash-chain seed.
- Added Policy / Authorization Model with standard
@policyfacet, deterministic condition DSL, and enforcement points (tool_expose,tool_call,lens_call,message_emit) - Added capability/effect classes for interface functions (
effect) and for Level‑1/2 lenses (effect_class) - Added mandatory Hypervisor Runtime Guard with fail-closed semantics and new error codes
F454–F456 - Added
metadata.policy_hashto Canonical JSON and standardized policy hashing - Added Execution Artifact specification (provenance events, guard decisions, hash-chain, optional attestation) in Appendix F
- Updated conformance checklist accordingly
- Defined
@input(...)as a directive-expression, permitted only as a@varsbase expression (optionally piped), and added ABNF coverage - Allowed
@metakeys to be identifiers or strings; string keys forbidden outside@meta - Fixed Canonical JSON ordering requirements for
toolsandmessages
- Reserved
F000–F999for FACET only; host diagnostics must useX.<host>.<code> - Required full-syntax parsing across profiles; disallowed constructs MUST raise
F801 - Tightened
@metavalues to atoms only and forbade compute constructs - Strengthened layout strategy requirements (total, locale/time/env independent)
- Required stability of host-provided budget by execution configuration tuple
- Fixed import error preference: sandbox violations MUST raise
F601
Hypervisor implementations MUST provide these Level‑0 lenses:
trim() -> stringlowercase() -> string(locale-independent)uppercase() -> string(locale-independent)split(separator: string) -> list<string>replace(pattern: string, replacement: string) -> string(safe regex subset)indent(level: int) -> string(2 spaces × level)
json(indent: int = 0) -> stringkeys() -> list<string>values() -> list<any>map(field: string) -> list<any>sort_by(field: string, desc: bool = false) -> list<any>default(value: any) -> anyensure_list() -> list<any>
This ABNF describes the normalized NFC+LF source form. Newlines are LF (%x0A).
Note: This ABNF covers the generic YAML-lite expression syntax used in most facets. Certain facets define specialized internal line grammars (e.g. @interface and @test) that are specified in their respective sections and are not fully captured here.
FACET-DOC = *(WSLINE / TOP)
WSLINE = *(SP / COMMENT) NL
COMMENT = "#" *(%x20-10FFFF) NL
TOP = IMPORT / FACET
IMPORT = "@import" SP STRING NL
FACET = "@" IDENT [ATTRS] NL BODY
ATTRS = "(" [ATTR *( "," *SP ATTR)] ")"
ATTR = IDENT "=" ATTR-ATOM
ATTR-ATOM = STRING / NUMBER / BOOL / NULL / VARREF
BODY = 1*(IND LINE)
LINE = (KV / LISTITEM) NL
KV = KEY ":" *SP VALUE
KEY = IDENT / STRING
LISTITEM = "-" SP VALUE
VALUE = ATOM *(SP "|>" SP LENS-CALL)
ATOM = STRING / NUMBER / BOOL / NULL / VARREF / INLINE-LIST / INLINE-MAP / INPUT-DIR
INPUT-DIR = "@input" [ATTRS]
LENS-CALL = IDENT "(" [ARGS] ")"
ARGS = ARG *( "," *SP ARG )
ARG = [IDENT "="] VALUE
VARREF = "$" IDENT *("." IDENT)
INLINE-LIST = "[" *SP [VALUE *(*SP "," *SP VALUE)] *SP "]"
INLINE-MAP = "{" *SP [KEY ":" *SP VALUE *(*SP "," *SP KEY ":" *SP VALUE)] *SP "}"
STRING = DQUOTE *CHAR DQUOTE
CHAR = ESC / %x20-21 / %x23-5B / %x5D-10FFFF
ESC = "\" ( DQUOTE / "\" / "n" / "t" / "r" / "u" 4HEXDIG )
NUMBER = ["-"] 1*DIGIT ["." 1*DIGIT] [("e"/"E") ["-"/"+"] 1*DIGIT]
BOOL = "true" / "false"
NULL = "null"
IDENT = ALPHA *(ALPHA / DIGIT / "_")
SP = %x20
NL = %x0A
IND = SP SP
DQUOTE = %x22For Level‑1 lenses:
CacheKey = sha256( JCS({ lens: { name, version }, input: canonical_input_value, args: canonical_args, named_args: canonical_named_args, host_profile_id, facet_version: "2.1.3" }))
Where:
canonical_*values are RFC 8785 canonical JSON encodings- multimodal values MUST include semantic digest and declared constraints
Pure Mode rule:
- Level‑1 MUST NOT perform network calls; cache hit only, else
F803.
string→{ "type": "string" }int→{ "type": "integer" }float→{ "type": "number" }bool→{ "type": "boolean" }null→{ "type": "null" }any→{}
Constraints:
min/max→minimum/maximumpattern→patternenum→enum
struct { a: T1, b: T2 } →
{
"type": "object",
"properties": { "a": <T1>, "b": <T2> },
"required": ["a", "b"],
"additionalProperties": false
}Optional fields MUST be expressed as T | null and map to oneOf including { "type": "null" }.
list<T>→{ "type": "array", "items": <T> }map<string,T>→{ "type": "object", "additionalProperties": <T> }
T1 | T2 → { "oneOf": [<T1>, <T2>] }
embedding<size=N> →
{
"type": "array",
"items": { "type": "number" },
"minItems": N,
"maxItems": N
}A Hypervisor implementation MUST implement:
-
Normalization: UTF‑8 validation, NFC normalization, LF normalization (§3)
-
Parsing: 2-space indentation, inline list/map, pipelines, attributes restrictions, directive-expression parsing (§5, App B)
-
AST: required node classes and normalized spans; ordered-map preservation (§6)
-
Imports: allowlisted roots, forbid absolute/
../URL, detect cycles, sandbox violations asF601(§7, §17.2) -
Merge: deterministic singleton-map merge with stable key positions; deterministic repeatable-block collection (§7.3–§7.4)
-
FTS: primitives/composites/unions/multimodal, assignability, constraints (§8)
-
Lens registry: name/version/types/trust/gas/determinism; effect_class for Level‑1/2; unknown lens error (§9, §16.5)
-
Gas: enforcement and
F902(§9.4) -
Modes: Pure vs Exec enforcement, including
F803cache-only for Level‑1 (§9.5, App C) -
R‑DAG: dependency analysis, topo evaluation, tie-break by merged ordered-map insertion order per §7.4.1 (override MUST NOT change key position), cycle detection (§10.3)
-
Token Box Model: FACET Units, deterministic ordering, truncation rules,
F901(§11) -
@context: budget + defaults schema, stability rule for host-provided budget (§12.2) -
@meta: atoms-only values; identifier-or-string keys; control-char restriction; string keys forbidden outside@meta(§12.1) -
@input: directive-expression placement and semantics;F453validation failures (§14.3) -
Canonical JSON: model, ordering rules for tools/messages, RFC 8785 canonicalization, resolved-source document hash, policy_hash; mandatory emission of
policy_versionin metadata; Hypervisor MUST applytool_exposeguard filtering before emittingcanonical.tools(§18) -
Interfaces: syntax + JSON Schema mapping conformance; mandatory fn effect attribute (§13, §16.5)
-
Policy:
@policyparsing, merge rules for allow/deny, condition typing, deterministic evaluation; conjunctive filter semantics per rule; canonical identifier form for name matching; short-circuit evaluation ofall/any;policy_versionincluded inpolicy_hashcomputation; formal definition of side-effecting operations (§16.2–§16.4.3) -
Runtime Guard: enforcement points, guard timing (before any associated computation), precise enumeration of guarded operations, fail-closed semantics, policy decision algorithm,
F454(deterministic deny) vsF455(undecidable) distinction;effect_class == nulltreated as side-effecting (§16.6) -
Provenance: Execution Artifact emission with guard decisions and hash-chain;
profile,mode, andpolicy_versionincluded in H0 seed;policy_versionin Execution Artifact metadata (Appendix F) -
Tests: minimal
@testexecution semantics; policy/guard interactions in tests (§15) -
Errors: emit standard codes per §19; host diagnostics namespaced (§2.2)
A Core implementation MUST implement items (1–5, 12, 13 as parsing+render-only, 15, 16 as “not supported” with F801 on use of @interface, 17 as type-check+policy_hash emission only, 21) and MUST reject Hypervisor-only constructs with F801.
Hypervisor run and test environments SHOULD emit an Execution Artifact. If emitted, it MUST be deterministic (given identical inputs, caches, and execution configuration) and MUST be serialized using RFC 8785 (JCS).
The Execution Artifact is distinct from Canonical JSON:
- Canonical JSON is the provider-agnostic request context (§18).
- Execution Artifact is an audit/provenance record of guarded operations and their decisions.
An Execution Artifact MUST be a JSON object with:
metadata(object):facet_version:"2.1.3"host_profile_id: stringdocument_hash: string (same ascanonical.metadata.document_hash)policy_hash: string | null (same ascanonical.metadata.policy_hash)policy_version: string (same ascanonical.metadata.policy_version)
provenance(object):events: list ofGuardDecisionobjects in increasingseqhash_chain: object describing a hash chain over events
attestation(object | null): optional signature envelope (§F.5)
A GuardDecision MUST be a JSON object with:
seq(int): starts at 1, increments by 1 with no gapsop(string): one oftool_call | lens_call | tool_expose | message_emitname(string): operation name (see §16.4.2)effect_class(string | null)mode(string):"pure"or"exec"decision(string):"allowed"or"denied"policy_rule_id(string | null):idof the first matching rule that determined the decision, elsenullinput_hash(string):"sha256:" + hex(sha256(JCS(InputObject)))
Where InputObject MUST be:
- For
tool_call:{ interface: <InterfaceName>, fn: <fn_name>, args: <canonical_args_object>, host_profile_id, facet_version: "2.1.3" }
- For
lens_call:{ lens: { name, version }, input: <canonical_input_value>, args: <canonical_args>, named_args: <canonical_named_args>, host_profile_id, facet_version: "2.1.3" }- Note: this aligns with Appendix C’s cache key contract but is distinct (event input hash does not need to equal cache key).
- For
tool_expose:{ interface: <InterfaceName>, host_profile_id, facet_version: "2.1.3" }
- For
message_emit:{ message_id: <id>, role: <role>, host_profile_id, facet_version: "2.1.3" }
Canonical values MUST follow RFC 8785 (JCS).
provenance.hash_chain MUST be:
{
"algo": "sha256",
"head": "sha256:..."
}The chain MUST be computed as:
-
H0 = sha256( JCS({ facet_version: "2.1.3", host_profile_id, document_hash, policy_hash, policy_version, profile, mode }) )Where
profileis"core"or"hypervisor"andmodeis"pure"or"exec", matchingcanonical.metadata.profileandcanonical.metadata.mode;policy_versionmatchescanonical.metadata.policy_version. Including all execution configuration fields in the seed prevents hash-chain collisions across configurations and policy semantic versions. -
For each event
Eiwithseq=i:Hi = sha256( JCS({ prev: "sha256:" + hex(H{i-1}), event: Ei }) )
-
head = "sha256:" + hex(Hn)wheren = len(events)
If attestation is non-null, it MUST be an object with:
algo(string):"ed25519"or a namespaced algorithm"x.<host>.<algo>"key_id(string): host-defined key identifiersig(string): base64url signature over the bytes ofprovenance.hash_chain.headstring (UTF‑8)
The host MUST document the public key discovery mechanism for key_id out of band. FACET does not standardize key distribution.
If execution is exposed to @test assertions (§15.2), it MUST be the exact Execution Artifact object emitted by the engine (or a strict subset that preserves metadata, provenance.events, and provenance.hash_chain).