Reference
The exact normative semantics of the Blue language and processing model — the language part and the contracts/runtime part. Long; use the section anchors to jump around.
Scope of Part I. Defines Blue’s content model, typing, resolution, schema, identity (BlueId), expansion and minimization. No runtime/contract processing is defined here (that is Part II). Where Part I references types like Text, Integer, List, Boolean, etc., their canonical definitions (with BlueIds) are in Appendix A.
Goal. Blue is a universal, deterministic content language with:
Out of scope. Runtime/workflow semantics, policies, operations, and processors (colloquially “contracts”) are not defined here. They live in Blue Contracts & Processor Model.
A Blue node is exactly one of:
Reserved keys (language keywords) that MAY appear in any object node:
1name, description, type, itemType, keyType, valueType,2value, items, blueId, blue, schema, mergePolicy, contracts
There is no properties field in the language. An “object” node is written as regular fields under that node. (properties may exist internally in some libraries; it MUST NOT appear in documents.)
To improve ergonomics, Blue admits two equivalent authoring forms for scalars and lists:
x: 1 ≡ x: { value: 1 } (equivalent only if the wrapper has no other keys)x: [a, b] ≡ x: { items: [a, b] } (equivalent only if the wrapper has no other keys)Object nodes are written directly:
1x:2 a: 13 b: 2
A node MUST NOT combine payload kinds: a node may have either value or items or child fields (object) — never more than one. (Authors MAY rely on preprocessing to normalize forms; see §7.)
(Matcher Neutrality.) Matchers MUST ignore name, description.
Dereferencing { blueId: X } to materialize the node may copy the referenced node’s name/description onto that node (because the node itself is being materialized). This is not inheritance from type and does not violate §3.2.
There is no “schema vs instance” bifurcation. Any node can appear under type. If T is used in type: T, T contributes:
Fixed-value invariant. A concrete value in a type is immutable in descendants at that path.
When resolving, descendants MUST satisfy:
Liskov. Every instance of a subtype MUST be substitutable for its parent.
Nodes representing individuals can be used as types:
All fixed values in Alice become invariants in Alice Smith. Alice’s name/description do not flow to Alice Smith (§3.2).
Attach schema to any node. All constraints accumulate along the type chain; stricter wins. Irreconcilable constraints MUST fail resolution (§11).
<non-negative integer><non-negative integer> (≥ minItems)<non-negative integer><non-negative integer> (≥ minFields)(Rationale: “fields” reflects there is no properties key in the language.)
<non-negative integer><non-negative integer> (≥ minLength)<ECMA-262 regex string>Note. There is no separate const keyword; a fixed value in the type (i.e., the type sets value or a concrete subtree value) already enforces a constant.
Goal. Produce a fully validated, resolved snapshot from authoring/overlay input.
Given a source node S:
An ancestor may partially constrain a subtree (overlay obligations) without binding a concrete type at that path:
1# Parent2name: A3prop1:4 x: 1 # requirement overlay at /prop1 (no type)5 schema: { ... } # optional constraints
A descendant may later set prop1.type: Some only if the merged result still satisfies all overlay obligations (fixed values and schema). If the overlay had a type, the descendant’s type must be equal or a subtype.
Conflicts (e.g., overlay forces x = 1 but Some forces x = 2) MUST fail resolution.
Resolvers SHOULD support path/depth limits to bound expansion of large graphs. Limits affect only materialization, not meaning.
The root MAY contain blue (string or object). Preprocessing:
Remote fetch of transforms is DISABLED by default (opt-in only).
BlueId = Base58( SHA-256( RFC 8785-canonical-JSON(node) ) ) computed bottom-up (children before parents).
Before producing RFC 8785 canonical JSON, implementations MUST normalize nodes as follows. The aims are to remove non-meaningful artifacts, ensure a single canonical shape, and preserve identity-relevant content.
{} at any depth (including within lists).• Cascading removal is permitted (a parent map may become {} and then be removed itself).Note: This “cleaning” is applied recursively across the entire tree before hashing.
Blue’s official canonical representation is the wrapped form. Authoring sugar MUST be normalized to wrapped form prior to hashing:
1x: 1
1x:2 value: 1
1x: [a, b]
1x:2 items: [a, b]
A node MUST NOT combine payload kinds (value, items, or object fields). After normalization, each node is exactly one of:
{ value: … },{ items: […] }, orEquivalence guarantee: Even if an implementation hashes authoring sugar directly, the hasher MUST treat
x: 1andx: { value: 1 }(and likewisex: [a,b]vsx: { items: [a,b] }) as identical (§ 8.3, § 8.4 ensure this by inlining value and domain-separating list folds).
Reserved list item forms are not content (except $empty) and MUST be handled specially before hashing:
These forms are recognized only at the top level of items for nodes whose type is List.
After normalization, an object node is a plain map of field → node.
There is no properties field in the language; that is an internal detail in some libraries and MUST NOT appear in documents. Key order is irrelevant (RFC 8785 canonicalization defines ordering).
1list: [a, null, b]
→ canonical wrapped content:
1list:2 items: [a, b]
1x: 1
→
1x:2 value: 1
1x: [a, b]
→
1x:2 items: [a, b]
1list:2 items: [a, { $empty: true }, b]
1list:2 items:3 - $pos: 04 value: a'5 - b
→ normalized content before hashing:
1list:2 items: [a', b]
1list:2 items:3 - $previous: { blueId: P }4 - c
If and only if the map is exactly { "blueId": "<id>" }, return <id> (pure reference short-circuit).
Otherwise, build a helper map H in lexicographic key order:
k: { "blueId": id(v) }.Then RFC 8785-canonicalize H and hash.
This rule ensures scalar wrappers
{ value: S }are treated as scalars for hashing, and that all nested structure contributes by BlueId, not by byte shape.
Use a domain-separated streaming fold over element BlueIds:
1id([]) = H({ "$list": "empty" })
1fold(prevId, x) =2 H({3 "$listCons": {4 "prev": { "blueId": prevId },5 "elem": { "blueId": id(x) }6 }7 })
1id([a₁,…,aₙ]) = fold( fold( … fold( id([]), a₁ ) … , aₙ ) )
Properties:
Seeding with $previous: If the final resolved list’s prefix exactly equals the list whose BlueId is provided by $previous, start the fold from that BlueId and only fold appended elements. Otherwise ignore the anchor and recompute from id([]).
Hash the RFC 8785 canonical JSON representation of the scalar value.
A node MUST NOT store its own BlueId as authoritative content. Using { "blueId": "…" } to reference other nodes is permitted and encouraged.
Expansion materializes content referenced by blueId without changing identity. It is distinct from Resolution (§6).
Given p: { blueId: X }, expansion fetches content for X and materializes it in place (or side-by-side) within limits (§9.3), allowing nested references to expand recursively.
Expansion MUST NOT change BlueId. Pure references hash to their blueId (short-circuit), and materialized subtrees contribute the same id(subtree) because map hashing replaces structure with { blueId: … } under the hood.
Implementations SHOULD support path/depth limits to avoid runaway traversal of large graphs. Limits affect only materialization, not identity.
Minimization produces a minimal authoring view that, when re-resolved, yields the same resolved snapshot and the same BlueId.
Given a resolved snapshot R, minimize(R) MUST:
{ blueId: … } forms when available.{ blueId: … } when the subtree equals a known blueId (this is an allowed minimization, not required).Some authoring graphs contain direct cycles across documents (e.g., Person ↔ Dog). Blue supports a combined BlueIdwith per-document suffixes.
During save/serialization of a cyclic set:
Rewrite placeholders:
These final BlueIds are stable, content-addressed identities for the cyclic set.
Example.
1# Person (#1 before sorting)2name: Person3pet:4 type: { blueId: 'this#1' }56# Dog (#0 before sorting)7name: Dog8owner:9 type: { blueId: 'this#0' }10breed:11 type: Text
If MASTER = "12345...", the final identities are Dog = "12345...#0", Person = "12345...#1".
A list field SHOULD be authored in typed form:
1<field>:2 type: List3 itemType: <Type> # RECOMMENDED4 mergePolicy: append-only | positional5 items:6 - ...elements...
A surface list (e.g., tags: [a, b, c]) is permitted for simple cases; the typed form is REQUIRED when mergePolicy, anchors, or overlays are used.
Each item MUST be exactly one of:
1- <scalar | object (optionally with type) | { blueId: "…" }>
1- $previous:2 blueId: <PrevListBlueId>
Map overlay (best for object elements):
1- $pos: 12 ...overlay fields merged into parent element at index 1...
Scalar/list overlay (when overlay isn’t a map):
1- $pos: 12 value: <scalar | list | { blueId: "…" }>
Constraints:
1- $empty: true
Scope of reserved attributes. The special keys $previous, $pos, $empty are recognized only as top-level keys of items within items of a node whose type: List. They have no special meaning elsewhere.
If mergePolicy is omitted, processors MUST assume positional. For histories/ledgers/timelines, authors MUST specify append-only explicitly.
{}, [], and $empty{} — ignored.Let parent list be the resolved list from the parent type/instance; let child overlay be the child’s items.
1- $previous: { blueId: <PrevListBlueId> }
The list hasher uses a domain-separated streaming fold:
id([]) = H({ "$list": "empty" })1acc := H({2 "$listCons": {3 "prev": { "blueId": acc },4 "elem": { "blueId": id(e) }5 }6})
Control forms:
Properties guaranteed:
null/{} but not [].Present-empty vs absent
1# Absent2doc: { }34# Present-empty5doc:6 list:7 type: List8 items: []
blueId(present-empty) != blueId(absent).
Append-only timeline (fast append, immutable prefix)
1# Parent2entries:3 type: List4 itemType: Timeline Entry5 mergePolicy: append-only6 items:7 - { type: Timeline Entry, ts: 2025-09-01T12:00:00Z, message: A }8 - { type: Timeline Entry, ts: 2025-09-01T12:05:00Z, message: B }910# Child (append C)11entries:12 type: List13 itemType: Timeline Entry14 mergePolicy: append-only15 items:16 - $previous: { blueId: PrevId } # PrevId = id(parent entries)17 - { type: Timeline Entry, ts: 2025-09-01T12:10:00Z, message: C }
Hasher seeds from PrevId and folds only the new element(s).
Positional hole and refinement
1# Parent2entries:3 type: List4 mergePolicy: positional5 items:6 - A7 - $empty: true8 - C910# Child11entries:12 type: List13 mergePolicy: positional14 items:15 - $pos: 116 value: B17# Resolved: [A, B, C]
Positional refine + append
1# Parent2items:3 type: List4 itemType: PhoneOrAccessory5 mergePolicy: positional6 items:7 - { type: Phone, os: any }8 - { type: Accessory }910# Child11items:12 type: List13 mergePolicy: positional14 items:15 - $pos: 016 type: iPhone17 os: iOS18 - { type: Warranty, months: 24 }1920# Resolved:21# - { type: iPhone, os: iOS }22# - { type: Accessory }23# - { type: Warranty, months: 24 }
A Blue Language 1.0 implementation:
Profiles (recommended):
1name: Simple Amount2amount: { type: Double }3currency:{ type: Text }4# => blueId: FgHZjS...56name: Person7age: { type: Integer }8spent: { type: { blueId: FgHZjS... } } # Simple Amount9# => blueId: GRwTYs...
Instance:
1name: Alice2type: { blueId: GRwTYs... } # Person3age: 254spent:5 amount: 27.156 currency: USD7# => blueId: 3JTd8s...
Fully expanded (type chain materialized) has the same BlueId (Expansion & Resolution do not change identity).
1blue: 5j04jf...2name: Alice3type: Person4age: 255spent:6 amount: 27.157 currency: USD
Preprocessing replaces Person with its blueId and may infer basic types for scalars. The blue key is removed before hashing.
1# A2name: Person to Avoid3description: This guy will kill you today4type: Image5image: { blueId: 123..456 }67# B8name: Family Member9description: Trust this person10type: Image11image: { blueId: 123..456 }
Different BlueIds (identity content differs), but structural/type checks ignore labels (§3).
1# Parent2name: A3prop1:4 x: 1 # overlay (no type)5 schema: { /* optional constraints */ }67# Child8name: B9type: A10prop1:11 type: Some # legal only if merged result preserves x = 1 (and schema)
If Some would force x = 2, resolution fails (§6.2).
1# Parent2name: Trip3segments:4 itemType: Flight Segment5 # (direct list form)6 - { type: Flight Segment, carrier: BA }78# Child9name: Trip LHR->SFO10type: Trip11segments:12 - { type: Flight Segment, carrier: BA, from: LHR, to: JFK } # refine index 013 - { type: Flight Segment, carrier: BA, from: JFK, to: SFO } # append
Reorder/delete of inherited prefix elements is invalid; append is valid.
Start from:
1blueId: 3JTd8s... # Alice
Expanding /spent/* hydrates only spent:
1name: Alice2type: { blueId: GRwTYs... }3age: 254spent:5 amount: 27.156 currency: USD
BlueId remains unchanged (§9).
From a resolved snapshot with fully materialized type subtrees, minimize():
{ blueId: … } when available,{ blueId: … } when exact matches are known.The minimized authoring form re-resolves to the same snapshot and BlueId.
1# Authoring (placeholders after algorithm determines positions)2# Dog (#0)3name: Dog4owner: { type: { blueId: 'this#1' } }56# Person (#1)7name: Person8pet: { type: { blueId: 'this#0' } }
The ordered list [Dog, Person] yields MASTER. Final identities: Dog = MASTER#0, Person = MASTER#1.
Purpose. Part II defines how a Blue document changes state when an event arrives. The processor is a deterministic function:
1PROCESS(document, event) → (new_doc, triggered_events, total_gas)2
Events are Blue nodes. Channels interpret Blue nodes by type/shape (and, where relevant, local routing context).
Every object node MAY contain a contracts dictionary:
1contracts: { <key: Text> → <value: Contract> } # see Appendix A (“Contract”)2
Contracts tell the processor what to do with a node. Each contract is identified by a type BlueId (published elsewhere in the registry). A processor either supports a contract’s type BlueId or returns a must-understand failure (see §22.1).
Contract roles (types in Appendix A):
consumeGas(units: Integer), and (iv) terminate(cause, reason?). No other side effects are permitted. (See Handler in Appendix A.)Matching rule (normative).
Handlers MUST only match channelized events produced by channels in the same scope (the node whose contracts map contains them). There is no engine-level “channel flag”; channels define their own recognition (by payload type/shape and, when needed, scope context).
Ordering (normative).
When multiple contracts match, the processor sorts channels, and then handlers within each channel, by:
order (number; missing = 0), thenkey (the contract map key, lexicographic).Reserved processor keys under a contracts map (normative).
embedded — Process Embedded (Appendix A)initialized— Processing Initialized Marker (Appendix A)terminated — Processing Terminated Marker (Appendix A)checkpoint — Channel Event Checkpoint (Appendix A)These keys are reserved for the processor’s own use. If any exist with an incompatible type, processing MUST terminate as runtime fatal (§22.2).
Write-protection of reserved keys (normative).
Contracts (handlers or channels) MUST NOT patch any reserved key path (/…/contracts/(embedded|initialized|terminated|checkpoint) or descendants). Attempting to add/replace/remove a reserved key path from a handler/channel is a deterministic runtime fatal at that scope (§22.2). Processor writes to these keys are permitted only as specified in §§20, 22, 23 and via Direct Writes (§19.1).
Read-only contract inputs (normative).
Contracts (channels and handlers) MUST treat delivered event objects as read-only. A contract MUST NOT mutate payload objects it receives; all document changes MUST occur only via explicit patch operations (Appendix A: Json Patch Entry).
Implementation note (non-normative): Processors MAY enforce this by cloning or freezing payloads, or by relying on contract implementations to obey the rule; this specification does not require cloning.
Channel vs Handler capability surface (normative).
consumeGas(units); terminate(cause, reason?).consumeGas(units); terminate(cause, reason?).No other effects are permitted.
(Validated by: T2, T3, T15, T23–T25; reserved-key tamper tests SHOULD be added.)
Some subtrees are independent documents processed alongside the parent.
A Process Embedded marker (Appendix A) under contracts/embedded declares embedded children:
1contracts:2 embedded:3 # (type defined in Appendix A)4 paths:5 - /payment6 - /shipping7 # Absolute JSON Pointers within the current scope8
Normative behavior.
paths before processing each child and re-reads it after each child finishes; additions/removals/reorderings take effect immediately for the next child.Let the current scope be absolute pointer S (e.g., /, /payment, /a/b). Let E be the set of embedded child roots declared by S’s Process Embedded.
A patch applied while executing in scope S is permitted iff:
patch.path starts with S, andpatch.path is not strictly inside any other embedded domain X ∈ E where X ≠ S.“Strictly inside” means below the child boundary:
1IS_STRICTLY_INSIDE(path, X) = path starts with (X + "/")2
Implications (normative).
add /payment, remove /payment), but MUST NOT reach inside it (e.g., replace /payment/amount).S + "/...").add, replace, or remove. Only ancestors may add/replace/remove a child root. Violations → runtime fatal (§22.2)."/" (§21.2).Rationale. “Cutting/replacing the balloon” is a parent-only operation; scopes only mutate strict descendants of themselves.
(Validated by: T2, T23–T25.)
The processor MUST support (and is the only entity that feeds) these channel families (definitions in Appendix A):
ABS(scope, path) (see §21.3).op (add|replace|remove), path (relative), and before/after snapshots. Payload content is uniform within a scope; channels do not reshape processor-managed payloads.Helper notation used in this Part (§21.3):
ABS(S, P) — absolute pointer for a channel path P declared at scope S.
relativize_pointer(S, ABS) — path relative to S (returns "/" when ABS == S).
relativize_snapshot(S, node) — subtree content as observed at S (clone or read-only view).
(Validated by: T4–T7, T14–T16, T28.)
Beyond the processor-managed channels, authors may define custom channels, handlers, and markers.
Normative requirements.
consumeGas(units), and terminate(cause, reason?). No other side effects are permitted.(Validated by: T8–T10, T20–T22.)
Signature. PROCESS(doc, event) → (new_doc, triggered_events, total_gas)
Events are Blue nodes. There is no envelope.
Important timings (normative):
_PROCESS (Phase 5).1function PROCESS(doc, event):2 RUN.root_events = []3 RUN.total_gas = 04 RUN.emitted_by_scope = {} # scope → [bridgeable node] (Triggered + lifecycle)5 RUN.fifo_by_scope = {} # scope → FIFO (persistent for the run)6 RUN.terminated_scopes= {} # scope → true when terminated7 try:8 (_doc, _scope_emitted) = _PROCESS(doc, event, scope="/")9 return (doc, RUN.root_events, RUN.total_gas)10 except TERMINAL_FAILURE:11 # A fatal termination at root ends the run.12 return (doc, RUN.root_events, RUN.total_gas)13
1function _PROCESS(doc, event, scope):2 scope_bucket = ensure_bucket(RUN.emitted_by_scope, scope) # bridgeable emissions (Triggered + lifecycle)3 scope_fifo = ensure_fifo(RUN.fifo_by_scope, scope) # persistent FIFO for THIS scope45 # PHASE 1 — Process embedded children (recursive, dynamic)6 processed_paths = insertion_ordered_set()7 loop:8 paths = read_process_embedded_paths(doc, scope) # validates contracts/embedded is Process Embedded (Appendix A)9 next_path = first p in paths where p ∉ processed_paths10 if next_path is None:11 break12 if node_exists(doc, next_path):13 (doc, _) = _PROCESS(doc, event, scope=next_path) # single document model14 processed_paths.add(next_path)15 # Re-reads 'paths' after each child: adds/removes/reorders take effect for the next child.16 # Stabilization: once a child path enters processed_paths, it will NOT be processed again in this run,17 # even if removed then re-added later ("no resurrection" within a run).1819 # Early-out if this scope was terminated by a child-side cascade or lifecycle20 if RUN.terminated_scopes.get(scope, false):21 return (doc, RUN.emitted_by_scope[scope])2223 # PHASE 2 — Initialize this scope (first run only)24 if not has_initialization_marker(doc, scope):25 pre_init_id = compute_blue_id_at_scope(doc, scope)2627 # 2.A Lifecycle: Document Processing Initiated (Appendix A)28 lifecycle_node = make_initiated_event(pre_init_id) # payload defined in Appendix A29 doc = DELIVER_LIFECYCLE(doc, scope, lifecycle_node)3031 # 2.B Add Processing Initialized Marker under reserved key 'initialized' (Appendix A)32 ensure_reserved_empty_or_compatible(doc, scope, "initialized", "Processing Initialized Marker")33 doc = APPLY_PATCH_WITH_CASCADE(doc, origin_scope=scope,34 patch = json_patch_add( scope + "/contracts/initialized",35 make_initialized_marker(pre_init_id) )) # marker definition in Appendix A3637 if RUN.terminated_scopes.get(scope, false):38 return (doc, RUN.emitted_by_scope[scope])3940 # PHASE 3 — Match channels; run handlers for the external event41 channels = sort_by_order_then_key( find_matching_channels(doc, scope, event) )42 for ch in channels:43 if RUN.terminated_scopes.get(scope, false):44 break4546 if is_external_channel(ch): # processor-fed families are NOT gated47 # Lazy checkpoint creation: if missing, create empty before newness evaluation48 if not has_checkpoint_marker(doc, scope):49 DIRECT_WRITE(doc, scope + "/contracts/checkpoint", make_empty_checkpoint())50 ckpt = read_checkpoint(doc, scope) # must exist now51 if not checkpoint_is_new(ckpt, ch.key, event):52 continue5354 handlers = sort_by_order_then_key( find_handlers_for_channel(doc, scope, ch) )55 for h in handlers:56 res = execute_handler(h, context_for(scope, event, scope_fifo))57 RUN.total_gas += res.gas_consumed5859 # 3.A Apply patches; each applied patch triggers a bottom-up Document Update cascade60 for p in res.patches:61 if boundary_violation(doc, scope, p): # includes "self-root", "root target", reserved-key write62 ENTER_TERMINAL_TERMINATION(doc, scope, "Boundary violation at " + p.path)63 doc = APPLY_PATCH_WITH_CASCADE(doc, origin_scope=scope, patch=p)6465 # 3.B Record & enqueue Triggered emissions66 for t in res.triggered_events:67 EMIT_TO_SCOPE(scope, t)6869 # 3.C Optional termination from handler70 if res.terminated:71 ENTER_GRACEFUL_TERMINATION(doc, scope, res.termination_reason)7273 if is_external_channel(ch) and not RUN.terminated_scopes.get(scope, false):74 # After successful channel processing, update checkpoint for ch.key with the entire event node75 ckpt2 = read_checkpoint(doc, scope) # exists76 DIRECT_WRITE(doc, scope + "/contracts/checkpoint", checkpoint_update(ckpt2, ch.key, event))7778 if RUN.terminated_scopes.get(scope, false):79 return (doc, RUN.emitted_by_scope[scope])8081 # PHASE 4 — Parent-only: bridge each child's emissions via Embedded Node82 # Bridge FIRST (may enqueue many items into THIS scope’s FIFO).83 for child_path in iteration_order(processed_paths):84 embedded_ch = find_embedded_node_channel(doc, scope, child_path)85 if not embedded_ch:86 continue87 child_events = RUN.emitted_by_scope.get(child_path, [])88 if child_events.is_empty():89 continue90 emb_handlers = sort_by_order_then_key( find_handlers_for_channel(doc, scope, embedded_ch) )91 for ev in child_events:92 for h in emb_handlers:93 res = execute_handler(h, context_for(scope, ev, scope_fifo))94 RUN.total_gas += res.gas_consumed95 for p in res.patches:96 if boundary_violation(doc, scope, p):97 ENTER_TERMINAL_TERMINATION(doc, scope, "Boundary violation at " + p.path)98 doc = APPLY_PATCH_WITH_CASCADE(doc, origin_scope=scope, patch=p)99 for t in res.triggered_events:100 EMIT_TO_SCOPE(scope, t)101102 if RUN.terminated_scopes.get(scope, false):103 return (doc, RUN.emitted_by_scope[scope])104105 # PHASE 5 — Drain THIS scope’s Triggered FIFO (if channel exists)106 if has_triggered_event_channel(doc, scope):107 doc = DRAIN_TRIGGERED_QUEUE(doc, scope) # drains once108109 return (doc, RUN.emitted_by_scope[scope])110
Notes.
1# Records a Triggered node as emitted by 'scope' and enqueues it for local delivery.2# - Appends to RUN.emitted_by_scope[scope] (for parent bridging)3# - Enqueues to RUN.fifo_by_scope[scope]4# - If scope is root, also appends to RUN.root_events (returned even if handled)5function EMIT_TO_SCOPE(scope, node):6 bucket = ensure_bucket(RUN.emitted_by_scope, scope)7 fifo = ensure_fifo(RUN.fifo_by_scope, scope)8 bucket.append(node)9 fifo.enqueue(node)10 if scope == "/":11 RUN.root_events.append(node)1213# Records a lifecycle node for bridging ONLY (no local Triggered delivery).14function RECORD_BRIDGEABLE(scope, node):15 bucket = ensure_bucket(RUN.emitted_by_scope, scope)16 bucket.append(node)17 if scope == "/":18 RUN.root_events.append(node)19
1function APPLY_PATCH_WITH_CASCADE(doc, origin_scope, patch):2 before = snapshot_at(doc, patch.path) # before mutation3 doc = apply_json_patch(doc, patch) # §21.2 (absolute pointers, array bounds, auto-materialization)4 after = snapshot_at(doc, patch.path) # after mutation (null for remove)56 for scope in ancestors_including_self_up_to_root(origin_scope): # e.g., /a/b → [/a/b, /a, /]7 channels = sort_by_order_then_key(8 filter_subtree_matches_by_absolute_path(9 find_document_update_channels(doc, scope),10 changed_abs_path = patch.path))1112 if channels.is_empty():13 continue1415 # One immutable payload object per scope (same for all handlers in S)16 event = make_document_update_event(scope, patch.op, patch.path, before, after) # Appendix A “Document Update”1718 fifo = ensure_fifo(RUN.fifo_by_scope, scope)19 for ch in channels:20 handlers = sort_by_order_then_key( find_handlers_for_channel(doc, scope, ch) )21 for h in handlers:22 res = execute_handler(h, context_for(scope, event, fifo))23 RUN.total_gas += res.gas_consumed24 for nested in res.patches:25 if boundary_violation(doc, scope, nested): # includes reserved-key write-protection26 ENTER_TERMINAL_TERMINATION(doc, scope, "Boundary violation at " + nested.path)27 doc = APPLY_PATCH_WITH_CASCADE(doc, origin_scope=scope, patch=nested)28 for emitted in res.triggered_events:29 EMIT_TO_SCOPE(scope, emitted)3031 return doc32
1function DRAIN_TRIGGERED_QUEUE(doc, scope):2 trig_ch = get_triggered_event_channel(doc, scope)3 if not trig_ch:4 return doc56 fifo = ensure_fifo(RUN.fifo_by_scope, scope)7 while not fifo.is_empty():8 ev = fifo.dequeue()9 handlers = sort_by_order_then_key( find_handlers_for_channel(doc, scope, trig_ch) )10 for h in handlers:11 res = execute_handler(h, context_for(scope, ev, fifo))12 RUN.total_gas += res.gas_consumed13 for p in res.patches:14 if boundary_violation(doc, scope, p):15 ENTER_TERMINAL_TERMINATION(doc, scope, "Boundary violation at " + p.path)16 doc = APPLY_PATCH_WITH_CASCADE(doc, origin_scope=scope, patch=p)17 for t in res.triggered_events:18 EMIT_TO_SCOPE(scope, t)19 return doc20
1function DELIVER_LIFECYCLE(doc, scope, lifecycle_node):2 # Record lifecycle node for bridging (always)3 RECORD_BRIDGEABLE(scope, lifecycle_node)45 life_channels = sort_by_order_then_key( find_lifecycle_channels(doc, scope) )6 fifo = ensure_fifo(RUN.fifo_by_scope, scope)7 for ch in life_channels:8 handlers = sort_by_order_then_key( find_handlers_for_channel(doc, scope, ch) )9 for h in handlers:10 res = execute_handler(h, context_for(scope, lifecycle_node, fifo))11 RUN.total_gas += res.gas_consumed12 for p in res.patches:13 if boundary_violation(doc, scope, p):14 ENTER_TERMINAL_TERMINATION(doc, scope, "Boundary violation at " + p.path)15 doc = APPLY_PATCH_WITH_CASCADE(doc, origin_scope=scope, patch=p)16 for t in res.triggered_events:17 EMIT_TO_SCOPE(scope, t)18 return doc19
A Direct Write is a processor mutation that does not produce a Document Update cascade and does not schedule any cascade work. It is used only for:
lastEvents[channelKey] after successful external channel processing (§23).Direct Writes are visible to subsequent logic in the same run and persist in new_doc. They appear only under reserved keys (e.g., /…/contracts/terminated, /…/contracts/checkpoint). Handlers/channels cannot perform Direct Writes; they are processor-internal (§15, §23).
On the first processing of a scope:
triggered_events).contracts/initialized (this patch MUST cause a Document Update cascade).Reserved keys are listed in §15; incompatible presence is fatal (§22.2).
No eager checkpoint creation. The processor MUST NOT create contracts/checkpoint during initialization solely to satisfy presence. The checkpoint is created lazily per §23 when an external channel is first evaluated at this scope.
(Validated by: T3, T15.)
One patch → a cascade of deliveries. When a handler’s patch applies successfully, the processor:
before/after snapshots at the patch’s absolute path,At each scope S:
ABS(S, P) with subtree semantics (equal or descendant).S see the same op and the same before/aftersnapshots; the path is scope-relative to S. Contracts MUST treat this event as read-only (§15).consumeGas/terminate.S’s persistent FIFO and MUST NOT be delivered during the cascade. S’s FIFO is drained once, at the end of S’s _PROCESS (Phase 5 in §19).(Validated by: T4, T5, T14, T17.)
Handlers emit Json Patch Entry objects (Appendix A). The processor applies each patch immediately to the shared document, then performs the Document Update cascade (§21.1). Blue supports a practical, deterministic subset of RFC 6902:
add, replace (upsert semantics), remove.move, copy, test, …) → deterministic runtime fatal.Pointer evaluation.
/); segments are interpreted literally.- is accepted (array append).~1) are treated verbatim (per JSON Pointer).Root-document target forbidden (normative).
The target path MUST NOT be "/" (document root). Replacing/removing the entire document is forbidden.
Object auto-materialization.
Missing intermediate objects are materialized as empty objects before applying the patch. Auto-created containers are part of the same patch; the Document Update describes the final requested path.
Object targets.
add inserts a new member or replaces an existing member.
replace behaves as upsert.
remove deletes a member. Removing a non-existent member → deterministic runtime fatal.
Array targets.
Segments may be numeric indices or -.
add with index inserts at that position (shifting).
add with - appends.
replace overwrites element at index (index must exist).
remove deletes the element at index (shifts left; index must exist).
Out-of-range indices (for replace/remove) → deterministic runtime fatal.
Boundary enforcement.
Every patch is validated against embedded-scope isolation (§16). Any attempt to modify another scope’s interior → deterministic runtime fatal. A parent may add/replace/remove a child root (e.g., /payment), but MUST NOT reach inside it (e.g., /payment/amount). A child MAY NOT target its own scope root S (self-root mutation forbidden).
These rules let authors grow deep structures and manipulate arrays predictably while preserving the one-patch → one-cascade guarantee and determinism.
ABS(S, P) — the absolute JSON Pointer for a channel path P declared at scope S (normalized concatenation of S and P).
relativize_pointer(S, ABS) — the relative pointer from scope S’s root to ABS (returns "/" when ABS == S).
relativize_snapshot(S, node) — a read-only view or clone of node as observed at S. Nodes carry no path context; the relative path provides the location for handlers.
If the document contains a contract type the processor does not support, the processor MUST return a must-understand failure and MUST NOT mutate the document or emit lifecycle fatals. (T9.)
A channel or handler may invoke terminate(cause, reason?), and the processor may terminate a scope fatally for deterministic runtime errors (e.g., boundary violation).
When a scope terminates (either cause):
contracts/terminated with a Processing Terminated Marker (Appendix A), setting:cause: "graceful" or "fatal"reason: <optional Text>RECORD_BRIDGEABLE (§19).RUN.terminated_scopes[scope]=true and drop its FIFO; further patch/emit from that scope are no-ops.Escalation rules.
raise TERMINAL_FAILURE).(Validated by: T23–T30.)
Termination helpers (informative pseudocode).
1function ENTER_GRACEFUL_TERMINATION(doc, scope, reason?):2 DIRECT_WRITE(doc, scope + "/contracts/terminated",3 make_terminated_marker(cause="graceful", reason=reason))4 term_ev = make_terminated_event(cause="graceful", reason=reason) # Appendix A5 doc = DELIVER_LIFECYCLE(doc, scope, term_ev)6 RUN.terminated_scopes[scope] = true7 clear_fifo(RUN.fifo_by_scope, scope)8 if scope == "/":9 # Root graceful ends the run after current call stack unwinds10 raise TERMINAL_FAILURE_GRACEFUL11 return doc1213function ENTER_TERMINAL_TERMINATION(doc, scope, reason):14 DIRECT_WRITE(doc, scope + "/contracts/terminated",15 make_terminated_marker(cause="fatal", reason=reason))16 term_ev = make_terminated_event(cause="fatal", reason=reason) # Appendix A17 doc = DELIVER_LIFECYCLE(doc, scope, term_ev)18 RUN.terminated_scopes[scope] = true19 clear_fifo(RUN.fifo_by_scope, scope)20 if scope == "/":21 # Outbox-only fatal lifecycle22 RUN.root_events.append(make_fatal_outbox(reason)) # Appendix A23 raise TERMINAL_FAILURE24 return doc25
Purpose. A Channel Event Checkpoint records, per external channel key, the last processed event node (entire node, not just an id), enabling idempotent and ordered processing for external sources. (Type in Appendix A.)
Presence & lazy creation.
A scope may lack contracts/checkpoint until it first evaluates an external channel. When an external channel at scope S is evaluated and contracts/checkpoint is absent, the processor MUST DIRECT_WRITE(S + "/contracts/checkpoint", make_empty_checkpoint()) before applying the newness test. From that point on, the scope has exactly one checkpoint. Multiple checkpoints in a scope are a deterministic runtime fatal.
Gating rule (external channels only).
For a matched external channel at scope S:
lastEvents[channelKey] (deterministic policy).DIRECT_WRITE an update setting lastEvents[channelKey] to the entire event node (no Document Update is emitted).Not gated. Processor-managed families (Document Update, Triggered, Lifecycle, Embedded Node) are never subject to checkpoint gating.
Tamper resistance. Reserved keys are write-protected (§15). Any handler attempt to patch contracts/checkpoint (or descendants) is a deterministic runtime fatal.
(Validated by: T8, T18 (lazy creation), T19–T22.)
Purpose. Gas accounting prevents resource-exhaustion while remaining proportional to actual work.
Unit. Gas is an abstract deterministic unit. Processors MUST NOT base gas on wall-clock, CPU model, memory, or I/O. (Informative calibration: profiles MAY document a human-readable mapping; conformance depends solely on the formulas below.)
Processors MUST add the following charges to RUN.total_gas at the exact points indicated. Charges apply in addition to any explicit consumeGas(units) from channels/handlers.
| Operation | Formula | Charge point |
|---|---|---|
| Scope entry | 50 + 10 × depth | On entry to _PROCESS for a scope (root depth=0) |
| Scope exit | 0 | On return from _PROCESS |
| Initialization (first run) | 1000 | When §20 initialization starts for a scope (covers initiated lifecycle + marker patch orchestration) |
depth = number of embedded edges from root (/→0; /a/b→2).
| Operation | Formula | Charge point |
|---|---|---|
| Channel match attempt | 5 per channel tested | Each channel considered in §19 Phase 3 |
| Handler call overhead | 50 | Just before executing each handler |
| Operation | Formula | Charge point |
|---|---|---|
| Boundary check | 2 per patch | Before applying each patch |
| Patch: add/replace | 20 + ceil(bytes/100) | After validation, before cascade |
| Patch: remove | 10 | After validation, before cascade |
| Cascade routing | 10 per participating scope | For each scope that receives the resulting Document Update |
bytes = UTF-8 length of the canonical JSON of the patch val after Part I §8.2 cleaning/normalization (RFC 8785).
| Operation | Formula | Charge point |
|---|---|---|
| Emit event | 20 + ceil(bytes/100) | When emitEvent(node) succeeds (enqueued) |
| Bridge child → parent | 10 per bridged node | For each node delivered by Embedded Node Channel in §19 Phase 4 (Triggered or lifecycle) |
| Drain FIFO | 10 per dequeued event | For each event dequeued in §19 Phase 5 (before handler overhead) |
bytes = canonical JSON size of the emitted event node.
| Operation | Formula | Charge point |
|---|---|---|
| Checkpoint read | 0 | When a channel consults the scope checkpoint |
| Checkpoint update (Direct Write) | 20 | After a matching external channel completes successfully |
| Termination marker write (Direct Write) | 20 | When writing contracts/terminated (§22) |
Direct Write is defined in §19.1; it never triggers Document Update.
| Operation | Formula | Charge point |
|---|---|---|
| Lifecycle delivery | 30 | Per DELIVER_LIFECYCLE call (once per call, before any handlers) |
| Graceful termination overhead | +0 | On terminate(cause="graceful") (marker + lifecycle already charged) |
| Fatal termination overhead | +100 | On fatal termination (in addition to termination marker & lifecycle delivery) |
| Must-understand failure | 0 | §22.1 capability failure (pre-execution) |
Fatal termination total for the termination step: 150 gas (= marker 20 + lifecycle 30 + fatal overhead 100), plus any prior work already charged. Graceful termination step: 50 gas (= marker 20 + lifecycle 30).
RUN.total_gas MUST include:
consumeGas(units) calls made by channels/handlers.Gas MUST be computed solely from:
Gas MUST NOT depend on:
Given the same input document and event, all conforming processors MUST return the same total_gas.
Accounting only. Part II defines gas accounting, not enforcement. Gas is recorded to RUN.total_gas for transparency and diagnostics. Absent an active policy, the processor MUST NOT modify behavior or terminate due to gas usage; it simply continues and returns the measured total.
Policy‐driven enforcement (external). Budgets/limits MAY be defined by separate, document-authored policies (markers/contracts outside this Part). Such policies may specify what budgets exist, when/how measurements are compared to budgets, and the exact behavior on overrun (e.g., graceful/fatal termination per §22, skipping work, emitting signals, etc.). The processor MUST implement any present policy it claims to support exactly. If a policy type is present but unsupported, the processor MUST return a must-understand failure (§22.1).
Determinism. Policies MUST be deterministic and base decisions only on data available within the run (including the gas totals defined in §24), to preserve cross-implementation consistency.
consumeGas).consumeGas).consumeGas).Sizes use canonical JSON per Part I §8.2.
Where to add charges in the algorithm (§19):
_PROCESS entry: scope-entry charge.Bytes measurement: apply Part I §8.2 cleaning & RFC 8785 canonicalization; use UTF-8 length of the canonical JSON string; use ceil(bytes/100) in formulas.
Feeder (out of scope here) — collects external events, orders/deduplicates them per the application’s policy (scheduler, vector clocks, chain logs…). The feeder may deliver stale/out-of-order items; Channel Event Checkpoint ensures scopes ignore stale external events.
Processor (this Part) — given (document, event), executes exactly the rules in this Part (initialization, matching, cascades, bridging, FIFO drains, failures/termination) to produce (new_doc, triggered_events, total_gas).
A compliant processor MUST:
Embedded traversal
"/" target forbidden.Contract capabilities
(order, key) at every scope.Initialization
Patch & cascade semantics
ABS(S,P) with subtree semantics._PROCESS(Phase 5).triggered_events.JSON Patch subset
add, replace (upsert), remove. Reject other RFC 6902 ops.- for append."/" as a patch target.Checkpoint presence & behavior
lastEvents[channelKey] = <entire event node> (no Document Update).Failure/termination
fatal/graceful), publish Document Processing Terminated, and deactivate the scope for the remainder of the run.Run result
(new_doc, triggered_events, total_gas) exactly as defined in §15/§19.T1 — Dynamic embedded list
Root declares paths: [/a, /b]. While processing /a, a root handler removes /b and adds /c.
Then: After /a, the processor re-reads paths and visits /c; /b is skipped (no longer exists).
T2 — Boundary enforcement
Root attempts replace /a/x while /a is an active embedded child.
Then: Fatal termination at root; contracts/terminated is written with Processing Terminated Marker (cause: fatal), a Document Processing Fatal Error is appended to the root run result; run aborts.
T3 — Initialization once
First run at /a writes Processing Initialized Marker (patch) and publishes Document Processing Initiated.
Then: The patch triggers a Document Update cascade; lifecycle may trigger additional work at /a.
T4 — Update cascades (absolute match & relative payload)
A handler at /a applies replace /a/z/k. Root has a Document Update Channel watching /a/z.
Then: At scope /a, payload path="/z/k"; at root, "/a/z/k". Matching uses absolute paths; each scope sees the same payload content relative to itself.
T5 — Cascade emissions are enqueued (not delivered)
Patch at /a/b causes a Document Update handler at /a to emit E.
Then: E is recorded under /a and enqueued; it is delivered later in /a’s Phase 5 (not during the cascade).
T6 — Triggered FIFO (deterministic order)
A handler at /a emits E1, E2. /a has a Triggered Event Channel.
Then: /a drains FIFO (E1 then E2); further emissions during drain append to the tail and are processed deterministically.
T7 — Bridging child emissions
Child /x emits events during its run. Parent has an Embedded Node Channel for /x.
Then: After parent completes external handling, it bridges /x’s emissions (patches cascade), then drains the parent FIFO once.
T8 — Checkpoint gating (external only)
A scope checkpoint exists. Two external channels match the event; one is stale, one is new.
Then: The stale one is skipped; the new one runs; checkpoint updates via Direct Write.
T9 — Capability failure (must-understand)
Document contains unknown contract type.
Then: Processor returns must-understand failure; no patches; no lifecycle fatals.
T10 — No-match
No channel matches anywhere.
Then: Processor returns unchanged document, empty triggered_events, measured total_gas.
T11 — Object auto-materialization
A handler applies add /a/b/c {…} where /a exists but /a/b does not.
Then: Processor creates /a/b as {} then writes /a/b/c; one cascade runs.
T12 — Array append and insert
Given /a/items: ["x","y"].
add /a/items/- "z" → ["x","y","z"] (append).
add /a/items/1 "q" → ["x","q","y"] (insert).
Then: Each patch triggers one cascade; out-of-range indices → fatal (T13).
T13 — Deterministic runtime fatals (arrays & objects)
replace /a/items/7 "z" when length < 8 → fatal.
remove /a/missingKey → fatal.
Then: The failing scope gets contracts/terminated with Processing Terminated Marker (cause: fatal); root fatal only if the scope is root.
T14 — Scope-relative payload
Patch at /a/b to replace /a/b/x.
Then: At /a/b: path="/x", at /a: "/b/x", at root: "/a/b/x". Same op, before, after; only path is scope-relative.
T15 — Root lifecycle inclusion
First processing at root publishes Document Processing Initiated; later, a fatal elsewhere.
Then: Root run result includes both lifecycle items in triggered_events.
T16 — Local delivery depends on Triggered Channel presence
During a cascade at /a, a handler emits E. /a lacks a Triggered Event Channel.
Then: E is recorded as emitted by /a (bridgeable upward if parent configured) but is not delivered locally at /a.
T17 — Uniform event per scope
Multiple Document Update Channels at /a match the same patch.
Then: All handlers at /a see the same Document Update payload object; differences arise only from channel/handler order/matching.
T18 — Lazy checkpoint creation
A scope processes an external event and contracts/checkpoint is absent.
Then: Before newness evaluation, the processor DIRECT_WRITEs an empty checkpoint at /…/contracts/checkpoint(lastEvents = {}). Newness evaluates as “no prior”; if handlers succeed, lastEvents[channelKey] becomes the entire event node by Direct Write. No Document Update is emitted for either write.
T19 — Duplicate checkpoint
A scope contains two Channel Event Checkpoint markers.
Then: Runtime fatal (only one is permitted).
T20 — Stale external event is gated
lastEvents.testEventsChannel holds E_old; incoming E_new is not newer.
Then: Channel is skipped; checkpoint unchanged.
T21 — Checkpoint updated after success
A new external event on testEventsChannel is processed successfully.
Then: lastEvents.testEventsChannel = <entire event node> via Direct Write (no Document Update).
T22 — Multiple external channels at a scope
Two external channels match the same event and both are “newer.”
Then: Both run in (order, key) order; each updates its own key in lastEvents.
T23 — Self-root mutation is forbidden
While executing at /a, a contract attempts remove /a (or replace /a, add /a).
Then: Fatal termination at /a.
T24 — Root-document mutation is forbidden
Any contract attempts to target "/" with any op.
Then: Fatal termination at the executing scope (root fatal ends the run).
T25 — Balloon cut-off on child removal
While /b is being processed, a parent watcher removes /b.
Then: The current /b handler finishes; no further work (no extra handlers, no drain) occurs for /b; already recorded /b emissions (including Terminated on fatal) are bridgeable; re-adding /b in this run does not schedule it again.
T26 — Termination is final
A scope terminates gracefully; later in the same run a handler at that scope attempts to emit or patch.
Then: No-op; scope is inactive for the remainder of the run.
T27 — Child fatal does not escalate by default
/a terminates fatally.
Then: /a is marked terminated; parent continues; child emissions (including Terminated) are bridgeable if parent configured Embedded Node Channel.
T28 — Child graceful termination bridges lifecycle
/a terminates gracefully.
Then: Parent may observe Document Processing Terminated via Embedded Node Channel if configured.
T29 — Root graceful termination ends run
Root terminates gracefully.
Then: Run ends; root outbox includes Document Processing Terminated.
T30 — Root fatal termination ends run with fatal outbox
Root terminates fatally.
Then: Run ends; root outbox includes Document Processing Terminated and Document Processing Fatal Error.
1name: Text2description: >3 Core primitive scalar representing Unicode text (a JSON string).45 - Authoring & wrappers:6 detail: >7 Instances may be authored as scalar sugar (myField: "hello") or as the8 wrapped form (myField: { value: "hello" }). Canonical hashing uses the9 wrapped form (§8.2.2). This type itself does NOT declare a `value`10 field: `value` is the language wrapper for instance payloads (§2.1);11 putting `value` on the type would fix a concrete payload on the type12 object (a fixed-value invariant, §4.1), which is not intended.1314 - Length semantics:15 detail: >16 minLength/maxLength count Unicode code points, not bytes and not UTF-1617 code units. A character outside the BMP (e.g., "𝄞") counts as 1.18 A CRLF pair counts as 2 code points ("\r" + "\n").1920 - Regex dialect:21 detail: >22 `pattern` uses ECMA-262 syntax. Matching is not implicitly anchored; use23 ^…$ for whole-string matches.2425 - Canonical JSON vs content:26 detail: >27 RFC 8785 canonicalization affects only JSON encoding (escapes, key28 ordering of parent objects). It NEVER changes the underlying code-point29 sequence. Equality (including `enum`) compares the parsed scalar value.3031 - Unicode normalization:32 detail: >33 Processors MUST NOT normalize Text by default (no NFC/NFD folding).34 The exact code-point sequence is preserved. Any optional normalization35 may occur only via profile-specific preprocessing in `blue` (§7.1).3637 - Case/locale:38 detail: >39 No case folding or locale-sensitive collation is implied in Part I.40 Perform such transforms explicitly during preprocessing (profile-41 dependent) or in higher-level contracts (Part II).4243 - Empty string:44 detail: >45 The empty string "" is valid unless restricted by schema (e.g., minLength > 0).4647 - Escapes & line breaks:48 detail: >49 JSON escapes (\uXXXX, \" \\ \n \r \t \b \f) are authoring/encoding50 details only; after parsing they contribute their code points to length51 and pattern checks.5253 - Applicable schema:54 detail: >55 §5.5 string constraints: minLength, maxLength, pattern.
1name: Integer2description: >3 Primitive numeric scalar for mathematical integers (ℤ).45 - Domain & arithmetic:6 detail: >7 Represents …, -2, -1, 0, 1, 2, … with arbitrary precision. There is no8 fixed bit width and no overflow. Arithmetic is exact; operations may9 fail only due to resource exhaustion.1011 - Authoring & wrappers:12 detail: >13 Scalar sugar (x: 1) and wrapped form (x: { value: 1 }) are equivalent14 for authoring; canonical hashing uses the wrapped form (§8.2.2). The15 type does NOT declare `value` because that wrapper belongs to instances;16 declaring it on the type would fix a payload on the type object (§2.1, §4.1).1718 - Canonical textual form:19 detail: >20 Optional leading "-" for negatives, followed by one or more decimal21 digits; no leading zeros except the single digit "0".2223 - Schema & combination:24 detail: >25 §5.4 numeric constraints apply: minimum, maximum, exclusiveMinimum,26 exclusiveMaximum, multipleOf (> 0). If multiple `multipleOf` appear in a27 type chain, combine via least common multiple (LCM) as specified.2829 - Equality & enums:30 detail: >31 Equality (incl. `enum`) compares the parsed scalar value in canonical JSON terms.
1name: Double2description: >3 Primitive numeric scalar for floating-point real numbers.45 - Semantics:6 detail: >7 Computation aligns with IEEE 754 binary64 ("double precision").8 JSON permits only finite numbers; NaN and ±Infinity are invalid Blue nodes.910 - Authoring & wrappers:11 detail: >12 Scalar sugar (x: 1.25) and wrapped form (x: { value: 1.25 }) are13 equivalent for authoring; canonical hashing uses the wrapped form14 (§8.2.2). The type does NOT declare `value` (instance wrapper); putting15 it on the type would fix an instance payload (§2.1, §4.1).1617 - Canonical textual form:18 detail: >19 RFC 8785 canonical-JSON number: base-10 notation, no leading zeros, no20 leading "+", optional exponent with lower-case "e", and no unnecessary21 trailing zeros or decimal point.2223 - Precision & comparison:24 detail: >25 Processors should be aware of binary64 rounding when performing numeric26 operations. Schema comparisons use the numeric value (per §5.4). Equality27 and `enum` compare by canonical JSON value semantics, not by byte shape.2829 - Schema:30 detail: >31 §5.4 numeric constraints apply: minimum, maximum, exclusiveMinimum,32 exclusiveMaximum, multipleOf (> 0).
1name: Boolean2description: >3 Primitive scalar with exactly two values: true and false.45 - Authoring & wrappers:6 detail: >7 Scalar sugar (x: true) and wrapped form (x: { value: true }) are8 equivalent; canonical hashing uses the wrapped form (§8.2.2). The type9 does NOT declare `value` because it is an instance-level wrapper; adding10 it to the type would fix a payload on the type object (§2.1, §4.1).1112 - Semantics:13 detail: >14 No truthiness beyond the two literals; only `true` and `false` are valid.1516 - Equality & enums:17 detail: >18 Equality (incl. `enum`) compares the parsed boolean value; canonical JSON19 atoms are lower-case `true` / `false`.
1name: Dictionary2description: >3 Object map from keys to values (a plain Blue object node).45 - Object shape (no properties field):6 detail: >7 Blue objects are plain maps of field → node; there is no `properties`8 key in the language (§2, §8.2.4). Dictionary is just an object with9 typing constraints for its keys/values.1011 - Why not `value`/`items`:12 detail: >13 Dictionary is neither a scalar nor a list wrapper; `value`/`items` are14 instance wrappers for scalars/lists (§2.1). They do not apply to maps.1516 - Key typing & serialization:17 detail: >18 Keys are serialized using the `keyType`'s canonical textual form. Because19 JSON member names are strings, non-Text keys are converted to strings20 (e.g., Integer 42 → "42", Double 1.0 → "1"). Distinct values that map to21 the same canonical string will collide; authors SHOULD choose a `keyType`22 that avoids ambiguity for their domain.2324 - Defaults & compatibility:25 detail: >26 `keyType` and `valueType` are OPTIONAL. If `keyType` is omitted, it27 defaults to Text. If `valueType` is omitted, values may be any Blue node.28 Subtyping/compatibility must be preserved for both keyType and valueType29 when present (§4.2).3031 - Ordering & equality:32 detail: >33 Key order is irrelevant for identity (RFC 8785 canonicalization).34 Equality compares by canonical JSON / BlueId rules.3536 - Applicable schema:37 detail: >38 §5.3 object constraints: minFields, maxFields.39keyType:40 description: >41 OPTIONAL. Type for keys. Allowed: Text, Integer, Double, Boolean.42 Defaults to Text when omitted. Keys serialize via the keyType's canonical43 textual form.44valueType:45 description: >46 OPTIONAL. Type constraint for values. If omitted, values are unconstrained47 (any Blue node). If present, each value MUST be equal to or a subtype of48 valueType (§4.2).
1name: List2description: >3 Ordered collection (array) of elements.45 - Authoring & wrappers:6 detail: >7 Surface array (x: [a, b]) and wrapped form (x: { items: [a, b] }) are8 equivalent for authoring; canonical hashing uses the wrapped form9 (§8.2.2). The type itself does NOT declare `items`: `items` is the10 instance payload container. Declaring it on the type would install a11 concrete element array on the type object (a fixed invariant, §4.1) and12 conflate constraints with payload.1314 - Order, multiplicity, identity:15 detail: >16 Order and multiplicity are preserved. List hashing is a domain-separated17 streaming fold over element BlueIds (§8.4). [A] ≠ A; [[] , A] ≠ [A].1819 - Control item forms (reserved):20 detail: >21 Recognized only at the top level of items when the node's type is List:22 $previous (append anchor), $pos (positional overlay), and $empty (content23 placeholder). $pos is consumed before hashing; $previous is an optimization24 seed only; $empty is content (§12.2–§12.6).2526 - Present-empty vs absent & null cleaning:27 detail: >28 Present empty list [] is preserved and hashes differently from null or29 absent. Nulls inside items are removed during cleaning (§8.2.1, §8.2.2).3031 - Uniqueness:32 detail: >33 uniqueItems compares by element BlueId (§5.2), not by textual rendering.3435 - Merge policy:36 detail: >37 If `mergePolicy` is omitted, assume "positional" (§12.3). "append-only"38 forbids changes to the inherited prefix (no $pos); "positional" allows39 $pos overlays within the inherited prefix. Refinements must remain type-40 compatible with inherited elements (§4.2, §12.5).4142 - Applicable schema:43 detail: >44 §5.2 list constraints: minItems, maxItems, uniqueItems.45itemType:46 description: >47 OPTIONAL. Type applied to each element. If omitted, elements are not48 constrained by itemType (still subject to overlays and type chain). Subtype49 compatibility MUST be preserved across refinements (§4.2, §12.5).50mergePolicy:51 type: Text52 description: >53 OPTIONAL. Authoring/merge policy. If omitted, processors MUST assume54 "positional" (§12.3). Allowed values: "append-only", "positional".55 schema:56 enum: [append-only, positional]
1name: Contract2description: >3 Core type of Blue Language v1.0.4 Base for all contracts (channels, handlers, markers). Contracts live under a5 scope’s `contracts` map (keyed by Text). At runtime (Part II), contract6 processors execute deterministically and only through explicit operations;7 there are no implicit side effects.8order:9 type: Integer10 description: Deterministic sort key within a scope; missing ≡ 0.11
1name: Json Patch Entry2description: >3 Core type of Blue Language v1.0.4 Deterministic subset of RFC 6902 used by handlers to request document changes5 (Part II §21.2). NOTE: field is named `val` (not `value`) because `value`6 has special meaning as Blue’s scalar wrapper in Part I; using `val` prevents7 shape collisions with wrapper equivalence.8op:9 type: Text10 schema:11 required: true12 enum: [add, replace, remove]13path:14 type: Text15 description: >16 Absolute JSON Pointer within the document (must begin with "/").17 Runtime forbids targeting "/" (root) as a patch destination (Part II §21.2).18 schema:19 required: true20val:21 description: >22 Payload for `add` and `replace` (any Blue node). Omitted for `remove`.23
1name: Channel2type: Contract3description: >4 Core type of Blue Language v1.0.5 Abstract base for event entry points within a scope. Channels decide whether6 an incoming event matches at this scope (Part II). External channels may also7 use the scope’s checkpoint to gate duplicates/stale events.8event:9 description: >10 Optional matcher payload used by the channel's processor to11 further restrict which incoming events it accepts at this scope.12
1name: Handler2type: Contract3description: >4 Core type of Blue Language v1.0.5 Abstract base for logic bound to exactly one channel (same scope). At runtime6 (Part II), a handler may: (1) apply patches (list of Json Patch Entry),7 (2) emit events (Blue nodes), (3) consume gas via `consumeGas(units: Integer)`,8 and (4) terminate (gracefully or fatally). No other effects are permitted.9channel:10 type: Text11 description: >12 The contracts-map key of the channel this handler binds to (same scope).13 schema:14 required: true15event:16 description: >17 Optional matcher payload used by the handler’s processor to further restrict18 which channelized events it will handle. IMPORTANT: the matching strategy19 (shape checks, field tests, etc.) is defined by the specific handler20 processor, not by this base schema.21
1name: Marker2type: Contract3description: >4 Core type of Blue Language v1.0.5 Abstract base for informational/policy contracts. Markers do not run logic;6 they carry state/policy enforced by the processor (Part II).7
1name: Process Embedded2type: Marker3description: >4 Core type of Blue Language v1.0.5 Declares embedded child scopes beneath the current scope (Part II §16).6 The processor reads this list dynamically and re-reads after each child finishes.7paths:8 type: List9 itemType:10 type: Text11 description: >12 Scope-relative absolute pointers to child roots (strings beginning with "/",13 resolved against the current scope).14 schema:15 required: true16 uniqueItems: true17
1name: Processing Initialized Marker2type: Marker3description: >4 Core type of Blue Language v1.0.5 Recorded exactly once at a scope on first processing; stores the pre-init6 BlueId of the scope’s subtree (Part II §20). Writing this marker is a patch7 that triggers a Document Update cascade.8documentId:9 type: BlueId10 schema:11 required: true12
1name: Processing Terminated Marker2type: Marker3description: >4 Core type of Blue Language v1.0.5 Final state for a scope (either graceful or fatal). Once present, the scope6 becomes permanently inactive both for the remainder of the current run and in7 subsequent runs until explicitly replaced by a parent. Written as a Direct8 Write (no Document Update) when termination occurs (Part II §22).9cause:10 type: Text11 schema:12 required: true13 enum: [fatal, graceful]14reason:15 type: Text16 description: Optional human-readable explanation.17
1name: Channel Event Checkpoint2type: Marker3description: >4 Core type of Blue Language v1.0.5 Stores last-seen events per external channel at this scope to enable6 idempotent processing and ordering (Part II §23). Updates are Direct Writes7 (no Document Update).8lastEvents:9 name: Last Events10 description: >11 Map of channelKey (the contracts key of an external channel in this scope)12 to the entire last event node seen for that channel. Values are unconstrained13 (any Blue node) to allow channel-specific shapes.14 type: Dictionary15 keyType:16 type: Text17 # valueType intentionally omitted → any Blue node18 schema:19 required: true20
1name: Document Update Channel2type: Channel3description: >4 Core type of Blue Language v1.0.5 Fires on successful patches with immediate bottom-up cascade6 (origin → ancestors → root). Matching uses subtree semantics against7 ABS(scope, path). Payload is the processor-emitted 'Document Update' event8 with scope-relative path (Part II §17, §21.1).9path:10 type: Text11 description: >12 Scope-relative absolute pointer (begins with "/") defining the watched13 subtree. Match iff the absolute changed path equals or is a descendant14 of ABS(scope, path).15 schema:16 required: true17
1name: Triggered Event Channel2type: Channel3description: >4 Core type of Blue Language v1.0.5 Delivers events previously enqueued by handlers into the scope’s FIFO.6 One drain per scope at the end of scope processing; never drains during7 cascades (Part II §17, §19).8
1name: Lifecycle Event Channel2type: Channel3description: >4 Core type of Blue Language v1.0.5 Delivers processor lifecycle notifications at this scope, e.g.,6 'Document Processing Initiated' and 'Document Processing Terminated'7 (Part II §17, §20, §22).8
1name: Embedded Node Channel2type: Channel3description: >4 Core type of Blue Language v1.0.5 Bridges a child scope’s emissions (including lifecycle nodes) into the parent6 after the child finishes (Part II §17, §19).7childPath:8 type: Text9 description: >10 Scope-relative absolute pointer to the child root to bridge.11 schema:12 required: true13
1name: Document Update2description: >3 Core type of Blue Language v1.0.4 Emitted once per participating scope for each successful patch5 (bottom-up delivery). 'op' uses lower-case enum; 'path' is scope-relative6 for the receiving scope. 'before' and 'after' are snapshots (immutable views)7 (Part II §21.1).8op:9 type: Text10 schema:11 required: true12 enum: [add, replace, remove]13path:14 type: Text15 description: >16 Scope-relative pointer. "/" when the receiving scope’s root was affected.17 schema:18 required: true19before:20 description: Snapshot before the patch at this path (may be null).21after:22 description: Snapshot after the patch at this path (may be null; often null for remove).23
1name: Document Processing Initiated2description: >3 Core type of Blue Language v1.0.4 Published once at a scope on first processing (before writing the5 Processing Initialized Marker). At root, it is also included in the run’s6 'triggered_events' outbox (Part II §20).7documentId:8 type: BlueId9 schema:10 required: true11
1name: Document Processing Terminated2description: >3 Core type of Blue Language v1.0.4 Published at the terminating scope when processing ends, either gracefully5 or fatally. Bridgeable to the parent via Embedded Node Channel if configured6 (Part II §22).7cause:8 type: Text9 schema:10 required: true11 enum: [fatal, graceful]12reason:13 type: Text14 description: Optional explanation.15