Java processors for executable Blue repository contracts.
This library lets a Java application process Blue documents that contain real repository contracts such as operations, workflow steps, timeline channels, triggered events, document updates, embedded scopes, and checkpoints.
Blue processing is deterministic: given the same input document and the same ordered timeline entries, the processor must produce the same canonical output document and emit the same events. This is the compatibility contract across all Blue-compliant document processors — including this Java processor, the open-source JavaScript processor in blue-js, the hosted processor in MyOS, and any other processor.
Read the language and runtime rules in the Blue Language Specification. Read the timeline/provider model in the Timelines white paper.
Gradle:
repositories {
mavenCentral()
}
dependencies {
implementation "blue.contract:blue-contract-java:1.0.0"
}This project targets Java 8 bytecode and depends on published artifacts:
api "blue.language:blue-language-java:2.0.0"
api "blue.repo:blue-repo-java:1.2.0"The example below is a complete executable Blue document. It declares:
- a concrete timeline provider channel;
- two operations,
incrementanddecrement; - two operation-backed sequential workflows;
- one update step that mutates
/counter; - one trigger-event step that emits a chat message after each operation.
The channel is a concrete MyOS timeline channel. Do not use
Conversation/Timeline Channel directly for executable production processing;
that type is generic timeline vocabulary. Concrete providers such as
MyOS/MyOS Timeline Channel own executable timeline routing.
name: Counter
counter: 0
contracts:
ownerChannel:
type: MyOS/MyOS Timeline Channel
timelineId: counter-demo
increment:
description: Increment the counter by the given number
type: Conversation/Operation
channel: ownerChannel
request:
type: Integer
incrementImpl:
type: Conversation/Sequential Workflow Operation
operation: increment
steps:
- type: Conversation/Update Document
changeset:
- op: replace
path: /counter
val: ${event.message.request + document('/counter')}
- type: Conversation/Trigger Event
event:
type: Conversation/Chat Message
message: Counter is now ${document('/counter')}
decrement:
description: Decrement the counter by the given number
type: Conversation/Operation
channel: ownerChannel
request:
type: Integer
decrementImpl:
type: Conversation/Sequential Workflow Operation
operation: decrement
steps:
- type: Conversation/Update Document
changeset:
- op: replace
path: /counter
val: ${document('/counter') - event.message.request}
- type: Conversation/Trigger Event
event:
type: Conversation/Chat Message
message: Counter is now ${document('/counter')}The document has no code outside the document. The contracts are the program.
import blue.contract.processor.BlueDocumentProcessors;
import blue.language.Blue;
import blue.language.model.Node;
import blue.language.processor.DocumentProcessingResult;
import blue.repo.BlueRepository;
public final class CounterExample {
private static final String COUNTER_DOCUMENT =
"name: Counter\n" +
"counter: 0\n" +
"contracts:\n" +
" ownerChannel:\n" +
" type: MyOS/MyOS Timeline Channel\n" +
" timelineId: counter-demo\n" +
" increment:\n" +
" description: Increment the counter by the given number\n" +
" type: Conversation/Operation\n" +
" channel: ownerChannel\n" +
" request:\n" +
" type: Integer\n" +
" incrementImpl:\n" +
" type: Conversation/Sequential Workflow Operation\n" +
" operation: increment\n" +
" steps:\n" +
" - type: Conversation/Update Document\n" +
" changeset:\n" +
" - op: replace\n" +
" path: /counter\n" +
" val: ${event.message.request + document('/counter')}\n" +
" - type: Conversation/Trigger Event\n" +
" event:\n" +
" type: Conversation/Chat Message\n" +
" message: Counter is now ${document('/counter')}\n" +
" decrement:\n" +
" description: Decrement the counter by the given number\n" +
" type: Conversation/Operation\n" +
" channel: ownerChannel\n" +
" request:\n" +
" type: Integer\n" +
" decrementImpl:\n" +
" type: Conversation/Sequential Workflow Operation\n" +
" operation: decrement\n" +
" steps:\n" +
" - type: Conversation/Update Document\n" +
" changeset:\n" +
" - op: replace\n" +
" path: /counter\n" +
" val: ${document('/counter') - event.message.request}\n" +
" - type: Conversation/Trigger Event\n" +
" event:\n" +
" type: Conversation/Chat Message\n" +
" message: Counter is now ${document('/counter')}\n";
public static void main(String[] args) {
BlueRepository repository = BlueRepository.v1_2_0();
Blue blue = repository.configure(new Blue());
blue.nodeProvider(repository.nodeProvider());
BlueDocumentProcessors.registerWith(blue);
Node document = blue.yamlToNode(COUNTER_DOCUMENT)
.blue(repository.typeAliasBlue());
DocumentProcessingResult initialized =
blue.initializeDocument(blue.preprocess(document));
DocumentProcessingResult afterAlice = blue.processDocument(
initialized.snapshot(),
operationEntry(blue, repository,
"alice-account",
"alice@example.com",
"increment",
5,
1L));
DocumentProcessingResult afterBob = blue.processDocument(
afterAlice.snapshot(),
operationEntry(blue, repository,
"bob-account",
"bob@example.com",
"decrement",
2,
2L));
System.out.println(afterBob.resolvedDocument().get("/counter")); // 3
System.out.println(afterAlice.triggeredEvents().get(0).getAsText("/message"));
System.out.println(afterBob.triggeredEvents().get(0).getAsText("/message"));
System.out.println(afterBob.blueId());
}
private static Node operationEntry(Blue blue,
BlueRepository repository,
String accountId,
String email,
String operation,
int request,
long timestamp) {
String yaml =
"type: MyOS/MyOS Timeline Entry\n" +
"timeline:\n" +
" timelineId: counter-demo\n" +
"timestamp: " + timestamp + "\n" +
"actor:\n" +
" type: MyOS/Principal Actor\n" +
" accountId: " + accountId + "\n" +
" email: " + email + "\n" +
"message:\n" +
" type: Conversation/Operation Request\n" +
" operation: " + operation + "\n" +
" request: " + request + "\n";
return blue.preprocess(blue.yamlToNode(yaml)
.blue(repository.typeAliasBlue()));
}
}Alice sends this timeline entry:
type: MyOS/MyOS Timeline Entry
timeline:
timelineId: counter-demo
timestamp: 1
actor:
type: MyOS/Principal Actor
accountId: alice-account
email: alice@example.com
message:
type: Conversation/Operation Request
operation: increment
request: 5Bob sends this timeline entry:
type: MyOS/MyOS Timeline Entry
timeline:
timelineId: counter-demo
timestamp: 2
actor:
type: MyOS/Principal Actor
accountId: bob-account
email: bob@example.com
message:
type: Conversation/Operation Request
operation: decrement
request: 2The result is deterministic:
- after Alice,
/counter == 5; - after Bob,
/counter == 3; - Alice's operation emits
Counter is now 5; - Bob's operation emits
Counter is now 3; - checkpoints are written so duplicate timeline entries do not run twice;
- stale timeline entries are rejected by provider recency rules;
result.blueId()is the content address of the canonical output document.
The processor is a deterministic state machine.
Input:
- one Blue document;
- a sequence of timeline entries or triggered/lifecycle events.
Output:
- one canonical Blue document;
- zero or more triggered events;
- a BlueId for the output document;
- gas accounting and checkpoints.
For any document that is understood by all runtimes, this Java processor, the open-source blue-js processor, and MyOS hosted processing are expected to produce the same output document and the same triggered events for the same ordered inputs. A mismatch is a processor bug or a version mismatch.
This is why document processors operate on canonical snapshots instead of
process-local mutable state. You can serialize result.canonicalDocument(),
load it again, and continue processing from the same resolved snapshot.
The document processor does not guess which external events exist. It processes entries that are delivered to it.
Timeline providers are responsible for fetching and ordering timeline entries.
A concrete provider channel, such as MyOS/MyOS Timeline Channel, tells the
processor how to recognize entries from that provider and when an entry is new
relative to the channel checkpoint.
Read the Timelines white paper for the full model: timeline entries, provider completeness, fetching, ordering, and how concrete timeline providers hand deterministic event streams to processors.
MyOS is the hosted SaaS environment for processing Blue
documents, timelines, and workflows. After signing up, switch to
Developer Mode from the top-right corner to access developer-oriented tools.
Developer documentation is available at developers.myos.blue.
The goal is portability:
- run locally in Java with this package;
- run locally or in services with blue-js;
- run hosted in MyOS.
For the same supported document, repository version, and timeline entries, the canonical output and triggered events should be the same.
This repository currently provides executable behavior for:
Conversation/Composite Timeline Channel;Conversation/Operation;Conversation/Sequential Workflow;Conversation/Sequential Workflow Operation;Conversation/Update Document;Conversation/Trigger Event;Conversation/JavaScript Code;MyOS/MyOS Timeline Channel.
The underlying blue-language-java runtime provides Core processing behavior
used by real repository contracts:
Core/Document Update Channel;Core/Embedded Node Channel;Core/Process Embedded;Core/Channel Event Checkpoint;Core/Lifecycle Event Channel;Core/Triggered Event Channel;- processing initialized and terminated markers;
- scope boundaries, patch application, snapshots, gas, and checkpointing.
Conversation/Update Document expressions and Conversation/JavaScript Code
steps run through QuickJS.
Available bindings:
event;eventCanonical;document(pointer);document.canonical(pointer);steps;currentContract;currentContractCanonical.
Example JavaScript code step:
- name: CreateMessage
type: Conversation/JavaScript Code
code: |-
const message =
`Counter is now ${document('/counter')}`;
return {
events: [
{
type: "Conversation/Chat Message",
message,
},
],
};The Java implementation currently uses a persistent Node bridge to a local
blue-quickjs checkout. By default it expects:
../blue-quickjs
Override the location if needed:
./gradlew test -Dblue.quickjs.root=/path/to/blue-quickjsBuild the QuickJS runtime first:
cd ../blue-quickjs
pnpm nx build quickjs-runtimeThe root blue-contract-java artifact remains Java 8 compatible and continues
to use NodeQuickJsRuntime by default. Chicory is optional and must be enabled
explicitly. The optional Java 11+ artifact is:
dependencies {
implementation "blue.contract:blue-contract-java:1.0.0"
implementation "blue.contract:blue-contract-java-quickjs-chicory:1.0.0"
}The Chicory runtime executes the canonical blue-quickjs wasm32 release artifact on the JVM. It does not require Node, V8, Javet, JNI, native Wasmtime bindings, or another native JavaScript runtime in the Chicory evaluation path.
Treat Chicory as an experimental AWS Lambda / JVM-only runtime because current
benchmarks show it is substantially slower than the Node bridge. The no-Node
classpath smoke and Java 11 AWS Lambda container smoke have passed, so the
remaining blocker is performance and release hardening, not basic packaging.
For the same pinned blue-quickjs artifact, Host.v1 ABI, source wrapper,
bindings, and gas limit, Chicory and NodeQuickJsRuntime must return identical
values/errors, wasmGasUsed, and hostGasUsed. Benchmark reports are
timing-only; gas must match exactly.
Build, publish locally, and test the optional module with a built blue-quickjs
checkout:
cd ../blue-quickjs
pnpm install --frozen-lockfile
bash tools/scripts/setup-emsdk.sh
WASM_VARIANTS=wasm32 WASM_BUILD_TYPES=release pnpm exec nx build quickjs-wasm-build
pnpm exec nx build quickjs-wasm
pnpm exec nx build abi-manifest
pnpm exec nx build quickjs-runtime
cd ../blue-contract-java
./gradlew :quickjs-chicory:test \
-PblueQuickJsRoot=../blue-quickjs \
-Dblue.quickjs.root=../blue-quickjs
./gradlew publishToMavenLocal -PblueQuickJsRoot=../blue-quickjsThe module verifies the pinned wasm artifact, engineBuildHash, Host.v1
manifest hash, gas version, and deterministic execution profile before
evaluation. Filesystem WASM resolution fails closed unless an expected engine
hash is supplied, and both filesystem and classpath modes require metadata with
engineBuildHash, gasVersion, executionProfile, and abiManifestHash.
Classpath-bundled WASM is self-pinned by generated metadata. For packaging,
generate classpath resources with:
./gradlew :quickjs-chicory:jar -PblueQuickJsRoot=../blue-quickjsApplications can use classpath-pinned WASM resources and inject the runtime without adding Chicory to the Java 8 core:
import blue.contract.processor.BlueDocumentProcessorOptions;
import blue.contract.processor.BlueDocumentProcessors;
import blue.contract.processor.conversation.javascript.JavaScriptRuntime;
import blue.contract.processor.conversation.javascript.chicory.ChicoryBlueQuickJsRuntime;
JavaScriptRuntime runtime = ChicoryBlueQuickJsRuntime.fromClasspathDefaults();
BlueDocumentProcessors.registerWith(
blue,
BlueDocumentProcessorOptions.builder()
.javaScriptRuntime(runtime)
.build());Filesystem WASM resources are also supported, but must be explicitly pinned by engine hash and must include the same metadata fields:
import blue.contract.processor.conversation.javascript.JavaScriptRuntime;
import blue.contract.processor.conversation.javascript.chicory.BlueQuickJsWasmRuntimeConfig;
import blue.contract.processor.conversation.javascript.chicory.ChicoryBlueQuickJsRuntime;
import java.nio.file.Paths;
JavaScriptRuntime runtime = new ChicoryBlueQuickJsRuntime(
BlueQuickJsWasmRuntimeConfig.builder()
.preferClasspathResources(false)
.blueQuickJsRoot(Paths.get("/opt/blue-quickjs"))
.expectedEngineBuildHash("f91091cb7feb788df340305a877a9cadb0c6f4d13aea8a7da4040b6367d178ea")
.build());NodeQuickJsRuntime remains the compatibility fallback and parity oracle.
The generated POM for blue-contract-java does not depend on Chicory; users
must opt in by depending on blue-contract-java-quickjs-chicory.
Container/Lambda status: CI runs a no-Node classpath smoke test that evaluates
1 + 2 through ChicoryBlueQuickJsRuntime.fromClasspathDefaults(), verifies gas
is charged, and checks that Node, V8, Javet, JNI, native Wasmtime, and other
native JavaScript runtime dependencies are absent from the Chicory runtime
classpath. The same smoke has also passed in Docker using the Java 11 AWS SAM
Lambda build image. A full AWS Lambda runtime/RIE invocation smoke can still be
added later if release policy requires it.
The generated quickjs-chicory metadata currently bridges fields that should
come from upstream blue-quickjs release metadata long term, especially
executionProfile and abiManifestHash. Java consumes and verifies those
generated fields; it must not infer gas version from documentation.
Most applications should use the one-call facade:
BlueRepository repository = BlueRepository.v1_2_0();
Blue blue = repository.configure(new Blue());
blue.nodeProvider(repository.nodeProvider());
BlueDocumentProcessors.registerWith(blue);For custom processor construction:
import blue.contract.processor.BlueDocumentProcessors;
import blue.language.processor.DocumentProcessor;
DocumentProcessor processor =
BlueDocumentProcessors.configure(DocumentProcessor.builder())
.build();You can register processor groups directly when needed:
ConversationProcessors.registerWith(blue);
MyOSProcessors.registerWith(blue);A Blue document is regular data. Its contracts map declares executable
behavior. A processor does not need app-specific Java code for every workflow;
it reads the contract graph and executes supported contracts.
An operation is invoked by a Conversation/Operation Request, usually carried
as the message of a timeline entry:
message:
type: Conversation/Operation Request
operation: increment
request: 5The handler contract references the operation:
incrementImpl:
type: Conversation/Sequential Workflow Operation
operation: incrementHandlers bind to channels. A channel decides whether a delivered event belongs to that channel. Provider channels also define recency rules so stale entries do not re-run handlers.
Sequential workflows execute step by step. Later steps can read previous named
step results through steps.
Conversation/Trigger Event emits a Blue event. Consumers bound to
Core/Triggered Event Channel can react to it in the same processing run.
./gradlew testBuild jars:
./gradlew buildPublish locally:
./gradlew publishToMavenLocalMaven Central release publishing follows the same JReleaser setup used by the
other Blue Java artifacts. The GitHub release workflow publishes
blue.contract:blue-contract-java:1.0.0 from main using:
JRELEASER_MAVENCENTRAL_USERNAME;JRELEASER_MAVENCENTRAL_PASSWORD;JRELEASER_GPG_PUBLIC_KEY;JRELEASER_GPG_SECRET_KEY;JRELEASER_GPG_PASSPHRASE;JRELEASER_GITHUB_TOKEN.
Current test areas:
- processor registration;
- must-understand failures;
- MyOS timeline matching;
- concrete test timeline provider behavior;
- composite timeline channel routing;
- operation request matching;
- sequential workflow execution;
- trigger-event execution;
- JavaScript code execution;
- Core runtime channels;
- repository-style Counter documents;
- snapshot round-trip stress processing.
src/main/java/blue/contract/processor
BlueDocumentProcessors.java
ConversationProcessors.java
MyOSProcessors.java
src/main/java/blue/contract/processor/conversation
CompositeTimelineChannelProcessor.java
OperationProcessor.java
OperationRequestMatcher.java
SequentialWorkflowProcessor.java
SequentialWorkflowOperationProcessor.java
TimelineProviderSupport.java
workflow/
expression/
javascript/
src/main/java/blue/contract/processor/myos
MyOSTimelineChannelProcessor.java