diff --git a/.cz.toml b/.cz.toml
index 6ffe4ea..40578c0 100644
--- a/.cz.toml
+++ b/.cz.toml
@@ -2,5 +2,5 @@
name = "cz_conventional_commits"
tag_format = "v$version"
version_scheme = "semver"
-version = "3.0.0-rc.1"
+version = "3.0.0-rc.3"
update_changelog_on_bump = true
diff --git a/.gitattributes b/.gitattributes
index cacb1e1..27d9da1 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -25,3 +25,7 @@ __MACOSX/** export-ignore
*.hprof export-ignore
hs_err_pid*.log export-ignore
replay_pid*.log export-ignore
+
+# These retained evidence bytes are checksum-bound and must not be normalized.
+stabilization/sdk-freeze-final/FINAL_RECEIPT.md -whitespace
+stabilization/sdk-freeze-final/changed-files.sha256 -whitespace
diff --git a/.github/scripts/prepare-rc-release.js b/.github/scripts/prepare-rc-release.js
index 38101ba..ddd5478 100644
--- a/.github/scripts/prepare-rc-release.js
+++ b/.github/scripts/prepare-rc-release.js
@@ -4,7 +4,7 @@ const { execFileSync } = require('node:child_process');
const fs = require('node:fs');
const CZ_TOML = '.cz.toml';
-const CANONICAL_EVIDENCE = 'docs/releases/3.0.0-rc.1-evidence.json';
+const RELEASE_AUTHORITY = 'docs/releases/3.0.0-rc.3.md';
const MAIN_REF = process.env.RC_BASE_REF || 'origin/main';
const VALID_BUMPS = new Set(['major', 'minor', 'patch']);
@@ -109,19 +109,19 @@ function nextVersionForCurrentRc(currentVersion, latestTaggedRc) {
return `${formatVersion(parsed)}-rc.${nextRc}`;
}
-function evidenceRelease(content) {
- const evidence = JSON.parse(content);
- if (typeof evidence.release !== 'string' || evidence.release.length === 0) {
- throw new Error(`Canonical evidence is missing a release: ${CANONICAL_EVIDENCE}`);
+function authorityRelease(content) {
+ const match = content.match(/^RC3_VERSION:\s*(\S+)\s*$/m);
+ if (!match) {
+ throw new Error(`Release authority is missing RC3_VERSION: ${RELEASE_AUTHORITY}`);
}
- return evidence.release;
+ return match[1];
}
-function assertEvidenceRelease(preparedVersion, content) {
- const canonicalRelease = evidenceRelease(content);
- if (canonicalRelease !== preparedVersion) {
+function assertAuthorityRelease(preparedVersion, content) {
+ const authorizedRelease = authorityRelease(content);
+ if (authorizedRelease !== preparedVersion) {
throw new Error(
- `Prepared RC ${preparedVersion} does not match canonical evidence release ${canonicalRelease}`,
+ `Prepared RC ${preparedVersion} does not match authorized release ${authorizedRelease}`,
);
}
}
@@ -147,9 +147,9 @@ function prepareRcRelease() {
console.log(`Aggregate bump: ${bump}`);
}
- assertEvidenceRelease(
+ assertAuthorityRelease(
nextVersion,
- fs.readFileSync(CANONICAL_EVIDENCE, 'utf8'),
+ fs.readFileSync(RELEASE_AUTHORITY, 'utf8'),
);
const nextContent = currentContent.replace(
@@ -170,8 +170,8 @@ if (require.main === module) {
}
module.exports = {
- assertEvidenceRelease,
- evidenceRelease,
+ assertAuthorityRelease,
+ authorityRelease,
nextVersionForCurrentRc,
parseVersion,
};
diff --git a/.github/scripts/prepare-rc-release.test.js b/.github/scripts/prepare-rc-release.test.js
index ffb372e..b5f9ce2 100644
--- a/.github/scripts/prepare-rc-release.test.js
+++ b/.github/scripts/prepare-rc-release.test.js
@@ -2,8 +2,8 @@ const assert = require('node:assert/strict');
const test = require('node:test');
const {
- assertEvidenceRelease,
- evidenceRelease,
+ assertAuthorityRelease,
+ authorityRelease,
nextVersionForCurrentRc,
parseVersion,
} = require('./prepare-rc-release.js');
@@ -32,24 +32,24 @@ test('advances an RC after the current tag exists', () => {
assert.equal(nextVersionForCurrentRc('3.0.0-rc.1', 8), '3.0.0-rc.9');
});
-test('reads the release bound by canonical evidence', () => {
- assert.equal(evidenceRelease('{"release":"3.0.0-rc.1"}'), '3.0.0-rc.1');
+test('reads the release bound by the current authority', () => {
+ assert.equal(authorityRelease('RC3_VERSION: 3.0.0-rc.3\n'), '3.0.0-rc.3');
assert.throws(
- () => evidenceRelease('{}'),
- /Canonical evidence is missing a release/,
+ () => authorityRelease('# missing marker\n'),
+ /Release authority is missing RC3_VERSION/,
);
});
-test('rejects a prepared RC that differs from canonical evidence', () => {
- assert.doesNotThrow(() => assertEvidenceRelease(
- '3.0.0-rc.1',
- '{"release":"3.0.0-rc.1"}',
+test('rejects a prepared RC that differs from its authority', () => {
+ assert.doesNotThrow(() => assertAuthorityRelease(
+ '3.0.0-rc.3',
+ 'RC3_VERSION: 3.0.0-rc.3\n',
));
assert.throws(
- () => assertEvidenceRelease(
- '2.0.0-rc.9',
- '{"release":"3.0.0-rc.1"}',
+ () => assertAuthorityRelease(
+ '3.0.0-rc.4',
+ 'RC3_VERSION: 3.0.0-rc.3\n',
),
- /Prepared RC 2\.0\.0-rc\.9 does not match canonical evidence release 3\.0\.0-rc\.1/,
+ /Prepared RC 3\.0\.0-rc\.4 does not match authorized release 3\.0\.0-rc\.3/,
);
});
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 9c4f617..42cd609 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -30,7 +30,7 @@ jobs:
- test-java: '17'
setup-java: '17.0.19+10'
- test-java: '21'
- setup-java: '21.0.11+10'
+ setup-java: '21.0.11+10.0.LTS'
env:
CI: true
defaults:
@@ -103,9 +103,8 @@ jobs:
- name: Verify RC staging readiness
if: ${{ matrix.test-java == '17' }}
run: >-
- ./gradlew --no-daemon --no-build-cache verifyRound13Readiness
+ ./gradlew --no-daemon --no-build-cache verifyRcReadiness
-PblueDependencyMode=published-artifact
- -PallowRound13LatencyException=true
-PtestJavaVersion=17
- name: Archive verification evidence
diff --git a/.github/workflows/release-rc.yml b/.github/workflows/release-rc.yml
index 62b2ebd..35edd21 100644
--- a/.github/workflows/release-rc.yml
+++ b/.github/workflows/release-rc.yml
@@ -39,7 +39,15 @@ jobs:
architecture: x64
check-latest: false
- - name: Restrict Gradle to the pinned JDK
+ - name: Set up Java 21 test JDK
+ uses: actions/setup-java@v5
+ with:
+ distribution: temurin
+ java-version: '21.0.11+10.0.LTS'
+ architecture: x64
+ check-latest: false
+
+ - name: Restrict Gradle to the pinned JDKs
shell: bash
run: |
set -euo pipefail
@@ -47,7 +55,7 @@ jobs:
{
echo "org.gradle.java.installations.auto-detect=false"
echo "org.gradle.java.installations.auto-download=false"
- echo "org.gradle.java.installations.paths=$JAVA_HOME_17_X64"
+ echo "org.gradle.java.installations.paths=$JAVA_HOME_17_X64,$JAVA_HOME_21_X64"
} >> "$GRADLE_USER_HOME/gradle.properties"
- name: Set up Node 22
@@ -109,13 +117,19 @@ jobs:
./gradlew --no-daemon dependencyPreflight \
-PblueDependencyMode=published-artifact
- - name: Build and stage from published dependencies
- # RC-only policy: Gradle rejects this exception unless the declared
- # canonical 3.0.0-rc.1 Round 13 evidence satisfies its exact guard.
+ - name: Verify the Java 21 release gate
run: >-
- ./gradlew --no-daemon --no-build-cache clean stageRelease
+ ./gradlew --no-daemon --no-build-cache clean releaseCheck
-PblueDependencyMode=published-artifact
- -PallowRound13LatencyException=true
+ -PtestJavaVersion=21
+
+ - name: Build and stage from published dependencies
+ run: |
+ set -euo pipefail
+ JAVA_HOME="$JAVA_HOME_17_X64" \
+ ./gradlew --no-daemon --no-build-cache clean stageRelease \
+ -PblueDependencyMode=published-artifact \
+ -PtestJavaVersion=17
- name: Publish to Maven Central
env:
@@ -126,10 +140,12 @@ jobs:
JRELEASER_GPG_PUBLIC_KEY: ${{ secrets.GPG_PUBLIC_KEY }}
JRELEASER_GPG_SECRET_KEY: ${{ secrets.GPG_SECRET_KEY }}
JRELEASER_REPRODUCIBLE: true
- run: >-
- ./gradlew --no-daemon --no-build-cache jreleaserDeploy
- -PblueDependencyMode=published-artifact
- -PallowRound13LatencyException=true
+ run: |
+ set -euo pipefail
+ JAVA_HOME="$JAVA_HOME_17_X64" \
+ ./gradlew --no-daemon --no-build-cache jreleaserDeploy \
+ -PblueDependencyMode=published-artifact \
+ -PtestJavaVersion=17
- name: Push release commit and tag
run: git push origin HEAD:next --follow-tags
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 81a0b07..9a842ef 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -40,7 +40,15 @@ jobs:
architecture: x64
check-latest: false
- - name: Restrict Gradle to the pinned JDK
+ - name: Set up Java 21 test JDK
+ uses: actions/setup-java@v5
+ with:
+ distribution: temurin
+ java-version: '21.0.11+10.0.LTS'
+ architecture: x64
+ check-latest: false
+
+ - name: Restrict Gradle to the pinned JDKs
shell: bash
run: |
set -euo pipefail
@@ -48,7 +56,7 @@ jobs:
{
echo "org.gradle.java.installations.auto-detect=false"
echo "org.gradle.java.installations.auto-download=false"
- echo "org.gradle.java.installations.paths=$JAVA_HOME_17_X64"
+ echo "org.gradle.java.installations.paths=$JAVA_HOME_17_X64,$JAVA_HOME_21_X64"
} >> "$GRADLE_USER_HOME/gradle.properties"
- name: Validate Gradle wrapper
@@ -62,10 +70,19 @@ jobs:
./gradlew --no-daemon dependencyPreflight
-PblueDependencyMode=published-artifact
- - name: Build and stage from published dependencies
+ - name: Verify the Java 21 release gate
run: >-
- ./gradlew --no-daemon --no-build-cache clean stageRelease
+ ./gradlew --no-daemon --no-build-cache clean releaseCheck
-PblueDependencyMode=published-artifact
+ -PtestJavaVersion=21
+
+ - name: Build and stage from published dependencies
+ run: |
+ set -euo pipefail
+ JAVA_HOME="$JAVA_HOME_17_X64" \
+ ./gradlew --no-daemon --no-build-cache clean stageRelease \
+ -PblueDependencyMode=published-artifact \
+ -PtestJavaVersion=17
- name: Publish to Maven Central
env:
@@ -76,9 +93,12 @@ jobs:
JRELEASER_GPG_PUBLIC_KEY: ${{ secrets.GPG_PUBLIC_KEY }}
JRELEASER_GPG_SECRET_KEY: ${{ secrets.GPG_SECRET_KEY }}
JRELEASER_REPRODUCIBLE: true
- run: >-
- ./gradlew --no-daemon --no-build-cache jreleaserDeploy
- -PblueDependencyMode=published-artifact
+ run: |
+ set -euo pipefail
+ JAVA_HOME="$JAVA_HOME_17_X64" \
+ ./gradlew --no-daemon --no-build-cache jreleaserDeploy \
+ -PblueDependencyMode=published-artifact \
+ -PtestJavaVersion=17
- name: Archive release evidence
uses: actions/upload-artifact@v4
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 53ca09f..7d19f97 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,20 +3,133 @@
This project follows Semantic Versioning. Release candidates may still refine
the new 3.x API before the first stable 3.0.0 release.
-## 3.0.0-rc.1 - unreleased
+## 3.0.0-rc.3 - bounded external-pilot cyclic-topology SDK candidate
+
+### Added
+
+- From-now admission of new managed lineages produced by an operation result.
+ The SDK binds exact draft values from `request.managed(...)` to complete
+ effective paths declared with `expectOccurrence(...)`, including multiple
+ occurrences that share one stable lineage, and publishes the affected
+ closure atomically.
+- The advanced `ManagedOccurrenceAudit` diagnostic, exposed through
+ `AdvancedCoordination.auditManagedOccurrence(...)`, for the retained target
+ lineage, activation generation, and active/inactive state.
+- Public SDK acceptance cases for the operation-produced Order draft and the
+ five-occurrence/three-lineage permutation, plus fail-closed malformed,
+ ambiguous, retry, and rollback coverage.
+
+### Changed
+
+- The Maven Central graph is pinned to Language `3.1.0-rc.21`, BEX
+ `1.1.0-rc.4`, Repository `3.0.0-rc.21`, and Coordination `3.0.0-rc.3`.
+ Repository rc.21's stale Language rc.20 edge is excluded in favor of the
+ direct rc.21 runtime pin.
+- Local composites, Maven Local, staged file repositories, source locks, and
+ the standalone staged-consumer fixture were retired from the live build.
+- Every test now follows the enforced lowercase `// given`, `// when`,
+ `// then` structure.
+- Managed-draft plans are preflighted before journal append and retained only
+ while retry can make progress; terminal results retire the plan without
+ erasing rollback evidence.
+
+### Known limitations
+
+- Operation-result managed admission supports only new `FROM_NOW` lineages.
+ Imported draft epochs and historical, frontier, attach-current, or passive
+ occurrence activation remain unsupported and fail closed.
+- The candidate remains in-memory, one-JVM, and sequential. It makes no
+ provider-completeness, provider-backed Mandate, parallel/distributed,
+ production MyOS durability, latency, or throughput claim.
+
+### Distribution status
+
+- `3.0.0-rc.3` is published by the RC workflow from the exact Maven Central
+ dependency graph. The release tag is pushed only after deployment succeeds.
+- The release tier remains bounded external pilot; stable and production
+ readiness are explicitly false.
+
+## 3.0.0-rc.2 - local-only freeze candidate
+
+### Added
+
+- The additive `blue.coordination.sdk` application facade, headed by
+ `BlueCoordination.inMemory()`, with the bundled Contracts 1.0 release as its
+ only normal default.
+- Immutable SDK document, closure, entry, result, diagnostic, event, revision,
+ and processing-stat values. Low-level closure inputs and proof structures do
+ not appear in normal SDK signatures.
+- Authored ordinary and complete cyclic-closure admission. The SDK derives the
+ effective `Process Embedded` graph, validates managed occurrence bindings,
+ and delegates exact finalization and proof verification to the pinned
+ Language/Contracts runtime.
+- Exact document-targeted operation calls, explicit broadcast events,
+ append-only `submit()`, append-and-drain `execute()`, terminal `NO_MATCH`,
+ precise target `REJECTED`, and disconnected per-closure results.
+- A built-JAR-only SDK consumer test and a standalone extracted consumer that
+ resolves the staged candidate on Java 17 and Java 21.
+- A separate local SDK freeze lane that consumes Language, BEX, Repository, and
+ Coordination from one explicit Maven-shaped file repository with composite
+ substitution and Maven Local disabled.
+
+### Changed
+
+- `CoordinationEngine` is now documented as an advanced host-integration and
+ legacy compatibility boundary. Its earlier acyclic `inMemory()` profile is
+ not the SDK default.
+- Public API/Javadoc, package ownership, dependency isolation, artifact
+ contents, and candidate-coordinate checks are release gates for the SDK
+ lane. Historical rc.1 staging and evidence remain unchanged.
+
+### Known limitations
+
+- Managed-child admission from an operation result is not implemented. Calls
+ carrying `request.managed(...)` or `expectOccurrence(...)` fail before append
+ with `UNSUPPORTED_MANAGED_DRAFT_ADMISSION`; the runtime does not emulate this
+ through the legacy admission path.
+- The candidate is in-memory, one-JVM, sequential, and has no fresh-process
+ recovery, durable provider-completeness adapter, Mandate resolver, stable
+ latency SLA, or production MyOS operational profile.
+- `implementationConformanceClaimed` remains `false` until the managed-draft
+ bridge and every artifact-bound acceptance/conformance gate are complete.
+
+### Distribution status
+
+- `3.0.0-rc.2` is staged locally only. The freeze workflow does not upload
+ packages, publish to Maven Local, push commits, or create/push tags.
+
+## 3.0.0-rc.1 - historical candidate
+
+This section records the earlier pre-SDK candidate. Its retained receipts and
+performance policy are historical evidence, not evidence for rc.2.
### Added
- A compact Java 17 in-memory Coordination engine with a small immutable
application API.
+- An explicit `CoordinationEngine.inMemoryContracts10(...)` lifecycle boundary
+ requiring final Language/Contracts SHA-256 artifact identities and public
+ Root lineages, plus typed `admitContractsClosure(...)` all-new-lineage
+ admission with one atomic durable receipt. Legacy `startDocument` and mixed
+ existing/new admission remain rejected in that mode.
+- Contracts 1.0 affected-closure capture and independent per-document
+ execution, with the same processor/context for acyclic and cyclic documents
+ and no ambient containing-document context.
+- A durable Root feeder/window over the union of Root and active embedded
+ Timelines, with exact lane-local `NeedsResources` no-overtake and
+ disconnected-Root progress.
+- Copy-on-write connected-closure publication with CAS-fenced per-document
+ heads/epochs, active/inactive occurrence inventory, active SCC component
+ state, per-document graph generations, exact subscriptions/routes,
+ checkpoints, outbox, and idempotency receipts.
- Exact whole-request and whole-Timeline-Entry admission.
- Environment-selected append/drain processing, immutable document snapshots
and revision history.
- Managed `Process Embedded.paths` and `collectionPaths`, historical catch-up,
shared-child convergence and nested synchronized barriers.
-- Immutable bindings with separate occurrence cursors, exact processor-owned
- child-epoch inputs, document-local commits, commit companions and idempotent
- retry behavior.
+- A legacy compatibility profile with immutable bindings, separate occurrence
+ cursors, exact processor-owned child-epoch inputs, document-local commits,
+ commit companions, and idempotent retry behavior.
- Explicit `FULL_HISTORY`, `FROM_FRONTIER`, and `FROM_NOW` top-level admission.
- Occurrence-specific embedded admission evidence with exact child epoch,
completeness proof, attachment identity, atomic consumption, and retry.
@@ -34,6 +147,11 @@ the new 3.x API before the first stable 3.0.0 release.
code-point-canonical occurrence order, nested settlement, and retry proofs.
- A reproducible extracted-source archive smoke that runs focused tests using
its own executable Gradle wrapper and authoritative `.cz.toml`.
+- A default local-composite implementation lane that substitutes the complete
+ Language graph (`blue-language-model`, `blue-language-core`,
+ `blue-language-mapping`, `blue-language-ipfs`, `blue-language-java`, and
+ `blue-contracts-core`) together with both BEX modules and Repository, with
+ source-lock and extracted-archive path verification.
### Changed
@@ -60,7 +178,7 @@ the new 3.x API before the first stable 3.0.0 release.
### Release prerequisites
-The RC resolves `blue.repo:blue-repo-java:3.0.0-rc.21`,
-`blue.bex:blue-bex-core:1.1.0-rc.3`, and
-`blue.bex:blue-bex-contracts:1.1.0-rc.3` from Maven Central. Release automation
-verifies the complete conflict-checked graph before building.
+At rc.1 time, matching Contracts and BEX artifacts were not yet available, so
+the published lane could not compile the later Contracts 1.0 source. This is a
+historical constraint only; rc.3 uses the published rc.21/rc.4 graph described
+above.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 72bf269..8694415 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -9,24 +9,21 @@ fragmentation layer, scheduler or cache hierarchy.
- Java 17 or newer; production bytecode is compiled with `--release 17`.
- Use the checked-in Gradle wrapper.
- Keep all dependency versions exact and commit lock-file changes.
-- Do not edit the Language, Repository or BEX sibling projects as part of a
- Coordination change.
+- Resolve Language, Repository, and BEX from Maven Central; do not add sibling
+ composites, Maven Local, or file-repository fallbacks.
+- Structure every `@Test` with one meaningful lowercase `// given`, `// when`,
+ `// then` sequence.
Run the focused gate while developing:
```bash
+./gradlew dependencyPreflight
./gradlew releaseCheck
```
The gate runs unit, integration, built-JAR consumer and end-to-end scenario
tests. It must remain independent of `../blue-basic`; that sibling exists only
-for historical timing and percentile comparisons. When a change intentionally
-affects performance, capture those optional metrics after publishing locally:
-
-```bash
-./gradlew publishToMavenLocal
-../blue-basic/gradlew -p ../blue-basic performanceTest runtimeCampaign
-```
+for historical timing and percentile comparisons and is not a release input.
Before opening a pull request, follow
[build and test](docs/development/build-and-test.md), update relevant docs and
diff --git a/README.md b/README.md
index 16a406f..443070d 100644
--- a/README.md
+++ b/README.md
@@ -9,42 +9,126 @@ document graph.
## Install
```groovy
+repositories {
+ mavenCentral()
+}
+
dependencies {
- implementation 'blue.coordination:blue-coordination-java:3.0.0-rc.1'
+ implementation 'blue.coordination:blue-coordination-java:3.0.0-rc.3'
}
```
-The artifact is compiled with `--release 17`. Version 3 is a breaking API reset;
-the removed 2.x planning, fragmentation, session-store, and fast-path APIs are
-not shimmed.
+`3.0.0-rc.3` is the bounded external-pilot candidate. It consumes Language
+`3.1.0-rc.21`, BEX `1.1.0-rc.4`, and Repository `3.0.0-rc.21` from Maven
+Central and is compiled with `--release 17`. It is not a stable or production
+release. Version 3 is a breaking API reset; the removed 2.x planning,
+fragmentation, session-store, and fast-path APIs are not shimmed.
## Counter quickstart
```java
-import blue.coordination.api.CoordinationEngine;
-import blue.coordination.api.DocumentId;
-import blue.coordination.api.Operation;
-
-try (CoordinationEngine engine = CoordinationEngine.inMemory()) {
- var alice = engine.registerTimeline("counter/alice", "alice");
- var bob = engine.registerTimeline("counter/bob", "bob");
- var counter = DocumentId.of("counter");
-
- engine.startDocument(counter, counterYaml);
- engine.append(
- alice, Operation.yaml("increment", "aliceChannel", "amount: 3"));
- engine.append(
- bob, Operation.yaml("decrement", "bobChannel", "amount: 1"));
-
- var receipt = engine.drain();
- assert receipt.quiescent();
-
- long value = ((java.math.BigInteger) engine.document(counter)
- .valueAt("/counter").copyNode().getValue()).longValueExact();
- assert value == 2L;
+import blue.coordination.sdk.BlueCoordination;
+import blue.coordination.sdk.ManagedClosure;
+import blue.coordination.sdk.ManagedDocument;
+
+try (BlueCoordination blue = BlueCoordination.inMemory()) {
+ var alice = blue.timelines().local("alice");
+ var bob = blue.timelines().local("bob");
+ var counter = blue.documents().admit(
+ ManagedDocument.yaml("counter", counterYaml)
+ .publicRoot()
+ .fromNow());
+
+ var plusThree = blue.operations().on(counter)
+ .from(alice)
+ .call("increment")
+ .through("aliceChannel")
+ .requestYaml("amount: 3")
+ .execute();
+ var minusOne = blue.operations().on(counter)
+ .from(bob)
+ .call("decrement")
+ .through("bobChannel")
+ .requestYaml("amount: 1")
+ .execute();
+
+ assert plusThree.applied();
+ assert minusOne.applied();
+ assert counter.snapshot().longAt("/counter") == 2L;
}
```
+## Contracts 1.0 is the SDK default
+
+`BlueCoordination.inMemory()` always creates the Contracts 1.0 profile and
+pins the exact release identities bundled in the Coordination JAR. Ordinary
+applications do not pass specification hashes, construct closure proofs, or
+predeclare public Root IDs. Public Roots are authorized when an authored
+document or closure is admitted.
+
+The SDK compiles a complete authored cyclic closure without introducing a
+second graph:
+
+```java
+var closure = blue.documents().admit(
+ ManagedClosure.builder()
+ .document("a", yamlA)
+ .document("b", yamlB)
+ .bindOccurrence("a", "/b", "b")
+ .bindOccurrence("b", "/a", "a")
+ .publicRoot("a")
+ .fromNow()
+ .build());
+```
+
+Each occurrence binding is stable managed-lineage evidence for an effective
+`Process Embedded` path. The compiler verifies the authored catalog and exact
+target value, then delegates finalization and complete-proof verification to
+the pinned Language/Contracts implementation.
+
+`submit()` appends only. `execute()` appends and canonically drains through the
+submitted entry, including earlier eligible work. Explicit broadcast entries
+use `blue.events()`; a valid broadcast accepted by no Channel returns
+`NO_MATCH`. A missing exact operation target returns `REJECTED` with a stable
+diagnostic instead of becoming a broadcast.
+
+## Advanced and legacy compatibility
+
+The older `blue.coordination.api.CoordinationEngine` surface remains an
+advanced host-integration and migration boundary. Its
+`inMemoryContracts10(...)` factory requires explicit release identities and its
+raw closure admission accepts low-level proof values. Its `inMemory()` factory
+retains the earlier acyclic compatibility profile; it is not the default SDK
+semantics. New applications should not start there.
+
+An SDK owner exposes the same low-level engine deliberately through
+`blue.advanced().rawEngine()`. Custom exact release identities are likewise an
+advanced option:
+
+```java
+try (BlueCoordination blue = BlueCoordination.builder()
+ .release(languageSpecificationIdentity, contractsSpecificationIdentity)
+ .build()) {
+ var raw = blue.advanced().rawEngine();
+}
+```
+
+The SDK admits new managed lineages produced by an operation when the caller
+supplies the exact initial value with `request.managed(...)`, binds every
+effective occurrence with `expectOccurrence(...)`, and selects `fromNow`
+activation. One draft can bind several occurrences without duplicating the
+lineage. The runtime verifies the request fields, occurrence paths, exact
+values, and complete affected closure before one atomic publication; a
+terminal failure leaves no partial document or topology mutation. Imported
+state (`ManagedDocumentDraft.atEpoch(...)`) and historical occurrence
+activation remain unsupported and fail closed.
+
+Operational tooling can inspect a retained occurrence without exposing graph
+internals through
+`blue.advanced().auditManagedOccurrence(sourceId, occurrencePath)`. The
+returned `ManagedOccurrenceAudit` reports the target `DocumentId`, activation
+generation, and active/inactive state.
+
`Operation.exact(...)` and `CoordinationEngine.referenceRequest(...)` expose the
optimized whole-object request path without YAML reserialization. For a
provider-supplied exact Timeline Entry, use `appendTimelineEntry(Node)`; append
@@ -58,30 +142,50 @@ state to operational tooling.
## Build and verification
+The normal and release builds use Maven Central artifacts only:
+
```bash
-./gradlew clean test
-./gradlew releaseCheck
-./gradlew stageRelease
+./gradlew --no-daemon dependencyPreflight
+./gradlew --no-daemon --no-build-cache clean releaseCheck \
+ -PtestJavaVersion=17
+./gradlew --no-daemon --no-build-cache verifyRcReadiness \
+ -PtestJavaVersion=17
```
-Published Maven Central artifacts are the default dependency source. Local
-composite substitution is available only as an explicit cross-repository
-diagnostic mode; it is not used by the normal build or release path.
+`releaseCheck` owns the complete verification surface: unit tests, compact-
+engine integration tests, tests compiled against the built JAR, realistic
+convergence scenarios, publication metadata, dependency isolation, source
+archive extraction, and documentation. Every `@Test` follows one meaningful
+lowercase `// given`, `// when`, `// then` sequence, enforced by
+`verifyTestArchitecture`.
+
+`dependencyPreflight` resolves the exact conflict-free Blue graph from Maven
+Central. Repository rc.21 still advertises Language rc.20 transitively, so the
+build and published POM exclude that one edge and directly own Language
+rc.21. Local composites, Maven Local, and file-based staging repositories are
+retired from the live build.
+
+The release workflow runs the same gates, stages signed artifacts, publishes
+through JReleaser, and pushes the rc.3 tag only after publication succeeds. See
+the [release procedure](docs/development/releasing.md) and
+[rc.3 release decision](docs/releases/3.0.0-rc.3.md).
-`releaseCheck` owns the library's complete verification surface: unit tests,
-compact-engine integration tests, tests compiled against the built JAR, and
-realistic convergence scenarios. It does not read or execute `../blue-basic`.
-That sibling is retained only as a historical performance/metrics laboratory.
+`releaseCheck` does not read or execute `../blue-basic`. That sibling is
+retained only as a historical performance/metrics laboratory.
Start with [START-HERE.md](START-HERE.md), then see the compact architecture,
managed `Process Embedded` semantics, catch-up rules, performance
interpretation, and limitations under `docs/`.
+## Historical release-candidate evidence
-## Release-candidate status
+The current release authority is the
+[3.0.0-rc.3 decision](docs/releases/3.0.0-rc.3.md). The documents below are
+retained evidence for rc.1 and are not reused as current artifact hashes.
-The source targets `3.0.0-rc.1` with the Round 10.1 Process Embedded temporal
-profile, the Round 11 readiness closure, and Round 12 initialization lifecycle
-and dynamic-activation proofs. Release status is split
+The retained 3.0.0-rc.1 report covers the earlier Round 10.1 Process Embedded
+temporal profile, Round 11 readiness closure, and Round 12 initialization
+lifecycle and dynamic-activation proofs. It does not cover the current
+Contracts 1.0 implementation. Its release status was split
into temporal architecture, in-memory engine, provider, Mandate, latency, and
public-RC evidence. The generic Timeline Entry's missing universal literal
`documentId` is an optional profile capability; exact provider-backed Mandate
@@ -103,8 +207,10 @@ Developer references:
- [Initialization causality](docs/semantics/initialization-causality.md)
- [Shared NBA Game lifecycle](docs/examples/nba-shared-game-lifecycle.md)
- [Five-occurrence Playground API example](docs/examples/playground-five-occurrence.md)
+- [3.0.0-rc.3 release decision](docs/releases/3.0.0-rc.3.md)
- [Canonical RC evidence report](docs/releases/3.0.0-rc.1-test-report.md)
- [Public API](docs/reference/public-api.md)
+- [SDK migration and ownership ledger](docs/reference/sdk-migration-and-ownership.md)
- [Metrics](docs/reference/metrics.md)
- [Failure and retry model](docs/operations/failure-model.md)
- [Contributing](CONTRIBUTING.md)
diff --git a/START-HERE.md b/START-HERE.md
index 942ab76..88b2d8d 100644
--- a/START-HERE.md
+++ b/START-HERE.md
@@ -1,19 +1,40 @@
# Start here
1. Use Java 17 or newer.
-2. Depend on `blue.coordination:blue-coordination-java:3.0.0-rc.1`.
-3. Create an in-memory `CoordinationEngine` in a try-with-resources block.
-4. Register each Timeline with its exact Timeline and actor identities.
-5. Admit each managed document with a stable `DocumentId`, authored initial
- YAML, and—when historical data exists—an explicit top-level admission policy.
-6. Append exact Timeline Entries without recipients, then call `drain()` or
- `drainThrough(cutoff)`. The environment, not the caller, selects canonical
- processing order and direct targets.
-7. Use `drain(DrainBudget)` when a host must pause after a deterministic amount
- of PROCESS work; resume with another drain call.
-8. Read coherent application state with READY-only `document(id)`. Reserve
- `auditDocument(id)` for explicit recovery/diagnostic inspection, and never
- retain mutable internal nodes.
+2. Resolve `blue.coordination:blue-coordination-java:3.0.0-rc.3` from Maven
+ Central. The build does not use sibling composites, Maven Local, or a staged
+ file repository.
+3. Create `BlueCoordination.inMemory()` in a try-with-resources block. This is
+ the one normal default and uses the bundled Contracts 1.0 identities.
+4. Register each Timeline with `blue.timelines().local(...)` or
+ `register(timelineId, accountId)`.
+5. Admit an authored public Root with `ManagedDocument...publicRoot()` or admit
+ a complete authored closure with `ManagedClosure`. Choose an activation
+ policy explicitly.
+6. Submit target-aware operations with `blue.operations().on(document)`. Use
+ `blue.events()` only for deliberate broadcasts; callers never name final
+ recipients.
+7. Call `submit()` for append-only behavior and `blue.processing().drain()` for
+ a later canonical drain, or call `execute()` to append and drain through the
+ submitted entry without overtaking older eligible work.
+8. Inspect `EntryDisposition` and `Diagnostic`, then read coherent application
+ state through the READY-only `DocumentHandle.snapshot()`. Reserve
+ `blue.advanced()` for host integration and operational diagnostics.
+
+The older `CoordinationEngine` surface is an advanced/legacy compatibility
+boundary. Its plain `inMemory()` factory retains the earlier acyclic profile;
+it does not share the SDK default's Contracts semantics. New application code
+should stay in `blue.coordination.sdk`.
+
+The rc.3 SDK admits a new managed child produced by an operation when its exact
+initial value is supplied with `request.managed(...)`, every effective path is
+declared with `expectOccurrence(...)`, and the activation policy is `fromNow`.
+Duplicate occurrences may share one stable draft lineage. Invalid or
+incomplete evidence fails closed, and a terminal processing failure publishes
+neither a partial child nor a partial topology expansion. Imported draft state
+and historical operation-result activation are not supported in this
+candidate. The bounded external-pilot claim is rechecked against the published
+dependency graph by the complete acceptance and fixture corpus.
The runtime is deliberately single-process and sequential. Each document
transition atomically commits its exact state, epoch, events, graph and
@@ -23,10 +44,10 @@ parent application instead of reprocessing the child. The bundled host is
in-memory, so process-restart durability and provider-backed completeness remain
explicit release gates rather than implied guarantees.
-The work budget does not preempt one frozen PROCESS invocation or epoch-zero
-INITIALIZE, so it is not a wall-clock timeout. Public phase metrics and the
-standalone performance campaign separate Coordination scheduling from frozen
-Language/Contracts/BEX time.
+The normal SDK does not expose the advanced `DrainBudget` boundary. Hosts that
+need deterministic work budgets can use `blue.advanced().rawEngine()` during
+migration. A work budget does not preempt one frozen PROCESS invocation or
+epoch-zero INITIALIZE and is not a wall-clock timeout.
Read next:
@@ -40,9 +61,12 @@ Read next:
- [Known limitations](docs/limitations.md)
- [Migration from 2.x](docs/migration-from-2.x.md)
- [Public API reference](docs/reference/public-api.md)
+- [SDK migration and ownership ledger](docs/reference/sdk-migration-and-ownership.md)
- [Metrics reference](docs/reference/metrics.md)
- [Failure and retry model](docs/operations/failure-model.md)
- [Build and test](docs/development/build-and-test.md)
- [Test strategy](docs/development/test-strategy.md)
- [Release process](docs/development/releasing.md)
-- [3.0.0-rc.1 readiness](docs/releases/3.0.0-rc.1.md)
+- [3.0.0-rc.3 release decision](docs/releases/3.0.0-rc.3.md)
+- [Current Contracts/SDK verification boundary](docs/releases/contracts-1.0-current-verification.md)
+- [Historical 3.0.0-rc.1 readiness](docs/releases/3.0.0-rc.1.md)
diff --git a/build.gradle b/build.gradle
index 6062ecd..87c247a 100644
--- a/build.gradle
+++ b/build.gradle
@@ -7,41 +7,38 @@ plugins {
}
group = 'blue.coordination'
+def dependencyMode = providers.gradleProperty('blueDependencyMode')
+ .getOrElse('published-artifact')
+ .trim()
def versionMatches = file('.cz.toml').getText('UTF-8') =~
/(?m)^version = "([^"]+)"$/
if (!versionMatches.find()) {
throw new GradleException('Missing version in .cz.toml')
}
-version = versionMatches.group(1)
-
-def dependencyMode = providers.gradleProperty('blueDependencyMode')
- .getOrElse('published-artifact')
- .trim()
-def localDependencies = dependencyMode == 'local-composite'
-def publishedRepository = providers.gradleProperty(
- 'bluePublishedRepository').orNull
+def declaredProjectVersion = versionMatches.group(1)
+version = declaredProjectVersion
+
+def publishedBlueCoordinates = [
+ 'blue.language:blue-language-model': '3.1.0-rc.21',
+ 'blue.language:blue-language-core': '3.1.0-rc.21',
+ 'blue.language:blue-language-mapping': '3.1.0-rc.21',
+ 'blue.language:blue-language-ipfs': '3.1.0-rc.21',
+ 'blue.language:blue-language-java': '3.1.0-rc.21',
+ 'blue.language:blue-contracts-core': '3.1.0-rc.21',
+ 'blue.repo:blue-repo-java': '3.0.0-rc.21',
+ 'blue.bex:blue-bex-core': '1.1.0-rc.4',
+ 'blue.bex:blue-bex-contracts': '1.1.0-rc.4'
+]
+if (dependencyMode != 'published-artifact') {
+ throw new GradleException(
+ 'Only blueDependencyMode=published-artifact is supported')
+}
base {
archivesName = 'blue-coordination-java'
}
repositories {
- if (!localDependencies && publishedRepository != null) {
- exclusiveContent {
- forRepository {
- maven {
- name = 'publishedBlueRepository'
- url = uri(publishedRepository)
- metadataSources { artifact() }
- }
- }
- filter {
- includeModule 'blue.repo', 'blue-repo-java'
- includeModule 'blue.bex', 'blue-bex-core'
- includeModule 'blue.bex', 'blue-bex-contracts'
- }
- }
- }
mavenCentral()
}
@@ -70,10 +67,8 @@ configurations {
dependencyLocking {
lockAllConfigurations()
- if (!localDependencies) {
- lockFile = layout.projectDirectory.file(
- 'gradle/published-artifact.lockfile')
- }
+ lockFile = layout.projectDirectory.file(
+ 'gradle/published-artifact.lockfile')
}
tasks.withType(JavaCompile).configureEach {
@@ -90,6 +85,7 @@ tasks.withType(JavaCompile).configureEach {
tasks.withType(Javadoc).configureEach {
source = fileTree('src/main/java') {
include 'blue/coordination/api/**/*.java'
+ include 'blue/coordination/sdk/**/*.java'
include 'blue/coordination/processor/**/*.java'
}
options.encoding = 'UTF-8'
@@ -110,10 +106,15 @@ tasks.withType(Jar).configureEach {
}
dependencies {
- api 'blue.language:blue-contracts-core:3.1.0-rc.20'
- implementation 'blue.repo:blue-repo-java:3.0.0-rc.21'
- api 'blue.bex:blue-bex-core:1.1.0-rc.3'
- api 'blue.bex:blue-bex-contracts:1.1.0-rc.3'
+ api 'blue.language:blue-contracts-core:3.1.0-rc.21'
+ implementation 'blue.language:blue-language-java:3.1.0-rc.21'
+ implementation('blue.repo:blue-repo-java:3.0.0-rc.21') {
+ // Repository rc.21 was published against Language rc.20. Coordination
+ // owns the rc.21 runtime and excludes that stale transitive edge.
+ exclude group: 'blue.language', module: 'blue-language-java'
+ }
+ api 'blue.bex:blue-bex-core:1.1.0-rc.4'
+ api 'blue.bex:blue-bex-contracts:1.1.0-rc.4'
implementation 'org.bouncycastle:bcprov-jdk18on:1.78.1'
testImplementation platform('org.junit:junit-bom:5.14.1')
@@ -128,7 +129,7 @@ dependencies {
scenarioTestImplementation sourceSets.integrationTest.output
consumerTestImplementation files(tasks.named('jar'))
- consumerTestImplementation 'blue.language:blue-contracts-core:3.1.0-rc.20'
+ consumerTestImplementation 'blue.language:blue-contracts-core:3.1.0-rc.21'
consumerTestImplementation platform('org.junit:junit-bom:5.14.1')
consumerTestImplementation 'org.junit.jupiter:junit-jupiter'
consumerTestRuntimeOnly files(configurations.runtimeClasspath)
@@ -201,77 +202,6 @@ def playgroundSmokeTest = tasks.register('playgroundSmokeTest', Test) {
}
}
-def round13SourceArchiveUnitTest = tasks.register(
- 'round13SourceArchiveUnitTest', Test) {
- group = 'verification'
- description = 'Runs the four focused Round 13 source-archive unit proofs.'
- testClassesDirs = sourceSets.test.output.classesDirs
- classpath = sourceSets.test.runtimeClasspath
- dependsOn tasks.named(sourceSets.test.classesTaskName)
- filter {
- includeTestsMatching(
- 'blue.coordination.api.DocumentRevisionInitializationCausalityTest')
- includeTestsMatching(
- 'blue.coordination.internal.DocumentAdmissionCauseTest')
- includeTestsMatching(
- 'blue.coordination.internal.EmbeddedEpochInputInitializationCausalityTest')
- includeTestsMatching(
- 'blue.coordination.internal.EmbeddingBindingCanonicalOrderTest')
- }
-}
-
-def round13SourceArchiveIntegrationTest = tasks.register(
- 'round13SourceArchiveIntegrationTest', Test) {
- group = 'verification'
- description = 'Runs the focused Round 13 recovery, retry, and five-occurrence correctness proofs.'
- testClassesDirs = sourceSets.integrationTest.output.classesDirs
- classpath = sourceSets.integrationTest.runtimeClasspath
- dependsOn tasks.named(sourceSets.integrationTest.classesTaskName)
- filter {
- includeTestsMatching(
- 'blue.coordination.internal.EmbeddedReceiptRetryIdentityIntegrationTest')
- includeTestsMatching(
- 'blue.coordination.integration.PlaygroundFiveOccurrenceCorrectnessIntegrationTest')
- includeTestsMatching(
- 'blue.coordination.integration.PlaygroundFiveOccurrenceRetryTest.retryCompletesFiveOccurrenceFanoutWithoutReinitializingChildren')
- }
-}
-
-def round13SourceArchiveScenarioTest = tasks.register(
- 'round13SourceArchiveScenarioTest', Test) {
- group = 'verification'
- description = 'Runs the focused Round 13 five-occurrence scenario proof.'
- testClassesDirs = sourceSets.scenarioTest.output.classesDirs
- classpath = sourceSets.scenarioTest.runtimeClasspath
- dependsOn tasks.named(sourceSets.scenarioTest.classesTaskName)
- filter {
- includeTestsMatching(
- 'blue.coordination.integration.PlaygroundFiveOccurrenceInitializationTest.fiveOccurrencesReuseThreeSessionsAndForwardFiveInitializationEvents')
- }
-}
-
-def round13SourceArchiveConsumerTest = tasks.register(
- 'round13SourceArchiveConsumerTest', Test) {
- group = 'verification'
- description = 'Runs the public-JAR five-occurrence consumer proof.'
- testClassesDirs = sourceSets.consumerTest.output.classesDirs
- classpath = sourceSets.consumerTest.runtimeClasspath
- dependsOn tasks.named(sourceSets.consumerTest.classesTaskName),
- tasks.named('jar')
- filter {
- includeTestsMatching(
- 'blue.coordination.consumer.PublishedArtifactConsumerTest.fiveEmbeddedOccurrencesReuseThreeManagedDocuments')
- }
-}
-
-tasks.register('round13SourceArchiveSmoke') {
- group = 'verification'
- description = 'Runs all focused Round 13 proofs required after archive extraction.'
- dependsOn round13SourceArchiveUnitTest,
- round13SourceArchiveIntegrationTest,
- round13SourceArchiveScenarioTest,
- round13SourceArchiveConsumerTest
-}
def configureRound13EvidenceRuntime = { JavaExec task ->
task.group = 'verification'
@@ -330,6 +260,48 @@ tasks.register('playgroundRuntimeCampaign', JavaExec) {
}
}
+tasks.register('cyclicPerformanceAcceptance', JavaExec) {
+ group = 'verification'
+ description = 'Writes the dedicated Java 17 cyclic correctness and performance acceptance report.'
+ classpath = sourceSets.test.runtimeClasspath
+ mainClass =
+ 'blue.coordination.internal.CyclicPerformanceAcceptance'
+ dependsOn tasks.named(sourceSets.test.classesTaskName)
+ javaLauncher = javaToolchains.launcherFor {
+ languageVersion = JavaLanguageVersion.of(17)
+ }
+ jvmArgs '-Xms2g',
+ '-Xmx2g',
+ '-XX:+UseG1GC',
+ '-Duser.language=en',
+ '-Duser.country=US',
+ '-Duser.timezone=UTC'
+ outputs.upToDateWhen { false }
+ outputs.doNotCacheIf(
+ 'Cyclic performance evidence is intentionally non-cacheable') {
+ true
+ }
+ doFirst {
+ systemProperty 'user.language', 'en'
+ systemProperty 'user.country', 'US'
+ systemProperty 'user.timezone', 'UTC'
+ systemProperty 'blue.coordination.cyclicPerformance.output',
+ providers.systemProperty(
+ 'blue.coordination.cyclicPerformance.output')
+ .getOrElse(layout.buildDirectory.dir(
+ 'reports/cyclic-performance')
+ .get().asFile.absolutePath)
+ systemProperty 'blue.coordination.cyclicPerformance.warmups',
+ providers.systemProperty(
+ 'blue.coordination.cyclicPerformance.warmups')
+ .getOrElse('20')
+ systemProperty 'blue.coordination.cyclicPerformance.samples',
+ providers.systemProperty(
+ 'blue.coordination.cyclicPerformance.samples')
+ .getOrElse('50')
+ }
+}
+
publishing {
publications {
mavenJava(MavenPublication) {
@@ -428,6 +400,22 @@ if (System.getenv('CI') != null) {
}
def productionSources = fileTree('src/main/java') { include '**/*.java' }
+def publicBoundarySources = files(
+ fileTree('src/main/java/blue/coordination/api') {
+ include '**/*.java'
+ },
+ fileTree('src/main/java/blue/coordination/sdk') {
+ include '**/*.java'
+ })
+// Current Contracts 1.0 maintainability guardrails. These are deliberately
+// bounded caps over the SDK baseline of 163 production source files, 39,656
+// lines, and 49 public api+sdk source types. They are not retained Round 13
+// counts and are not performance evidence.
+def currentProductionShapeLimits = [
+ classes: 170,
+ lines: 42_000L,
+ publicApiTypes: 50
+]
def forbiddenArchitectureTokens = [
'AutonomousLink', 'TemporalWave', 'ConsistencyMode',
'ObservingLink', 'CoherentLink', 'SccScheduler',
@@ -436,26 +424,28 @@ def forbiddenArchitectureTokens = [
tasks.register('validateProductionShape') {
group = 'verification'
- description = 'Enforces the production class, line, API, and architecture budgets.'
- inputs.files(productionSources)
+ description = 'Enforces current Contracts 1.0 maintainability and architecture guardrails.'
+ inputs.files(productionSources, publicBoundarySources)
doLast {
def sources = productionSources.files.sort()
int classes = sources.size()
long lines = sources.sum { it.readLines('UTF-8').size() } ?: 0L
- def apiSources = fileTree('src/main/java/blue/coordination/api') {
- include '**/*.java'
- }.files
+ def publicTypePattern = ~/(?m)^public\s+(?:(?:final|abstract|sealed|non-sealed)\s+)?(?:class|interface|record|enum)\s+/
+ def apiSources = publicBoundarySources.files.findAll { source ->
+ source.getText('UTF-8') =~ publicTypePattern
+ }
def failures = []
- if (classes > 115) failures << "production classes ${classes} > 115"
- // Round 13 transparently retains a bounded 252-line revision for the
- // occurrence-path audit receipt, closed retry-counter vocabulary and
- // retry-monitor producers, timestamp-consumption and attachment-
- // timestamp guards, committed-input recovery evidence, deterministic
- // invalid-entry preflight and terminalization, and injective binding
- // identities. Do not minify or hide this deliberate semantic work.
- if (lines > 25_952L) failures << "production lines ${lines} > 25952"
- if (apiSources.size() > 16) {
- failures << "public API types ${apiSources.size()} > 16"
+ if (classes > currentProductionShapeLimits.classes) {
+ failures << "production classes ${classes} > "
+ + currentProductionShapeLimits.classes
+ }
+ if (lines > currentProductionShapeLimits.lines) {
+ failures << "production lines ${lines} > "
+ + currentProductionShapeLimits.lines
+ }
+ if (apiSources.size() > currentProductionShapeLimits.publicApiTypes) {
+ failures << "public API types ${apiSources.size()} > "
+ + currentProductionShapeLimits.publicApiTypes
}
['engine', 'fastpath'].each { legacy ->
if (file("src/main/java/blue/coordination/${legacy}").exists()) {
@@ -513,12 +503,20 @@ tasks.register('verifyPublicApiBoundary') {
}
}
}
+ def allowedPublicInternalSources = [
+ 'src/main/java/blue/coordination/internal/DefaultCoordinationEngine.java',
+ 'src/main/java/blue/coordination/internal/BundledContracts10Release.java',
+ 'src/main/java/blue/coordination/internal/Contracts10AuthoredClosureCompiler.java',
+ 'src/main/java/blue/coordination/internal/ContractsManagedDraftPlan.java'
+ ] as Set
fileTree('src/main/java/blue/coordination/internal') {
include '**/*.java'
- exclude 'DefaultCoordinationEngine.java'
}.each { source ->
String body = source.getText('UTF-8')
- if (body =~ /(?m)^public\s+(?:final\s+|abstract\s+)?(?:class|interface|record|enum)\s+/) {
+ String sourcePath = rootDir.toPath().relativize(
+ source.toPath()).toString().replace('\\', '/')
+ if ((body =~ /(?m)^public\s+(?:(?:final|abstract|sealed|non-sealed)\s+)?(?:class|interface|record|enum)\s+/)
+ && !allowedPublicInternalSources.contains(sourcePath)) {
violations << "public implementation type ${source}"
}
}
@@ -529,6 +527,129 @@ tasks.register('verifyPublicApiBoundary') {
}
}
+tasks.register('verifySdkPublicApiBoundary') {
+ group = 'verification'
+ description = 'Allows only DocumentId from the compatibility API and rejects every implementation dependency in normal SDK signatures.'
+ dependsOn tasks.named('compileJava')
+ inputs.files(fileTree('src/main/java/blue/coordination/sdk') {
+ include '**/*.java'
+ }, sourceSets.main.output.classesDirs)
+ def inspectionLauncher = javaToolchains.launcherFor {
+ languageVersion = JavaLanguageVersion.of(17)
+ }
+ doLast {
+ File sdkClasses = layout.buildDirectory.dir(
+ 'classes/java/main/blue/coordination/sdk').get().asFile
+ if (!sdkClasses.isDirectory()) {
+ throw new GradleException(
+ 'Compiled SDK classes are missing: ' + sdkClasses)
+ }
+ String executableName = System.getProperty('os.name')
+ .toLowerCase(java.util.Locale.ROOT).contains('windows')
+ ? 'javap.exe' : 'javap'
+ File javap = inspectionLauncher.get().metadata.installationPath
+ .file('bin/' + executableName).asFile
+ if (!javap.isFile()) {
+ throw new GradleException('Java API inspector is missing: ' + javap)
+ }
+ def inspectClass = { String className, String visibility ->
+ Process process = new ProcessBuilder(
+ javap.absolutePath,
+ visibility,
+ '-classpath',
+ sourceSets.main.output.classesDirs.asPath
+ + File.pathSeparator
+ + configurations.compileClasspath.asPath,
+ className)
+ .redirectErrorStream(true)
+ .start()
+ String signature = process.inputStream.getText('UTF-8')
+ if (process.waitFor() != 0) {
+ throw new GradleException(
+ "javap failed for ${className}: ${signature}")
+ }
+ signature
+ }
+ def forbiddenSignaturePrefixes = [
+ 'blue.coordination.internal.',
+ 'blue.coordination.processor.',
+ 'blue.coordination.api.',
+ 'blue.language.',
+ 'blue.bex.'
+ ]
+ def violations = []
+ def packagePrivateHelpers = [
+ 'SdkCoordinationRuntime',
+ 'SdkDrainResultMapper',
+ 'SdkPreconditions'
+ ]
+ packagePrivateHelpers.each { helper ->
+ File source = file(
+ "src/main/java/blue/coordination/sdk/${helper}.java")
+ if (!source.isFile()) {
+ violations << "missing SDK package-private helper ${helper}"
+ return
+ }
+ def sourcePublicDeclaration = java.util.regex.Pattern.compile(
+ '(?m)^\\s*public\\s+(?:(?:final|abstract|sealed|non-sealed)\\s+)*'
+ + '(?:class|interface|record|enum)\\s+'
+ + java.util.regex.Pattern.quote(helper)
+ + '\\b')
+ if (sourcePublicDeclaration.matcher(
+ source.getText('UTF-8')).find()) {
+ violations << "SDK helper ${helper} is public in source"
+ }
+ String className = "blue.coordination.sdk.${helper}"
+ String binaryDeclaration = inspectClass(className, '-p')
+ def binaryPublicDeclaration = java.util.regex.Pattern.compile(
+ '(?m)^public\\s+.*\\b(?:class|interface|enum)\\s+'
+ + java.util.regex.Pattern.quote(className)
+ + '(?:\\s|\\{|<)')
+ if (binaryPublicDeclaration.matcher(binaryDeclaration).find()) {
+ violations << "SDK helper ${helper} is public in bytecode"
+ }
+ }
+ fileTree(sdkClasses) { include '**/*.class' }.files.sort().each {
+ File classFile ->
+ String relative = sdkClasses.toPath().relativize(
+ classFile.toPath()).toString().replace('\\', '/')
+ String className = "blue.coordination.sdk.${relative
+ .substring(0, relative.length() - 6)
+ .replace('/', '.')}"
+ if (className == 'blue.coordination.sdk.AdvancedCoordination'
+ || className.startsWith(
+ 'blue.coordination.sdk.AdvancedCoordination$')
+ || packagePrivateHelpers.any { helper ->
+ className == "blue.coordination.sdk.${helper}"
+ || className.startsWith(
+ "blue.coordination.sdk.${helper}\$")
+ }) {
+ return
+ }
+ String signature = inspectClass(className, '-public')
+ def publicDeclaration = java.util.regex.Pattern.compile(
+ '(?m)^public\\s+.*\\b(?:class|interface|enum)\\s+'
+ + java.util.regex.Pattern.quote(className)
+ + '(?:\\s|\\{|<)')
+ if (!publicDeclaration.matcher(signature).find()) {
+ return
+ }
+ String normalSignature = signature.replaceAll(
+ /blue\.coordination\.api\.DocumentId\b/, '')
+ forbiddenSignaturePrefixes.findAll {
+ normalSignature.contains(it)
+ }.each { forbidden ->
+ violations << "${className} exposes ${forbidden}"
+ }
+ }
+ if (!violations.empty) {
+ throw new GradleException(
+ 'Normal SDK signature leakage: '
+ + violations.join(', '))
+ }
+ }
+}
+
tasks.register('dependencyPreflight') {
group = 'verification'
description = 'Resolves every release dependency from published repositories.'
@@ -538,18 +659,40 @@ tasks.register('dependencyPreflight') {
'dependencyPreflight requires '
+ '-PblueDependencyMode=published-artifact')
}
+ def repositoryDependency = dependencies.create(
+ 'blue.repo:blue-repo-java:3.0.0-rc.21')
+ repositoryDependency.exclude(
+ group: 'blue.language', module: 'blue-language-java')
def releaseDependencies = configurations.detachedConfiguration(
dependencies.create(
- 'blue.language:blue-contracts-core:3.1.0-rc.20'),
- dependencies.create('blue.repo:blue-repo-java:3.0.0-rc.21'),
- dependencies.create('blue.bex:blue-bex-core:1.1.0-rc.3'),
+ 'blue.language:blue-contracts-core:3.1.0-rc.21'),
+ dependencies.create(
+ 'blue.language:blue-language-java:3.1.0-rc.21'),
+ repositoryDependency,
+ dependencies.create('blue.bex:blue-bex-core:1.1.0-rc.4'),
dependencies.create(
- 'blue.bex:blue-bex-contracts:1.1.0-rc.3'),
+ 'blue.bex:blue-bex-contracts:1.1.0-rc.4'),
dependencies.create('org.bouncycastle:bcprov-jdk18on:1.78.1'))
releaseDependencies.transitive = true
releaseDependencies.resolutionStrategy.failOnVersionConflict()
releaseDependencies.resolve()
- logger.lifecycle('All published release dependencies resolved.')
+ def selectedBlue = releaseDependencies.incoming.resolutionResult
+ .allComponents.findAll { component ->
+ component.id instanceof
+ org.gradle.api.artifacts.component.ModuleComponentIdentifier
+ && ['blue.language', 'blue.bex', 'blue.repo'].contains(
+ component.id.group)
+ }.collectEntries { component ->
+ [(component.id.group + ':' + component.id.module):
+ component.id.version]
+ }
+ if (selectedBlue != publishedBlueCoordinates) {
+ throw new GradleException(
+ 'Published Blue dependency graph differs from the release '
+ + "pin: ${selectedBlue}")
+ }
+ logger.lifecycle(
+ 'All published release dependencies resolved at exact pins.')
}
}
@@ -567,13 +710,24 @@ tasks.register('verifyArtifactContents') {
'CoordinationTestControl'
] + forbiddenArchitectureTokens
def violations = []
- zipTree(tasks.named('jar').get().archiveFile).visit { details ->
+ def artifactTree = zipTree(
+ tasks.named('jar').get().archiveFile)
+ artifactTree.visit { details ->
if (!details.directory && forbidden.any {
details.path.contains(it)
}) {
violations << details.path
}
}
+ [
+ 'blue/coordination/sdk/BlueCoordination.class',
+ 'blue/coordination/sdk/EntryDisposition.class',
+ 'blue/coordination/sdk/contracts-1.0-release.properties'
+ ].each { required ->
+ if (artifactTree.matching { include required }.isEmpty()) {
+ violations << "missing required SDK entry ${required}"
+ }
+ }
if (!violations.empty) {
throw new GradleException(
"Unsupported production JAR entries: ${violations}")
@@ -590,13 +744,18 @@ tasks.register('verifyPublicationPom') {
.get().asFile.getText('UTF-8')
def scopes = [:]
pom.split('').each { block ->
- def groups = (block =~ /([^<]+)<\/groupId>/)
+ int dependencyStart = block.lastIndexOf('')
+ String dependencyBlock = dependencyStart >= 0
+ ? block.substring(dependencyStart) : ''
+ def groups = (dependencyBlock
+ =~ /([^<]+)<\/groupId>/)
.collect { it[1] }
- def artifacts = (block =~ /([^<]+)<\/artifactId>/)
+ def artifacts = (dependencyBlock
+ =~ /([^<]+)<\/artifactId>/)
.collect { it[1] }
- def scope = (block =~ /([^<]+)<\/scope>/)
+ def scope = (dependencyBlock =~ /([^<]+)<\/scope>/)
if (!groups.empty && !artifacts.empty) {
- scopes[groups.last() + ':' + artifacts.last()] =
+ scopes[groups.first() + ':' + artifacts.first()] =
scope.find() ? scope.group(1) : 'compile'
}
}
@@ -608,7 +767,8 @@ tasks.register('verifyPublicationPom') {
"${coordinate} must be compile scoped because public APIs expose its types")
}
}
- ['blue.repo:blue-repo-java', 'org.bouncycastle:bcprov-jdk18on']
+ ['blue.language:blue-language-java',
+ 'blue.repo:blue-repo-java', 'org.bouncycastle:bcprov-jdk18on']
.each { coordinate ->
if (scopes[coordinate] != 'runtime') {
throw new GradleException(
@@ -616,10 +776,11 @@ tasks.register('verifyPublicationPom') {
}
}
def expectedVersions = [
- 'blue.language:blue-contracts-core': '3.1.0-rc.20',
+ 'blue.language:blue-contracts-core': '3.1.0-rc.21',
+ 'blue.language:blue-language-java': '3.1.0-rc.21',
'blue.repo:blue-repo-java': '3.0.0-rc.21',
- 'blue.bex:blue-bex-core': '1.1.0-rc.3',
- 'blue.bex:blue-bex-contracts': '1.1.0-rc.3',
+ 'blue.bex:blue-bex-core': '1.1.0-rc.4',
+ 'blue.bex:blue-bex-contracts': '1.1.0-rc.4',
'org.bouncycastle:bcprov-jdk18on': '1.78.1'
]
expectedVersions.each { coordinate, expectedVersion ->
@@ -635,6 +796,19 @@ tasks.register('verifyPublicationPom') {
"${coordinate} must use ${expectedVersion}")
}
}
+ def repositoryDependency = pom.split('').find { block ->
+ block.contains('blue.repo')
+ && block.contains(
+ 'blue-repo-java')
+ }
+ if (repositoryDependency == null
+ || !repositoryDependency.contains(
+ 'blue.language')
+ || !repositoryDependency.contains(
+ 'blue-language-java')) {
+ throw new GradleException(
+ 'Repository rc.21 must exclude its stale Language rc.20 edge')
+ }
['MIT License', '', '', '']
.each { marker ->
if (!pom.contains(marker)) {
@@ -688,6 +862,7 @@ tasks.register('verifyReleaseMetadata') {
'docs/development/build-and-test.md',
'docs/development/test-strategy.md',
'docs/development/releasing.md',
+ 'docs/releases/3.0.0-rc.3.md',
'docs/releases/3.0.0-rc.1-test-report.md',
'docs/releases/3.0.0-rc.1-evidence.json',
'docs/releases/round11-verification.schema.json',
@@ -715,18 +890,130 @@ tasks.register('verifyReleaseMetadata') {
tasks.named('javadocJar').get().archiveFile)
if (javadocEntries.matching {
include 'blue/coordination/api/CoordinationEngine.html'
+ }.isEmpty() || javadocEntries.matching {
+ include 'blue/coordination/sdk/BlueCoordination.html'
}.isEmpty() || javadocEntries.matching {
include 'blue/coordination/processor/CoordinationProcessors.html'
}.isEmpty()) {
throw new GradleException(
- 'Javadoc JAR must cover API and processor surfaces')
+ 'Javadoc JAR must cover SDK, compatibility API, and processor surfaces')
}
}
}
+def currentContractsDocumentation = tasks.register(
+ 'verifyCurrentContractsDocumentation') {
+ group = 'verification'
+ description = 'Validates current Contracts 1.0 source/docs and writes non-performance integrity evidence.'
+ def semantics = file('docs/semantics/process-embedded-documents.md')
+ def architecture = file('docs/architecture/compact-engine.md')
+ def currentVerification = file(
+ 'docs/releases/contracts-1.0-current-verification.md')
+ def historicalReport = file(
+ 'docs/releases/3.0.0-rc.1-test-report.md')
+ def contractsTests = fileTree('src/test/java') {
+ include '**/Contracts*Test.java'
+ include '**/ManagedOccurrenceInventoryTest.java'
+ include '**/ProcessEmbeddedComponentIndexTest.java'
+ include '**/MultiDocumentPublicationTransactionTest.java'
+ }
+ inputs.files(productionSources, contractsTests, semantics, architecture,
+ currentVerification, historicalReport)
+ def integrityReport = layout.buildDirectory.file(
+ 'reports/contracts10/current-source-integrity.json')
+ outputs.file(integrityReport)
+ doLast {
+ def requiredSources = [
+ 'src/main/java/blue/coordination/api/Contracts10Configuration.java',
+ 'src/main/java/blue/coordination/api/ContractsClosureAdmissionReceipt.java',
+ 'src/main/java/blue/coordination/internal/ContractsClosureAdapter.java',
+ 'src/main/java/blue/coordination/internal/ContractsClosureAdmissionAdapter.java',
+ 'src/main/java/blue/coordination/internal/ContractsRootFeederCoordinator.java',
+ 'src/main/java/blue/coordination/internal/MultiDocumentPublicationTransaction.java'
+ ]
+ def missingSources = requiredSources.findAll { !file(it).isFile() }
+ String semanticText = semantics.getText('UTF-8')
+ String architectureText = architecture.getText('UTF-8')
+ def requiredStatements = [
+ separateDocuments: semanticText.contains(
+ 'Each selected embedded managed document is processed separately'),
+ noContainerAwareness: semanticText.contains(
+ 'completely unaware of documents that contain it'),
+ sharedCyclicProcessor: semanticText.contains(
+ 'the same Contracts closure execution context'),
+ rootUnionFeeder: semanticText.contains(
+ 'one ordered feeder/window for a public Root over the union'),
+ durableHeadsAndEpochs: semanticText.contains(
+ 'Per-document durable heads and epochs therefore'),
+ sameFrozenProcessor: architectureText.contains(
+ 'Every selected managed document crosses the same frozen Contracts processor')
+ ]
+ if (!missingSources.empty || requiredStatements.any {
+ key, present -> !present
+ }) {
+ throw new GradleException(
+ 'Current Contracts 1.0 source/docs integrity failed: missing '
+ + missingSources + ', statements '
+ + requiredStatements.findAll {
+ key, present -> !present
+ }.keySet())
+ }
+ if (!historicalReport.getText('UTF-8').contains(
+ 'ROUND13_HISTORICAL_EVIDENCE_ONLY')
+ || !currentVerification.getText('UTF-8').contains(
+ 'CONTRACTS10_CURRENT_PERFORMANCE_CLAIM: NONE')) {
+ throw new GradleException(
+ 'Historical Round 13 and current Contracts 1.0 evidence are not clearly separated')
+ }
+ def sha256 = { byte[] bytes ->
+ java.security.MessageDigest.getInstance('SHA-256')
+ .digest(bytes).encodeHex().toString()
+ }
+ def relativePath = { File source ->
+ rootDir.toPath().relativize(source.toPath()).toString()
+ .replace(File.separator, '/')
+ }
+ List mainSources = productionSources.files.sort {
+ source -> relativePath(source)
+ }
+ String manifest = mainSources.collect { source ->
+ "${relativePath(source)} ${sha256(source.bytes)}"
+ }.join('\n')
+ List testSources = contractsTests.files.sort {
+ source -> relativePath(source)
+ }
+ def report = [
+ schemaId: 'blue-coordination-contracts10-source-integrity-v1',
+ dependencyMode: dependencyMode,
+ productionSourceManifestSha256: sha256(manifest.getBytes(
+ java.nio.charset.StandardCharsets.UTF_8)),
+ productionClasses: mainSources.size(),
+ productionLines: mainSources.sum {
+ it.readLines('UTF-8').size()
+ } ?: 0L,
+ maintainabilityGuardrails: currentProductionShapeLimits,
+ contractsTestClasses: testSources.size(),
+ contractsTestMethods: testSources.sum { source ->
+ (source.getText('UTF-8') =~ /(?m)^\s*@Test\b/).count
+ } ?: 0L,
+ documentedInvariants: requiredStatements,
+ historicalRound13Evidence: 'HISTORICAL_ONLY',
+ performanceEvidence: 'NOT_CLAIMED'
+ ]
+ File target = integrityReport.get().asFile
+ target.parentFile.mkdirs()
+ target.setText(groovy.json.JsonOutput.prettyPrint(
+ groovy.json.JsonOutput.toJson(report)) + '\n', 'UTF-8')
+ logger.lifecycle(
+ 'Current Contracts 1.0 source integrity: {}',
+ report.productionSourceManifestSha256)
+ }
+}
+
tasks.register('verifyDocumentation') {
group = 'verification'
- description = 'Validates maintained docs and canonical release evidence.'
+ description = 'Validates maintained docs and retained historical release evidence.'
+ dependsOn currentContractsDocumentation
inputs.files(fileTree('docs') { include '**/*.md' },
'README.md', 'START-HERE.md', 'CHANGELOG.md',
'CONTRIBUTING.md', 'SECURITY.md',
@@ -762,6 +1049,104 @@ tasks.register('verifyDocumentation') {
def evidenceFile = file(
'docs/releases/3.0.0-rc.1-evidence.json')
def evidence = new groovy.json.JsonSlurper().parse(evidenceFile)
+ boolean verifyRound13CandidateSnapshot = providers.gradleProperty(
+ 'verifyRound13CandidateSnapshot').getOrElse('false') == 'true'
+ if (evidence.schemaVersion == '4.1.0'
+ && evidence.profile
+ == 'ROUND13_PLAYGROUND_FIVE_OCCURRENCE'
+ && !verifyRound13CandidateSnapshot) {
+ def schema = new groovy.json.JsonSlurper().parse(file(
+ 'docs/releases/round13-verification.schema.json'))
+ String sha256Pattern = /^[0-9a-f]{64}$/
+ String commitPattern = /^[0-9a-f]{40}$/
+ String report = file(
+ 'docs/releases/3.0.0-rc.1-test-report.md')
+ .getText('UTF-8')
+ if (evidence.'$schema' != 'round13-verification.schema.json'
+ || schema.'$id' != 'round13-verification.schema.json'
+ || evidence.evidenceState != 'FINAL'
+ || evidence.release != '3.0.0-rc.1') {
+ failures << 'Retained Round 13 evidence has an invalid historical identity'
+ }
+ if (!(evidence.source.candidateCommit ==~ commitPattern)
+ || evidence.source.binding != 'CANDIDATE_COMMIT'
+ || evidence.source.worktree != 'CLEAN'
+ || !(evidence.source.mainSourceManifestSha256
+ ==~ sha256Pattern)
+ || evidence.executionBinding.candidateCommit
+ != evidence.source.candidateCommit
+ || evidence.executionBinding.mainSourceManifestSha256
+ != evidence.source.mainSourceManifestSha256) {
+ failures << 'Retained Round 13 source binding is internally inconsistent'
+ }
+ def shapeValues = [
+ 'productionClasses', 'productionLines',
+ 'publicApiSourceTypes'
+ ]
+ if (evidence.shape.status != 'PASS'
+ || evidence.shape.basis != 'STATIC_WORKTREE_SNAPSHOT'
+ || shapeValues.any { key ->
+ !(evidence.shape[key] instanceof Number)
+ || !(evidence.shape.limits[key]
+ instanceof Number)
+ || evidence.shape[key]
+ > evidence.shape.limits[key]
+ }) {
+ failures << 'Retained Round 13 source shape is internally inconsistent'
+ }
+ def expectedSuites = [
+ 'test', 'integrationTest', 'consumerTest', 'scenarioTest'
+ ] as Set
+ def suites = evidence.tests.suites
+ if (evidence.tests.inventoryBasis != 'STATIC_SOURCE_INVENTORY'
+ || evidence.tests.executionStatus != 'PASS'
+ || (suites.collect { it.name } as Set) != expectedSuites
+ || suites.sum { it.tests } != evidence.tests.tests
+ || suites.sum { it.classes } != evidence.tests.classes
+ || ['failures', 'errors', 'skipped'].any { field ->
+ suites.sum { it[field] } != evidence.tests[field]
+ }) {
+ failures << 'Retained Round 13 test inventory is internally inconsistent'
+ }
+ def campaign = evidence.performance.round13Campaign
+ if (campaign.status != 'PENDING_VERIFICATION'
+ || campaign.measuredSamples != 0
+ || campaign.runtimeMarkdown != null
+ || campaign.runtimeJson != null
+ || campaign.runtimeProvenanceJson != null
+ || !(campaign.pendingReason instanceof String)
+ || campaign.pendingReason.isBlank()
+ || evidence.status.playgroundLatencyReady
+ != 'PASS_WITH_KNOWN_PERFORMANCE_LIMITATION'
+ || evidence.verdict
+ != 'PASS_WITH_KNOWN_PERFORMANCE_LIMITATION') {
+ failures << 'Retained Round 13 performance limitation was relabeled'
+ }
+ def artifactHashes = [
+ 'mainJarSha256', 'sourcesJarSha256',
+ 'javadocJarSha256', 'testFixturesJarSha256'
+ ]
+ if (evidence.artifacts.status != 'PASS'
+ || artifactHashes.any { key ->
+ !(evidence.artifacts[key] ==~ sha256Pattern)
+ }) {
+ failures << 'Retained Round 13 artifact evidence is incomplete'
+ }
+ if (!report.contains('ROUND13_HISTORICAL_EVIDENCE_ONLY')
+ || !report.contains(
+ evidence.source.mainSourceManifestSha256)
+ || !report.contains("**${evidence.tests.classes}**")
+ || !report.contains("**${evidence.tests.tests}**")) {
+ failures << 'Retained Round 13 report no longer matches its historical evidence'
+ }
+ if (!failures.empty) {
+ throw new GradleException(failures.join('\n'))
+ }
+ logger.lifecycle(
+ 'Validated retained Round 13 evidence as historical-only; '
+ + 'it was not compared with the current Contracts 1.0 source tree.')
+ return
+ }
if (evidence.schemaVersion == '4.1.0'
&& evidence.profile
== 'ROUND13_PLAYGROUND_FIVE_OCCURRENCE') {
@@ -2125,11 +2510,15 @@ tasks.register('verifySourceArchiveHygiene') {
def declaredVersions = versionSource =~
/(?m)^version = "([^"]+)"$/
if (!declaredVersions.find()
- || declaredVersions.group(1) != configuredArchiveVersion
+ || declaredVersions.group(1) != declaredProjectVersion
|| declaredVersions.find()) {
throw new GradleException(
'.cz.toml must contain one authoritative project version')
}
+ if (configuredArchiveVersion != declaredProjectVersion) {
+ throw new GradleException(
+ 'Configured source archive version does not match .cz.toml')
+ }
String attributes = file('.gitattributes').getText('UTF-8')
[
'*.zip export-ignore',
@@ -2152,8 +2541,9 @@ def sourceArchiveIncludes = [
'.cz.toml', '.gitattributes', '.github/**', '.gitignore',
'CHANGELOG.md', 'CONTRIBUTING.md', 'LICENSE', 'README.md',
'SECURITY.md', 'START-HERE.md', 'build.gradle', 'docs/**',
- 'gradle/**', 'gradle.lockfile', 'gradle.properties', 'gradlew',
- 'gradlew.bat', 'scripts/**', 'settings.gradle', 'src/**'
+ 'gradle/**', 'gradle.properties', 'gradlew',
+ 'gradlew.bat', 'scripts/**', 'settings.gradle', 'src/**',
+ 'stabilization/cyclic-topology-rc3-final/**'
]
def sourceArchiveExcludes = [
'**/.git/**', '**/.gradle/**', '**/.idea/**', '**/build/**',
@@ -2201,7 +2591,7 @@ def sourceArchiveChecksum = tasks.register(
def extractedSourceArchive = tasks.register('verifyExtractedSourceArchive') {
group = 'verification'
- description = 'Extracts, configures, and runs the focused Round 13 archive proofs.'
+ description = 'Verifies the extracted archive against published dependencies.'
dependsOn sourceArchiveChecksum
inputs.file(coordinationSourceArchive.flatMap { it.archiveFile })
inputs.property('dependencyMode', dependencyMode)
@@ -2209,7 +2599,7 @@ def extractedSourceArchive = tasks.register('verifyExtractedSourceArchive') {
'testJavaVersion').getOrElse('17'))
outputs.dir(layout.buildDirectory.dir('source-archive-smoke'))
def verificationReceipt = layout.buildDirectory.file(
- 'reports/round13/source-archive-verification.json')
+ 'reports/contracts10/source-archive-verification.json')
outputs.file(verificationReceipt)
doLast {
File sourceArchive = coordinationSourceArchive.get()
@@ -2270,29 +2660,13 @@ def extractedSourceArchive = tasks.register('verifyExtractedSourceArchive') {
? ['cmd', '/d', '/c', wrapper.absolutePath]
: [wrapper.absolutePath]
command.addAll([
- '--no-daemon', '--max-workers=1',
- 'help', 'round13SourceArchiveSmoke',
+ '--no-daemon', '--max-workers=1', 'help',
'-PtestJavaVersion=' + providers.gradleProperty(
- 'testJavaVersion').getOrElse('17')
+ 'testJavaVersion').getOrElse('17'),
+ 'verifyDependencyModeIsolation',
+ 'verifyPublishedDependencyIsolation',
+ '-PblueDependencyMode=published-artifact'
])
- if (localDependencies) {
- command.addAll([
- '-PblueDependencyMode=local-composite',
- '-PblueBexCompositePath=' + file(providers.gradleProperty(
- 'blueBexCompositePath').getOrElse(
- '../blue-bex-java')).canonicalPath,
- '-PblueRepositoryCompositePath=' + file(
- providers.gradleProperty(
- 'blueRepositoryCompositePath').getOrElse(
- '../blue-repository-java')).canonicalPath
- ])
- } else {
- command.add('-PblueDependencyMode=published-artifact')
- if (publishedRepository != null) {
- command.add('-PbluePublishedRepository='
- + uri(publishedRepository).toString())
- }
- }
Process process = new ProcessBuilder(command)
.directory(extracted).redirectErrorStream(true).start()
process.inputStream.eachLine { logger.lifecycle(it) }
@@ -2313,7 +2687,7 @@ def extractedSourceArchive = tasks.register('verifyExtractedSourceArchive') {
File receipt = verificationReceipt.get().asFile
receipt.parentFile.mkdirs()
def receiptValue = [
- schemaId: 'blue-coordination-round13-source-archive-verification-v1',
+ schemaId: 'blue-coordination-contracts10-source-archive-verification-v1',
archiveName: sourceArchive.name,
archiveSha256: archiveHash,
checksumMode: 'DETACHED_SHA256_SIDECAR',
@@ -2323,15 +2697,11 @@ def extractedSourceArchive = tasks.register('verifyExtractedSourceArchive') {
java: providers.gradleProperty(
'testJavaVersion').getOrElse('17'),
extractedConfiguration: 'PASS',
- focusedTestsStatus: 'PASS',
- publishedArtifactSmokeBuild: localDependencies
- ? 'PENDING_VERIFICATION' : 'PASS',
- focusedTasks: [
- 'round13SourceArchiveUnitTest',
- 'round13SourceArchiveIntegrationTest',
- 'round13SourceArchiveScenarioTest',
- 'round13SourceArchiveConsumerTest'
- ]
+ dependencyIsolationStatus: 'PASS',
+ focusedTestsStatus: 'NOT_EXECUTED',
+ publishedArtifactCompatibility: 'PASS',
+ publishedArtifactCompatibilityReason: null,
+ focusedTasks: []
]
receipt.setText(groovy.json.JsonOutput.prettyPrint(
groovy.json.JsonOutput.toJson(receiptValue)) + '\n',
@@ -2339,9 +2709,9 @@ def extractedSourceArchive = tasks.register('verifyExtractedSourceArchive') {
}
}
-tasks.register('verifyPublishedModeIsolation') {
+tasks.register('verifyDependencyModeIsolation') {
group = 'verification'
- description = 'Proves published mode is the default and has no Git or sibling checks.'
+ description = 'Proves Maven Central artifacts are the only live dependency lane.'
inputs.files('settings.gradle', 'build.gradle')
doLast {
String settings = file('settings.gradle').getText('UTF-8')
@@ -2356,16 +2726,88 @@ tasks.register('verifyPublishedModeIsolation') {
throw new GradleException(
'Published artifacts must remain the default dependency mode')
}
- if (!settings.contains("dependencyMode == 'local-composite'")) {
+ def forbiddenLiveModes = [
+ 'include' + 'Build(', 'local-' + 'composite',
+ 'staged-' + 'artifact',
+ 'blueLanguage' + 'CompositePath',
+ 'blueBex' + 'CompositePath',
+ 'blueRepository' + 'CompositePath',
+ 'blueStaging' + 'Repository', 'maven' + 'Local()'
+ ]
+ def leaked = forbiddenLiveModes.findAll { token ->
+ settings.contains(token) || buildScript.contains(token)
+ }
+ if (!leaked.empty) {
+ throw new GradleException(
+ 'Retired local dependency plumbing remains: ' + leaked)
+ }
+ if (!settings.contains("dependencyMode != 'published-artifact'")
+ || !buildScript.contains(
+ "dependencyMode != 'published-artifact'")) {
throw new GradleException(
- 'Local composite inclusion is not mode-gated')
+ 'Published-only dependency mode is not fail-closed')
}
}
}
+def publishedDependencyIsolation = tasks.register(
+ 'verifyPublishedDependencyIsolation') {
+ group = 'verification'
+ description = 'Resolves the explicit published-artifact graph without included-build substitution.'
+ doLast {
+ if (dependencyMode != 'published-artifact') {
+ throw new GradleException(
+ 'verifyPublishedDependencyIsolation requires '
+ + '-PblueDependencyMode=published-artifact')
+ }
+ def components = configurations.testRuntimeClasspath
+ .incoming.resolutionResult.allComponents
+ def leakedProjects = components.findAll { component ->
+ component.id instanceof
+ org.gradle.api.artifacts.component.ProjectComponentIdentifier
+ && component.id.displayName
+ != "root project '${rootProject.name}'"
+ }.collect { component -> component.id.displayName }.sort()
+ def selectedBlue = components.findAll { component ->
+ component.id instanceof
+ org.gradle.api.artifacts.component.ModuleComponentIdentifier
+ && ['blue.language', 'blue.bex', 'blue.repo'].contains(
+ component.id.group)
+ }.collectEntries { component ->
+ [(component.id.group + ':' + component.id.module):
+ component.id.version]
+ }
+ if (!leakedProjects.empty || selectedBlue != publishedBlueCoordinates) {
+ throw new GradleException(
+ 'Published dependency isolation failed: included projects '
+ + leakedProjects + ', selected modules '
+ + selectedBlue)
+ }
+ logger.lifecycle(
+ 'Published dependency lane resolved without sibling substitution.')
+ }
+}
+
+tasks.register('verifyPublishedArtifactDependencies') {
+ group = 'verification'
+ description = 'Compiles against the explicit isolated published-artifact graph.'
+ if (dependencyMode == 'published-artifact') {
+ dependsOn publishedDependencyIsolation, tasks.named('compileJava')
+ }
+ doLast {
+ if (dependencyMode != 'published-artifact') {
+ throw new GradleException(
+ 'verifyPublishedArtifactDependencies requires '
+ + '-PblueDependencyMode=published-artifact')
+ }
+ logger.lifecycle(
+ 'Published dependencies resolve in isolation and provide the required source API.')
+ }
+}
+
tasks.register('verifyTestArchitecture') {
group = 'verification'
- description = 'Protects release-owned test depth and JAR-only consumer isolation.'
+ description = 'Protects test depth, Given/When/Then shape, and JAR-only consumer isolation.'
def suites = [
unit: fileTree('src/test/java') { include '**/*Test.java' },
integration: fileTree('src/integrationTest/java') {
@@ -2383,11 +2825,11 @@ tasks.register('verifyTestArchitecture') {
dependsOn tasks.named('jar')
doLast {
def minimumTests = [unit: 175, integration: 24,
- consumer: 5, scenario: 2]
+ consumer: 6, scenario: 2]
def failures = []
suites.each { name, sources ->
int methods = sources.files.sum { source ->
- (source.getText('UTF-8') =~ /(?m)^\s*@Test\b/).count
+ (source.getText('UTF-8') =~ /(?m)^[ \t]*@Test\b/).count
} ?: 0
if (methods < minimumTests[name]) {
failures << "${name} has ${methods} @Test methods; "
@@ -2396,6 +2838,89 @@ tasks.register('verifyTestArchitecture') {
logger.lifecycle(
"${name} tests: ${sources.files.size()} classes, "
+ "${methods} methods")
+ sources.files.sort().each { source ->
+ String body = source.getText('UTF-8')
+ def tests = body =~ /(?m)^[ \t]*@Test\b/
+ def starts = []
+ while (tests.find()) {
+ starts << tests.start()
+ }
+ starts.eachWithIndex { start, index ->
+ int annotationLineEnd = body.indexOf('\n', start)
+ if (annotationLineEnd < 0) {
+ annotationLineEnd = body.length()
+ }
+ String annotationLine = body.substring(
+ start, annotationLineEnd)
+ def indentMatch = annotationLine =~ /^(\s*)@Test\b/
+ if (!indentMatch.find()) {
+ failures << "${source}: test ${index + 1} has an " +
+ 'unreadable @Test annotation line'
+ return
+ }
+ String closePattern = ('(?m)^'
+ + java.util.regex.Pattern.quote(
+ indentMatch.group(1))
+ + '\\}\\s*$')
+ def methodClose = body.substring(start) =~ closePattern
+ if (!methodClose.find()) {
+ failures << "${source}: test ${index + 1} has no " +
+ 'matching method close'
+ return
+ }
+ int end = start + methodClose.end()
+ String testBody = body.substring(start, end)
+ def markerRanges = [:]
+ ['given', 'when', 'then'].each { phase ->
+ def marker = testBody =~
+ /(?m)^\s*\/\/ ${phase}\s*$/
+ def ranges = []
+ while (marker.find()) {
+ ranges << [start: marker.start(), end: marker.end()]
+ }
+ markerRanges[phase] = ranges
+ }
+ if (markerRanges.any { phase, ranges ->
+ ranges.size() != 1
+ }) {
+ failures << ("${source}: test ${index + 1} must "
+ + 'contain exactly one // given, // when, '
+ + '// then marker')
+ } else if (!(markerRanges.given[0].start
+ < markerRanges.when[0].start
+ && markerRanges.when[0].start
+ < markerRanges.then[0].start)) {
+ failures << ("${source}: test ${index + 1} must order "
+ + '// given before // when before // then')
+ } else {
+ Closure hasCode = { String phaseBody ->
+ phaseBody.readLines().any { line ->
+ String trimmed = line.trim()
+ !trimmed.isEmpty()
+ && !trimmed.startsWith('//')
+ && !trimmed.startsWith('/*')
+ && !trimmed.startsWith('*')
+ && !['{', '}'].contains(trimmed)
+ }
+ }
+ String givenBody = testBody.substring(
+ markerRanges.given[0].end,
+ markerRanges.when[0].start)
+ String whenBody = testBody.substring(
+ markerRanges.when[0].end,
+ markerRanges.then[0].start)
+ String thenBody = testBody.substring(
+ markerRanges.then[0].end)
+ if (!hasCode(givenBody)
+ || !hasCode(whenBody)
+ || !hasCode(thenBody)) {
+ failures << ("${source}: test ${index + 1} must "
+ + 'contain meaningful given, when, and '
+ + 'then phases')
+ }
+ }
+ }
+ }
}
String integrationSources = fileTree('src/integrationTest/java') {
@@ -2425,6 +2950,28 @@ tasks.register('verifyTestArchitecture') {
}
}
}
+ File sdkConsumer = file(
+ 'src/consumerTest/java/blue/coordination/consumer/'
+ + 'SdkBuiltJarConsumerTest.java')
+ if (!sdkConsumer.isFile()) {
+ failures << 'SDK built-JAR consumer test is missing'
+ } else {
+ String body = sdkConsumer.getText('UTF-8')
+ [
+ 'blue.coordination.api',
+ 'blue.coordination.internal',
+ 'blue.coordination.processor',
+ 'blue.language',
+ 'blue.bex'
+ ].each { forbidden ->
+ if (body.contains(forbidden)) {
+ failures << "SDK built-JAR consumer imports ${forbidden}"
+ }
+ }
+ if (!body.contains('blue.coordination.sdk.BlueCoordination')) {
+ failures << 'SDK built-JAR consumer does not use BlueCoordination'
+ }
+ }
Set mainOutputs = sourceSets.main.output.files.collect {
it.canonicalFile
} as Set
@@ -2452,8 +2999,9 @@ tasks.register('verifyTestArchitecture') {
tasks.register('productionizationCheck') {
group = 'verification'
dependsOn 'build', 'validateProductionShape',
- 'verifyPublicApiBoundary', 'verifyArtifactContents',
- 'verifyPublicationPom', 'verifyPublishedModeIsolation',
+ 'verifyPublicApiBoundary', 'verifySdkPublicApiBoundary',
+ 'verifyArtifactContents',
+ 'verifyPublicationPom', 'verifyDependencyModeIsolation',
'verifyReleaseMetadata', 'verifyDocumentation',
'verifySourceArchiveHygiene', 'scenarioTest',
'verifyTestArchitecture', extractedSourceArchive
@@ -2463,434 +3011,174 @@ tasks.register('releaseCheck') {
group = 'verification'
description = 'Runs the production, API, artifact, and publication gates.'
dependsOn 'build', 'validateProductionShape', 'verifyPublicApiBoundary',
- 'verifyArtifactContents', 'verifyPublicationPom',
- 'verifyPublishedModeIsolation', 'verifyReleaseMetadata',
+ 'verifySdkPublicApiBoundary', 'verifyArtifactContents',
+ 'verifyPublicationPom',
+ 'verifyDependencyModeIsolation', 'verifyReleaseMetadata',
'verifyDocumentation', 'verifySourceArchiveHygiene', 'test',
'integrationTest', 'consumerTest', 'scenarioTest',
'verifyTestArchitecture', extractedSourceArchive
}
-def round13Readiness = tasks.register('verifyRound13Readiness') {
+def rcReadiness = tasks.register('verifyRcReadiness') {
group = 'verification'
- description = 'Requires FINAL, complete Round 13 Playground evidence before staging.'
- inputs.files('docs/releases/3.0.0-rc.1-test-report.md',
- 'docs/releases/3.0.0-rc.1-evidence.json',
- 'docs/releases/round13-verification.schema.json')
- dependsOn sourceArchiveChecksum, extractedSourceArchive,
- 'verifyDocumentation',
+ description = 'Runs the published-dependency gate for the bounded external-pilot RC.'
+ inputs.files('.cz.toml', 'settings.gradle', 'build.gradle',
+ 'gradle/published-artifact.lockfile',
+ 'stabilization/cyclic-topology-rc3-final/final-receipt.json',
+ 'docs/releases/3.0.0-rc.3.md')
+ def rcTestSources = files(
+ fileTree('src/test/java') { include '**/*Test.java' },
+ fileTree('src/integrationTest/java') {
+ include '**/*Test.java'
+ },
+ fileTree('src/consumerTest/java') { include '**/*Test.java' },
+ fileTree('src/scenarioTest/java') { include '**/*Test.java' })
+ inputs.files(rcTestSources)
+ inputs.files(tasks.named('jar').flatMap { it.archiveFile },
+ tasks.named('sourcesJar').flatMap { it.archiveFile },
+ tasks.named('javadocJar').flatMap { it.archiveFile },
+ tasks.named('testFixturesJar').flatMap { it.archiveFile })
+ dependsOn 'releaseCheck', 'dependencyPreflight',
'jar', 'sourcesJar', 'javadocJar', 'testFixturesJar'
+ def readinessReport = layout.buildDirectory.file(
+ 'reports/release/3.0.0-rc.3-readiness.json')
+ outputs.file(readinessReport)
doLast {
- String report = file(
- 'docs/releases/3.0.0-rc.1-test-report.md')
+ def failures = []
+ if (version.toString() != '3.0.0-rc.3') {
+ failures << "release version is ${version}; expected 3.0.0-rc.3"
+ }
+ if (dependencyMode != 'published-artifact') {
+ failures << 'published-artifact is not the active dependency mode'
+ }
+ if (!gradle.includedBuilds.empty) {
+ failures << ('included builds leaked into the release: '
+ + gradle.includedBuilds.collect { it.name }.sort())
+ }
+
+ String lock = file('gradle/published-artifact.lockfile')
.getText('UTF-8')
- def evidence = new groovy.json.JsonSlurper().parse(
- file('docs/releases/3.0.0-rc.1-evidence.json'))
- File sourceArchive = coordinationSourceArchive.get()
- .archiveFile.get().asFile
- File sourceArchiveSidecar = new File(
- sourceArchive.parentFile, sourceArchive.name + '.sha256')
- String sourceArchiveHash = java.security.MessageDigest
- .getInstance('SHA-256').digest(sourceArchive.bytes)
- .encodeHex().toString()
- boolean detachedHashMatches = sourceArchiveSidecar.isFile()
- && sourceArchiveSidecar.getText('UTF-8')
- .startsWith(sourceArchiveHash + ' ' + sourceArchive.name)
- def publishableLatency = ['PASS', 'PASS_HARD_GATE'] as Set
- Closure containsUnfinalized
- containsUnfinalized = { value, String field = null ->
- if (field != 'duplicatesStrategy'
- && ['PENDING_VERIFICATION', 'FAIL', 'BLOCKED',
- 'BLOCKED_UPSTREAM'].contains(value)) {
- return true
- }
- if (value instanceof Map) {
- return value.any { key, nested ->
- containsUnfinalized(nested, key as String)
- }
- }
- if (value instanceof List) {
- return value.any { containsUnfinalized(it, field) }
- }
- return false
- }
-
- Closure gitOutput = { String... arguments ->
- def command = ['git']
- command.addAll(arguments as List)
- Process process = new ProcessBuilder(command)
- .directory(rootDir).start()
- String stdout = process.inputStream.getText('UTF-8')
- String stderr = process.errorStream.getText('UTF-8')
- if (process.waitFor() != 0) {
- throw new GradleException("Git failed: ${stderr}")
- }
- stdout.trim()
- }
- String actualHead = gitOutput('rev-parse', 'HEAD')
- String actualStatus = gitOutput(
- 'status', '--porcelain', '--untracked-files=normal')
- Process ancestorProcess = new ProcessBuilder(
- 'git', 'merge-base', '--is-ancestor',
- evidence.source.candidateCommit ?: 'INVALID', actualHead)
- .directory(rootDir).start()
- boolean testedCommitIsAncestor = ancestorProcess.waitFor() == 0
- Closure sha256 = { byte[] bytes ->
- java.security.MessageDigest.getInstance('SHA-256')
- .digest(bytes).encodeHex().toString()
+ publishedBlueCoordinates.each { coordinate, expectedVersion ->
+ if (!lock.contains("${coordinate}:${expectedVersion}=")) {
+ failures << "dependency lock does not pin ${coordinate}:${expectedVersion}"
+ }
}
- Closure relativeSourcePath = { File source ->
- rootDir.toPath().relativize(source.toPath()).toString()
- .replace(File.separator, '/')
+
+ def semanticReceipt = new groovy.json.JsonSlurper().parse(
+ file('stabilization/cyclic-topology-rc3-final/final-receipt.json'))
+ if (semanticReceipt.receiptState
+ != 'FINAL_EXTERNAL_PILOT_RC_EVIDENCE'
+ || semanticReceipt.overallStatus
+ != 'PASS_FOR_BOUNDED_EXTERNAL_PILOT'
+ || semanticReceipt.implementationConformanceClaimed != true
+ || semanticReceipt.externalPilotReady != true
+ || semanticReceipt.releaseReady != false
+ || semanticReceipt.publicReleaseReady != false
+ || semanticReceipt.productionReleaseReady != false
+ || semanticReceipt.stableLatencySlaClaimed != false) {
+ failures << 'retained semantic evidence does not authorize the bounded pilot tier'
}
- String currentSourceManifest = productionSources.files.sort {
- source -> relativeSourcePath(source)
- }.collect { source ->
- "${relativeSourcePath(source)} ${sha256(source.bytes)}"
- }.join('\n')
- boolean testedSourceStillExact = sha256(currentSourceManifest.getBytes(
- java.nio.charset.StandardCharsets.UTF_8))
- == evidence.source.mainSourceManifestSha256
- String sha256Pattern = /^[0-9a-f]{64}$/
- String sha1Pattern = /^[0-9a-f]{40}$/
- def artifactTasks = [
- mainJarSha256: 'jar',
- sourcesJarSha256: 'sourcesJar',
- javadocJarSha256: 'javadocJar',
- testFixturesJarSha256: 'testFixturesJar'
- ]
- def artifactHashMismatches = []
- artifactTasks.each { evidenceKey, taskName ->
- File artifact = tasks.named(taskName).get()
- .archiveFile.get().asFile
- String actualHash = java.security.MessageDigest
- .getInstance('SHA-256').digest(artifact.bytes)
- .encodeHex().toString()
- def expectedHash = evidence.artifacts[evidenceKey]
- if (!(expectedHash ==~ sha256Pattern)
- || expectedHash != actualHash) {
- artifactHashMismatches << ("${artifact.name}: expected "
- + "${expectedHash}, actual ${actualHash}")
- }
- }
- boolean artifactHashesMatch = artifactHashMismatches.empty
- def campaign = evidence.performance.round13Campaign
- def campaignProvenance = campaign.runtimeProvenanceJson
- && file(campaign.runtimeProvenanceJson).isFile()
- ? new groovy.json.JsonSlurper().parse(
- file(campaign.runtimeProvenanceJson)) : [:]
- def expectedLimitedRcPolicy = [
- mode: 'RC_WITH_KNOWN_PERFORMANCE_LIMITATION',
- exactRelease: '3.0.0-rc.1',
- decision: 'PASS_WITH_KNOWN_PERFORMANCE_LIMITATION',
- performanceReleaseBlocking: false,
- stableReleaseEligible: false,
- nonPerformanceGatesRequired: true,
- explicitWorkflowOptInRequired: true
- ]
- boolean limitedRcPolicy = version.toString() == '3.0.0-rc.1'
- && evidence.release == version.toString()
- && evidence.releasePolicy == expectedLimitedRcPolicy
- boolean latencyExceptionOptIn = providers.gradleProperty(
- 'allowRound13LatencyException').orNull == 'true'
- boolean normalLatencyDecision = evidence.verdict == 'PASS'
- && publishableLatency.contains(
- evidence.status.playgroundLatencyReady)
- && evidence.status.publicRcReady == 'PASS'
- && publishableLatency.contains(campaign.status)
- && campaignProvenance.candidate?.implementationHead
- == evidence.source.candidateCommit
- && campaignProvenance.candidate?.worktreeStatusSha256
- == java.security.MessageDigest.getInstance('SHA-256')
- .digest(new byte[0]).encodeHex().toString()
- && campaign.comparison == 'SAME_MACHINE_INTERLEAVED_AB'
- && campaign.requiredSamples >= 30
- && campaign.measuredSamples >= campaign.requiredSamples
- && [campaign.runtimeMarkdown, campaign.runtimeJson,
- campaign.runtimeProvenanceJson].every {
- it instanceof String && file(it).isFile()
- }
- && campaign.pendingReason == null
- && evidence.proofs.every { it.status == 'PASS' }
- boolean limitedLatencyDecision = latencyExceptionOptIn
- && limitedRcPolicy
- && evidence.verdict
- == 'PASS_WITH_KNOWN_PERFORMANCE_LIMITATION'
- && evidence.status.playgroundLatencyReady
- == 'PASS_WITH_KNOWN_PERFORMANCE_LIMITATION'
- && evidence.status.publicRcReady
- == 'PASS_WITH_KNOWN_PERFORMANCE_LIMITATION'
- && campaign.status == 'PENDING_VERIFICATION'
- && campaign.comparison == 'SAME_MACHINE_INTERLEAVED_AB'
- && campaign.requiredSamples >= 30
- && campaign.measuredSamples == 0
- && campaign.runtimeMarkdown == null
- && campaign.runtimeJson == null
- && campaign.runtimeProvenanceJson == null
- && campaign.pendingReason instanceof String
- && !campaign.pendingReason.isBlank()
- && evidence.proofs.every {
- it.status == 'PASS'
- || (it.id
- == 'five-occurrence-host-versus-frozen-time'
- && it.status == 'PENDING_VERIFICATION')
- }
- def finalizedEvidenceScope = evidence
- if (limitedLatencyDecision) {
- finalizedEvidenceScope = new LinkedHashMap(evidence)
- finalizedEvidenceScope.status = evidence.status.findAll {
- key, ignored -> key != 'playgroundLatencyReady'
- }
- finalizedEvidenceScope.performance = new LinkedHashMap(
- evidence.performance)
- finalizedEvidenceScope.performance.remove('round13Campaign')
- finalizedEvidenceScope.proofs = evidence.proofs.findAll {
- it.id != 'five-occurrence-host-versus-frozen-time'
- }
- }
- File archiveReceiptFile = file(
- 'build/reports/round13/source-archive-verification.json')
- def archiveReceipt = archiveReceiptFile.isFile()
- ? new groovy.json.JsonSlurper().parse(archiveReceiptFile)
- : [:]
- boolean archiveReceiptMatches = archiveReceipt.schemaId
- == 'blue-coordination-round13-source-archive-verification-v1'
- && archiveReceipt.archiveName == sourceArchive.name
- && archiveReceipt.archiveSha256 == sourceArchiveHash
- && archiveReceipt.dependencyMode == 'published-artifact'
- && archiveReceipt.extractedConfiguration == 'PASS'
- && archiveReceipt.focusedTestsStatus == 'PASS'
- && archiveReceipt.publishedArtifactSmokeBuild == 'PASS'
- if (evidence.schemaVersion != '4.1.0'
- || evidence.profile
- != 'ROUND13_PLAYGROUND_FIVE_OCCURRENCE'
- || evidence.evidenceState != 'FINAL'
- || !(normalLatencyDecision || limitedLatencyDecision)
- || !(evidence.source.candidateCommit ==~ sha1Pattern)
- || !(testedCommitIsAncestor || testedSourceStillExact)
- || evidence.source.binding != 'CANDIDATE_COMMIT'
- || evidence.source.worktree != 'CLEAN'
- || evidence.source.dirtyReason != null
- || !actualStatus.isEmpty()
- || evidence.executionBinding.kind
- != 'CANDIDATE_COMMIT_EXECUTION'
- || evidence.executionBinding.candidateCommit
- != evidence.source.candidateCommit
- || evidence.executionBinding.mainSourceManifestSha256
- != evidence.source.mainSourceManifestSha256
- || evidence.executionBinding.reason != null
- || evidence.status.temporalArchitectureReady != 'PASS'
- || evidence.status.inMemoryEngineWorkingReady != 'PASS'
- || evidence.status.playgroundCoreReady != 'PASS'
- || evidence.status.providerBackedReady != 'OUT_OF_SCOPE'
- || evidence.status.mandateAgentReady != 'OUT_OF_SCOPE'
- || evidence.tests.executionStatus != 'PASS'
- || evidence.tests.failures != 0
- || evidence.tests.errors != 0
- || evidence.tests.skipped != 0
- || evidence.tests.suites.any {
- it.status != 'PASS' || it.failures != 0
- || it.errors != 0 || it.skipped != 0
- }
- || evidence.shape.status != 'PASS'
- || evidence.artifacts.status != 'PASS'
- || !artifactHashesMatch
- || evidence.runtimes.any { it.status != 'PASS' }
- || evidence.structuralEvidence.status != 'PASS'
- || evidence.structuralEvidence.requiredZeroCounters.any {
- key, value -> value != 0
- }
- || evidence.sourceArchive.extractedConfiguration != 'PASS'
- || evidence.sourceArchive.focusedTestsStatus != 'PASS'
- || evidence.sourceArchive.publishedArtifactSmokeBuild != 'PASS'
- || evidence.sourceArchive.verificationReceipt.path
- != 'build/reports/round13/source-archive-verification.json'
- || evidence.sourceArchive.checksum.mode
- != 'DETACHED_SHA256_SIDECAR'
- || evidence.sourceArchive.checksum.path
- != 'build/distributions/' + sourceArchive.name + '.sha256'
- || evidence.sourceArchive.checksum.digest != null
- || evidence.sourceArchive.pendingReason != null
- || !archiveReceiptMatches
- || !detachedHashMatches
- || evidence.publicationEvidence.status != 'PASS'
- || !(evidence.publicationEvidence.durationSeconds
- instanceof Number)
- || !evidence.blockers.empty
- || containsUnfinalized(finalizedEvidenceScope)
- || (normalLatencyDecision
- && report.contains('PENDING_VERIFICATION'))
- || report.contains('ROUND13_INTERIM_VERIFICATION:')
- || (normalLatencyDecision && !report.contains(
- 'ROUND13_FINAL_VERIFICATION: PASS'))
- || (limitedLatencyDecision && !report.contains(
- 'ROUND13_FINAL_VERIFICATION: '
- + 'PASS_WITH_KNOWN_PERFORMANCE_LIMITATION'))) {
- String artifactDetails = artifactHashMismatches.empty ? ''
- : ('\nArtifact SHA-256 mismatches:\n - '
- + artifactHashMismatches.join('\n - '))
+ String releaseNotes = file('docs/releases/3.0.0-rc.3.md')
+ .getText('UTF-8')
+ if (!releaseNotes.contains(
+ 'RC3_RELEASE_TIER: BOUNDED_EXTERNAL_PILOT')
+ || !releaseNotes.contains(
+ 'RC3_DEPENDENCY_MODE: PUBLISHED_ARTIFACTS_ONLY')
+ || !releaseNotes.contains(
+ 'RC3_PUBLIC_ARTIFACT_READY: true')
+ || !releaseNotes.contains(
+ 'RC3_PRODUCTION_READY: false')) {
+ failures << 'rc.3 release notes do not state the exact release tier'
+ }
+
+ if (System.getenv('CI') != null) {
+ Process statusProcess = new ProcessBuilder(
+ 'git', 'status', '--porcelain',
+ '--untracked-files=normal').directory(rootDir).start()
+ String status = statusProcess.inputStream.getText('UTF-8').trim()
+ String error = statusProcess.errorStream.getText('UTF-8').trim()
+ if (statusProcess.waitFor() != 0) {
+ failures << "cannot inspect release worktree: ${error}"
+ } else if (!status.isEmpty()) {
+ failures << 'CI release worktree is not clean'
+ }
+ }
+
+ if (!failures.empty) {
throw new GradleException(
- 'Canonical Round 13 evidence does not permit staging'
- + artifactDetails)
+ 'RC readiness failed:\n - ' + failures.join('\n - '))
+ }
+
+ Closure sha256 = { File artifact ->
+ java.security.MessageDigest.getInstance('SHA-256')
+ .digest(artifact.bytes).encodeHex().toString()
}
+ def artifacts = [
+ mainJar: tasks.named('jar').get().archiveFile.get().asFile,
+ sourcesJar: tasks.named('sourcesJar').get()
+ .archiveFile.get().asFile,
+ javadocJar: tasks.named('javadocJar').get()
+ .archiveFile.get().asFile,
+ testFixturesJar: tasks.named('testFixturesJar').get()
+ .archiveFile.get().asFile
+ ]
+ long testMethods = rcTestSources.files.sum { source ->
+ (source.getText('UTF-8') =~ /(?m)^\s*@Test\b/).count
+ } ?: 0L
+ File report = readinessReport.get().asFile
+ report.parentFile.mkdirs()
+ report.setText(groovy.json.JsonOutput.prettyPrint(
+ groovy.json.JsonOutput.toJson([
+ schemaId: 'blue-coordination-rc-readiness-v1',
+ release: version.toString(),
+ status: 'PASS_FOR_BOUNDED_EXTERNAL_PILOT',
+ dependencyMode: dependencyMode,
+ dependencies: publishedBlueCoordinates,
+ testMethods: testMethods,
+ semanticEvidence:
+ 'stabilization/cyclic-topology-rc3-final/final-receipt.json',
+ publicArtifactReady: true,
+ productionReady: false,
+ stableLatencySlaClaimed: false,
+ artifacts: artifacts.collectEntries { name, artifact ->
+ [(name): [
+ file: artifact.name,
+ sha256: sha256(artifact)
+ ]]
+ }
+ ])) + '\n', 'UTF-8')
}
}
tasks.register('stageRelease') {
group = 'publishing'
description = 'Builds the verified Maven Central staging repository.'
- dependsOn 'releaseCheck', round13Readiness,
+ dependsOn(version.toString() == '3.0.0-rc.3'
+ ? rcReadiness
+ : tasks.named('releaseCheck'))
+ dependsOn 'dependencyPreflight',
'publishMavenJavaPublicationToStagingRepository'
+ doFirst {
+ if (dependencyMode != 'published-artifact') {
+ throw new GradleException(
+ 'stageRelease requires the explicit isolated lane: '
+ + '-PblueDependencyMode=published-artifact')
+ }
+ }
}
tasks.named('publishMavenJavaPublicationToStagingRepository') {
- dependsOn round13Readiness
+ dependsOn(version.toString() == '3.0.0-rc.3'
+ ? rcReadiness
+ : tasks.named('releaseCheck'))
+ dependsOn 'dependencyPreflight'
}
-if (localDependencies) {
- File localBexCheckout = file(providers.gradleProperty(
- 'blueBexCompositePath').getOrElse('../blue-bex-java'))
- File localRepositoryCheckout = file(providers.gradleProperty(
- 'blueRepositoryCompositePath')
- .getOrElse('../blue-repository-java'))
- def localSourceInputs = tasks.register('verifyLocalSourceInputs') {
- group = 'verification'
- description = 'Verifies the exact local Repository, BEX, and Language inputs.'
- inputs.files('gradle/bex-source.lock',
- 'gradle/repository-source.lock')
- doLast {
- def readLock = { File lock ->
- lock.readLines('UTF-8')
- .findAll { !it.startsWith('#') && it.contains('=') }
- .collectEntries { line ->
- int separator = line.indexOf('=')
- [(line.substring(0, separator)):
- line.substring(separator + 1)]
- }
- }
- def gitBytes = { File root, String... arguments ->
- def command = ['git', '-C', root.absolutePath]
- command.addAll(arguments as List)
- Process process = new ProcessBuilder(command).start()
- byte[] stdout = process.inputStream.bytes
- String stderr = process.errorStream.getText('UTF-8')
- if (process.waitFor() != 0) {
- throw new GradleException(
- "Git failed in ${root}: ${stderr}")
- }
- stdout
- }
- def sha256 = { byte[] bytes ->
- java.security.MessageDigest.getInstance('SHA-256')
- .digest(bytes).encodeHex().toString()
- }
- def bexLock = readLock(file('gradle/bex-source.lock'))
- def repositoryLock = readLock(
- file('gradle/repository-source.lock'))
- File bex = localBexCheckout
- File repository = localRepositoryCheckout
- File language = new File(repository, '../blue-language-java')
- .canonicalFile
- String bexHead = new String(
- gitBytes(bex, 'rev-parse', 'HEAD'), 'UTF-8').trim()
- String repositoryHead = new String(
- gitBytes(repository, 'rev-parse', 'HEAD'), 'UTF-8').trim()
- String languageHead = new String(
- gitBytes(language, 'rev-parse', 'HEAD'), 'UTF-8').trim()
- if (bexHead != bexLock.commit
- || repositoryHead != repositoryLock.baseCommit
- || languageHead != repositoryLock.languageCommit) {
- throw new GradleException('Local dependency commit drift')
- }
- if (new String(gitBytes(bex, 'status', '--porcelain'),
- 'UTF-8').trim()
- || new String(gitBytes(language, 'status', '--porcelain'),
- 'UTF-8').trim()) {
- throw new GradleException(
- 'Pinned BEX and Language inputs must be clean')
- }
- byte[] repositoryDiff = gitBytes(repository, 'diff', '--binary',
- 'HEAD', '--', 'build.gradle', 'settings.gradle',
- '.cz.toml', 'src/main/java', 'src/main/resources')
- if (sha256(repositoryDiff)
- != repositoryLock.workspaceDiffSha256) {
- throw new GradleException(
- 'Local Repository production diff fingerprint drift')
- }
- logger.lifecycle('Local source inputs match pinned fingerprints.')
- }
- }
- tasks.named('releaseCheck') {
- dependsOn localSourceInputs
- }
-
- def localPrerequisites = tasks.register(
- 'stageLocalPrerequisiteArtifacts') {
- group = 'publishing'
- description = 'Stages source-built prerequisites for local artifact verification.'
- dependsOn gradle.includedBuild('blue-repository-java').task(':jar')
- dependsOn gradle.includedBuild('blue-bex-java')
- .task(':blue-bex-core:jar')
- dependsOn gradle.includedBuild('blue-bex-java')
- .task(':blue-bex-contracts:jar')
- inputs.files(
- fileTree(new File(localRepositoryCheckout, 'build/libs')) {
- include 'blue-repo-java-*-SNAPSHOT.jar'
- exclude '*-sources.jar', '*-javadoc.jar'
- },
- fileTree(new File(localBexCheckout,
- 'blue-bex-core/build/libs')) {
- include 'blue-bex-core-*-SNAPSHOT.jar'
- exclude '*-sources.jar', '*-javadoc.jar'
- },
- fileTree(new File(localBexCheckout,
- 'blue-bex-contracts/build/libs')) {
- include 'blue-bex-contracts-*-SNAPSHOT.jar'
- exclude '*-sources.jar', '*-javadoc.jar'
- })
- outputs.dir(layout.buildDirectory.dir('local-prerequisites'))
- doLast {
- def targetRoot = layout.buildDirectory
- .dir('local-prerequisites').get().asFile
- if (targetRoot.exists() && !targetRoot.deleteDir()) {
- throw new GradleException(
- "Cannot refresh generated artifacts at ${targetRoot}")
- }
- copy {
- from fileTree(new File(
- localRepositoryCheckout, 'build/libs')) {
- include 'blue-repo-java-*-SNAPSHOT.jar'
- exclude '*-sources.jar', '*-javadoc.jar'
- }.singleFile
- into new File(targetRoot,
- 'blue/repo/blue-repo-java/3.0.0-rc.21')
- rename { 'blue-repo-java-3.0.0-rc.21.jar' }
- }
- copy {
- from fileTree(new File(localBexCheckout,
- 'blue-bex-core/build/libs')) {
- include 'blue-bex-core-*-SNAPSHOT.jar'
- exclude '*-sources.jar', '*-javadoc.jar'
- }.singleFile
- into new File(targetRoot,
- 'blue/bex/blue-bex-core/1.1.0-rc.3')
- rename { 'blue-bex-core-1.1.0-rc.3.jar' }
- }
- copy {
- from fileTree(
- new File(localBexCheckout,
- 'blue-bex-contracts/build/libs')) {
- include 'blue-bex-contracts-*-SNAPSHOT.jar'
- exclude '*-sources.jar', '*-javadoc.jar'
- }.singleFile
- into new File(targetRoot,
- 'blue/bex/blue-bex-contracts/1.1.0-rc.3')
- rename { 'blue-bex-contracts-1.1.0-rc.3.jar' }
- }
- }
- }
- tasks.named('publishToMavenLocal') {
- dependsOn localPrerequisites
- }
+tasks.named('releaseCheck') {
+ dependsOn 'verifyPublishedArtifactDependencies'
+}
+tasks.named('productionizationCheck') {
+ dependsOn 'verifyPublishedArtifactDependencies'
}
tasks.matching {
@@ -2902,6 +3190,7 @@ tasks.matching {
tasks.named('check') {
dependsOn 'validateProductionShape', 'verifyPublicApiBoundary',
+ 'verifySdkPublicApiBoundary',
'verifyArtifactContents', 'integrationTest', 'consumerTest',
'verifyTestArchitecture'
}
diff --git a/docs/architecture/compact-engine.md b/docs/architecture/compact-engine.md
index d32b2b1..d3c374a 100644
--- a/docs/architecture/compact-engine.md
+++ b/docs/architecture/compact-engine.md
@@ -4,8 +4,8 @@ The supported runtime has three layers:
1. `blue.coordination.api` is the small immutable application boundary.
2. `blue.coordination.internal` owns one exact journal, whole-object store,
- document store, Channel route index, Process Embedded graph, sequential
- coordinator, processor, and document-local commit boundary.
+ document store, Channel route index, Process Embedded inventories, Root
+ feeder, processor, and copy-on-write closure publication boundary.
3. `blue.coordination.processor` retains the semantic Contracts/BEX workflow
closure used by the compact runtime and advanced processor registration.
@@ -14,30 +14,48 @@ Timeline Entry BlueId, stores the entry once, and publishes its journal
coordinates only after success. It records no recipients and invokes no
document processor.
-The sequential drain coordinator owns processing order. It obtains source
-completeness evidence, selects the next canonical eligible entry, freezes its
-pre-entry direct targets from the active Channel index, and processes one
-dependency-aware entry frame. Descendants finish their complete entry-caused
-epoch segment before an ancestor applies it, and ancestor direct handling runs
-only after those applications. No caller-selected entry may jump the queue.
+The Contracts 1.0 path has one ordered feeder/window for each public Root. Its
+source surface is the union of that Root Timeline and every active embedded
+Timeline reachable from the Root, but exact route selection still identifies
+the directly selected documents. One event may select several disconnected
+Root lanes. A `NeedsResources` result holds only its own lane, so another public
+Root can advance without overtaking work in the blocked lane.
The only dependency topology is derived from effective `Process Embedded.paths`
-and `Process Embedded.collectionPaths`. `EmbeddingBinding` is immutable
-topology and activation identity. Parent progress is a separate
-`EmbeddedEpochCursor`; changing a cursor does not mutate an earlier graph
-snapshot or its generation. One attachment transition owns one extendable
-catch-up barrier, including nested prerequisites.
-
-Initialization, external handling, and parent synchronization all cross the
-frozen processor boundary. Parent synchronization uses an exact private
-`EmbeddedEpochInput` carrying old/new child identity and indexed event
-occurrences; it is never appended as a synthetic Timeline Entry. One document
-transition atomically publishes state, epoch, events, graph/subscription deltas,
-delivery or cursor progress, idempotency receipt, and commit companion. A child
-and its parents are separate commits, so retry can resume a missing parent
-application without rerunning the child.
+and `Process Embedded.collectionPaths`. One complete immutable
+`ManagedOccurrenceInventory` retains the Contracts-owned active and inactive
+typed rows. All authoritative rows determine the affected publication cohort;
+only active rows project into the cycle-capable
+`ProcessEmbeddedComponentIndex`. Coordination never reimplements occurrence,
+binding, component, or invocation identities.
+
+Every selected managed document crosses the same frozen Contracts processor
+boundary as Root. A document receives its own state and exact work evidence; it
+receives no containing-document, parent-path, or reverse-containment context.
+Acyclic documents and members of cyclic components use the same document
+processing function and closure execution context. Contracts alone schedules
+active SCCs and validates their convergence.
+
+One connected closure result publishes as one copy-on-write transaction. The
+transaction CAS-fences every selected per-document head and the relevant
+topology generations, then swaps copied session images together with
+Contracts-owned occurrence rows, component states, subscription projection,
+routes, outbox, checkpoints, and publication receipt. Each document retains its
+own durable head and epoch. Disconnected cohorts share no document-head fence
+and may commit independently.
Ordinary nodes and requests are never sent through a generic splitter. The
layout compiler retains one whole root shell and one whole object for each
effective managed Process Embedded document. The semantic root remains exact
and can be reconstructed from those content-addressed whole objects.
+
+## Legacy compatibility path
+
+`DefaultCoordinationEngine.create()` preserves the earlier acyclic temporal
+profile for compatibility. Its `SequentialDrainCoordinator`,
+`EmbeddingBinding`, `EmbeddedEpochCursor`, private `EmbeddedEpochInput`, and
+child-then-parent document-local commits describe that legacy path only. They
+do not define Contracts 1.0 closure execution. New Contracts hosts opt in with
+`CoordinationEngine.inMemoryContracts10(Contracts10Configuration)`, supplying
+exact final Language and Contracts artifact identities and the public Root
+lineages.
diff --git a/docs/development/build-and-test.md b/docs/development/build-and-test.md
index da68503..7444b06 100644
--- a/docs/development/build-and-test.md
+++ b/docs/development/build-and-test.md
@@ -1,88 +1,97 @@
# Build and test
-## Prerequisites
+## Requirements
-Use Java 17+ and the checked-in Gradle wrapper. Production compiles with Java 17,
-`-Xlint:all` and `-Werror`. Tests can run on a newer LTS with
-`-PtestJavaVersion=21`.
+Use the checked-in Gradle wrapper and JDK 17 or newer. Production classes are
+compiled with `--release 17`; CI executes the complete suite on Java 17 and
+Java 21.
-Published Maven Central artifacts are the default and are used by ordinary
-development, consumer, CI, and release builds. This mode runs no Git commands
-and never reads sibling checkouts.
+## Published dependency graph
-`local-composite` remains an explicit diagnostic mode for coordinated changes
-that have not been published. It substitutes `../blue-bex-java` and
-`../blue-repository-java`, or paths supplied with `-PblueBexCompositePath` and
-`-PblueRepositoryCompositePath`; it must not be used as release evidence.
+Maven Central is the only live dependency source. The default and only accepted
+`blueDependencyMode` is `published-artifact`; the property may be omitted.
+Sibling composite builds, Maven Local, and file-based staging repositories are
+rejected.
-## Coordination gates
+| Modules | Version |
+| --- | --- |
+| `blue.language:*` | `3.1.0-rc.21` |
+| `blue.bex:blue-bex-core`, `blue-bex-contracts` | `1.1.0-rc.4` |
+| `blue.repo:blue-repo-java` | `3.0.0-rc.21` |
+
+Repository rc.21 advertises `blue-language-java:3.1.0-rc.20`. The project
+excludes that one stale transitive edge and directly owns Language rc.21. The
+same exclusion is published in the Coordination POM. The exact graph is locked
+in `gradle/published-artifact.lockfile`.
+
+Verify fresh remote availability and the conflict-free graph with:
```bash
-./gradlew test
-./gradlew integrationTest consumerTest scenarioTest
-./gradlew releaseCheck
-./gradlew stageRelease
+./gradlew --no-daemon dependencyPreflight --refresh-dependencies
+./gradlew --no-daemon verifyPublishedDependencyIsolation
```
-The release-owned suites have distinct responsibilities:
-
-- `test` exercises public value contracts, internal atomic primitives and
- retained workflow/BEX processor semantics.
-- `integrationTest` exercises exact append, engine-selected drain, entry-frame
- ordering, document-local atomic retry, embedded-only storage, `paths` and
- `collectionPaths`, synchronized catch-up, reattachment and ownership.
-- `consumerTest` compiles against the built production JAR, never main source
- output or test fixtures, and verifies the supported public API as a real
- consumer sees it.
-- `scenarioTest` runs NBA admission-order/multi-game convergence and the
- complete large-host/PayNote lifecycle.
-
-`releaseCheck` runs all four suites. It also enforces minimum suite depth,
-validates the production class/line budget and small application API boundary,
-scans the production JAR, validates POM scopes and versions, and checks legal,
-documentation, source and Javadoc artifacts. The current verified status is
-recorded in the RC report; commands listed here are gates to run, not claims
-that a changed source snapshot has passed them.
-`stageRelease` creates a Maven Central-shaped repository at
-`build/staging-deploy`.
-
-To prove external dependency availability:
+## Verification
+
+The complete local release gate is:
```bash
-./gradlew dependencyPreflight
+./gradlew --no-daemon --no-build-cache clean releaseCheck \
+ -PtestJavaVersion=17
```
-That command fails closed unless every pinned prerequisite is published.
+Repeat with `-PtestJavaVersion=21` before release. The suites are:
-## Historical performance evidence
+| Task | Boundary |
+| --- | --- |
+| `test` | SDK, compiler, immutable values, and compact internals |
+| `integrationTest` | In-memory engine behavior, retries, topology, and atomicity |
+| `consumerTest` | Compilation and execution against the built production JAR |
+| `scenarioTest` | Complete business and convergence scenarios |
-`../blue-basic` is deliberately outside the library's correctness and release
-gate. It retains historical step timings, percentile campaigns and comparative
-metrics so performance investigations remain reproducible without coupling the
-published library to a sibling checkout. Run it only when collecting or
-comparing performance evidence:
+Every `@Test` must contain exactly one ordered, meaningful lowercase
+`// given`, `// when`, `// then` sequence.
+`verifyTestArchitecture` enforces that source shape together with suite depth
+and built-JAR consumer isolation.
-```bash
-./gradlew publishToMavenLocal
-../blue-basic/gradlew -p ../blue-basic performanceTest runtimeCampaign
-```
+`releaseCheck` also verifies public API boundaries, artifact contents,
+publication POM metadata and exclusions, documentation links, dependency
+isolation, source-archive hygiene, and an extracted source-archive build.
+
+## RC readiness
-The runtime campaign is deliberately slower: it collects repeated samples so
-percentile comparisons are not based on one noisy run. A missing or failing
-`../blue-basic` checkout cannot make `releaseCheck` pass or fail.
+For the current bounded external-pilot candidate, run:
-See [test strategy](test-strategy.md) for the behavior-to-suite map and the
-rules that prevent release verification from drifting back into a demo module.
+```bash
+./gradlew --no-daemon --no-build-cache verifyRcReadiness \
+ -PtestJavaVersion=17
+```
-## Lock files
+This task includes `releaseCheck` and `dependencyPreflight`, validates the
+rc.3 release authority and explicit non-claims, then records the freshly built
+artifact hashes in
+`build/reports/release/3.0.0-rc.3-readiness.json`.
-Regenerate the appropriate dependency lock only after an intentional version
-change:
+The source distribution and checksum can be built independently with:
```bash
-./gradlew dependencies --write-locks
+./gradlew coordinationSourceArchive coordinationSourceArchiveChecksum
+./gradlew verifyExtractedSourceArchive
```
-Review the entire lock diff. Never hand-wave an unexpected transitive version.
-Only regenerate the local-composite lock during an explicit cross-repository
-diagnostic by adding `-PblueDependencyMode=local-composite`.
+The extracted archive resolves the same Maven Central graph and never reaches
+an adjacent checkout.
+
+## Focused development
+
+Use focused Gradle test filters while iterating, but finish with
+`releaseCheck`. Tests compiled against the built JAR must not import
+`blue.coordination.internal`, processor implementations, integration
+fixtures, Language, or BEX types.
+
+The optional `../blue-basic` checkout is historical performance tooling. It
+is not read by the build and is not release evidence.
+
+See [Test strategy](test-strategy.md),
+[Releasing](releasing.md), and the
+[3.0.0-rc.3 decision](../releases/3.0.0-rc.3.md).
diff --git a/docs/development/internals.md b/docs/development/internals.md
index 0e1bc9a..5ff58ec 100644
--- a/docs/development/internals.md
+++ b/docs/development/internals.md
@@ -4,42 +4,150 @@ The engine has one mutation owner: `DefaultCoordinationEngine`. Calls are
synchronized because the supported boundary is deterministic, single-process
coordination rather than parallel publication.
+## SDK delegation boundary
+
+`BlueCoordination` and the public values in `blue.coordination.sdk` are an
+additive facade over that same mutation owner. They do not contain a scheduler,
+graph algorithm, gas policy, cyclic identity algorithm, or publication store.
+`SdkCoordinationRuntime` owns the low-level engine and translates SDK calls;
+`SdkDrainResultMapper` translates retained Contracts attempts into immutable
+SDK results. Both, together with `SdkPreconditions`, must remain package-private.
+
+Ordinary `ManagedDocument` admission is compiled as a complete one-member
+Contracts closure. `ManagedClosure` admission passes authored documents,
+aliases, occurrence-lineage evidence, public Roots, and activation inputs to
+`Contracts10AuthoredClosureCompiler`. The compiler resolves each authored
+document, derives the effective `Process Embedded` catalog, validates exact
+binding agreement and completeness, and calls the pinned cyclic finalizer and
+proof verifier. It must never accept caller-supplied component membership,
+cyclic proofs, snapshots, or an independent graph.
+
+The SDK's exact release root comes from `BundledContracts10Release`. Bundled
+identities are release evidence, not user configuration. The only intentionally
+public implementation types in `blue.coordination.internal` are the low-level
+factory boundary `DefaultCoordinationEngine`, the bundled release loader
+`BundledContracts10Release`, and the authored compiler
+`Contracts10AuthoredClosureCompiler`; the build maintains that exact allowlist.
+
+Operation targeting is evidence on the appended request. It narrows the
+profile's eligible target without allowing the caller to name the resulting
+recipient set. Broadcast admission remains a different, explicit SDK path.
+The result mapper consumes retained per-entry Contracts attempts directly; it
+does not call `onlyOutcome()` and therefore preserves valid zero-recipient
+`NO_MATCH` and independent disconnected closure outcomes.
+
+From-now managed drafts use the same Contracts execution and publication path.
+`SdkCoordinationRuntime` converts owner-bound exact drafts, managed request
+fields, and expected effective paths into one `ContractsManagedDraftPlan`.
+`ContractsClosureAdapter` preflights the target and declared paths before
+journal append, then validates the PROCESS result against the request evidence,
+expands the affected closure, and stages every new head, occurrence row,
+component, route, checkpoint, event, and receipt in the existing atomic
+publication. It does not call the legacy child/parent lane or introduce a
+second graph. A retry retains the plan while progress remains possible; a
+terminal result retires it. Known-epoch imports and activation modes other than
+new `FROM_NOW` fail closed before append.
+
The append path validates and retains one exact request and Timeline Entry,
then commits its journal coordinates and logical clock. It does not scan
documents, encode a target document, or invoke PROCESS.
-The drain path uses an ordered journal cursor and `OperationRouteIndex` to
-select the canonical next entry and its direct document targets. A frozen graph
-snapshot supplies the ancestor closure. `SequentialDrainCoordinator` closes one
-child-first entry frame before selecting another external entry; append order
-and caller choice are not semantic order.
+The Contracts drain path uses exact `OperationRouteIndex` rows to freeze the
+direct deliveries for one canonical Timeline Entry. The Root feeder retains one
+ordered lane over the union of each public Root and its active embedded
+Timelines. `ContractsRootFeederWindow` records progress by exact event, source
+order, lane, cohort, and invocation identity. A resource suspension blocks only
+that lane; terminal disconnected cohorts are never re-driven.
+
+Route publication is exact-key incremental. A resulting Contracts subscription
+surface is reduced to exact active intervals: ADD begins after its triggering
+source order, REMOVE disappears, and stable REPLACE retains its interval while
+refreshing the header and checkpoint evidence. The route projection and its
+generation publish before a cohort receipt becomes terminal.
+
+`ProcessEmbeddedComponentIndex` is the cycle-capable active topology view. It
+collapses exact directed SCCs, indexes weakly connected active cohorts,
+and orders each condensation target before its sources with `DocumentId`
+scalar ordering as the only tie-breaker. `ManagedOccurrenceInventory` is the
+complete immutable source: it retains Contracts-owned active and
+inactive occurrence rows, verifies loaded identity assertions through the
+Contracts factory, and projects only active rows as component-index edges.
+Retirement and later activation are separate atomic inventory transitions.
+All authoritative rows, including inactive reservations, connect the affected
+closure publication cohort. Building the active component index does not alter
+that durable all-row cohort boundary.
+
+The SDK exposes only the narrow operational projection of one retained row:
+`AdvancedCoordination.auditManagedOccurrence(sourceId, path)` returns the
+target lineage, activation generation, and active flag as a
+`ManagedOccurrenceAudit`. It does not expose inventory records, components,
+proofs, or mutable topology state.
-Route publication is exact-key incremental: a staged document surface is
-diffed against its published `RouteKey` rows, unchanged rows retain identity,
-and only changed keys are removed or inserted. Graph publication compares
-already-cached direct occurrences; unchanged topology opens no reconciliation
-or publication savepoint. A topology change replaces only the affected parent
-bucket, changed child reverse buckets, and changed binding records, while all
-unchanged bucket lists and binding objects remain structurally shared.
+`InMemoryDocumentStore` exposes the package-internal Contracts publication
+seam. One attempt fences every selected document head by durable
+epoch and exact BlueId, plus the occurrence-inventory and component-index
+generations. It copies only selected `DocumentSession` images, stages the
+Contracts-owned inventory, per-document graph generations, components,
+subscriptions, routes, outbox, checkpoints, and receipt, then swaps one
+immutable store state. A stale fence or staging failure leaves the published
+state reference untouched. There is deliberately no global document-head
+fence: transactions over disconnected cohorts may commit independently.
`EmbeddedOnlyLayoutBuilder` cuts only active `Process Embedded` fields. Ordinary
content remains inline; managed children are stored as whole exact objects.
Both explicit `paths` and direct stable-key members under `collectionPaths`
-produce bindings. Binding topology is immutable; per-occurrence epoch cursors
-are persisted separately.
-
-Historical catch-up is an iterative feeder, not a precomputed list. After every
-committed historical step it refreshes subscriptions, graph bindings, nested
-barriers, and completeness evidence before selecting the next candidate. Parent
-synchronization uses exact processor-owned `EmbeddedEpochInput`; the host never
-pre-replaces the child field and never fabricates an internal Timeline Entry.
-
-Atomicity is document-local. A successful child epoch remains committed if a
-later parent application fails. Receipts and cursors publish with the document
-transition they describe, and commit companions reconcile an uncertain return.
-Do not reintroduce a whole-engine snapshot/restore transaction.
-
-Types in `blue.coordination.internal` are package-private except the concrete
-engine factory target. Applications must depend on `blue.coordination.api`.
-Test-only inspection and failure injection live in the test-fixtures artifact,
-never the main JAR.
+produce Contracts-owned occurrence rows. This storage representation is not
+ambient processor context: each managed document is captured and processed
+from its own exact state as Root.
+
+`ContractsClosureAdapter` captures a connected affected closure, while
+`ContractsRootFeederCoordinator` invokes each eligible cohort independently.
+Inside an invocation, Contracts executes every selected document as a separate
+ordinary work occurrence. Acyclic and cyclic documents use the same processor
+and execution context; Coordination neither recurses into containers nor
+implements a second cyclic scheduler.
+
+The feeder's durable state retains lane-local resource barriers and terminal
+frontiers across coordinator restart. A terminal publication receipt binds the
+entry BlueId, source order, cohort/lane, and invocation identity. Restart
+rebuilds route state from the durable store before receipt reconciliation, so a
+crash after copy-on-write swap cannot execute the committed cohort again.
+
+`BlueCoordination.inMemory()` is the normal lifecycle boundary and creates the
+Contracts 1.0 engine with bundled exact identities and initially empty public
+Root authorization. Authored SDK admission extends that authorization with the
+admitted public Roots. `CoordinationEngine.inMemoryContracts10(...)` remains the
+advanced explicit-identity lifecycle; `DefaultCoordinationEngine.createContracts10(...)`
+implements both paths and preserves feeder recovery state when reconstructing
+coordinators from stores. It never invents release digest placeholders.
+
+`ContractsClosureAdmissionAdapter` owns the bounded all-new admission lane. It
+verifies the exact `ADMIT_CLOSURE` operation, environment, execution policy,
+public Roots, and complete member set, executes the real Contracts admission,
+then stages expected-absent fences and every new `DocumentSession` in the same
+copy-on-write publication as heads at epoch zero, inventory, components and
+proofs, graph generations, subscriptions, checkpoints, outbox, and one typed
+durable admission receipt. `NeedsResources` and non-committing results return
+without mutation. A retry with the exact host publication identity restores
+route-cache rows from durable sessions and returns the retained attempt without
+executing Contracts again.
+
+This 1.0 lane requires every member to be absent. An all-present request without
+the exact receipt is stale, and mixed existing/new membership fails closed
+because complete existing-head fences have not been supplied. Contracts-mode
+`startDocument` also remains fail-closed: it never seeds a legacy singleton
+`DocumentSession`/DAG and presents that state as admitted closure evidence.
+
+## Legacy compatibility internals
+
+`DefaultCoordinationEngine.create()` retains `SequentialDrainCoordinator`, the
+acyclic child-first graph, iterative historical catch-up, `EmbeddedEpochInput`,
+per-occurrence cursors, and document-local child/parent commits. These classes
+remain for the earlier temporal profile and must not be used to infer Contracts
+1.0 closure semantics.
+
+Applications should depend on `blue.coordination.sdk`. Existing hosts may use
+`blue.coordination.api` or `blue.advanced().rawEngine()` during migration.
+Except for the exact allowlist above, types in `blue.coordination.internal`
+remain package-private. Test-only inspection and failure injection live in the
+test-fixtures artifact, never the main JAR.
diff --git a/docs/development/releasing.md b/docs/development/releasing.md
index fe03598..7ee6c57 100644
--- a/docs/development/releasing.md
+++ b/docs/development/releasing.md
@@ -1,107 +1,96 @@
# Releasing
-## Candidate prerequisites
-
-An RC is releasable only when all exact coordinates in `build.gradle` resolve
-from Maven Central. In particular, 3.0.0-rc.1 requires Repository rc.21 and BEX
-rc.3. Local composite success is semantic evidence, but it is not proof that an
-external consumer can resolve the release.
-
-Repository rc.21 is the first pinned release containing the Repository surface
-required by this Coordination candidate.
-
-## RC workflow
-
-1. Merge the candidate to `next`.
-2. The RC workflow derives the next version, updates `.cz.toml`, creates a
- release commit and annotated tag locally.
-3. `dependencyPreflight` resolves all prerequisites in published-artifact mode.
-4. `clean stageRelease` reruns the full release gate and builds the staging
- repository.
-5. JReleaser's deploy task verifies, signs, checksums and uploads the staged
- artifacts to Maven Central.
-6. Only after successful publication does the workflow push the release commit
- and tag.
-
-The workflow uses GitHub Actions concurrency to serialize releases. Required
-secrets are `WORKFLOW_PAT`, Maven Central username/password and the JReleaser GPG
-public key, secret key and passphrase.
-
-Release artifacts have one canonical producer: GitHub's Ubuntu 24.04 `x64`
-runner with Eclipse Temurin 17.0.19+10. The workflow disables Gradle toolchain
-auto-discovery/download and the build cache while generating and checking the
-published bytes. The pull-request Java 17 lane uses that same producer and runs
-`verifyRound13Readiness`, so toolchain or artifact-hash drift is rejected before
-merge instead of first appearing in the post-merge release job. The Java 21
-lane uses Eclipse Temurin 21.0.11+10 for test execution while production
-artifacts continue to be compiled by the canonical Java 17 toolchain.
-
-## Stable workflow
-
-Stable release is manual, restricted to `main`, and requires an exact
-`MAJOR.MINOR.PATCH` version in `.cz.toml`. It follows the same dependency
-preflight, staging, signing and publication path as an RC.
-
-## Verification checklist
-
-- `releaseCheck` passes all library-owned unit, integration, built-JAR consumer
- and end-to-end scenario suites without `../blue-basic`.
-- The one canonical Round 13 report and JSON evidence use the Round 13
- Playground schema. For 3.0.0-rc.1 only, `verifyRound13Readiness` accepts
- `FINAL` evidence with policy mode
- `RC_WITH_KNOWN_PERFORMANCE_LIMITATION` when the release workflow explicitly
- opts in. The verdict, public-RC status, and latency status must be
- `PASS_WITH_KNOWN_PERFORMANCE_LIMITATION`; the current campaign and performance
- proof remain `PENDING_VERIFICATION`. This exception never waives the clean-
- commit binding, Java 17/21 lanes, six non-performance proof rows, eight
- measured zero counters, final artifact hashes, detached source-archive
- verification, published-mode evidence, POM metadata, checksums, or signatures.
- The tested implementation commit may precede the clean evidence commit, but
- it must be an ancestor and the current main-source manifest must still match
- exactly.
-- Java 17 and Java 21 CI jobs pass, including the Java 17 pre-merge staging-
- readiness check.
-- POM dependencies and scopes match `docs/reference/public-api.md`.
-- Main, sources and Javadoc JAR hashes reproduce across two clean builds.
-- Staged POM, checksum and signature inventory is complete.
-- Changelog, migration notes, limitations and RC notes are current.
-- The external Maven consumer resolves without adjacent sibling repositories.
-- The exported source archive contains the authoritative `.cz.toml`, configures
- from its own contents, and excludes nested ZIPs, build output, macOS metadata,
- profiler recordings, and heap dumps.
-
-Historical `blue-basic` metrics may be captured for performance comparison,
-but they are not an RC correctness prerequisite and are never substituted for
-the library-owned suites.
-
-Never bypass dependency preflight or publish from local composite resolution.
-
-## 3.0.0-rc.1 known-performance-limitation policy
-
-The 3.0.0-rc.1 workflow has one narrow exception so the release candidate can
-be published for external evaluation:
-
-- `mode`: `RC_WITH_KNOWN_PERFORMANCE_LIMITATION`
-- `exactRelease`: `3.0.0-rc.1`
-- `decision`: `PASS_WITH_KNOWN_PERFORMANCE_LIMITATION`
-- `performanceReleaseBlocking`: `false`
-- `stableReleaseEligible`: `false`
-- `nonPerformanceGatesRequired`: `true`
-- `explicitWorkflowOptInRequired`: `true`
-
-The workflow must opt in explicitly; a normal local staging call, another RC,
-or a stable release cannot inherit the exception. Every non-performance gate
-listed above remains fail-closed.
-
-The retained historical campaign remains `FAIL`: append p95 was 18.680667 ms
-against a 1.000000 ms hard limit, and Coordination-host p95 was 872.356126 ms
-against 250.000000 ms. Route and total passed their hard limits, but all four
-preferred targets were missed. The old Markdown, JSON, and provenance receipts
-remain unchanged as audit evidence. Their temporary `Archive.zip` input is not
-a release artifact, is not needed to build or publish 3.0.0-rc.1, and must not be
-reintroduced as a staging prerequisite. The current published-artifact campaign
-and performance proof remain `PENDING_VERIFICATION`; no latency pass is claimed.
-
-Performance remediation and a passing campaign are required before any stable
-release. The tracked source-archive evidence intentionally leaves its digest
-`null`; the generated detached `.sha256` sidecar remains the checksum authority.
+## Current decision
+
+`3.0.0-rc.3` is authorized as a bounded external-pilot release candidate.
+It is not stable or production-ready. The exact scope and non-claims are in the
+[rc.3 release decision](../releases/3.0.0-rc.3.md).
+
+The release consumes only Maven Central artifacts:
+
+| Component | Version |
+| --- | --- |
+| Language | `3.1.0-rc.21` |
+| BEX core/contracts | `1.1.0-rc.4` |
+| Repository | `3.0.0-rc.21` |
+| Coordination | `3.0.0-rc.3` |
+
+Repository rc.21's stale Language rc.20 transitive edge is excluded; the direct
+Language rc.21 pin is authoritative and the generated POM publishes the same
+exclusion.
+
+## Before merging to `next`
+
+From a clean feature branch:
+
+```bash
+node --test .github/scripts/prepare-rc-release.test.js
+./gradlew --no-daemon dependencyPreflight --refresh-dependencies
+./gradlew --no-daemon --no-build-cache clean releaseCheck \
+ -PtestJavaVersion=17
+./gradlew --no-daemon --no-build-cache clean releaseCheck \
+ -PtestJavaVersion=21
+./gradlew --no-daemon --no-build-cache verifyRcReadiness \
+ -PtestJavaVersion=17
+git diff --check
+```
+
+Confirm that the branch contains the current `next` tip, has no unresolved
+merge entries, and has no uncommitted changes. Do not create the release tag
+manually.
+
+## Automated RC workflow
+
+A push to `next` starts `.github/workflows/release-rc.yml`. It:
+
+1. checks out the complete history and tags;
+2. pins Temurin 17.0.19+10 for the canonical build and Temurin
+ 21.0.11+10.0.LTS for compatibility verification;
+3. validates release credentials and the wrapper;
+4. prepares the version authorized by `docs/releases/3.0.0-rc.3.md`;
+5. creates the annotated tag locally and verifies push permissions;
+6. resolves the exact published dependency graph;
+7. runs the complete Java 21 release gate before any staging;
+8. runs `stageRelease` on Java 17, including the complete release and rc.3
+ gates;
+9. deploys the signed bundle to Maven Central;
+10. pushes the release commit, if any, and tag only after deployment succeeds;
+11. archives JARs, source distribution, reports, test results, staging output,
+ and JReleaser evidence.
+
+The tag is intentionally absent while Maven Central publication is pending.
+A failed gate or deployment leaves the remote tag untouched.
+
+The separate Build workflow independently repeats `releaseCheck` on Java 17
+and Java 21 and runs `verifyRcReadiness` on the canonical Java 17 lane.
+
+## Manual diagnostics
+
+These commands are read-only with respect to remote Git and Maven Central:
+
+```bash
+./gradlew dependencyPreflight --refresh-dependencies
+./gradlew verifyPublishedDependencyIsolation verifyPublicationPom
+./gradlew verifyTestArchitecture
+```
+
+`stageRelease` writes only `build/staging-deploy`; remote deployment is
+owned by JReleaser in CI.
+
+## Release tier and limitations
+
+The candidate supports one JVM, in-memory state, sequential drain,
+public-Root-scope closures, bounded cyclic components, and new exact
+`FROM_NOW` operation-produced lineages. It does not claim process-restart
+recovery, durable provider completeness, provider-backed Mandates,
+parallel/distributed scheduling, production MyOS operations, or a stable
+latency SLA.
+
+The retained local rc.3 receipt proves the semantic bounded-pilot profile. Its
+local artifact hashes are historical and are not compared with Maven Central
+bytes. `verifyRcReadiness` produces fresh artifact hashes after executing the
+current published-dependency build.
+
+The rc.1 Round 13 reports and schemas are immutable historical evidence. Their
+performance exception is rc.1-specific and is not part of rc.3 or any future
+stable release.
diff --git a/docs/development/test-strategy.md b/docs/development/test-strategy.md
index 7e5df60..773bb2c 100644
--- a/docs/development/test-strategy.md
+++ b/docs/development/test-strategy.md
@@ -8,18 +8,75 @@ consumer checkout to prove that it works.
| Suite | Boundary | Primary guarantees |
| --- | --- | --- |
-| `test` | Types and compact internals | Immutable values, closed inputs, graph/cursor immutability, exact event occurrences, ordered journal cursors, plan caches, workflow state and BEX accounting |
+| `test` | SDK, types, compiler and compact internals | Immutable SDK values, authored closure compilation, exact targeting/results, closed inputs, graph/cursor immutability, exact event occurrences, workflow state and BEX accounting |
| `integrationTest` | In-memory engine with public operations | Append/process separation, engine-selected drain, entry-frame ordering, admission, collection paths, catch-up barriers, identity, ownership, atomic retry and removal/re-addition |
-| `consumerTest` | Built production JAR only | Published append/drain API, runtime dependency completeness and representative managed-document behavior |
+| `consumerTest` | Built production JAR only | SDK compilation without main-source output or test fixtures, runtime dependency completeness and representative managed-document behavior |
| `scenarioTest` | Complete business lifecycles | Multi-order NBA convergence and the large host/PayNote lifecycle |
The suites intentionally overlap at important boundaries. Atomicity has focused
integration coverage and is exercised again by realistic scenarios. The
consumer suite repeats representative behavior because compilation and
execution against the JAR catch packaging and dependency mistakes that
-source-based tests cannot.
+source-based tests cannot. The complete graph resolves from Maven Central in
+every suite and CI repeats the release gate on Java 17 and Java 21.
-## Round 10.1 semantic gates
+## Given/When/Then structure
+
+Every `@Test` has exactly one meaningful lowercase sequence:
+
+```java
+// given
+
+// when
+
+// then
+```
+
+Setup belongs under `given`, the behavior being exercised under `when`, and
+observable outcomes under `then`. Exception tests may prepare an `Executable`
+under `when` and assert it under `then`. `verifyTestArchitecture` rejects
+missing, duplicated, or misordered markers across all four source sets.
+
+## SDK freeze acceptance
+
+SDK acceptance tests stay in `blue.coordination.sdk`, use public facade values,
+and never construct `ClosureInvocationInput`, component snapshots, occurrence
+bindings, cyclic proofs, or an internal evidence factory. The current suite
+proves:
+
+- counter `+3/-1` through exact targeted operations;
+- target isolation from an unrelated PayNote;
+- terminal broadcast `NO_MATCH` and precise missing-target `REJECTED`;
+- finite two-, three-, and five-member cyclic shapes with exact step order,
+ epochs, BlueIds, gas, changes, and public events;
+- two disconnected affected closures retained as independent results;
+- shared-gas loop rollback and deterministic retry evidence;
+- detach followed by a terminating call;
+- remove/re-add with fresh authenticated cyclic identities;
+- append-only `submit()` parity with `execute()`;
+- an operation-produced Order draft admitted as a new `FROM_NOW` lineage;
+- five effective occurrences mapped to three new lineages, including duplicate
+ lineage reuse and declaration-order permutations;
+- managed-draft preflight, exact-path/value completeness, atomic rollback, and
+ deterministic retry failure matrices;
+- immutable owner-bound values and a consumer compiled from the built JAR.
+
+Operation-result managed admission is deliberately limited to new `FROM_NOW`
+lineages. Acceptance tests prove that a known imported epoch and every
+historical/frontier/attach-current/passive activation request fail before
+append, without partial document or topology mutation. The final conformance
+decision is rechecked by the complete published-dependency acceptance and
+fixture corpus; a focused source-suite pass alone is insufficient.
+
+## Recovered topology evidence
+
+The recovered cyclic-topology branch is verified with the bounded focused
+campaign: A-B-A, A-B-C-A, five-member shared-A, disconnected cycles, detach and
+split, post-detach termination, remove/re-add, 1,000-unrelated locality, and
+the short topology smoke. The old long percentile campaign is not rerun for the
+SDK delta. Its retained receipts are historical evidence and remain unchanged.
+
+## Historical Round 10.1 semantic gates
No test may choose processing order with a named entry. Tests append all facts,
call `drain()` or `drainThrough(cutoff)`, and assert the environment-selected
@@ -39,6 +96,9 @@ Release-owned coverage must prove:
- exact processor-owned parent inputs with indexed event identity;
- document-local failure/retry, commit-companion reconciliation and coordinator
reconstruction through the same-live-engine retained-state seam;
+- copy-on-write multi-head success, stale-CAS rejection, pre-swap injected
+ failure rollback, and disconnected transaction isolation at the unwired
+ closure-publication store seam;
- known current/older states, divergent-state rejection and independent equal
BlueIds under different DocumentIds;
- both `paths` and direct stable-key `collectionPaths` discovery;
@@ -49,7 +109,8 @@ Release-owned coverage must prove:
replay, and whole post-PROCESS projections.
The exact same-source results, counts, skips, runtime evidence and structural
-counters belong in the [RC test report](../releases/3.0.0-rc.1-test-report.md).
+counters for that historical candidate belong in the
+[rc.1 test report](../releases/3.0.0-rc.1-test-report.md).
A test-count floor is only a regression tripwire; it is not proof that the
requirements above pass.
diff --git a/docs/limitations.md b/docs/limitations.md
index 0bd7a89..f32adf0 100644
--- a/docs/limitations.md
+++ b/docs/limitations.md
@@ -1,5 +1,21 @@
# Known limitations
+- The rc.3 artifact is a bounded external-pilot release candidate resolved
+ from Maven Central. It is not stable, production-ready, or a production MyOS
+ runtime.
+- Managed-child admission from an operation result supports only new
+ `FROM_NOW` lineages with exact draft/request evidence and a complete set of
+ effective occurrence paths. Imported draft epochs and
+ full-history/frontier/attach-current/passive activation are unsupported and
+ fail closed. There is no fallback to legacy child/parent admission.
+- The supported external-pilot profile is one JVM, in-memory, sequential drain,
+ public-Root-scope closures, and bounded cyclic components. It has no
+ fresh-process durable recovery, provider-completeness adapter, provider-backed
+ Mandate resolver, parallel/distributed scheduling, or stable latency SLA.
+- Production MyOS still requires durable stores, exact restart recovery,
+ authorization and tenant isolation, provider completeness, outbox recovery,
+ operational backpressure, and production observability. Those are separate
+ adapter/profile phases and are not simulated by the SDK.
- Managed embedded-document epochs and historical synchronization are a
next-version Coordination temporal profile. They are not claimed as frozen
Contracts 1.0 semantics.
@@ -9,6 +25,11 @@
subscription surface remains a frozen-API gap.
- Journal completeness is proven only for the current in-memory journal. There
is no durable or distributed transaction protocol.
+- The copy-on-write multi-document publication API is currently package
+ internal and in-memory. It proves selected-head and managed-topology CAS plus
+ one-swap rollback, but `SequentialDrainCoordinator` is not wired to it and no
+ serialized adapter yet reloads its inventory, component state, outbox,
+ checkpoint evidence, or publication receipts.
- The pinned generic Timeline Entry has no universal literal `documentId`
field. This is an optional generalized targeting-profile gap, not a blocker
for append-once/environment-derived routing: concrete Channel/message types
@@ -28,8 +49,9 @@
cursor, epoch, entry-frame, commit-companion, and provider-completeness
evidence is durably available.
- Drain is intentionally sequential. Parallel document processing, leasing,
- distributed scheduling, SCC planning, and caller-selected target sets are out
- of scope.
+ distributed scheduling, a second SCC planner, and caller-selected recipient
+ sets are out of scope. The normal SDK may select an exact operation target;
+ the environment still derives the resulting recipients.
- `DrainBudget` bounds selected entries and committed PROCESS transitions. It
cannot preempt one frozen processor call, does not count epoch-zero
INITIALIZE inside an atomic attachment, and is not a hard latency deadline.
@@ -41,10 +63,9 @@
- The retained Round 13 campaign failed append p95 (18.680667 ms against a
1.000000 ms hard limit) and Coordination-host p95 (872.356126 ms against a
250.000000 ms hard limit); route and total passed hard, while all four metrics
- missed their preferred targets. The exact 3.0.0-rc.1 workflow policy permits
- publication only as `PASS_WITH_KNOWN_PERFORMANCE_LIMITATION`. It does not
- claim a latency pass, cannot apply to a stable release, and does not waive any
- non-performance release gate.
+ missed their preferred targets. The historical 3.0.0-rc.1 workflow policy
+ permitted only `PASS_WITH_KNOWN_PERFORMANCE_LIMITATION`. It does not claim a
+ latency pass and cannot be applied to rc.3 or a stable release.
- Immutable graph generations structurally share unchanged forward/reverse
buckets and binding records, but a topology-changing publication still makes
shallow copies of the three top-level in-memory directory maps. This RC does
diff --git a/docs/operations/failure-model.md b/docs/operations/failure-model.md
index b07967d..3355418 100644
--- a/docs/operations/failure-model.md
+++ b/docs/operations/failure-model.md
@@ -17,6 +17,24 @@ state CAS succeeds but the caller loses the response, retry reconciles the
commit companion and does not invoke frozen PROCESS or publish another
revision. Duplicate exact journal admission is similarly idempotent.
+The Contracts closure-publication store seam has a stronger, deliberately
+narrow boundary for one affected closure. PROCESS checks every selected
+epoch/BlueId head and the occurrence-inventory/component-index generations.
+All-new `ADMIT_CLOSURE` instead fences every member as expected absent. The
+resulting sessions or revisions, complete occurrence inventory, affected
+component states/proofs, graph generations, subscriptions, public outbox,
+checkpoint receipts, and typed publication receipt are built off-store and
+become visible by one state-reference swap. Any stale CAS or injected pre-swap
+failure publishes none of them.
+
+A committing admission can durably swap the store immediately before an
+in-memory route-cache publication fails. The durable admission receipt remains
+authoritative; an exact retry rebuilds the missing route rows from retained
+sessions and reports `ALREADY_PUBLISHED` without repeating Contracts. A
+`NeedsResources` or non-committing result creates no receipt or state mutation.
+Mixed existing/new admission and an all-present closure without the exact
+receipt fail closed.
+
Child and parent synchronization are intentionally separate commits. If a
child epoch commits and parent application fails, the child remains committed,
the parent cursor remains behind, and the parent stays `CATCHING_UP` or
diff --git a/docs/reference/contracts-authored-admission.md b/docs/reference/contracts-authored-admission.md
new file mode 100644
index 0000000..085d26e
--- /dev/null
+++ b/docs/reference/contracts-authored-admission.md
@@ -0,0 +1,51 @@
+# Contracts authored admission boundary
+
+## Current low-level host boundary
+
+Contracts mode currently admits a complete typed
+`ClosureInvocationInput.Operation.ADMIT_CLOSURE` through
+`CoordinationEngine.admitContractsClosure(input, policy, verifiedFrontier)`.
+This is an expert host boundary, not an ordinary authored-document API. Before
+the call, the host must freeze and supply the complete affected-closure
+snapshot: exact document states, verified `Process Embedded` occurrence rows,
+the graph-derived component partition, complete cyclic proofs, public Roots,
+the execution policy, and the Contracts/Language environment identities.
+
+The call does not trust asserted identities. Contracts recomputes and verifies
+the invocation, graph, component, proof, policy, and environment evidence, and
+Coordination publishes all admitted members atomically. Admission contains no
+direct deliveries, and neither this method nor the test-only authored facade
+accepts a caller-selected recipient set.
+
+`Contracts10ScenarioBuilder` is test-only characterization support. It authors
+exact documents and `Process Embedded.paths` or `collectionPaths`, derives
+occurrence bindings from those locations in a frozen environment, runs the real
+component finalizer and proof verifier, and then creates the same low-level
+input. Its `expectedComponent(...)` value is a literal test oracle checked
+against the derived partition; it is not graph evidence passed to Contracts.
+
+## Future high-level API sketch (non-normative, unmerged)
+
+A future application boundary could have a shape similar to:
+
+```java
+ContractsClosureAdmissionReceipt admitContractsDocuments(
+ List documents,
+ AdmissionPolicy policy,
+ VerifiedFrontier verifiedFrontier);
+```
+
+The exact type and signature require a separate API design review. The
+essential ownership split should remain:
+
+- the caller supplies authored documents and, for later processing, exact
+ external Timeline Entries;
+- the frozen environment discovers active `Process Embedded` occurrences and
+ derives any event targets;
+- Language/Contracts derives and verifies occurrence bindings, the graph,
+ components, cyclic identities, and proofs;
+- Coordination performs one atomic closure admission/publication;
+- the caller never supplies SCCs, direct recipients, or route snapshots.
+
+This proposal does not change the current public API or its fail-closed
+Contracts 1.0 behavior.
diff --git a/docs/reference/public-api.md b/docs/reference/public-api.md
index ee99ac9..31b5648 100644
--- a/docs/reference/public-api.md
+++ b/docs/reference/public-api.md
@@ -1,116 +1,228 @@
# Public API reference
-The supported application boundary is the small set of types in
-`blue.coordination.api`. Full signatures and contracts are in the generated
-Javadocs.
-
-## Lifecycle and commands
-
-- `CoordinationEngine` creates the in-memory environment and owns resources.
-- `Timeline` identifies one authenticated append-only stream.
-- `Operation` describes an operation/channel and either YAML or an `ExactValue`
- request.
-- `DocumentId` identifies one continuing managed document history; a state
- BlueId identifies one exact immutable state within that history.
-- `ActivationMode` names supported embedded-document temporal behavior.
-- `startDocument(..., AdmissionPolicy, verifiedFrontier)` selects top-level
- `FULL_HISTORY`, `FROM_FRONTIER`, or `FROM_NOW` behavior.
-- `appendTimelineEntry(Node)` validates and stores one externally supplied exact
- entry without routing or PROCESS. `append` and `appendAt` are convenience
- builders with the same append/process separation.
-- `drain()` selects canonical work to quiescence; `drainThrough(cutoff)` stops at
- an inclusive upper bound without skipping earlier eligible work.
-- `drain(new DrainBudget(processCommits, selectedEntries))` pauses only at a
- deterministic safe boundary. Its receipt reports `paused()` and the exact
- frozen PROCESS transitions committed by that call; a later drain resumes the
- retained entry frame without repeating them.
-- `document(id)` returns only a coherent `READY` snapshot. Operational audit and
- recovery tooling can use `auditDocument(id)` to inspect committed
- `CATCHING_UP` or `BLOCKED` state deliberately.
-
-The caller never supplies document recipients and cannot select an exact entry
-to process ahead of earlier eligible work. `routeTargetCount` is diagnostic; it
-uses canonical journal evidence, reports only targets expressible by the pinned
-provider model, and does not process the entry.
-
-## Embedded admission evidence
-
-The three-argument `configureEmbeddedAdmission(childId, mode, frontier)` is a
-convenience default for future occurrences of that child. When attachment
-identity matters, append the attachment entry first, then register the
-occurrence-specific overload before draining it. That plan binds the parent
-DocumentId, canonical absolute occurrence path, child DocumentId, supplied
-state BlueId, optional exact child epoch, activation mode, verified frontier,
-completeness-proof identity, and expected attachment-entry BlueId.
-
-Occurrence plans take precedence over the child default and are consumed only
-with successful graph publication. A failed publication restores the plan for
-an exact retry. If the same exact child-state BlueId occurs at more than one
-committed epoch, omitting `admittedEpoch` fails closed; content identity alone
-cannot choose temporal position.
-
-`DrainBudget` limits selected canonical entries and committed frozen PROCESS
-transitions, not elapsed time. One frozen PROCESS invocation is atomic and
-non-preemptible, and epoch-zero INITIALIZE work performed by an attachment is
-outside the PROCESS-commit count. Use `elapsedNanos()` for observed duration,
-not as evidence of a deadline guarantee.
-
-## Target derivation and upstream boundary
-
-The environment derives recipients from exact active subscription intervals.
-Scalar Timeline Channels, Composite Timeline Channels, and the frozen
-same-scope All Timelines family are supported. The caller never supplies a
-recipient set.
-
-Repository-native `OperationRequest.document` targeting is supported as a
-separate feature. With `requireExactDocumentVersion: true`, only a candidate at
-that exact current state is eligible. With a false or absent flag, any retained
-known epoch of that candidate is eligible. An absent document leaves routing
-unrestricted.
-
-The pinned generic Timeline Entry model has no universal literal `documentId`
-target. That is an optional generalized-profile capability, not a blocker for
-environment-derived routing; concrete Channel/message profiles may define exact
-target derivation and must continue to fail closed when their evidence is
-missing.
-
-The pinned provider boundary separately has no general Mandate-state resolver
-for per-target eligibility. This RC does not infer or simulate authority;
-authority-bearing `onBehalfOf` entries fail closed. Exact provider-backed
-Mandate resolution remains an upstream blocker.
-
-## Immutable results
-
-- `TimelineEntry` is the exact journaled event.
-- `TimelineAppendReceipt` proves exact journal admission.
-- `ProcessingDrainReceipt` reports environment-selected entry order and groups
- `DocumentDispatchOutcome` values by entry. For a bounded call, it contains
- only work committed by that call, even when it pauses or resumes an older
- entry frame. `quiescent()`, `paused()`, and `blocked()` distinguish completion,
- a caller-selected work boundary, and unavailable prerequisite evidence.
-- `DocumentSnapshot` is current state plus readiness/frontier evidence.
-- `DocumentRevision` is one immutable state transition with provenance.
-- `ExactValue` retains verified content identity and frozen form.
-- `CoordinationMetrics` exposes cumulative phase timers, work counters and
- gauges.
-
-## Failures
-
-`CoordinationException` carries a stable `CoordinationErrorCode` plus immutable
-details. Invalid identities, missing/not-ready documents, unavailable or
-invalid history evidence, route misses, frozen processing failures, atomic
-commit failures and ownership violations are explicit.
-
-## Dependency surface
-
-The POM exposes `blue-contracts-core`, `blue-bex-core` and
-`blue-bex-contracts` at compile scope because public API values and processor
-signatures expose their types. Repository and Bouncy Castle remain
-runtime-scoped implementation dependencies. All coordinates are exact and
-dependency locked.
-
-`blue.coordination.processor` is an advanced semantic integration surface used
-to assemble the retained Contracts/BEX processors. It is documented in the
-Javadoc JAR, but ordinary applications should start at `CoordinationEngine`.
-`blue.coordination.internal` is never an application API and may change between
-release candidates.
+The normal application boundary is `blue.coordination.sdk`.
+`BlueCoordination.inMemory()` is the single default: it owns an in-memory
+Contracts 1.0 environment pinned to the release manifest bundled in the JAR.
+Full signatures are in the generated Javadocs.
+
+`blue.coordination.api` remains available for advanced host integration and
+legacy migration. Its plain `CoordinationEngine.inMemory()` factory is the
+earlier acyclic compatibility profile and must not be treated as equivalent to
+the SDK default.
+
+## Runtime owner and catalogs
+
+`BlueCoordination` is `AutoCloseable` and exposes these owned catalogs:
+
+- `timelines()` registers authenticated local Timeline handles.
+- `documents()` admits and reads managed lineages and complete closures.
+- `operations()` starts exact document-targeted operation calls.
+- `events()` starts deliberate broadcast Timeline Entry admission.
+- `processing()` performs a canonical drain of submitted work.
+- `values()` resolves authored YAML to an immutable exact value.
+- `advanced()` exposes explicit diagnostics and low-level compatibility.
+
+Handles are owner-bound. Passing a Timeline, document, draft, entry, or other
+owned value to a different `BlueCoordination` instance fails instead of
+silently crossing environments.
+
+`BlueCoordination.builder().release(languageIdentity, contractsIdentity)` is
+an advanced custom-release option. Both values must be lowercase `sha256:`
+identities. Ordinary callers use `inMemory()` and never type release hashes.
+
+## Timelines and exact values
+
+`timelines().local(accountId)` registers a Timeline whose id and actor account
+are the same. `register(timelineId, accountId)` keeps them explicit.
+
+`values().yaml(source)` resolves with the runtime's pinned Language release and
+returns `ExactBlueValue`. An exact value exposes its authoritative BlueId and
+cyclic-member status while retaining immutable verified content. Snapshot
+scalar helpers provide exact long, text, and boolean reads by JSON Pointer.
+
+## Ordinary document admission
+
+An ordinary top-level document is authored, explicitly authorized as a public
+Root, and given a temporal policy:
+
+```java
+DocumentHandle order = blue.documents().admit(
+ ManagedDocument.yaml("order-123", orderYaml)
+ .publicRoot()
+ .fromNow());
+```
+
+The SDK resolves the authored value, compiles a one-member complete closure,
+authenticates its public Root, and atomically admits it through Contracts. It
+does not seed a legacy singleton session. A top-level definition without
+`publicRoot()` or an explicit activation policy fails closed. Top-level SDK
+admission currently supports `fromNow()`, `importFullHistory()`, and
+`importFromFrontier(exactEvidence)`; attach-current and passive-snapshot values
+remain vocabulary for future occurrence evidence and are rejected at this
+boundary.
+
+## Complete closure admission
+
+```java
+ClosureHandle closure = blue.documents().admit(
+ ManagedClosure.builder()
+ .document("a", yamlA)
+ .document("b", yamlB)
+ .bindOccurrence("a", "/b", "b")
+ .bindOccurrence("b", "/a", "a")
+ .publicRoot("a")
+ .fromNow()
+ .build());
+```
+
+Aliases are immutable construction names; each member has a stable
+`DocumentId`. `bindOccurrence(sourceAlias, path, targetAlias)` is managed
+lineage evidence, not an authored graph. The SDK requires the source's
+effective `Process Embedded` catalog to declare the canonical path, verifies
+that the exact value at that path agrees with the target, and rejects missing,
+duplicate, extra, or ambiguous bindings. Language owns cyclic finalization and
+complete-proof verification; the SDK only supplies the authored boundary.
+
+`ClosureHandle` exposes the authenticated closure identity, members by alias,
+and public Roots. It does not expose component snapshots, occurrence internals,
+proof objects, or invocation environments.
+
+## Targeted operations
+
+```java
+EntryResult result = blue.operations().on(order)
+ .from(alice)
+ .call("attachPayNoteAsCustomer")
+ .through("customerChannel")
+ .request(request -> request.exact("payNote", payNote))
+ .execute();
+```
+
+`on(DocumentHandle)` binds the exact current state. `on(DocumentId)` can name a
+currently absent lineage so execution returns a terminal `REJECTED` result with
+`TARGET_DOCUMENT_NOT_FOUND`; constructing the call does not throw merely
+because the target is missing. A changed exact target returns `STALE`.
+Missing operations, target Channels, and source/Channel matches return precise
+diagnostics such as `OPERATION_NOT_FOUND`, `TARGET_CHANNEL_NOT_FOUND`, and
+`TARGET_CHANNEL_SOURCE_MISMATCH`.
+
+The target is evidence used by the selected Contracts profile. The caller does
+not supply the final recipient set, and an Order-specific request is not
+silently converted into a broadcast.
+
+`requestYaml(yaml)` supplies one ordinary authored request. The structured
+request builder uses `exact(field, value)` to preserve whole exact values.
+
+## Managed drafts produced by operations
+
+`documents().draft(id, exactInitial)` creates immutable stable-lineage evidence
+for a new managed occurrence. Supply the same draft in the exact request and
+declare every effective result path that must bind it:
+
+```java
+ManagedDocumentDraft child = blue.documents().draft(
+ childId, blue.values().yaml(childYaml));
+
+EntryResult result = blue.operations().on(parent)
+ .from(alice)
+ .call("createChild")
+ .through("ownerChannel")
+ .request(request -> request.managed("child", child))
+ .expectOccurrence("/children/child-456", child)
+ .activation(ActivationPolicy.fromNow())
+ .execute();
+```
+
+The SDK verifies owner identity, exact request value, canonical effective
+`Process Embedded` paths, and complete result agreement. A single draft may be
+bound at several paths to express one stable lineage with multiple
+occurrences. All new heads and topology changes publish atomically with the
+parent result; a terminal failure leaves no partial expansion.
+
+This candidate supports only new `FROM_NOW` lineages. Imported-state evidence
+created with `draft.atEpoch(...)` and historical, frontier, attach-current, or
+passive operation-result activation fail closed. The SDK never emulates this
+lane through legacy child/parent admission.
+
+## Broadcast events
+
+```java
+EntryResult result = blue.events()
+ .from(alice)
+ .exact(completeTimelineEntry)
+ .execute();
+```
+
+`exact(...)` accepts a complete exact Timeline Entry envelope whose source
+Timeline and actor agree with the selected handle. Broadcast is explicit and
+still environment-routed. A valid entry accepted by no active Channel is a
+terminal `NO_MATCH`, not an empty low-level receipt error.
+
+## Append and process separation
+
+Every operation and event call is single-use and supports:
+
+- `submit()`: validate and append exactly once without PROCESS;
+- `execute()`: append, then canonically drain through that entry.
+
+`execute()` includes earlier eligible entries and cannot overtake them. The
+portable split is:
+
+```java
+EntryHandle submitted = call.submit();
+DrainResult drained = blue.processing().drain();
+EntryResult result = drained.entry(submitted);
+```
+
+`DrainResult.entries()` is in canonical processing order. `find(handle)` keeps
+absence distinct from `NO_MATCH`; `entry(handle)` requires a result in that
+specific drain. Drain-wide state distinguishes quiescent, paused, and blocked
+frontiers.
+
+## Results and reads
+
+`EntryDisposition` contains `APPLIED`, `NO_MATCH`, `STALE`, `MIXED`,
+`REJECTED`, `NEEDS_RESOURCES`, `GAS_LIMIT_EXCEEDED`,
+`PORTABLE_LIMIT_EXCEEDED`, and `BLOCKED`.
+
+One appended entry can affect disconnected closures independently.
+`EntryResult.closures()` therefore retains each `ClosureResult`, its committed
+`DocumentChange` values, public events, processing statistics, and diagnostic.
+The aggregate disposition is `MIXED` when terminal closure dispositions differ.
+`ProcessingStats` reports gas, committed transitions, documents opened, exact
+document-step order, elapsed time, and named counters.
+
+`DocumentHandle.snapshot()` is READY-only and exposes application state,
+DocumentId, epoch, BlueId, exact content, and public events. `history()` returns
+immutable application-safe revisions. Physical objects, topology generations,
+proofs, and storage layout are not part of the normal snapshot.
+
+## Advanced boundary
+
+`AdvancedCoordination.rawEngine()` returns the owned low-level
+`CoordinationEngine` for a host that must migrate an existing integration.
+`auditDocument(id)` deliberately permits non-READY reads. Advanced identity
+accessors expose the exact Language, Contracts, fixture package, gas manifest,
+cyclic finalizer, and proof-verifier identities used by evidence tooling.
+
+`auditManagedOccurrence(sourceId, occurrencePath)` returns an optional
+`ManagedOccurrenceAudit` for a retained occurrence row. The value contains the
+target `DocumentId`, positive activation generation, and active/inactive flag;
+it intentionally omits component snapshots, proof values, and mutable
+inventory internals.
+
+Low-level types such as `ClosureInvocationInput`, occurrence bindings,
+component/closure snapshots, cyclic proofs, closure environments, and execution
+policies are not permitted in normal SDK signatures.
+
+## Dependency and package surface
+
+The POM exposes Contracts and BEX artifacts at compile scope where retained
+advanced API and processor signatures require their types. Repository and
+Bouncy Castle remain runtime implementation dependencies. Every coordinate is
+exact and dependency-locked.
+
+`blue.coordination.processor` is an advanced semantic-integration surface.
+`blue.coordination.internal` is not application API and may change between
+release candidates. The exact package ownership and migration policy are in the
+[SDK migration and ownership ledger](sdk-migration-and-ownership.md).
diff --git a/docs/reference/sdk-migration-and-ownership.md b/docs/reference/sdk-migration-and-ownership.md
new file mode 100644
index 0000000..1612323
--- /dev/null
+++ b/docs/reference/sdk-migration-and-ownership.md
@@ -0,0 +1,115 @@
+# SDK migration and ownership ledger
+
+This ledger fixes the application boundary for the `3.0.0-rc.3` SDK release
+candidate. It is normative for package ownership and migration guidance, but it
+does not replace the Contracts 1.0 specification.
+
+```text
+candidate: 3.0.0-rc.3
+distribution: Maven Central, bounded external-pilot tier
+normal default: BlueCoordination.inMemory() -> Contracts 1.0
+implementationConformanceClaimed: true
+productionReleaseReady: false
+```
+
+## Default-profile decision
+
+| Entry point | Intended caller | Semantics | Status |
+| --- | --- | --- | --- |
+| `BlueCoordination.inMemory()` | normal application | bundled Contracts 1.0 release, authored admission, dynamic public Roots | default |
+| `BlueCoordination.builder().release(...)` | controlled host/evidence tooling | Contracts 1.0 with explicit exact release identities | advanced |
+| `blue.advanced().rawEngine()` | migrating host integrator | low-level engine owned by the SDK runtime | advanced escape hatch |
+| `CoordinationEngine.inMemoryContracts10(...)` | existing Contracts host | explicit identities, roots, closure inputs and receipts | compatibility |
+| `CoordinationEngine.inMemory()` | existing pre-Contracts host | earlier acyclic Process Embedded profile | legacy compatibility; not the SDK default |
+
+The two `inMemory()` names are not interchangeable. New application examples,
+consumer fixtures, and Javadocs start at `BlueCoordination`.
+
+## Package ownership
+
+| Package | Owner and stability | Permitted use |
+| --- | --- | --- |
+| `blue.coordination.sdk` | application-facing SDK | normal application imports and built-JAR consumer tests |
+| `blue.coordination.api` | low-level host compatibility | advanced integration, existing-host migration, and SDK `DocumentId` interop |
+| `blue.coordination.processor` | semantic integration | assembling retained Contracts/BEX processors; not ordinary application code |
+| `blue.coordination.internal` | implementation | no application imports; exact build-governed public allowlist only |
+| `testFixtures` source set/artifact | test support | library/conformance tests only; never the main runtime JAR |
+
+Within the SDK, `SdkCoordinationRuntime`, `SdkDrainResultMapper`, and
+`SdkPreconditions` are package-private implementation details. Public normal
+signatures must not expose `ClosureInvocationInput`, affected-closure or
+component snapshots, managed occurrence bindings, complete cyclic proofs,
+closure environments, execution policies, processor types, internal types, or
+raw Blue nodes.
+
+The normal facade may expose stable SDK values and the retained stable
+`blue.coordination.api.DocumentId`. Low-level types are reachable only after an
+explicit `advanced()` choice.
+
+## Semantic ownership
+
+| Concern | Owning layer | SDK responsibility |
+| --- | --- | --- |
+| authored document resolution and effective `Process Embedded` catalog | Language/Contracts | pass authored YAML and surface validation failures |
+| managed occurrence lineage | Contracts model | collect source/path/target aliases and verify exact agreement |
+| cyclic finalization and proof verification | pinned Language/Contracts runtime | invoke; never reimplement or accept caller SCC oracles |
+| canonical entry and closure scheduling | Coordination engine | delegate; never introduce a facade queue or graph |
+| gas weights, limits, trace, and rollback | Contracts | preserve typed results and exact statistics |
+| atomic multi-document publication | Coordination store/Contracts adapter | expose independent immutable closure results |
+| operation-result managed expansion | Contracts processor plus Coordination publication adapter | bind exact draft/request/path evidence; support new `FROM_NOW` lineages only |
+| target evidence | selected Contracts/Repository profile | bind exact document evidence; never accept final recipient sets |
+| public broadcast | Coordination environment | keep explicit through `events()` and preserve terminal `NO_MATCH` |
+| physical storage/proofs/topology generations | advanced diagnostics | exclude from normal snapshots |
+
+## Migration map
+
+| Existing low-level pattern | SDK replacement | Notes |
+| --- | --- | --- |
+| `CoordinationEngine.inMemoryContracts10(configuration)` | `BlueCoordination.inMemory()` | bundled identities; roots authorized during authored admission |
+| `registerTimeline(id, actor)` | `timelines().register(id, actor)` | `local(account)` is the equal-id convenience |
+| `startDocument(...)` in legacy mode | `documents().admit(ManagedDocument...)` | compiles a one-member Contracts closure; requires public Root and activation |
+| hand-built `ClosureInvocationInput` | `documents().admit(ManagedClosure...)` | application supplies authored members and lineage bindings only |
+| `Operation.yaml/exact` plus `append` | `operations().on(document)...submit()` | target is exact evidence; append still does no PROCESS |
+| `appendTimelineEntry(Node)` | `events().from(timeline).exact(value).submit()` | exact value must be a complete matching Timeline Entry envelope |
+| `engine.drain()` plus receipt parsing | `processing().drain()` and `DrainResult` | preserves canonical order and disconnected closure outcomes |
+| `onlyOutcome()` | `DrainResult.entry(handle)` / `EntryResult.closures()` | `NO_MATCH` is terminal and multi-closure results are not collapsed |
+| `engine.document(id)` | `DocumentHandle.snapshot()` | READY-only application state without physical layout |
+| `auditDocument(id)` | `advanced().auditDocument(id)` | explicit non-READY operational read |
+| retained occurrence inventory inspection | `advanced().auditManagedOccurrence(sourceId, path)` | returns only target lineage, activation generation, and active state |
+| host-specific managed-child call | `request.managed(...)` plus `expectOccurrence(...)` | exact new-lineage value and every effective path are required; duplicate occurrences may share one draft |
+| raw release SHA strings in normal construction | bundled release manifest | explicit SHA pairs remain builder/advanced only |
+
+Migration is additive. Existing hosts can keep the low-level boundary while
+moving one workflow at a time, but they must not mix handles or semantics from
+the legacy and SDK runtimes.
+
+## Managed-draft boundary
+
+`ManagedDocumentDraft`, `RequestBuilder.managed(...)`, and
+`expectOccurrence(...)` are the supported rc.3 boundary for a new managed
+lineage produced by an operation. Request content remains separate from the
+stable draft identity and activation evidence. Before append, the SDK verifies
+ownership, draft consistency, canonical unique paths, and effective
+`Process Embedded` declarations. During PROCESS, the bridge verifies the exact
+result value and complete occurrence set, rejects zero, missing, extra, or
+ambiguous matches, and publishes the expanded affected closure atomically. It
+does not call the legacy child/parent path or create a second dependency graph.
+
+This lane supports only new `FROM_NOW` lineages. `draft.atEpoch(...)` and
+historical, frontier, attach-current, or passive operation-result activation
+fail closed. The Order-draft and five-occurrence/three-lineage cases are part of
+the rc.3 acceptance corpus. The final implementation-conformance value remains
+an artifact-bound receipt decision, not a claim made from source shape alone.
+
+## Candidate and release ownership
+
+The rc.3 build resolves only Maven Central artifacts. Dependency isolation,
+exact component versions, Java 17/21 verification, publication metadata, and
+built-artifact checks are owned by `releaseCheck` and
+`verifyRcReadiness`. Local composites, Maven Local, and file-staged
+repositories are not supported fallbacks.
+
+Passing the gate authorizes only the bounded external-pilot tier documented in
+the [rc.3 release decision](../releases/3.0.0-rc.3.md). Remote publication and
+tagging remain owned by the release workflow, which pushes the tag only after
+Maven Central deployment succeeds.
diff --git a/docs/releases/3.0.0-rc.1-test-report.md b/docs/releases/3.0.0-rc.1-test-report.md
index 125c188..c738b93 100644
--- a/docs/releases/3.0.0-rc.1-test-report.md
+++ b/docs/releases/3.0.0-rc.1-test-report.md
@@ -2,13 +2,23 @@
Evidence date: 2026-08-13.
+```text
+ROUND13_HISTORICAL_EVIDENCE_ONLY
+```
+
+This report is an immutable historical record for candidate commit
+`de55822240af4e263bb95dbb98c0579522cdc330`. It does not describe the current
+Contracts 1.0 worktree, its source shape, its correctness gates, or its
+performance. Uses of “current” below mean current at the evidence date and
+within that candidate record only.
+
## Evidence state
```text
ROUND13_FINAL_VERIFICATION: PASS_WITH_KNOWN_PERFORMANCE_LIMITATION
```
-This is the `FINAL` record for the
+This is the historical `FINAL` record for the
`ROUND13_PLAYGROUND_FIVE_OCCURRENCE` profile. The exact 3.0.0-rc.1 policy permits
publication for external RC evaluation with one explicitly disclosed known
performance limitation. It does not relabel the retained failed latency
diff --git a/docs/releases/3.0.0-rc.1.md b/docs/releases/3.0.0-rc.1.md
index a5f5ce9..1163165 100644
--- a/docs/releases/3.0.0-rc.1.md
+++ b/docs/releases/3.0.0-rc.1.md
@@ -1,12 +1,17 @@
# 3.0.0-rc.1 Round 13 Playground five-occurrence profile
+This page and its linked evidence are historical records for the pre-Contracts
+1.0 candidate. They are not evidence for the current Contracts 1.0 source,
+tests, artifacts, or performance. “Current” within the retained record means
+current at its 2026-08-13 evidence date.
+
This candidate profile narrows release readiness to the single-process,
single-writer, synchronous, in-memory Playground engine. Its flagship scenario
attaches five Process Embedded occurrences that reuse three unique managed
documents, initialize each child once, and forward five parent-visible
initialization-event occurrences in canonical path order.
-## Current status
+## Historical candidate status
The canonical [verification report](3.0.0-rc.1-test-report.md) and
[machine-readable evidence](3.0.0-rc.1-evidence.json) are a `FINAL` record for
@@ -29,7 +34,7 @@ All non-performance release gates remain mandatory.
| Mandate-backed agent authority | `OUT_OF_SCOPE` |
| Public RC | `PASS_WITH_KNOWN_PERFORMANCE_LIMITATION` |
-The current source-derived inventory is 342 tests in 85 classes: 231/33 unit,
+The candidate source-derived inventory was 342 tests in 85 classes: 231/33 unit,
91/41 integration, 6/1 built-JAR consumer, and 14/10 scenario tests. Both fresh
lanes passed all 342 tests with zero failures, errors, or skips: Java 17 in
882 seconds (14m42s) across the clean build, isolated scenario rerun, and
diff --git a/docs/releases/3.0.0-rc.3.md b/docs/releases/3.0.0-rc.3.md
new file mode 100644
index 0000000..df7901c
--- /dev/null
+++ b/docs/releases/3.0.0-rc.3.md
@@ -0,0 +1,70 @@
+# Blue Coordination Java 3.0.0-rc.3
+
+RC3_VERSION: 3.0.0-rc.3
+
+RC3_RELEASE_TIER: BOUNDED_EXTERNAL_PILOT
+
+RC3_DEPENDENCY_MODE: PUBLISHED_ARTIFACTS_ONLY
+
+RC3_PUBLIC_ARTIFACT_READY: true
+
+RC3_PRODUCTION_READY: false
+
+## Decision
+
+This candidate is authorized as a public Maven Central artifact for bounded
+external pilots. It is built and tested only against artifacts resolved from
+Maven Central; sibling composite builds, Maven Local, and file-based staging
+repositories are not supported dependency inputs.
+
+The exact Blue graph is:
+
+| Component | Version |
+| --- | --- |
+| Language modules | `3.1.0-rc.21` |
+| BEX core and contracts | `1.1.0-rc.4` |
+| Repository | `3.0.0-rc.21` |
+| Coordination | `3.0.0-rc.3` |
+
+Repository rc.21 was published with a stale transitive dependency on
+`blue-language-java:3.1.0-rc.20`. Coordination excludes that one transitive
+edge and directly pins the complete Language graph to rc.21. The generated POM,
+dependency lock, dependency preflight, and runtime-graph gate enforce this
+choice.
+
+## Release gate
+
+`verifyRcReadiness` requires the exact version and dependency graph, runs the
+complete `releaseCheck` and Maven Central dependency preflight, verifies every
+test uses one ordered `// given`, `// when`, `// then` structure, and writes
+fresh artifact hashes to
+`build/reports/release/3.0.0-rc.3-readiness.json`. CI runs the complete suite on
+Java 17 and Java 21; Java 17 owns the canonical staging build.
+
+The retained receipt at
+[`stabilization/cyclic-topology-rc3-final/final-receipt.json`](../../stabilization/cyclic-topology-rc3-final/final-receipt.json)
+is semantic evidence for the bounded pilot profile. That receipt did not
+authorize remote distribution, and its local staging hashes are historical;
+this release decision adds public RC artifact authorization only after the new
+published-graph gate passes. The current workflow produces fresh build evidence
+from the published graph.
+
+## Supported profile
+
+- Java 17 bytecode and Java 17/21 verification;
+- one JVM, in-memory state, and sequential drain;
+- public-Root-scope closures with bounded cyclic components;
+- new operation-produced managed lineages using exact `FROM_NOW` evidence;
+- deterministic retry and atomic publication within the in-memory profile.
+
+## Explicit non-claims
+
+This RC is not a stable or production release. It does not claim fresh-process
+durability, provider completeness, a provider-backed Mandate resolver,
+parallel or distributed scheduling, production MyOS operations, or a stable
+latency SLA. Imported managed drafts and historical/frontier/attach-current/
+passive activation of operation-produced lineages remain unsupported and fail
+closed.
+
+The historical rc.1 Round 13 evidence remains unchanged and does not authorize
+this candidate.
diff --git a/docs/releases/contracts-1.0-current-verification.md b/docs/releases/contracts-1.0-current-verification.md
new file mode 100644
index 0000000..aa3f04b
--- /dev/null
+++ b/docs/releases/contracts-1.0-current-verification.md
@@ -0,0 +1,56 @@
+# Contracts 1.0 and SDK current verification boundary
+
+This tree is the `3.0.0-rc.3` bounded external-pilot candidate.
+`BlueCoordination.inMemory()` uses the bundled Contracts 1.0 release manifest;
+the older `CoordinationEngine` surface remains an advanced/legacy boundary.
+
+All current verification resolves Language `3.1.0-rc.21`, BEX
+`1.1.0-rc.4`, and Repository `3.0.0-rc.21` from Maven Central. No sibling
+composite, Maven Local, or file-staged dependency path participates.
+
+## Current claim
+
+The public SDK acceptance corpus covers authored ordinary and cyclic admission,
+exact targeting, broadcasts, typed multi-closure results, append/drain parity,
+built-JAR consumption, and new-lineage `FROM_NOW` operation-result admission.
+The Order-draft and five-occurrence/three-lineage cases, malformed evidence,
+rollback, and deterministic retry are included.
+
+```text
+implementationConformanceClaimed = true
+externalPilotReady = true
+publicRcArtifactReady = true
+retainedSemanticReceipt.publicReleaseReady = false
+productionReleaseReady = false
+CONTRACTS10_CURRENT_PERFORMANCE_CLAIM: NONE
+```
+
+The claim is deliberately bounded. Imported known-epoch drafts and historical,
+frontier, attach-current, or passive operation-result activation remain
+unsupported and fail closed. There is no durability, provider-completeness,
+Mandate, distributed scheduling, production operations, or stable latency SLA
+claim.
+
+## Evidence ownership
+
+`verifyCurrentContractsDocumentation` derives current source integrity.
+`releaseCheck` runs all four suites, public and artifact boundaries,
+publication metadata, documentation, and extracted-source verification against
+published dependencies. `verifyRcReadiness` adds exact rc.3 policy and graph
+checks and writes fresh built-artifact hashes to
+`build/reports/release/3.0.0-rc.3-readiness.json`.
+
+The retained
+[`cyclic-topology-rc3-final` receipt](../../stabilization/cyclic-topology-rc3-final/final-receipt.json)
+authorizes the semantic bounded-pilot profile. Its file-repository artifact
+hashes are historical and are not claimed to be Maven Central hashes. Current
+artifact evidence is regenerated by the release workflow.
+
+The maintainability guardrails remain at most 170 production Java files,
+42,000 production Java lines, and 50 public source types across
+`blue.coordination.api` and `blue.coordination.sdk`. These are engineering
+tripwires, not semantic or performance evidence.
+
+The rc.1 Round 13 report, JSON, schemas, and provenance remain immutable
+historical evidence. They are not compared with current source counts and do
+not authorize rc.3.
diff --git a/docs/semantics/identity-and-revisions.md b/docs/semantics/identity-and-revisions.md
index a29250d..ba57ab6 100644
--- a/docs/semantics/identity-and-revisions.md
+++ b/docs/semantics/identity-and-revisions.md
@@ -21,11 +21,18 @@ revision kind, before/after exact values, source Timeline Entry, catch-up cause,
emitted events, and processing gas. Initialization has no source entry; a
Timeline revision always has one.
-One immutable `EmbeddingBinding` names a parent occurrence, child DocumentId,
-path, activation generation, admitted child state, and attachment evidence.
-`EmbeddedEpochCursor` separately names the latest child epoch incorporated by
-that occurrence. Removing and re-adding a path creates a new activation
-generation and a fresh cursor without deleting immutable child audit history.
+One Contracts-owned managed occurrence row names a source DocumentId/path,
+target DocumentId, activation generation, binding policy, exact expected target
+state, active status, and nullable historical cursor. Its occurrence identity
+is stable across same-lineage exact-state churn; its binding identity changes
+with the expected target BlueId. `ManagedOccurrenceInventory` retains active
+and inactive rows and delegates all identity derivation and assertion checking
+to Contracts. Removing an active row allocates its inactive same-lineage
+successor at generation plus one. Later re-add activates that committed row
+without incrementing again or reusing the retired occurrence/checkpoint
+lineage. Failed transitions publish no generation. `EmbeddedEpochCursor`
+remains the legacy coordinator's separate per-occurrence progress state during
+the migration.
The journal owns global and per-Timeline sequence numbers. Failed append parsing
does not consume either sequence or logical time. Failed top-level admission
diff --git a/docs/semantics/process-embedded-documents.md b/docs/semantics/process-embedded-documents.md
index 445d492..286d99c 100644
--- a/docs/semantics/process-embedded-documents.md
+++ b/docs/semantics/process-embedded-documents.md
@@ -8,22 +8,40 @@ wave model, consistency mode, or SCC planner.
Every processable document has a stable `DocumentId` and immutable epoch
history. A state BlueId identifies one exact state, not the continuing history.
-The same DocumentId may occur under several parent paths: its direct Timeline
-transition runs once, while each occurrence applies the resulting epochs through
-its own cursor. Different DocumentIds remain independent even when their current
-state BlueIds are equal.
-
-`EmbeddingBinding` contains immutable topology and activation identity.
-`EmbeddedEpochCursor` contains progress as replaceable persisted state. This
-separation keeps graph snapshots stable and lets one parent lag or retry without
-changing another parent or the child history.
-
-A containing document applies a child epoch through exact frozen PROCESS. The
-engine supplies a private `EmbeddedEpochInput` with parent/path/generation
-evidence, old and new child BlueIds, and ordered `EventOccurrence` records. The
-parent processor verifies the old state, performs replacement, runs reactions,
-and commits its new epoch. The host never pre-mutates the child field, and the
-input is never a synthetic provider Timeline Entry.
+Each selected embedded managed document is processed separately from its own
+exact state, using the ordinary document-processing function with that document
+as Root. A document is completely unaware of documents that contain it: no
+parent identity, containing path, or reverse-containment graph is ambient
+processor input. Different DocumentIds remain independent even when their
+current state BlueIds are equal.
+
+The immutable `ManagedOccurrenceInventory` retains complete Contracts-owned
+occurrence rows, including inactive reservations. Contracts remains the only
+authority that derives or verifies occurrence, binding, component, invocation,
+and exact-state identities. Active rows project into the SCC/condensation view;
+all authoritative rows define the connected affected cohort so removal and a
+later reactivation cannot silently disconnect durable closure state.
+
+Cycles do not select a different Coordination mode. Acyclic documents and
+documents in cyclic components use the same document-processing function and
+the same Contracts closure execution context. Contracts schedules active SCCs
+and validates convergence; Coordination does not add a recursive parent walk,
+wave mode, or second cyclic scheduler.
+
+MyOS may retain one ordered feeder/window for a public Root over the union of
+the Root Timeline and all active embedded Timelines. That wider source surface
+does not merge document execution. Exact direct deliveries select affected
+documents, connected closure cohorts execute independently, and a
+`NeedsResources` result prevents overtaking only in its Root lane. Disconnected
+public Roots may continue.
+
+A committing connected-closure result publishes atomically through a
+copy-on-write store swap. The transaction fences every selected document's
+durable head and epoch plus the relevant graph generations, and publishes
+occurrences, components, subscriptions, routes, checkpoints, outbox, and the
+idempotency receipt together. Per-document durable heads and epochs therefore
+fit directly; a containing document is not the durability owner of an embedded
+document.
Activation policy is admission metadata, not another field on the canonical
`Process Embedded` contract. A new occurrence may be born at attachment, import
@@ -32,13 +50,22 @@ state, or remain a passive snapshot. A document with no effective contracts and
no processable descendants remains ordinary content unless explicitly admitted
as a managed process.
-Removing an occurrence retires its binding and source projection but preserves
-history. Re-adding the path creates a new activation generation and cursor. A
-known current child state attaches directly; a known older epoch catches up
-through missing epochs; an unknown divergent state fails closed or requires an
-explicit fork. Cycles are rejected before topology or receipts publish.
+Removing an active occurrence preserves history and atomically replaces its row
+with one inactive same-lineage successor at exactly generation plus one. That
+successor has fresh Contracts-derived occurrence and binding identities and is
+output-only for the removing invocation. A later invocation may activate the
+committed successor without another generation or occurrence-identity change;
+same-invocation remove-then-re-add and different-lineage retarget remain
+unsupported. A known current child state attaches directly; a known older epoch
+catches up through missing epochs; an unknown divergent state fails closed or
+requires an explicit fork. The inventory's active projection supports SCCs
+without changing the ordinary per-document processing contract.
+
+## Legacy compatibility profile
-This managed epoch/history behavior is the Round 10.1 Coordination temporal
-profile. It uses frozen Language, Contracts, BEX, and Repository transitions but
-does not claim these cross-document histories are already normative Contracts
-1.0 behavior.
+The earlier `DefaultCoordinationEngine.create()` profile represents containment
+with `EmbeddingBinding`, per-occurrence `EmbeddedEpochCursor` values, and a
+private parent/path `EmbeddedEpochInput`. It commits a child and its parents as
+separate document-local transitions and rejects cycles. Those mechanisms remain
+available for compatibility, but they are not the Contracts 1.0 processing or
+publication model described above.
diff --git a/gradle.lockfile b/gradle.lockfile
deleted file mode 100644
index e89dbeb..0000000
--- a/gradle.lockfile
+++ /dev/null
@@ -1,37 +0,0 @@
-# This is a Gradle generated file for dependency locking.
-# Manual edits can break the build and are not advised.
-# This file is expected to be part of source control.
-# To regenerate this file, run: ./gradlew :dependencies --write-locks
-blue.language:blue-contracts-core:3.1.0-rc.20=compileClasspath,consumerTestCompileClasspath,consumerTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,runtimeClasspath,scenarioTestCompileClasspath,scenarioTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath
-blue.language:blue-language-core:3.1.0-rc.20=compileClasspath,consumerTestCompileClasspath,consumerTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,runtimeClasspath,scenarioTestCompileClasspath,scenarioTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath
-blue.language:blue-language-ipfs:3.1.0-rc.20=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,runtimeClasspath,scenarioTestCompileClasspath,scenarioTestRuntimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath
-blue.language:blue-language-java:3.1.0-rc.20=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,runtimeClasspath,scenarioTestCompileClasspath,scenarioTestRuntimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath
-blue.language:blue-language-mapping:3.1.0-rc.20=compileClasspath,consumerTestCompileClasspath,consumerTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,runtimeClasspath,scenarioTestCompileClasspath,scenarioTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath
-blue.language:blue-language-model:3.1.0-rc.20=compileClasspath,consumerTestCompileClasspath,consumerTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,runtimeClasspath,scenarioTestCompileClasspath,scenarioTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath
-com.fasterxml.jackson.core:jackson-annotations:2.15.2=compileClasspath,consumerTestCompileClasspath,consumerTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,runtimeClasspath,scenarioTestCompileClasspath,scenarioTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath
-com.fasterxml.jackson.core:jackson-core:2.15.2=compileClasspath,consumerTestCompileClasspath,consumerTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,runtimeClasspath,scenarioTestCompileClasspath,scenarioTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath
-com.fasterxml.jackson.core:jackson-databind:2.15.2=compileClasspath,consumerTestCompileClasspath,consumerTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,runtimeClasspath,scenarioTestCompileClasspath,scenarioTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath
-com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.15.2=consumerTestRuntimeClasspath,integrationTestRuntimeClasspath,runtimeClasspath,scenarioTestRuntimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath
-com.fasterxml.jackson:jackson-bom:2.15.2=compileClasspath,consumerTestCompileClasspath,consumerTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,runtimeClasspath,scenarioTestCompileClasspath,scenarioTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath
-com.google.code.findbugs:jsr305:3.0.2=consumerTestRuntimeClasspath,integrationTestRuntimeClasspath,runtimeClasspath,scenarioTestRuntimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath
-commons-codec:commons-codec:1.11=integrationTestRuntimeClasspath,runtimeClasspath,scenarioTestRuntimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath
-commons-logging:commons-logging:1.2=integrationTestRuntimeClasspath,runtimeClasspath,scenarioTestRuntimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath
-io.github.erdtman:java-json-canonicalization:1.1=consumerTestRuntimeClasspath,integrationTestRuntimeClasspath,runtimeClasspath,scenarioTestRuntimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath
-org.apache.httpcomponents:httpclient:4.5.14=integrationTestRuntimeClasspath,runtimeClasspath,scenarioTestRuntimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath
-org.apache.httpcomponents:httpcore:4.4.16=integrationTestRuntimeClasspath,runtimeClasspath,scenarioTestRuntimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath
-org.apiguardian:apiguardian-api:1.1.2=consumerTestCompileClasspath,integrationTestCompileClasspath,scenarioTestCompileClasspath,testCompileClasspath
-org.bouncycastle:bcprov-jdk18on:1.78.1=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,runtimeClasspath,scenarioTestCompileClasspath,scenarioTestRuntimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath
-org.javassist:javassist:3.28.0-GA=consumerTestRuntimeClasspath,integrationTestRuntimeClasspath,runtimeClasspath,scenarioTestRuntimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath
-org.junit.jupiter:junit-jupiter-api:5.14.1=consumerTestCompileClasspath,consumerTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,scenarioTestCompileClasspath,scenarioTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.junit.jupiter:junit-jupiter-engine:5.14.1=consumerTestRuntimeClasspath,integrationTestRuntimeClasspath,scenarioTestRuntimeClasspath,testRuntimeClasspath
-org.junit.jupiter:junit-jupiter-params:5.14.1=consumerTestCompileClasspath,consumerTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,scenarioTestCompileClasspath,scenarioTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.junit.jupiter:junit-jupiter:5.14.1=consumerTestCompileClasspath,consumerTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,scenarioTestCompileClasspath,scenarioTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.junit.platform:junit-platform-commons:1.14.1=consumerTestCompileClasspath,consumerTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,scenarioTestCompileClasspath,scenarioTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.junit.platform:junit-platform-engine:1.14.1=consumerTestRuntimeClasspath,integrationTestRuntimeClasspath,scenarioTestRuntimeClasspath,testRuntimeClasspath
-org.junit.platform:junit-platform-launcher:1.14.1=consumerTestRuntimeClasspath,integrationTestRuntimeClasspath,scenarioTestRuntimeClasspath,testRuntimeClasspath
-org.junit:junit-bom:5.14.1=consumerTestCompileClasspath,consumerTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,scenarioTestCompileClasspath,scenarioTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.opentest4j:opentest4j:1.3.0=consumerTestCompileClasspath,consumerTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,scenarioTestCompileClasspath,scenarioTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.reflections:reflections:0.10.2=consumerTestRuntimeClasspath,integrationTestRuntimeClasspath,runtimeClasspath,scenarioTestRuntimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath
-org.slf4j:slf4j-api:1.7.32=consumerTestRuntimeClasspath,integrationTestRuntimeClasspath,runtimeClasspath,scenarioTestRuntimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath
-org.yaml:snakeyaml:2.0=consumerTestRuntimeClasspath,integrationTestRuntimeClasspath,runtimeClasspath,scenarioTestRuntimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath
-empty=annotationProcessor,consumerTestAnnotationProcessor,integrationTestAnnotationProcessor,scenarioTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor
diff --git a/gradle/bex-source.lock b/gradle/bex-source.lock
deleted file mode 100644
index e23c6b4..0000000
--- a/gradle/bex-source.lock
+++ /dev/null
@@ -1,3 +0,0 @@
-coordinate=blue.bex:blue-bex-core:1.1.0-rc.3
-contractsCoordinate=blue.bex:blue-bex-contracts:1.1.0-rc.3
-commit=ffd78f1732a86e99bc0a28d42ff90a0eba4e90e9
diff --git a/gradle/published-artifact.lockfile b/gradle/published-artifact.lockfile
index 0aea4da..c5fae46 100644
--- a/gradle/published-artifact.lockfile
+++ b/gradle/published-artifact.lockfile
@@ -2,14 +2,14 @@
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
# To regenerate this file, run: ./gradlew :dependencies --write-locks
-blue.bex:blue-bex-contracts:1.1.0-rc.3=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,runtimeClasspath,scenarioTestCompileClasspath,scenarioTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath
-blue.bex:blue-bex-core:1.1.0-rc.3=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,runtimeClasspath,scenarioTestCompileClasspath,scenarioTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath
-blue.language:blue-contracts-core:3.1.0-rc.20=compileClasspath,consumerTestCompileClasspath,consumerTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,runtimeClasspath,scenarioTestCompileClasspath,scenarioTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath
-blue.language:blue-language-core:3.1.0-rc.20=compileClasspath,consumerTestCompileClasspath,consumerTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,runtimeClasspath,scenarioTestCompileClasspath,scenarioTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath
-blue.language:blue-language-ipfs:3.1.0-rc.20=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,runtimeClasspath,scenarioTestCompileClasspath,scenarioTestRuntimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath
-blue.language:blue-language-java:3.1.0-rc.20=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,runtimeClasspath,scenarioTestCompileClasspath,scenarioTestRuntimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath
-blue.language:blue-language-mapping:3.1.0-rc.20=compileClasspath,consumerTestCompileClasspath,consumerTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,runtimeClasspath,scenarioTestCompileClasspath,scenarioTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath
-blue.language:blue-language-model:3.1.0-rc.20=compileClasspath,consumerTestCompileClasspath,consumerTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,runtimeClasspath,scenarioTestCompileClasspath,scenarioTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath
+blue.bex:blue-bex-contracts:1.1.0-rc.4=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,runtimeClasspath,scenarioTestCompileClasspath,scenarioTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath
+blue.bex:blue-bex-core:1.1.0-rc.4=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,runtimeClasspath,scenarioTestCompileClasspath,scenarioTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath
+blue.language:blue-contracts-core:3.1.0-rc.21=compileClasspath,consumerTestCompileClasspath,consumerTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,runtimeClasspath,scenarioTestCompileClasspath,scenarioTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath
+blue.language:blue-language-core:3.1.0-rc.21=compileClasspath,consumerTestCompileClasspath,consumerTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,runtimeClasspath,scenarioTestCompileClasspath,scenarioTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath
+blue.language:blue-language-ipfs:3.1.0-rc.21=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,runtimeClasspath,scenarioTestCompileClasspath,scenarioTestRuntimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath
+blue.language:blue-language-java:3.1.0-rc.21=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,runtimeClasspath,scenarioTestCompileClasspath,scenarioTestRuntimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath
+blue.language:blue-language-mapping:3.1.0-rc.21=compileClasspath,consumerTestCompileClasspath,consumerTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,runtimeClasspath,scenarioTestCompileClasspath,scenarioTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath
+blue.language:blue-language-model:3.1.0-rc.21=compileClasspath,consumerTestCompileClasspath,consumerTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,runtimeClasspath,scenarioTestCompileClasspath,scenarioTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath
blue.repo:blue-repo-java:3.0.0-rc.21=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,runtimeClasspath,scenarioTestCompileClasspath,scenarioTestRuntimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath
com.fasterxml.jackson.core:jackson-annotations:2.15.2=compileClasspath,consumerTestCompileClasspath,consumerTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,runtimeClasspath,scenarioTestCompileClasspath,scenarioTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath
com.fasterxml.jackson.core:jackson-core:2.15.2=compileClasspath,consumerTestCompileClasspath,consumerTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,runtimeClasspath,scenarioTestCompileClasspath,scenarioTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath
diff --git a/gradle/repository-source.lock b/gradle/repository-source.lock
deleted file mode 100644
index 09a0eb5..0000000
--- a/gradle/repository-source.lock
+++ /dev/null
@@ -1,5 +0,0 @@
-# Optional local-composite diagnostic input matching the published rc.21 API.
-coordinate=blue.repo:blue-repo-java:3.0.0-rc.21
-baseCommit=2fcf29bf060ed114c971194adb6f8b747899aee2
-workspaceDiffSha256=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
-languageCommit=505a654699b86b42bf0e282ddf94560a91529bcf
diff --git a/settings.gradle b/settings.gradle
index cecc5b8..b49b641 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -10,33 +10,9 @@ rootProject.name = 'blue-coordination-java'
def dependencyMode = providers.gradleProperty('blueDependencyMode')
.getOrElse('published-artifact')
.trim()
-if (!(dependencyMode in ['local-composite', 'published-artifact'])) {
+if (dependencyMode != 'published-artifact') {
throw new GradleException(
- 'blueDependencyMode must be local-composite or published-artifact')
-}
-
-if (dependencyMode == 'local-composite') {
- def localBex = file(providers.gradleProperty('blueBexCompositePath')
- .getOrElse('../blue-bex-java'))
- if (localBex.isDirectory()) {
- includeBuild(localBex) {
- dependencySubstitution {
- substitute(module('blue.bex:blue-bex-core'))
- .using(project(':blue-bex-core'))
- substitute(module('blue.bex:blue-bex-contracts'))
- .using(project(':blue-bex-contracts'))
- }
- }
- }
- def localRepository = file(providers.gradleProperty(
- 'blueRepositoryCompositePath')
- .getOrElse('../blue-repository-java'))
- if (localRepository.isDirectory()) {
- includeBuild(localRepository) {
- dependencySubstitution {
- substitute(module('blue.repo:blue-repo-java'))
- .using(project(':'))
- }
- }
- }
+ 'Only blueDependencyMode=published-artifact is supported; '
+ + 'local composite and staged file-repository modes '
+ + 'were retired for the 3.0.0-rc.3 release line')
}
diff --git a/src/consumerTest/java/blue/coordination/consumer/PublishedArtifactConsumerTest.java b/src/consumerTest/java/blue/coordination/consumer/PublishedArtifactConsumerTest.java
index ac872af..9d9ae5d 100644
--- a/src/consumerTest/java/blue/coordination/consumer/PublishedArtifactConsumerTest.java
+++ b/src/consumerTest/java/blue/coordination/consumer/PublishedArtifactConsumerTest.java
@@ -22,7 +22,8 @@ final class PublishedArtifactConsumerTest {
@Test
void counterExternalApiExample() throws Exception {
- try (CoordinationEngine engine = CoordinationEngine.inMemory()) {
+ try (CoordinationEngine engine = CoordinationEngine.legacyInMemory()) {
+ // given
Timeline alice = engine.registerTimeline(
"examples/clean-counter/alice", "alice");
Timeline bob = engine.registerTimeline(
@@ -34,12 +35,16 @@ void counterExternalApiExample() throws Exception {
"increment", "aliceChannel", "amount: 3"));
engine.append(bob, Operation.yaml(
"decrement", "bobChannel", "amount: 1"));
+
+ // when
var first = engine.drain(
new CoordinationEngine.DrainBudget(1L, 1L));
- assertTrue(first.paused());
- assertEquals(1L, first.committedProcessTransitions());
var second = engine.drain(
new CoordinationEngine.DrainBudget(1L, 1L));
+
+ // then
+ assertTrue(first.paused());
+ assertEquals(1L, first.committedProcessTransitions());
assertTrue(second.quiescent());
assertEquals(1L, second.committedProcessTransitions());
assertEquals(engine.document(counter).blueId(),
@@ -50,15 +55,20 @@ void counterExternalApiExample() throws Exception {
@Test
void largeOrdinaryRequestAppendsWholeWithoutTarget() throws Exception {
- try (CoordinationEngine engine = CoordinationEngine.inMemory()) {
+ try (CoordinationEngine engine = CoordinationEngine.legacyInMemory()) {
+ // given
Timeline unmatched = engine.registerTimeline(
"consumer/unmatched", "consumer");
ExactValue payNote = engine.exactValue(
resource("examples/clean/large-paynote.yaml"));
ExactValue request = engine.referenceRequest("payload", payNote);
+
+ // when
var entry = engine.append(
unmatched,
Operation.exact("store", "unmatchedChannel", request));
+
+ // then
assertEquals(0, engine.routeTargetCount(entry));
assertEquals(1, engine.metrics().journalEntryCount());
assertEquals(0L, engine.metrics().counter(
@@ -70,7 +80,8 @@ void largeOrdinaryRequestAppendsWholeWithoutTarget() throws Exception {
@Test
void largeHostCanAttachAuthorizeAndConfirmPayNote() throws Exception {
- try (CoordinationEngine engine = CoordinationEngine.inMemory()) {
+ try (CoordinationEngine engine = CoordinationEngine.legacyInMemory()) {
+ // given
Timeline alice = engine.registerTimeline(
"examples/large-order/alice", "alice");
Timeline admin = engine.registerTimeline(
@@ -83,6 +94,8 @@ void largeHostCanAttachAuthorizeAndConfirmPayNote() throws Exception {
"examples/clean/large-paynote.yaml");
engine.startDocument(
host, resource("examples/clean/large-order-host.yaml"));
+
+ // when
appendAndDrain(engine,
alice,
Operation.exact(
@@ -114,6 +127,7 @@ void largeHostCanAttachAuthorizeAndConfirmPayNote() throws Exception {
"providerChannel",
"confirmationReference: CONSUMER-DINNER"));
+ // then
assertEquals("Authorized", text(
engine, payNote, "/authorization/state"));
assertEquals("Authorized", text(
@@ -127,7 +141,8 @@ void largeHostCanAttachAuthorizeAndConfirmPayNote() throws Exception {
@Test
void existingSharedChildAdvancesTwoParents() throws Exception {
- try (CoordinationEngine engine = CoordinationEngine.inMemory()) {
+ try (CoordinationEngine engine = CoordinationEngine.legacyInMemory()) {
+ // given
String childYaml = resource(
"examples/clean/embedded-counter.yaml");
Timeline childTimeline = engine.registerTimeline(
@@ -152,6 +167,8 @@ void existingSharedChildAdvancesTwoParents() throws Exception {
"bob-two"));
ExactValue childReference = engine.referenceRequest(
"document", engine.exactValue(childYaml));
+
+ // when
appendAndDrain(engine, firstTimeline, Operation.exact(
"attachChild", "ownerChannel", childReference));
appendAndDrain(engine, secondTimeline, Operation.exact(
@@ -159,6 +176,7 @@ void existingSharedChildAdvancesTwoParents() throws Exception {
appendAndDrain(engine, childTimeline, Operation.yaml(
"increment", "ownerChannel", "amount: 5"));
+ // then
assertEquals(7L, integer(engine, child, "/counter"));
assertEquals(7L, integer(engine, first, "/child/counter"));
assertEquals(7L, integer(engine, second, "/child/counter"));
@@ -167,7 +185,8 @@ void existingSharedChildAdvancesTwoParents() throws Exception {
@Test
void nbaHistoricalGameCatchesStatisticsUp() throws Exception {
- try (CoordinationEngine engine = CoordinationEngine.inMemory()) {
+ try (CoordinationEngine engine = CoordinationEngine.legacyInMemory()) {
+ // given
String gameYaml = resource("examples/clean/nba-game.yaml");
Timeline gameFeed = engine.registerTimeline(
"examples/nba/game-2016-lal-min", "nba-feed");
@@ -184,6 +203,8 @@ void nbaHistoricalGameCatchesStatisticsUp() throws Exception {
engine.startDocument(
statistics,
resource("examples/clean/nba-statistics.yaml"));
+
+ // when
appendAndDrain(engine,
commissioner,
Operation.exact(
@@ -192,6 +213,7 @@ void nbaHistoricalGameCatchesStatisticsUp() throws Exception {
engine.referenceRequest(
"document", engine.exactValue(gameYaml))));
+ // then
assertEquals("Final", text(
engine, statistics, "/observedStatus"));
assertEquals(2L, integer(
@@ -206,7 +228,8 @@ void nbaHistoricalGameCatchesStatisticsUp() throws Exception {
@Test
void fiveEmbeddedOccurrencesReuseThreeManagedDocuments() throws Exception {
- try (CoordinationEngine engine = CoordinationEngine.inMemory()) {
+ try (CoordinationEngine engine = CoordinationEngine.legacyInMemory()) {
+ // given
Timeline owner = engine.registerTimeline(
"examples/playground/five-occurrence/host",
"playground-owner");
@@ -228,15 +251,19 @@ void fiveEmbeddedOccurrencesReuseThreeManagedDocuments() throws Exception {
engine.metrics().wholeObjectCount();
ExactValue request = engine.exactValue(
fiveDocumentRequest(engine));
- assertEquals(4,
+ int wholeObjectsRetainedByRequest =
engine.metrics().wholeObjectCount()
- - wholeObjectsBeforeRequest,
- "three unique child bodies plus one whole request");
+ - wholeObjectsBeforeRequest;
+
+ // when
appendAndDrain(engine, owner, Operation.exact(
"attachFiveDocuments",
"ownerChannel",
request));
+ // then
+ assertEquals(4, wholeObjectsRetainedByRequest,
+ "three unique child bodies plus one whole request");
assertEquals(4, engine.metrics().documentCount());
assertEquals(5, engine.document(host).embeddedChildren().size());
assertEquals(5L, integer(
diff --git a/src/consumerTest/java/blue/coordination/consumer/SdkBuiltJarConsumerTest.java b/src/consumerTest/java/blue/coordination/consumer/SdkBuiltJarConsumerTest.java
new file mode 100644
index 0000000..3e6c7a7
--- /dev/null
+++ b/src/consumerTest/java/blue/coordination/consumer/SdkBuiltJarConsumerTest.java
@@ -0,0 +1,78 @@
+package blue.coordination.consumer;
+
+import blue.coordination.sdk.BlueCoordination;
+import blue.coordination.sdk.DocumentHandle;
+import blue.coordination.sdk.EntryDisposition;
+import blue.coordination.sdk.ManagedDocument;
+import blue.coordination.sdk.TimelineHandle;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/** Compiles and runs solely against the built Coordination JAR surface. */
+final class SdkBuiltJarConsumerTest {
+ @Test
+ void bundledContractsSdkRunsFromTheBuiltJar() {
+ // given
+ String timelineId = "consumer/sdk-counter/alice";
+ String id = "consumer-sdk-counter";
+ try (BlueCoordination coordination = BlueCoordination.inMemory()) {
+ TimelineHandle timeline = coordination.timelines().register(
+ timelineId, "alice");
+ DocumentHandle counter = coordination.documents().admit(
+ ManagedDocument.yaml(id, counterYaml(id, timelineId))
+ .publicRoot()
+ .fromNow());
+
+ // when
+ var result = coordination.operations().on(counter)
+ .from(timeline)
+ .call("increment")
+ .through("ownerChannel")
+ .requestYaml("amount: 3")
+ .execute();
+
+ // then
+ assertEquals(EntryDisposition.APPLIED,
+ result.disposition());
+ assertEquals(3L, counter.snapshot().longAt("/counter"));
+ assertEquals(1L, counter.snapshot().epoch());
+ assertTrue(result.stats().gas() > 0L);
+ }
+ }
+
+ private static String counterYaml(
+ String id,
+ String timelineId) {
+ return """
+ documentId: %s
+ counter: 0
+ contracts:
+ ownerChannel:
+ type: Coordination/Timeline Channel
+ timeline:
+ type: MyOS/MyOS Timeline
+ timelineId: %s
+ actor:
+ type: MyOS/Principal Actor
+ accountId: alice
+ increment:
+ type: Coordination/Sequential Workflow Operation
+ channel: ownerChannel
+ request:
+ amount: {type: Integer}
+ steps:
+ - type: Coordination/Compute
+ do:
+ - $appendChange:
+ op: replace
+ path: /counter
+ val:
+ $add:
+ - $document: /counter
+ - $binding: event/message/request/amount
+ - $return: true
+ """.formatted(id, timelineId);
+ }
+}
diff --git a/src/integrationTest/java/blue/coordination/integration/AppendAdmissionAtomicityTest.java b/src/integrationTest/java/blue/coordination/integration/AppendAdmissionAtomicityTest.java
index f334da0..b2f4d1b 100644
--- a/src/integrationTest/java/blue/coordination/integration/AppendAdmissionAtomicityTest.java
+++ b/src/integrationTest/java/blue/coordination/integration/AppendAdmissionAtomicityTest.java
@@ -12,21 +12,22 @@ final class AppendAdmissionAtomicityTest {
void invalidEntryDoesNotConsumeClockSequenceOrPredecessor() {
try (TestEngine engine = TestEngine.create();
TestEngine fresh = TestEngine.create()) {
+ // given
var timeline = engine.timeline("atomic/alice", "alice");
var freshTimeline = fresh.timeline("atomic/alice", "alice");
long clockBefore = engine.logicalClockMicros();
int objectsBefore = engine.wholeObjectCount();
+ // when
assertThrows(
RuntimeException.class,
() -> engine.append(
timeline,
Operation.yaml(
"increment", "ownerChannel", "[")));
-
- assertEquals(0, engine.journalSize());
- assertEquals(clockBefore, engine.logicalClockMicros());
- assertEquals(objectsBefore, engine.wholeObjectCount());
+ int journalSizeAfterFailure = engine.journalSize();
+ long clockAfterFailure = engine.logicalClockMicros();
+ int objectsAfterFailure = engine.wholeObjectCount();
var retry = engine.append(
timeline,
@@ -37,6 +38,10 @@ void invalidEntryDoesNotConsumeClockSequenceOrPredecessor() {
Operation.yaml(
"increment", "ownerChannel", "amount: 1"));
+ // then
+ assertEquals(0, journalSizeAfterFailure);
+ assertEquals(clockBefore, clockAfterFailure);
+ assertEquals(objectsBefore, objectsAfterFailure);
assertEquals(expected.timestampMicros(), retry.timestampMicros());
assertEquals(expected.blueId(), retry.blueId());
assertEquals(1L, retry.globalSequence());
@@ -48,9 +53,11 @@ void invalidEntryDoesNotConsumeClockSequenceOrPredecessor() {
@Test
void invalidExplicitTimestampAppendDoesNotAdvanceClock() {
try (TestEngine engine = TestEngine.create()) {
+ // given
var timeline = engine.timeline("atomic/alice", "alice");
long clockBefore = engine.logicalClockMicros();
+ // when
assertThrows(
RuntimeException.class,
() -> engine.appendAt(
@@ -58,13 +65,16 @@ void invalidExplicitTimestampAppendDoesNotAdvanceClock() {
Operation.yaml(
"increment", "ownerChannel", "["),
clockBefore + 100L));
-
- assertEquals(clockBefore, engine.logicalClockMicros());
- assertEquals(0, engine.journalSize());
+ long clockAfterFailure = engine.logicalClockMicros();
+ int journalSizeAfterFailure = engine.journalSize();
var entry = engine.append(
timeline,
Operation.yaml(
"increment", "ownerChannel", "amount: 1"));
+
+ // then
+ assertEquals(clockBefore, clockAfterFailure);
+ assertEquals(0, journalSizeAfterFailure);
assertEquals(clockBefore + 1L, entry.timestampMicros());
assertEquals(1L, entry.globalSequence());
assertEquals(1L, entry.timelineSequence());
diff --git a/src/integrationTest/java/blue/coordination/integration/CacheSemanticParityIntegrationTest.java b/src/integrationTest/java/blue/coordination/integration/CacheSemanticParityIntegrationTest.java
index 30df98a..848b17b 100644
--- a/src/integrationTest/java/blue/coordination/integration/CacheSemanticParityIntegrationTest.java
+++ b/src/integrationTest/java/blue/coordination/integration/CacheSemanticParityIntegrationTest.java
@@ -25,6 +25,7 @@ void coldAndWarmRetryCommitIdenticalRevisionsEventsCausalityAndGas()
throws Exception {
try (TestEngine cold = TestEngine.create();
TestEngine warm = TestEngine.create()) {
+ // given
String source = resource("examples/clean/counter.yaml");
Timeline coldTimeline = cold.timeline(
"examples/clean-counter/alice", "alice");
@@ -53,6 +54,7 @@ void coldAndWarmRetryCommitIdenticalRevisionsEventsCausalityAndGas()
"the retry must run against a populated runtime cache");
warm.clearFailureInjection();
+ // when
cold.dispatch(coldEntry);
warm.dispatch(warmEntry);
@@ -60,6 +62,8 @@ void coldAndWarmRetryCommitIdenticalRevisionsEventsCausalityAndGas()
cold.history("counter"));
List warmTrace = evidence(
warm.history("counter"));
+
+ // then
assertEquals(coldTrace, warmTrace);
assertEquals(cold.session("counter").current().blueId(),
warm.session("counter").current().blueId());
diff --git a/src/integrationTest/java/blue/coordination/integration/ConcurrentEmbeddedChildCreationTest.java b/src/integrationTest/java/blue/coordination/integration/ConcurrentEmbeddedChildCreationTest.java
index 7fb53ea..6113573 100644
--- a/src/integrationTest/java/blue/coordination/integration/ConcurrentEmbeddedChildCreationTest.java
+++ b/src/integrationTest/java/blue/coordination/integration/ConcurrentEmbeddedChildCreationTest.java
@@ -18,6 +18,7 @@ final class ConcurrentEmbeddedChildCreationTest {
@Test
void twoParentsAttachingSameUnseenChildCreateOneSession() throws Exception {
try (TestEngine engine = TestEngine.create()) {
+ // given
String childInitial = resource(
"examples/clean/embedded-counter.yaml");
Timeline childTimeline = engine.timeline(
@@ -54,6 +55,8 @@ void twoParentsAttachingSameUnseenChildCreateOneSession() throws Exception {
EngineMetrics.MetricsSnapshot before = engine.metricsSnapshot();
ExecutorService executor = Executors.newFixedThreadPool(2);
+
+ // when
try {
CountDownLatch ready = new CountDownLatch(2);
CountDownLatch start = new CountDownLatch(1);
@@ -77,6 +80,7 @@ void twoParentsAttachingSameUnseenChildCreateOneSession() throws Exception {
EngineTestSupport.MetricDelta work = delta(
before, engine.metricsSnapshot());
+ // then
assertEquals(1L, work.counter("embedding.childSessionsCreated"));
assertEquals(1L, work.counter("embedding.childSessionsReused"));
assertEquals(1L, work.counter("preparedRuntimeCompilations"),
diff --git a/src/integrationTest/java/blue/coordination/integration/CoreBehaviorIntegrationTest.java b/src/integrationTest/java/blue/coordination/integration/CoreBehaviorIntegrationTest.java
index a6e9e59..4005a03 100644
--- a/src/integrationTest/java/blue/coordination/integration/CoreBehaviorIntegrationTest.java
+++ b/src/integrationTest/java/blue/coordination/integration/CoreBehaviorIntegrationTest.java
@@ -29,6 +29,7 @@ final class CoreBehaviorIntegrationTest {
void counterRoutesAliceAndBobExactlyOnceWithoutGenericSplitting()
throws Exception {
try (TestEngine engine = TestEngine.create()) {
+ // given
Timeline alice = engine.timeline(
"examples/clean-counter/alice", "alice");
Timeline bob = engine.timeline(
@@ -36,6 +37,7 @@ void counterRoutesAliceAndBobExactlyOnceWithoutGenericSplitting()
engine.start("counter", resource("examples/clean/counter.yaml"));
EngineMetrics.MetricsSnapshot before = engine.metricsSnapshot();
+ // when
engine.appendAndDispatch(alice, Operation.yaml(
"increment", "aliceChannel", "amount: 3"));
engine.appendAndDispatch(bob, Operation.yaml(
@@ -43,6 +45,8 @@ void counterRoutesAliceAndBobExactlyOnceWithoutGenericSplitting()
EngineTestSupport.MetricDelta work = delta(
before, engine.metricsSnapshot());
+
+ // then
assertEquals(2L, integer(engine, "counter", "/counter"));
assertEquals(2L, engine.session("counter").epoch());
assertEquals(2, engine.journalSize());
@@ -60,6 +64,7 @@ void counterRoutesAliceAndBobExactlyOnceWithoutGenericSplitting()
@Test
void ordinaryPayNoteIsOneWholeInlineValue() throws Exception {
try (TestEngine engine = TestEngine.create()) {
+ // given
Timeline alice = engine.timeline(
"examples/whole-request/alice", "alice");
engine.start("whole-request-sink", resource(
@@ -70,6 +75,7 @@ void ordinaryPayNoteIsOneWholeInlineValue() throws Exception {
"payload", payNoteYaml);
EngineMetrics.MetricsSnapshot before = engine.metricsSnapshot();
+ // when
engine.appendAndDispatch(alice, Operation.exact(
"storePayload", "aliceChannel", request));
@@ -80,6 +86,8 @@ void ordinaryPayNoteIsOneWholeInlineValue() throws Exception {
? stored.getBlueId()
: DirectBlueIdCalculator.calculateBlueId(stored);
Node expected = engine.exactRequest(payNoteYaml).copyNode();
+
+ // then
assertEquals(DirectBlueIdCalculator.calculateBlueId(expected),
storedBlueId);
assertEquals(1, engine.session("whole-request-sink")
@@ -97,6 +105,7 @@ void ordinaryPayNoteIsOneWholeInlineValue() throws Exception {
void existingChildRevisionsCatchParentUpWithoutSourceReplay()
throws Exception {
try (TestEngine engine = TestEngine.create()) {
+ // given
String childInitial = resource(
"examples/clean/embedded-counter.yaml");
Timeline child = engine.timeline("examples/embedded/A", "alice");
@@ -111,6 +120,8 @@ void existingChildRevisionsCatchParentUpWithoutSourceReplay()
engine.start("embedded-parent-B", resource(
"examples/clean/embedded-parent.yaml"));
EngineMetrics.MetricsSnapshot before = engine.metricsSnapshot();
+
+ // when
engine.dispatch(engine.appendAt(parent, Operation.exact(
"attachChild", "ownerChannel",
engine.embeddedDocumentRequest(childInitial)),
@@ -118,6 +129,8 @@ void existingChildRevisionsCatchParentUpWithoutSourceReplay()
EngineTestSupport.MetricDelta work = delta(
before, engine.metricsSnapshot());
+
+ // then
assertEquals(SessionStatus.READY,
engine.session("embedded-parent-B").status());
assertEquals(6L, integer(
@@ -138,6 +151,7 @@ void existingChildRevisionsCatchParentUpWithoutSourceReplay()
void completedNbaGameCatchesUpAndContinuesLiveWithoutReplay()
throws Exception {
try (TestEngine engine = TestEngine.create()) {
+ // given
String gameInitial = resource("examples/clean/nba-game.yaml");
Timeline gameFeed = engine.timeline(
"examples/nba/game-2016-lal-min", "nba-feed");
@@ -154,6 +168,8 @@ void completedNbaGameCatchesUpAndContinuesLiveWithoutReplay()
engine.start("nba-statistics", resource(
"examples/clean/nba-statistics.yaml"));
EngineMetrics.MetricsSnapshot before = engine.metricsSnapshot();
+
+ // when
engine.dispatch(engine.appendAt(commissioner, Operation.exact(
"attachGame", "commissionerChannel",
engine.embeddedDocumentRequest(gameInitial)),
@@ -178,6 +194,8 @@ void completedNbaGameCatchesUpAndContinuesLiveWithoutReplay()
"homeScores", "points: 1");
EngineTestSupport.MetricDelta live = delta(
beforeLive, engine.metricsSnapshot());
+
+ // then
assertEquals(3L, integer(
engine, "nba-statistics", "/observedHomeScore"));
assertEquals(3L, integer(
diff --git a/src/integrationTest/java/blue/coordination/integration/DeepSameEntryOrderingIntegrationTest.java b/src/integrationTest/java/blue/coordination/integration/DeepSameEntryOrderingIntegrationTest.java
index 2550740..bab5ef8 100644
--- a/src/integrationTest/java/blue/coordination/integration/DeepSameEntryOrderingIntegrationTest.java
+++ b/src/integrationTest/java/blue/coordination/integration/DeepSameEntryOrderingIntegrationTest.java
@@ -27,6 +27,7 @@ final class DeepSameEntryOrderingIntegrationTest {
void oneEntryProcessesDeepestFirstAndSettlesEveryEpochBeforeItsParent()
throws Exception {
try (TestEngine engine = TestEngine.create()) {
+ // given
String leafSource = resource(
"examples/clean/deep-same-entry-a11.yaml");
String middleSource = resource(
@@ -68,10 +69,12 @@ void oneEntryProcessesDeepestFirstAndSettlesEveryEpochBeforeItsParent()
assertEquals(3, engine.routeTargetCount(entry));
EngineMetrics.MetricsSnapshot before = engine.metricsSnapshot();
+ // when
ProcessingDrainReceipt receipt = engine.dispatch(entry);
EngineTestSupport.MetricDelta work = delta(
before, engine.metricsSnapshot());
+ // then
assertEquals(List.of(entry), receipt.processedEntries());
assertEquals(List.of(
"deep-same-entry-a11|TIMELINE_ENTRY",
diff --git a/src/integrationTest/java/blue/coordination/integration/DynamicHistoricalSourceSurfaceIntegrationTest.java b/src/integrationTest/java/blue/coordination/integration/DynamicHistoricalSourceSurfaceIntegrationTest.java
index 53b5dff..d418950 100644
--- a/src/integrationTest/java/blue/coordination/integration/DynamicHistoricalSourceSurfaceIntegrationTest.java
+++ b/src/integrationTest/java/blue/coordination/integration/DynamicHistoricalSourceSurfaceIntegrationTest.java
@@ -31,6 +31,7 @@ final class DynamicHistoricalSourceSurfaceIntegrationTest {
void historicalAddAndRemovalRefreshTheSurfaceBeforeNextSelection()
throws Exception {
try (TestEngine engine = TestEngine.create()) {
+ // given
Timeline owner = engine.timeline(OWNER_TIMELINE, "owner");
Timeline dynamic = engine.timeline(
DYNAMIC_TIMELINE, "dynamic-owner");
@@ -71,40 +72,57 @@ void historicalAddAndRemovalRefreshTheSurfaceBeforeNextSelection()
TimelineEntry afterRetirement = engine.appendAt(
dynamic, applyDynamic(1_000L), T0 + 400L);
+ // when
engine.start(
DOCUMENT,
resource("examples/clean/dynamic-source-surface.yaml"),
CoordinationEngine.AdmissionPolicy.FULL_HISTORY,
null);
+ long totalAfterHistoricalStart = integer(
+ engine, DOCUMENT, "/total");
+ Object activatedAfterHistoricalStart =
+ engine.value(DOCUMENT, "/activated").getValue();
+ Object retiredAfterHistoricalStart =
+ engine.value(DOCUMENT, "/retired").getValue();
+ List processedAfterHistoricalStart =
+ processedTimelineEntries(engine);
+ Set timelinesAfterHistoricalStart =
+ engine.effectiveTimelineIds(DOCUMENT);
+ int beforeActivationTargets =
+ engine.routeTargetCount(beforeActivation);
+ int activeTargets = engine.routeTargetCount(active);
+ int afterRetirementTargets =
+ engine.routeTargetCount(afterRetirement);
+ int activationTargets = engine.routeTargetCount(activation);
+ int retirementTargets = engine.routeTargetCount(retirement);
+ int historySize = engine.history(DOCUMENT).size();
+ TimelineEntry liveAfterRetirement = engine.appendAt(
+ dynamic, applyDynamic(10_000L), T0 + 500L);
+ int liveAfterRetirementTargets =
+ engine.routeTargetCount(liveAfterRetirement);
+ engine.dispatch(liveAfterRetirement);
- assertEquals(2L, integer(engine, DOCUMENT, "/total"),
+ // then
+ assertEquals(2L, totalAfterHistoricalStart,
"only the entry inside the dynamic active interval runs");
- assertEquals(Boolean.TRUE,
- engine.value(DOCUMENT, "/activated").getValue());
- assertEquals(Boolean.TRUE,
- engine.value(DOCUMENT, "/retired").getValue());
+ assertEquals(Boolean.TRUE, activatedAfterHistoricalStart);
+ assertEquals(Boolean.TRUE, retiredAfterHistoricalStart);
assertEquals(
List.of(
activation.blueId(),
active.blueId(),
retirement.blueId()),
- processedTimelineEntries(engine),
+ processedAfterHistoricalStart,
"the feeder must reselect after both surface changes");
assertEquals(Set.of(OWNER_TIMELINE),
- engine.effectiveTimelineIds(DOCUMENT),
+ timelinesAfterHistoricalStart,
"the retired dynamic Timeline must leave the surface");
- assertEquals(0, engine.routeTargetCount(beforeActivation));
- assertEquals(0, engine.routeTargetCount(active));
- assertEquals(0, engine.routeTargetCount(afterRetirement));
- assertEquals(1, engine.routeTargetCount(activation));
- assertEquals(1, engine.routeTargetCount(retirement));
-
- int historySize = engine.history(DOCUMENT).size();
- TimelineEntry liveAfterRetirement = engine.appendAt(
- dynamic, applyDynamic(10_000L), T0 + 500L);
- assertEquals(0, engine.routeTargetCount(liveAfterRetirement));
- engine.dispatch(liveAfterRetirement);
-
+ assertEquals(0, beforeActivationTargets);
+ assertEquals(0, activeTargets);
+ assertEquals(0, afterRetirementTargets);
+ assertEquals(1, activationTargets);
+ assertEquals(1, retirementTargets);
+ assertEquals(0, liveAfterRetirementTargets);
assertEquals(2L, integer(engine, DOCUMENT, "/total"));
assertEquals(historySize, engine.history(DOCUMENT).size(),
"removed handlers cannot receive later live entries");
diff --git a/src/integrationTest/java/blue/coordination/integration/DynamicProcessEmbeddedHistoricalPathActivationTest.java b/src/integrationTest/java/blue/coordination/integration/DynamicProcessEmbeddedHistoricalPathActivationTest.java
index a38e3ff..63a3801 100644
--- a/src/integrationTest/java/blue/coordination/integration/DynamicProcessEmbeddedHistoricalPathActivationTest.java
+++ b/src/integrationTest/java/blue/coordination/integration/DynamicProcessEmbeddedHistoricalPathActivationTest.java
@@ -27,6 +27,7 @@ final class DynamicProcessEmbeddedHistoricalPathActivationTest {
@Test
void activationInitializesThenCatchesHistoryBeforeTheNextParentEntry()
throws Exception {
+ // given
String gameYaml = Round12NbaFixtures.game(
GAME_ID,
"examples/round12/dynamic/history/game",
@@ -68,10 +69,13 @@ void activationInitializesThenCatchesHistoryBeforeTheNextParentEntry()
nextParent.sourceOrderKey()) < 0);
EngineMetrics.MetricsSnapshot before = engine.metricsSnapshot();
+
+ // when
engine.dispatch(nextParent);
EngineTestSupport.MetricDelta work = delta(
before, engine.metricsSnapshot());
+ // then
assertEquals(SessionStatus.READY,
engine.session(HOST_ID).status());
assertEquals(SessionStatus.READY,
diff --git a/src/integrationTest/java/blue/coordination/integration/DynamicProcessEmbeddedPathActivationTest.java b/src/integrationTest/java/blue/coordination/integration/DynamicProcessEmbeddedPathActivationTest.java
index eadeea0..4422328 100644
--- a/src/integrationTest/java/blue/coordination/integration/DynamicProcessEmbeddedPathActivationTest.java
+++ b/src/integrationTest/java/blue/coordination/integration/DynamicProcessEmbeddedPathActivationTest.java
@@ -23,6 +23,7 @@ final class DynamicProcessEmbeddedPathActivationTest {
@Test
void addingProcessEmbeddedPathInitializesExistingInlineDocument()
throws Exception {
+ // given
String gameYaml = Round12NbaFixtures.game(
GAME_ID,
"examples/round12/dynamic/path/game",
@@ -45,6 +46,7 @@ void addingProcessEmbeddedPathInitializesExistingInlineDocument()
assertEquals(1L, integer(engine, HOST_ID,
"/hostInitializationCount"));
+ // when
TimelineEntry activation = engine.append(
owner,
Operation.yaml(
@@ -86,6 +88,8 @@ void addingProcessEmbeddedPathInitializesExistingInlineDocument()
EngineTestSupport.MetricDelta work = delta(
before, engine.metricsSnapshot());
+
+ // then
assertEquals(1L, work.counter(
"embedding.childSessionsCreated"));
assertEquals(1L, work.counter(
diff --git a/src/integrationTest/java/blue/coordination/integration/EmbeddedEpochEventOccurrenceIntegrationTest.java b/src/integrationTest/java/blue/coordination/integration/EmbeddedEpochEventOccurrenceIntegrationTest.java
index fe32b7b..5fb789b 100644
--- a/src/integrationTest/java/blue/coordination/integration/EmbeddedEpochEventOccurrenceIntegrationTest.java
+++ b/src/integrationTest/java/blue/coordination/integration/EmbeddedEpochEventOccurrenceIntegrationTest.java
@@ -26,6 +26,7 @@ final class EmbeddedEpochEventOccurrenceIntegrationTest {
void duplicateEventIdentitiesRetainBothOrderedOccurrencesInParentProcess()
throws Exception {
try (TestEngine engine = TestEngine.create()) {
+ // given
Timeline childTimeline = engine.timeline(
"examples/duplicate-events/child", "alice");
engine.start(PARENT, resource(
@@ -33,6 +34,7 @@ void duplicateEventIdentitiesRetainBothOrderedOccurrencesInParentProcess()
int parentHistoryBefore = engine.history(PARENT).size();
EngineMetrics.MetricsSnapshot before = engine.metricsSnapshot();
+ // when
TimelineEntry entry = engine.append(
childTimeline,
Operation.yaml("advance", "childChannel", "{}"));
@@ -46,6 +48,8 @@ void duplicateEventIdentitiesRetainBothOrderedOccurrencesInParentProcess()
assertEquals(2, events.size());
String eventBlueId = DirectBlueIdCalculator.calculateBlueId(
events.get(0));
+
+ // then
assertEquals(eventBlueId,
DirectBlueIdCalculator.calculateBlueId(events.get(1)),
"the two semantic events deliberately share one BlueId");
diff --git a/src/integrationTest/java/blue/coordination/integration/EmbeddedOnlyStoragePolicyTest.java b/src/integrationTest/java/blue/coordination/integration/EmbeddedOnlyStoragePolicyTest.java
index 816118a..07014cb 100644
--- a/src/integrationTest/java/blue/coordination/integration/EmbeddedOnlyStoragePolicyTest.java
+++ b/src/integrationTest/java/blue/coordination/integration/EmbeddedOnlyStoragePolicyTest.java
@@ -22,9 +22,12 @@ final class EmbeddedOnlyStoragePolicyTest {
void ordinaryLargeDocumentAndRequestsStayWholeWhileEmbeddedChildIsOneCut()
throws Exception {
try (TestEngine engine = TestEngine.create()) {
+ // given
String payNote = resource("examples/clean/package-paynote.yaml");
EngineMetrics.MetricsSnapshot beforePayNote =
engine.metricsSnapshot();
+
+ // when
engine.start("standalone-paynote", payNote);
EngineTestSupport.MetricDelta payNoteWork = delta(
beforePayNote, engine.metricsSnapshot());
@@ -72,6 +75,8 @@ void ordinaryLargeDocumentAndRequestsStayWholeWhileEmbeddedChildIsOneCut()
EmbeddedOnlyLayout parentLayout =
engine.session("embedded-parent-B").layout();
+
+ // then
assertEquals(2, parentLayout.physicalObjectCount());
assertEquals(1, parentLayout.embeddedDocumentCount());
assertEquals(1, parentLayout.splitterCreatedEdgeCount());
diff --git a/src/integrationTest/java/blue/coordination/integration/EngineTestSupportMetricVocabularyTest.java b/src/integrationTest/java/blue/coordination/integration/EngineTestSupportMetricVocabularyTest.java
index 3290b5f..3ce64e4 100644
--- a/src/integrationTest/java/blue/coordination/integration/EngineTestSupportMetricVocabularyTest.java
+++ b/src/integrationTest/java/blue/coordination/integration/EngineTestSupportMetricVocabularyTest.java
@@ -13,18 +13,23 @@
final class EngineTestSupportMetricVocabularyTest {
@Test
void unknownCounterAndTimerNamesCannotMasqueradeAsZero() {
+ // given
+ EngineMetrics.MetricsSnapshot before = snapshot(
+ Map.of(
+ "REQUEST_FRAGMENTS", 0L,
+ "temporal.parentEpochApplications", 7L),
+ Map.of("process.frozen", 11L));
+ EngineMetrics.MetricsSnapshot after = snapshot(
+ Map.of(
+ "REQUEST_FRAGMENTS", 0L,
+ "temporal.parentEpochApplications", 7L),
+ Map.of("process.frozen", 11L));
+
+ // when
EngineTestSupport.MetricDelta work = delta(
- snapshot(
- Map.of(
- "REQUEST_FRAGMENTS", 0L,
- "temporal.parentEpochApplications", 7L),
- Map.of("process.frozen", 11L)),
- snapshot(
- Map.of(
- "REQUEST_FRAGMENTS", 0L,
- "temporal.parentEpochApplications", 7L),
- Map.of("process.frozen", 11L)));
+ before, after);
+ // then
assertEquals(0L, work.counter("REQUEST_FRAGMENTS"),
"a registered canonical counter may legitimately stay zero");
assertEquals(0L,
diff --git a/src/integrationTest/java/blue/coordination/integration/ExistingEmbeddedStateOnlyCatchUpTest.java b/src/integrationTest/java/blue/coordination/integration/ExistingEmbeddedStateOnlyCatchUpTest.java
index 17263ae..20f4876 100644
--- a/src/integrationTest/java/blue/coordination/integration/ExistingEmbeddedStateOnlyCatchUpTest.java
+++ b/src/integrationTest/java/blue/coordination/integration/ExistingEmbeddedStateOnlyCatchUpTest.java
@@ -20,6 +20,7 @@ final class ExistingEmbeddedStateOnlyCatchUpTest {
void attachmentReusesChildHistoryAndProcessesEveryParentEpochExactlyOnce()
throws Exception {
try (TestEngine engine = TestEngine.create()) {
+ // given
String childInitial = resource(
"examples/clean/embedded-counter.yaml");
Timeline childTimeline = engine.timeline(
@@ -40,6 +41,8 @@ void attachmentReusesChildHistoryAndProcessesEveryParentEpochExactlyOnce()
"embedded-state-parent",
resource("examples/clean/embedded-state-parent.yaml"));
EngineMetrics.MetricsSnapshot before = engine.metricsSnapshot();
+
+ // when
engine.appendAndDispatch(
parentTimeline,
Operation.exact(
@@ -49,6 +52,7 @@ void attachmentReusesChildHistoryAndProcessesEveryParentEpochExactlyOnce()
EngineTestSupport.MetricDelta work = delta(
before, engine.metricsSnapshot());
+ // then
assertEquals(20L, integer(
engine, "embedded-state-parent", "/child/counter"));
assertEquals(childRevisions, engine.history(
diff --git a/src/integrationTest/java/blue/coordination/integration/FailureRetryAtomicityTest.java b/src/integrationTest/java/blue/coordination/integration/FailureRetryAtomicityTest.java
index 955d81a..462ede1 100644
--- a/src/integrationTest/java/blue/coordination/integration/FailureRetryAtomicityTest.java
+++ b/src/integrationTest/java/blue/coordination/integration/FailureRetryAtomicityTest.java
@@ -21,6 +21,7 @@ final class FailureRetryAtomicityTest {
void stagedChildFailureRollsBackOnlyHostDeltaAndTerminalRetryReconciles()
throws Exception {
try (TestEngine engine = TestEngine.create()) {
+ // given
String childInitial = resource(
"examples/clean/embedded-counter.yaml");
Timeline childTimeline = engine.timeline(
@@ -91,8 +92,11 @@ void stagedChildFailureRollsBackOnlyHostDeltaAndTerminalRetryReconciles()
"terminal retry cannot rerun the committed parent PROCESS");
engine.clearFailureInjection();
+
+ // when
engine.dispatch(attachment);
+ // then
assertEquals(2, engine.documentCount());
assertEquals(SessionStatus.READY, engine.session(
"embedded-state-parent").status());
@@ -115,6 +119,7 @@ void stagedChildFailureRollsBackOnlyHostDeltaAndTerminalRetryReconciles()
void childCommitSurvivesARepeatedFailureBeforeParentCommit()
throws Exception {
try (TestEngine engine = TestEngine.create()) {
+ // given
String childInitial = resource(
"examples/clean/embedded-counter.yaml");
Timeline childTimeline = engine.timeline(
@@ -171,10 +176,13 @@ void childCommitSurvivesARepeatedFailureBeforeParentCommit()
engine.clearFailureInjection();
EngineMetrics.MetricsSnapshot beforeRetry =
engine.metricsSnapshot();
+
+ // when
engine.dispatch(liveChildEntry);
EngineTestSupport.MetricDelta retry = delta(
beforeRetry, engine.metricsSnapshot());
+ // then
assertEquals(1L, retry.counter("frozenProcessCalls"),
"retry runs only the missing parent application");
assertEquals(2, engine.history("embedded-counter-A").size());
@@ -195,6 +203,7 @@ void childCommitSurvivesARepeatedFailureBeforeParentCommit()
void restartRebuildsQueueAndAppliesOnlyTheMissingParentTransition()
throws Exception {
try (TestEngine engine = TestEngine.create()) {
+ // given
String childInitial = resource(
"examples/clean/embedded-counter.yaml");
Timeline childTimeline = engine.timeline(
@@ -244,6 +253,8 @@ void restartRebuildsQueueAndAppliesOnlyTheMissingParentTransition()
assertNotReady(engine, "embedded-state-parent");
EngineMetrics.MetricsSnapshot beforeRestart =
engine.metricsSnapshot();
+
+ // when
engine.restartFromStores();
EngineTestSupport.MetricDelta restart = delta(
beforeRestart, engine.metricsSnapshot());
@@ -259,6 +270,7 @@ void restartRebuildsQueueAndAppliesOnlyTheMissingParentTransition()
EngineTestSupport.MetricDelta resume = delta(
beforeResume, engine.metricsSnapshot());
+ // then
assertEquals(5L, integer(
engine, "embedded-state-parent", "/child/counter"));
assertEquals(2, engine.history("embedded-counter-A").size(),
@@ -296,6 +308,7 @@ void restartRebuildsQueueAndAppliesOnlyTheMissingParentTransition()
void committedEmbeddedEpochSurvivesRestartAndReconcilesItsCursorOnce()
throws Exception {
try (TestEngine engine = TestEngine.create()) {
+ // given
String childInitial = resource(
"examples/clean/embedded-counter.yaml");
Timeline childTimeline = engine.timeline(
@@ -332,6 +345,7 @@ void committedEmbeddedEpochSurvivesRestartAndReconcilesItsCursorOnce()
"embedded-state-parent").get("/child"));
assertEquals(0L, engine.session("embedded-counter-A").epoch());
+ // when
engine.restartFromStores();
engine.dispatch(attachment);
assertEquals(3L, integer(
@@ -345,6 +359,8 @@ void committedEmbeddedEpochSurvivesRestartAndReconcilesItsCursorOnce()
engine.dispatch(attachment);
EngineTestSupport.MetricDelta duplicate = delta(
beforeDuplicate, engine.metricsSnapshot());
+
+ // then
assertEquals(0L, duplicate.counter("frozenProcessCalls"));
}
}
@@ -353,6 +369,7 @@ void committedEmbeddedEpochSurvivesRestartAndReconcilesItsCursorOnce()
void committedStateWithLostResponseIsReconciledFromDeliveryReceipt()
throws Exception {
try (TestEngine engine = TestEngine.create()) {
+ // given
Timeline alice = engine.timeline(
"examples/clean-counter/alice", "alice");
engine.start(
@@ -376,11 +393,15 @@ void committedStateWithLostResponseIsReconciledFromDeliveryReceipt()
engine.clearFailureInjection();
EngineMetrics.MetricsSnapshot beforeRetry =
engine.metricsSnapshot();
+
+ // when
assertEquals(0, engine.dispatch(entry).outcomes().size(),
"receipt reconciliation commits no new transition in "
+ "the retry call");
EngineTestSupport.MetricDelta retry = delta(
beforeRetry, engine.metricsSnapshot());
+
+ // then
assertEquals(0L, retry.counter("frozenProcessCalls"));
assertEquals(0L, retry.counter("EXTERNAL_PROCESS_CALLS"));
assertEquals(3L, integer(engine, "counter", "/counter"));
@@ -390,6 +411,7 @@ void committedStateWithLostResponseIsReconciledFromDeliveryReceipt()
void privateEmbeddedInputNeverChangesExternalJournalFrontier()
throws Exception {
try (TestEngine engine = TestEngine.create()) {
+ // given
String childInitial = resource(
"examples/clean/embedded-middle.yaml");
engine.start("embedded-middle-A", childInitial);
@@ -421,6 +443,8 @@ void privateEmbeddedInputNeverChangesExternalJournalFrontier()
"embedded-root-B").get("/child"));
engine.clearFailureInjection();
+
+ // when
engine.dispatch(attachment);
assertEquals(journalBeforeDispatch, engine.journalSize(),
"retry reconciles the document-local receipt only");
@@ -437,6 +461,8 @@ void privateEmbeddedInputNeverChangesExternalJournalFrontier()
var next = engine.append(
rootTimeline,
Operation.yaml("ignored", "ownerChannel", "{}"));
+
+ // then
assertEquals(attachment.timestampMicros() + 1L,
next.timestampMicros());
assertEquals(attachment.globalSequence() + 1L,
diff --git a/src/integrationTest/java/blue/coordination/integration/HistoricalSourceSurfaceIntervalIntegrationTest.java b/src/integrationTest/java/blue/coordination/integration/HistoricalSourceSurfaceIntervalIntegrationTest.java
index 588ef79..363210a 100644
--- a/src/integrationTest/java/blue/coordination/integration/HistoricalSourceSurfaceIntervalIntegrationTest.java
+++ b/src/integrationTest/java/blue/coordination/integration/HistoricalSourceSurfaceIntervalIntegrationTest.java
@@ -26,6 +26,7 @@ final class HistoricalSourceSurfaceIntervalIntegrationTest {
void historicalAttachmentActivatesOnlyItsExactNestedSourceInterval()
throws Exception {
try (TestEngine engine = TestEngine.create()) {
+ // given
String leaf = leafSource();
String controller = parentSource(
CONTROLLER,
@@ -68,6 +69,7 @@ void historicalAttachmentActivatesOnlyItsExactNestedSourceInterval()
attach(engine, controller),
T0 + 1_000L);
+ // when
engine.dispatch(rootAttachment);
assertEquals(0, engine.routeTargetCount(excluded),
@@ -103,6 +105,8 @@ void historicalAttachmentActivatesOnlyItsExactNestedSourceInterval()
int leafHistorySize = engine.history(LEAF).size();
engine.dispatch(live);
+
+ // then
assertEquals(leafHistorySize, engine.history(LEAF).size(),
"re-draining the live cutoff must be idempotent");
}
diff --git a/src/integrationTest/java/blue/coordination/integration/InitializationLifecycleEventOrderingIntegrationTest.java b/src/integrationTest/java/blue/coordination/integration/InitializationLifecycleEventOrderingIntegrationTest.java
index 06839e2..6dfdc1e 100644
--- a/src/integrationTest/java/blue/coordination/integration/InitializationLifecycleEventOrderingIntegrationTest.java
+++ b/src/integrationTest/java/blue/coordination/integration/InitializationLifecycleEventOrderingIntegrationTest.java
@@ -33,9 +33,12 @@ final class InitializationLifecycleEventOrderingIntegrationTest {
@Test
void lifecycleHandlerRetainsTwoIdenticalInitializationEvents()
throws Exception {
+ // given
String childId = "round12-duplicate-initialization-events";
try (TestEngine engine = TestEngine.create()) {
EngineMetrics.MetricsSnapshot before = engine.metricsSnapshot();
+
+ // when
TimelineEntry attachment = attach(engine,
"examples/round12/initialization-duplicate-events-child.yaml");
@@ -49,6 +52,8 @@ void lifecycleHandlerRetainsTwoIdenticalInitializationEvents()
String eventBlueId = DirectBlueIdCalculator.calculateBlueId(
events.get(0));
+
+ // then
assertEquals(eventBlueId,
DirectBlueIdCalculator.calculateBlueId(events.get(1)),
"the lifecycle handler deliberately emits one exact value twice");
@@ -64,9 +69,12 @@ void lifecycleHandlerRetainsTwoIdenticalInitializationEvents()
@Test
void lifecycleHandlerRetainsInitializationEventsInEmissionOrder()
throws Exception {
+ // given
String childId = "round12-ordered-initialization-events";
try (TestEngine engine = TestEngine.create()) {
EngineMetrics.MetricsSnapshot before = engine.metricsSnapshot();
+
+ // when
TimelineEntry attachment = attach(engine,
"examples/round12/initialization-ordered-events-child.yaml");
@@ -83,6 +91,8 @@ void lifecycleHandlerRetainsInitializationEventsInEmissionOrder()
events.get(0));
String secondBlueId = DirectBlueIdCalculator.calculateBlueId(
events.get(1));
+
+ // then
assertNotEquals(firstBlueId, secondBlueId);
assertParentEvidence(engine, firstBlueId, secondBlueId,
kind(events.get(0)), kind(events.get(1)));
diff --git a/src/integrationTest/java/blue/coordination/integration/InitializationRetryIdempotencyTest.java b/src/integrationTest/java/blue/coordination/integration/InitializationRetryIdempotencyTest.java
index 554c74a..c1c010c 100644
--- a/src/integrationTest/java/blue/coordination/integration/InitializationRetryIdempotencyTest.java
+++ b/src/integrationTest/java/blue/coordination/integration/InitializationRetryIdempotencyTest.java
@@ -21,6 +21,7 @@ final class InitializationRetryIdempotencyTest {
@Test
void retryPublishesOneChildInitializationWithoutRepeatingParentOperation()
throws Exception {
+ // given
String gameYaml = Round12NbaFixtures.game(
GAME_ID,
"examples/round12/retry/game",
@@ -59,8 +60,11 @@ void retryPublishesOneChildInitializationWithoutRepeatingParentOperation()
"the already committed parent operation must not repeat");
engine.clearFailureInjection();
+
+ // when
engine.dispatch(activation);
+ // then
assertEquals(SessionStatus.READY,
engine.readyDocument(HOST_ID).status());
assertEquals(1L, engine.history(HOST_ID).stream()
@@ -87,6 +91,7 @@ void retryPublishesOneChildInitializationWithoutRepeatingParentOperation()
@Test
void failedParentInitializationApplicationDoesNotRollbackChildOrPeer()
throws Exception {
+ // given
String gameId = "round12-isolation-game";
String firstHostId = "round12-isolation-host-one";
String secondHostId = "round12-isolation-host-two";
@@ -180,10 +185,13 @@ void failedParentInitializationApplicationDoesNotRollbackChildOrPeer()
engine.clearFailureInjection();
EngineMetrics.MetricsSnapshot beforeRetry =
engine.metricsSnapshot();
+
+ // when
engine.dispatch(secondAttachment);
EngineTestSupport.MetricDelta retry = delta(
beforeRetry, engine.metricsSnapshot());
+ // then
assertEquals(0L, retry.counter("temporal.externalProcessCalls"));
assertEquals(1L, retry.counter(
"process.embeddedEpochProcessCalls"));
@@ -208,6 +216,7 @@ void failedParentInitializationApplicationDoesNotRollbackChildOrPeer()
@Test
void restartBeforeParentApplicationReusesCommittedInitializationEpoch()
throws Exception {
+ // given
String gameId = "round12-recovery-game";
String firstHostId = "round12-recovery-host-one";
String recoveringHostId = "round12-recovery-host-two";
@@ -268,6 +277,7 @@ void restartBeforeParentApplicationReusesCommittedInitializationEpoch()
assertEquals(initializationCause, engine.history(gameId).get(0)
.causalEntryBlueId().orElseThrow());
+ // when
engine.restartFromStores();
assertEquals(SessionStatus.CATCHING_UP,
engine.session(recoveringHostId).status());
@@ -280,6 +290,7 @@ void restartBeforeParentApplicationReusesCommittedInitializationEpoch()
EngineTestSupport.MetricDelta resume = delta(
beforeResume, engine.metricsSnapshot());
+ // then
assertEquals(0L, resume.counter(
"temporal.externalProcessCalls"),
"the committed attachment must not run again");
@@ -311,6 +322,7 @@ void restartBeforeParentApplicationReusesCommittedInitializationEpoch()
@Test
void postCommitInitializationRecoveryDoesNotCountTheReceiptTwice()
throws Exception {
+ // given
String gameYaml = Round12NbaFixtures.game(
GAME_ID,
"examples/round12/retry/game",
@@ -359,6 +371,7 @@ void postCommitInitializationRecoveryDoesNotCountTheReceiptTwice()
assertThrows(CoordinationException.class,
() -> engine.readyDocument(HOST_ID));
+ // when
engine.restartFromStores();
EngineMetrics.MetricsSnapshot beforeRetry =
engine.metricsSnapshot();
@@ -366,6 +379,7 @@ void postCommitInitializationRecoveryDoesNotCountTheReceiptTwice()
EngineTestSupport.MetricDelta retry = delta(
beforeRetry, engine.metricsSnapshot());
+ // then
assertEquals(0L, retry.counter(
"process.embeddedEpochProcessCalls"));
assertEquals(0L, retry.counter(
diff --git a/src/integrationTest/java/blue/coordination/integration/LateAdmissionEmbeddedHistoryTest.java b/src/integrationTest/java/blue/coordination/integration/LateAdmissionEmbeddedHistoryTest.java
index 28fe15e..966791e 100644
--- a/src/integrationTest/java/blue/coordination/integration/LateAdmissionEmbeddedHistoryTest.java
+++ b/src/integrationTest/java/blue/coordination/integration/LateAdmissionEmbeddedHistoryTest.java
@@ -19,6 +19,7 @@ final class LateAdmissionEmbeddedHistoryTest {
void missingChildSessionIsCreatedThenCaughtUpFromCompleteHistory()
throws Exception {
try (TestEngine engine = TestEngine.create()) {
+ // given
String childInitial = resource(
"examples/clean/embedded-counter.yaml");
Timeline childTimeline = engine.timeline(
@@ -54,10 +55,13 @@ void missingChildSessionIsCreatedThenCaughtUpFromCompleteHistory()
"ownerChannel",
engine.embeddedDocumentRequest(childInitial)),
T0 + 1_000);
+
+ // when
engine.dispatch(attach);
EngineTestSupport.MetricDelta work = delta(
before, engine.metricsSnapshot());
+ // then
assertEquals(SessionStatus.READY,
engine.session("embedded-parent-B").status());
assertEquals(6L, integer(
@@ -82,6 +86,7 @@ void missingChildSessionIsCreatedThenCaughtUpFromCompleteHistory()
void laterAppendWithEarlierSourceOrderIsSelectedBeforeAttachment()
throws Exception {
try (TestEngine engine = TestEngine.create()) {
+ // given
String childInitial = resource(
"examples/clean/embedded-counter.yaml");
Timeline childTimeline = engine.timeline(
@@ -112,18 +117,22 @@ void laterAppendWithEarlierSourceOrderIsSelectedBeforeAttachment()
EngineMetrics.MetricsSnapshot beforeAttach =
engine.metricsSnapshot();
+
+ // when
engine.dispatch(attachment);
EngineTestSupport.MetricDelta attachWork = delta(
beforeAttach, engine.metricsSnapshot());
- assertEquals(3L, integer(
- engine, "embedded-state-parent", "/child/counter"));
+ long parentCounterAfterAttachment = integer(
+ engine, "embedded-state-parent", "/child/counter");
+ engine.dispatch(laterAppend);
+
+ // then
+ assertEquals(3L, parentCounterAfterAttachment);
assertEquals(2L, attachWork.counter(
"childHistoricalProcessCalls"),
"the global feeder ordered both entries before the "
+ "attachment; late admission then replays them "
+ "into the newly managed child");
-
- engine.dispatch(laterAppend);
assertEquals(3L, integer(
engine, "embedded-counter-A", "/counter"));
assertEquals(3L, integer(
diff --git a/src/integrationTest/java/blue/coordination/integration/ManagedChildCollectionMembershipMutationIntegrationTest.java b/src/integrationTest/java/blue/coordination/integration/ManagedChildCollectionMembershipMutationIntegrationTest.java
index 8fd1627..131ed74 100644
--- a/src/integrationTest/java/blue/coordination/integration/ManagedChildCollectionMembershipMutationIntegrationTest.java
+++ b/src/integrationTest/java/blue/coordination/integration/ManagedChildCollectionMembershipMutationIntegrationTest.java
@@ -30,6 +30,7 @@ final class ManagedChildCollectionMembershipMutationIntegrationTest {
void childApplicationAddsThenRemovesParentOwnedCollectionSibling()
throws Exception {
try (TestEngine engine = TestEngine.create()) {
+ // given
String parent = parentFixture(
registeredBetaBlueId(engine),
engine.exactRequest("{}").blueId());
@@ -60,11 +61,14 @@ void childApplicationAddsThenRemovesParentOwnedCollectionSibling()
EngineMetrics.MetricsSnapshot beforeRemoval =
engine.metricsSnapshot();
+
+ // when
engine.appendAndDispatch(alpha, Operation.yaml(
"increment", "ownerChannel", "amount: 1"));
EngineTestSupport.MetricDelta removal = delta(
beforeRemoval, engine.metricsSnapshot());
+ // then
assertEquals(Map.of("/games/alpha", ALPHA),
engine.embeddedDocuments(PARENT));
assertEquals(SessionStatus.READY, engine.session(PARENT).status());
@@ -88,6 +92,7 @@ void childApplicationAddsThenRemovesParentOwnedCollectionSibling()
void childApplicationRevalidatesPortableCollectionLimit()
throws Exception {
try (TestEngine engine = TestEngine.create()) {
+ // given
String parent = parentFixture(
registeredBetaBlueId(engine),
engine.exactRequest(overflowMembers()).blueId());
@@ -95,11 +100,13 @@ void childApplicationRevalidatesPortableCollectionLimit()
engine.start(PARENT, parent);
int parentHistory = engine.history(PARENT).size();
+ // when
IllegalStateException failure = assertThrows(
IllegalStateException.class,
() -> engine.appendAndDispatch(alpha, Operation.yaml(
"increment", "ownerChannel", "amount: 2")));
+ // then
assertTrue(failure.getMessage().contains(
"PORTABLE_LIMIT_EXCEEDED: Portable limit exceeded: "
+ "processEmbeddedPathsPerScope"));
diff --git a/src/integrationTest/java/blue/coordination/integration/ManagedChildOwnershipGuardTest.java b/src/integrationTest/java/blue/coordination/integration/ManagedChildOwnershipGuardTest.java
index 5664d12..6f105a9 100644
--- a/src/integrationTest/java/blue/coordination/integration/ManagedChildOwnershipGuardTest.java
+++ b/src/integrationTest/java/blue/coordination/integration/ManagedChildOwnershipGuardTest.java
@@ -19,6 +19,7 @@ final class ManagedChildOwnershipGuardTest {
void rejectedParentMutationRollsBackAndCreatesNoDeliveryReceipt()
throws Exception {
try (TestEngine engine = TestEngine.create()) {
+ // given
Timeline shared = engine.timeline(
"examples/root-isolation/shared", "alice");
engine.start(
@@ -42,6 +43,7 @@ void rejectedParentMutationRollsBackAndCreatesNoDeliveryReceipt()
"amount: 7"));
EngineMetrics.MetricsSnapshot before = engine.metricsSnapshot();
+ // when
IllegalStateException first = assertThrows(
IllegalStateException.class,
() -> engine.dispatch(illegal));
@@ -51,6 +53,7 @@ void rejectedParentMutationRollsBackAndCreatesNoDeliveryReceipt()
EngineTestSupport.MetricDelta work = delta(
before, engine.metricsSnapshot());
+ // then
assertTrue(first.getMessage().contains(
"attempted to mutate managed child"),
first::getMessage);
diff --git a/src/integrationTest/java/blue/coordination/integration/ManagedDocumentIsolationTest.java b/src/integrationTest/java/blue/coordination/integration/ManagedDocumentIsolationTest.java
index 6904d36..c9b3d1d 100644
--- a/src/integrationTest/java/blue/coordination/integration/ManagedDocumentIsolationTest.java
+++ b/src/integrationTest/java/blue/coordination/integration/ManagedDocumentIsolationTest.java
@@ -16,6 +16,7 @@ final class ManagedDocumentIsolationTest {
void sharedOperationExecutesOncePerManagedDocumentThenOneEpochPropagation()
throws Exception {
try (TestEngine engine = TestEngine.create()) {
+ // given
Timeline shared = engine.timeline(
"examples/root-isolation/shared", "alice");
engine.start(
@@ -30,12 +31,15 @@ void sharedOperationExecutesOncePerManagedDocumentThenOneEpochPropagation()
"examples/clean/root-isolation-child.yaml"))));
EngineMetrics.MetricsSnapshot before = engine.metricsSnapshot();
+
+ // when
engine.appendAndDispatch(
shared,
Operation.yaml("collide", "sharedChannel", "{}"));
EngineTestSupport.MetricDelta work = delta(
before, engine.metricsSnapshot());
+ // then
assertEquals(1L, integer(
engine, "root-isolation-parent", "/rootCount"));
assertEquals(1L, integer(
diff --git a/src/integrationTest/java/blue/coordination/integration/MultiChildSynchronizedCatchUpTest.java b/src/integrationTest/java/blue/coordination/integration/MultiChildSynchronizedCatchUpTest.java
index a8e32e5..d3d3c00 100644
--- a/src/integrationTest/java/blue/coordination/integration/MultiChildSynchronizedCatchUpTest.java
+++ b/src/integrationTest/java/blue/coordination/integration/MultiChildSynchronizedCatchUpTest.java
@@ -25,9 +25,15 @@ final class MultiChildSynchronizedCatchUpTest {
@Test
void threeHistoriesMergeByCanonicalOrderUnderOneBarrier()
throws Exception {
- Outcome canonical = run(AppendOrder.CANONICAL);
- Outcome shuffled = run(AppendOrder.SHUFFLED);
+ // given
+ AppendOrder canonicalOrder = AppendOrder.CANONICAL;
+ AppendOrder shuffledOrder = AppendOrder.SHUFFLED;
+ // when
+ Outcome canonical = run(canonicalOrder);
+ Outcome shuffled = run(shuffledOrder);
+
+ // then
assertNotEquals(canonical.appendSequences(),
shuffled.appendSequences(),
"the two runs must use genuinely different insertion order");
diff --git a/src/integrationTest/java/blue/coordination/integration/NestedEmbeddedCatchUpTest.java b/src/integrationTest/java/blue/coordination/integration/NestedEmbeddedCatchUpTest.java
index c0ea896..ea7700a 100644
--- a/src/integrationTest/java/blue/coordination/integration/NestedEmbeddedCatchUpTest.java
+++ b/src/integrationTest/java/blue/coordination/integration/NestedEmbeddedCatchUpTest.java
@@ -21,6 +21,7 @@ final class NestedEmbeddedCatchUpTest {
void nestedInitialStatesCatchUpRecursivelyAndLiveRevisionPropagatesOnce()
throws Exception {
try (TestEngine engine = TestEngine.create()) {
+ // given
String leafInitial = resource(
"examples/clean/embedded-counter.yaml");
String middleInitial = resource(
@@ -100,6 +101,8 @@ void nestedInitialStatesCatchUpRecursivelyAndLiveRevisionPropagatesOnce()
assertNoGenericSplitting(rootAttachWork);
EngineMetrics.MetricsSnapshot beforeLive = engine.metricsSnapshot();
+
+ // when
var plusThree = engine.appendAt(
leafTimeline,
Operation.yaml(
@@ -109,6 +112,7 @@ void nestedInitialStatesCatchUpRecursivelyAndLiveRevisionPropagatesOnce()
EngineTestSupport.MetricDelta liveWork = delta(
beforeLive, engine.metricsSnapshot());
+ // then
assertEquals(5L, integer(
engine, "embedded-counter-A", "/counter"));
assertEquals(5L, integer(
diff --git a/src/integrationTest/java/blue/coordination/integration/NestedOwnedScopePlanInvalidationIntegrationTest.java b/src/integrationTest/java/blue/coordination/integration/NestedOwnedScopePlanInvalidationIntegrationTest.java
index 858a269..748efbb 100644
--- a/src/integrationTest/java/blue/coordination/integration/NestedOwnedScopePlanInvalidationIntegrationTest.java
+++ b/src/integrationTest/java/blue/coordination/integration/NestedOwnedScopePlanInvalidationIntegrationTest.java
@@ -30,6 +30,7 @@ final class NestedOwnedScopePlanInvalidationIntegrationTest {
void nestedChannelAdditionDoesNotInvalidateItsContainingRootPlan()
throws Exception {
try (TestEngine engine = TestEngine.create()) {
+ // given
Timeline owner = engine.timeline(
"examples/nested-owned-surface/owner", "nested-owner");
Timeline dynamic = engine.timeline(
@@ -71,8 +72,10 @@ void nestedChannelAdditionDoesNotInvalidateItsContainingRootPlan()
"applyDynamic", "dynamicChannel", "amount: 2"),
T0 + 200L);
+ // when
engine.dispatch(dynamicEntry);
+ // then
assertEquals(2L, integer(engine, CHILD, "/total"));
assertEquals(2L, integer(engine, ROOT, "/child/total"));
assertEquals(
diff --git a/src/integrationTest/java/blue/coordination/integration/NestedSiblingGlobalCatchUpOrderingTest.java b/src/integrationTest/java/blue/coordination/integration/NestedSiblingGlobalCatchUpOrderingTest.java
index 89b7ca4..0d841ce 100644
--- a/src/integrationTest/java/blue/coordination/integration/NestedSiblingGlobalCatchUpOrderingTest.java
+++ b/src/integrationTest/java/blue/coordination/integration/NestedSiblingGlobalCatchUpOrderingTest.java
@@ -29,6 +29,7 @@ final class NestedSiblingGlobalCatchUpOrderingTest {
void earlierSiblingHistoryPrecedesLaterNestedHistoryUnderOneRootBarrier()
throws Exception {
try (TestEngine engine = TestEngine.create()) {
+ // given
String a11Source = leafSource();
ExactValue a1 = engine.registerType(middleSource(a11Source));
ExactValue a2 = engine.registerType(a2Source());
@@ -65,10 +66,13 @@ void earlierSiblingHistoryPrecedesLaterNestedHistoryUnderOneRootBarrier()
Operation.exact(
"attachBoth",
"ownerChannel",
- attachmentRequest(a1, a2)),
+ attachmentRequest(a1, a2)),
T0 + 1_000L);
+
+ // when
engine.dispatch(attachment);
+ // then
assertEquals(List.of(
trace(DocumentRevision.Kind.INITIALIZATION,
attachment),
@@ -146,6 +150,7 @@ void earlierSiblingHistoryPrecedesLaterNestedHistoryUnderOneRootBarrier()
void unavailableHistoryDefersTheEntryFrameAndResumesWithoutOvertaking()
throws Exception {
try (TestEngine engine = TestEngine.create()) {
+ // given
ExactValue a1 = engine.registerType(middleSource(leafSource()));
ExactValue a2 = engine.registerType(a2Source());
Timeline a11Timeline = engine.timeline(
@@ -170,6 +175,8 @@ void unavailableHistoryDefersTheEntryFrameAndResumesWithoutOvertaking()
T0 + 1_000L);
engine.makeHistoricalUnavailable("provider temporarily offline");
+
+ // when
ProcessingDrainReceipt deferred = engine.dispatch(attachment);
assertFalse(deferred.processedEntries().contains(attachment));
@@ -186,6 +193,7 @@ void unavailableHistoryDefersTheEntryFrameAndResumesWithoutOvertaking()
engine.makeHistoricalAvailable();
ProcessingDrainReceipt resumed = engine.dispatch(attachment);
+ // then
assertEquals(List.of(attachment), resumed.processedEntries());
assertTrue(resumed.quiescent());
assertEquals(2L, integer(
diff --git a/src/integrationTest/java/blue/coordination/integration/NonScalarRoutingIntegrationTest.java b/src/integrationTest/java/blue/coordination/integration/NonScalarRoutingIntegrationTest.java
index 635c883..d3d17c7 100644
--- a/src/integrationTest/java/blue/coordination/integration/NonScalarRoutingIntegrationTest.java
+++ b/src/integrationTest/java/blue/coordination/integration/NonScalarRoutingIntegrationTest.java
@@ -30,24 +30,41 @@ final class NonScalarRoutingIntegrationTest {
@Test
void compositeTimelineRoutesOnlyItsDeclaredMemberSources()
throws Exception {
- verifyAggregateRouting(
- DocumentId.of("composite-routing-counter"),
- "examples/clean/composite-routing-counter.yaml");
+ // given
+ DocumentId documentId = DocumentId.of("composite-routing-counter");
+ String fixture = "examples/clean/composite-routing-counter.yaml";
+
+ // when
+ AggregateRoutingEvidence evidence = runAggregateRouting(
+ documentId,
+ fixture);
+
+ // then
+ assertAggregateRouting(evidence);
}
@Test
void allTimelinesRoutesOnlyTheFrozenSameScopeTimelineFamily()
throws Exception {
- verifyAggregateRouting(
- DocumentId.of("all-timelines-routing-counter"),
- "examples/clean/all-timelines-routing-counter.yaml");
+ // given
+ DocumentId documentId = DocumentId.of("all-timelines-routing-counter");
+ String fixture = "examples/clean/all-timelines-routing-counter.yaml";
+
+ // when
+ AggregateRoutingEvidence evidence = runAggregateRouting(
+ documentId,
+ fixture);
+
+ // then
+ assertAggregateRouting(evidence);
}
@Test
void fromNowRouteIntervalExcludesBacklogStillInTheGlobalJournal()
throws Exception {
+ // given
DocumentId counter = DocumentId.of("counter");
- try (CoordinationEngine engine = CoordinationEngine.inMemory()) {
+ try (CoordinationEngine engine = CoordinationEngine.legacyInMemory()) {
Timeline alice = engine.registerTimeline(
"examples/clean-counter/alice", "alice");
TimelineEntry oldOne = engine.appendAt(
@@ -69,8 +86,10 @@ void fromNowRouteIntervalExcludesBacklogStillInTheGlobalJournal()
long processBefore = engine.metrics().counter(
CoordinationMetrics.Counter.EXTERNAL_PROCESS_CALLS);
+ // when
ProcessingDrainReceipt receipt = engine.drain();
+ // then
assertEquals(
List.of(oldOne.blueId(), oldTwo.blueId(), live.blueId()),
entryIds(receipt.processedEntries()));
@@ -90,10 +109,10 @@ void fromNowRouteIntervalExcludesBacklogStillInTheGlobalJournal()
}
}
- private static void verifyAggregateRouting(
+ private static AggregateRoutingEvidence runAggregateRouting(
DocumentId documentId,
String resourcePath) throws Exception {
- try (CoordinationEngine engine = CoordinationEngine.inMemory()) {
+ try (CoordinationEngine engine = CoordinationEngine.legacyInMemory()) {
Timeline alice = engine.registerTimeline(
ALICE_TIMELINE, "alice");
Timeline bob = engine.registerTimeline(
@@ -109,26 +128,64 @@ private static void verifyAggregateRouting(
TimelineEntry unrelated = engine.appendAt(
charlie, add(7), T0 + 300L);
- assertEquals(1, engine.routeTargetCount(aliceEntry));
- assertEquals(1, engine.routeTargetCount(bobEntry));
- assertEquals(0, engine.routeTargetCount(unrelated));
- assertEquals(
- Set.of(ALICE_TIMELINE, BOB_TIMELINE),
- engine.effectiveTimelineIds(documentId));
+ int aliceTargets = engine.routeTargetCount(aliceEntry);
+ int bobTargets = engine.routeTargetCount(bobEntry);
+ int unrelatedTargets = engine.routeTargetCount(unrelated);
+ Set effectiveTimelineIds =
+ engine.effectiveTimelineIds(documentId);
ProcessingDrainReceipt receipt = engine.drain();
- assertEquals(
+ return new AggregateRoutingEvidence(
+ aliceTargets,
+ bobTargets,
+ unrelatedTargets,
+ effectiveTimelineIds,
List.of(
aliceEntry.blueId(),
bobEntry.blueId(),
unrelated.blueId()),
- entryIds(receipt.processedEntries()));
- assertEquals(1, receipt.outcomesFor(aliceEntry.blueId()).size());
- assertEquals(1, receipt.outcomesFor(bobEntry.blueId()).size());
- assertTrue(receipt.outcomesFor(unrelated.blueId()).isEmpty());
- assertEquals(5L, counter(engine, documentId));
- assertEquals(2L, engine.document(documentId).epoch());
+ entryIds(receipt.processedEntries()),
+ receipt.outcomesFor(aliceEntry.blueId()).size(),
+ receipt.outcomesFor(bobEntry.blueId()).size(),
+ receipt.outcomesFor(unrelated.blueId()).isEmpty(),
+ counter(engine, documentId),
+ engine.document(documentId).epoch());
+ }
+ }
+
+ private static void assertAggregateRouting(
+ AggregateRoutingEvidence evidence) {
+ assertEquals(1, evidence.aliceTargets());
+ assertEquals(1, evidence.bobTargets());
+ assertEquals(0, evidence.unrelatedTargets());
+ assertEquals(Set.of(ALICE_TIMELINE, BOB_TIMELINE),
+ evidence.effectiveTimelineIds());
+ assertEquals(evidence.expectedEntryIds(),
+ evidence.processedEntryIds());
+ assertEquals(1, evidence.aliceOutcomes());
+ assertEquals(1, evidence.bobOutcomes());
+ assertTrue(evidence.unrelatedOutcomeEmpty());
+ assertEquals(5L, evidence.counter());
+ assertEquals(2L, evidence.epoch());
+ }
+
+ private record AggregateRoutingEvidence(
+ int aliceTargets,
+ int bobTargets,
+ int unrelatedTargets,
+ Set effectiveTimelineIds,
+ List expectedEntryIds,
+ List processedEntryIds,
+ int aliceOutcomes,
+ int bobOutcomes,
+ boolean unrelatedOutcomeEmpty,
+ long counter,
+ long epoch) {
+ private AggregateRoutingEvidence {
+ effectiveTimelineIds = Set.copyOf(effectiveTimelineIds);
+ expectedEntryIds = List.copyOf(expectedEntryIds);
+ processedEntryIds = List.copyOf(processedEntryIds);
}
}
diff --git a/src/integrationTest/java/blue/coordination/integration/PlaygroundFiveOccurrenceCorrectnessIntegrationTest.java b/src/integrationTest/java/blue/coordination/integration/PlaygroundFiveOccurrenceCorrectnessIntegrationTest.java
index cd12973..ece03e9 100644
--- a/src/integrationTest/java/blue/coordination/integration/PlaygroundFiveOccurrenceCorrectnessIntegrationTest.java
+++ b/src/integrationTest/java/blue/coordination/integration/PlaygroundFiveOccurrenceCorrectnessIntegrationTest.java
@@ -32,6 +32,7 @@ final class PlaygroundFiveOccurrenceCorrectnessIntegrationTest {
@Test
void conflictingStatesAreRejectedAndCoherentRetryWorksOnSameEngine()
throws Exception {
+ // given
String alphaYaml = Round12NbaFixtures.game(
PlaygroundFiveOccurrenceFixtures.ALPHA_ID,
PlaygroundFiveOccurrenceFixtures.alphaTimeline(),
@@ -55,6 +56,7 @@ void conflictingStatesAreRejectedAndCoherentRetryWorksOnSameEngine()
PlaygroundFiveOccurrenceFixtures.ALPHA_SECOND,
conflictingAlpha)));
+ // when
IllegalStateException failure = assertThrows(
IllegalStateException.class,
() -> engine.dispatch(rejected));
@@ -85,6 +87,7 @@ void conflictingStatesAreRejectedAndCoherentRetryWorksOnSameEngine()
PlaygroundFiveOccurrenceFixtures.ALPHA_SECOND,
alpha)));
+ // then
assertEquals(2, engine.documentCount());
assertEquals(Map.of(
ALPHA_FIRST_PATH,
@@ -112,6 +115,7 @@ void conflictingStatesAreRejectedAndCoherentRetryWorksOnSameEngine()
void removingAndReaddingOneDuplicateUsesOneSourceProcessAndFreshCursor()
throws Exception {
try (TestEngine engine = initializedHostWithAlphaReattachment()) {
+ // given
Timeline owner = owner(engine);
engine.appendAndDispatch(
owner, PlaygroundFiveOccurrenceFixtures.attachFive(engine));
@@ -140,6 +144,8 @@ void removingAndReaddingOneDuplicateUsesOneSourceProcessAndFreshCursor()
Timeline alpha = engine.timeline(
PlaygroundFiveOccurrenceFixtures.alphaTimeline(),
"playground-feed-alpha");
+
+ // when
engine.appendAndDispatch(alpha, Round12NbaFixtures.startGame());
EngineTestSupport.MetricDelta advance = delta(
beforeAdvance, engine.metricsSnapshot());
@@ -166,6 +172,8 @@ void removingAndReaddingOneDuplicateUsesOneSourceProcessAndFreshCursor()
CoordinationTestControl.EmbeddedOccurrenceEvidence newSecond =
occurrence(engine, ALPHA_SECOND_PATH);
+
+ // then
assertNotEquals(oldSecond.bindingId(), newSecond.bindingId());
assertTrue(newSecond.activationGeneration()
> oldSecond.activationGeneration());
@@ -199,12 +207,18 @@ void removingAndReaddingOneDuplicateUsesOneSourceProcessAndFreshCursor()
void oneInitializationEventBlueIdHasDistinctOccurrenceReceipts()
throws Exception {
try (TestEngine engine = initializedHost()) {
+ // given
+ Timeline owner = owner(engine);
+
+ // when
engine.appendAndDispatch(
- owner(engine),
+ owner,
PlaygroundFiveOccurrenceFixtures.attachFive(engine));
DocumentRevision alphaInitialization = engine.history(
PlaygroundFiveOccurrenceFixtures.ALPHA_ID).get(0);
+
+ // then
assertEquals(1, alphaInitialization.emittedEvents().size());
String eventBlueId = DirectBlueIdCalculator.calculateBlueId(
alphaInitialization.emittedEvents().get(0));
diff --git a/src/integrationTest/java/blue/coordination/integration/PlaygroundFiveOccurrenceRetryTest.java b/src/integrationTest/java/blue/coordination/integration/PlaygroundFiveOccurrenceRetryTest.java
index 5701f52..965af28 100644
--- a/src/integrationTest/java/blue/coordination/integration/PlaygroundFiveOccurrenceRetryTest.java
+++ b/src/integrationTest/java/blue/coordination/integration/PlaygroundFiveOccurrenceRetryTest.java
@@ -21,6 +21,7 @@ final class PlaygroundFiveOccurrenceRetryTest {
void retryCompletesFiveOccurrenceFanoutWithoutReinitializingChildren()
throws Exception {
try (TestEngine engine = TestEngine.create()) {
+ // given
Timeline owner = engine.timeline(
PlaygroundFiveOccurrenceFixtures.HOST_TIMELINE,
PlaygroundFiveOccurrenceFixtures.HOST_ACTOR);
@@ -33,6 +34,7 @@ void retryCompletesFiveOccurrenceFanoutWithoutReinitializingChildren()
PlaygroundFiveOccurrenceFixtures.attachFive(engine));
EngineMetrics.MetricsSnapshot before = engine.metricsSnapshot();
+ // when
engine.failOnceAt(
TestEngine.FailurePoint.AFTER_APPLYING_CHILD_REVISION);
assertThrows(TestEngine.InjectedFailureException.class,
@@ -74,6 +76,7 @@ void retryCompletesFiveOccurrenceFanoutWithoutReinitializingChildren()
EngineTestSupport.MetricDelta completed = delta(
before, engine.metricsSnapshot());
+ // then
assertEquals(SessionStatus.READY,
engine.readyDocument(
PlaygroundFiveOccurrenceFixtures.HOST_ID).status());
diff --git a/src/integrationTest/java/blue/coordination/integration/ProcessEmbeddedCollectionPathsIntegrationTest.java b/src/integrationTest/java/blue/coordination/integration/ProcessEmbeddedCollectionPathsIntegrationTest.java
index 8327b98..5cc224d 100644
--- a/src/integrationTest/java/blue/coordination/integration/ProcessEmbeddedCollectionPathsIntegrationTest.java
+++ b/src/integrationTest/java/blue/coordination/integration/ProcessEmbeddedCollectionPathsIntegrationTest.java
@@ -23,6 +23,7 @@ final class ProcessEmbeddedCollectionPathsIntegrationTest {
void discoversAddsAndRemovesCanonicalMapMembersWithoutOrdinarySplitting()
throws Exception {
try (TestEngine engine = TestEngine.create()) {
+ // given
Timeline owner = engine.timeline(
"examples/embedded/collection-parent", "bob");
engine.start("embedded-collection-parent", resource(
@@ -35,6 +36,8 @@ void discoversAddsAndRemovesCanonicalMapMembersWithoutOrdinarySplitting()
EngineMetrics.MetricsSnapshot beforeFirst =
engine.metricsSnapshot();
+
+ // when
engine.appendAndDispatch(owner, Operation.exact(
"attachGameA",
"ownerChannel",
@@ -91,6 +94,7 @@ void discoversAddsAndRemovesCanonicalMapMembersWithoutOrdinarySplitting()
EngineTestSupport.MetricDelta removal = delta(
beforeRemoval, engine.metricsSnapshot());
+ // then
assertEquals(Map.of(
"/games/game~1a~0b", "embedded-counter-B"),
engine.embeddedDocuments("embedded-collection-parent"));
diff --git a/src/integrationTest/java/blue/coordination/integration/PublicTemporalFeederIntegrationTest.java b/src/integrationTest/java/blue/coordination/integration/PublicTemporalFeederIntegrationTest.java
index 097849f..0efa7b8 100644
--- a/src/integrationTest/java/blue/coordination/integration/PublicTemporalFeederIntegrationTest.java
+++ b/src/integrationTest/java/blue/coordination/integration/PublicTemporalFeederIntegrationTest.java
@@ -39,8 +39,9 @@ final class PublicTemporalFeederIntegrationTest {
@Test
void exactNodeAdmissionIsIdempotentAndRejectsClaimedIdentityForgery()
throws Exception {
- try (CoordinationEngine source = CoordinationEngine.inMemory();
- CoordinationEngine target = CoordinationEngine.inMemory()) {
+ try (CoordinationEngine source = CoordinationEngine.legacyInMemory();
+ CoordinationEngine target = CoordinationEngine.legacyInMemory()) {
+ // given
Timeline sourceAlice = source.registerTimeline(
ALICE_TIMELINE, "alice");
TimelineEntry canonical = source.append(
@@ -52,6 +53,7 @@ void exactNodeAdmissionIsIdempotentAndRejectsClaimedIdentityForgery()
target.exactValue("amount: 3");
Node exactEntry = canonical.exactEvent().copyNode();
+ // when
TimelineAppendReceipt admitted =
target.appendTimelineEntry(exactEntry);
assertTrue(admitted.stored());
@@ -103,6 +105,8 @@ void exactNodeAdmissionIsIdempotentAndRejectsClaimedIdentityForgery()
target.drain();
TimelineAppendReceipt replayAfterDrain =
target.appendTimelineEntry(canonical.exactEvent().copyNode());
+
+ // then
assertFalse(replayAfterDrain.stored());
assertEquals(1, replayAfterDrain.journalEntryCount());
}
@@ -111,8 +115,9 @@ void exactNodeAdmissionIsIdempotentAndRejectsClaimedIdentityForgery()
@Test
void oneExactAdmissionBuildsAndStoresOneEntryForSeveralRecipients()
throws Exception {
- try (CoordinationEngine source = CoordinationEngine.inMemory();
- CoordinationEngine target = CoordinationEngine.inMemory()) {
+ try (CoordinationEngine source = CoordinationEngine.legacyInMemory();
+ CoordinationEngine target = CoordinationEngine.legacyInMemory()) {
+ // given
Timeline sourceAlice = source.registerTimeline(
ALICE_TIMELINE, "alice");
TimelineEntry canonical = source.append(
@@ -131,11 +136,16 @@ void oneExactAdmissionBuildsAndStoresOneEntryForSeveralRecipients()
CoordinationTestControl.MetricsSnapshot before =
control.metricsSnapshot();
+ // when
TimelineAppendReceipt admission = target.appendTimelineEntry(
canonical.exactEvent().copyNode());
CoordinationTestControl.MetricsSnapshot after =
control.metricsSnapshot();
+ int routeTargetCountAfterAdmission =
+ target.routeTargetCount(admission.entry());
+ ProcessingDrainReceipt drained = target.drain();
+ // then
assertTrue(admission.stored());
assertEquals(canonical.blueId(), admission.entry().blueId());
assertEquals(1, admission.journalEntryCount());
@@ -145,9 +155,8 @@ void oneExactAdmissionBuildsAndStoresOneEntryForSeveralRecipients()
before, after, "wholeObjectStore.insertions"));
assertEquals(1L, diagnosticDelta(
before, after, "journal.entriesStoredWhole"));
- assertEquals(2, target.routeTargetCount(admission.entry()));
+ assertEquals(2, routeTargetCountAfterAdmission);
- ProcessingDrainReceipt drained = target.drain();
assertEquals(List.of(COUNTER_A, COUNTER_B),
drained.outcomesFor(admission.entry().blueId()).stream()
.map(outcome -> outcome.documentId())
@@ -160,7 +169,8 @@ void oneExactAdmissionBuildsAndStoresOneEntryForSeveralRecipients()
@Test
void normalReadsFailClosedUntilAuditStateBecomesReady() throws Exception {
- try (CoordinationEngine engine = CoordinationEngine.inMemory()) {
+ try (CoordinationEngine engine = CoordinationEngine.legacyInMemory()) {
+ // given
Timeline alice = engine.registerTimeline(ALICE_TIMELINE, "alice");
engine.append(alice, Operation.yaml(
"increment", "aliceChannel", "amount: 3"));
@@ -184,8 +194,12 @@ void normalReadsFailClosedUntilAuditStateBecomesReady() throws Exception {
assertEquals(1, engine.history(COUNTER).size(),
"audit history stays available during catch-up");
+ // when
control.makeHistoricalAvailable();
- assertTrue(engine.drain().quiescent());
+ boolean quiescent = engine.drain().quiescent();
+
+ // then
+ assertTrue(quiescent);
assertEquals(SessionStatus.READY,
engine.document(COUNTER).status());
assertEquals(3L, counter(engine));
@@ -195,7 +209,8 @@ void normalReadsFailClosedUntilAuditStateBecomesReady() throws Exception {
@Test
void boundedDrainResumesAnOpenEntryWithoutRepeatingFrozenProcess()
throws Exception {
- try (CoordinationEngine engine = CoordinationEngine.inMemory()) {
+ try (CoordinationEngine engine = CoordinationEngine.legacyInMemory()) {
+ // given
Timeline alice = engine.registerTimeline(ALICE_TIMELINE, "alice");
engine.startDocument(COUNTER_A, counterYaml(COUNTER_A));
engine.startDocument(COUNTER_B, counterYaml(COUNTER_B));
@@ -208,6 +223,7 @@ void boundedDrainResumesAnOpenEntryWithoutRepeatingFrozenProcess()
long callsBefore = engine.metrics().counter(
CoordinationMetrics.Counter.EXTERNAL_PROCESS_CALLS);
+ // when
ProcessingDrainReceipt transitionPause = engine.drain(
new CoordinationEngine.DrainBudget(1L, 10L));
assertTrue(transitionPause.paused());
@@ -234,6 +250,8 @@ void boundedDrainResumesAnOpenEntryWithoutRepeatingFrozenProcess()
ProcessingDrainReceipt completed = engine.drain(
new CoordinationEngine.DrainBudget(10L, 1L));
+
+ // then
assertFalse(completed.paused());
assertFalse(completed.blocked());
assertTrue(completed.quiescent());
@@ -257,7 +275,8 @@ void boundedDrainResumesAnOpenEntryWithoutRepeatingFrozenProcess()
@Test
void appendStoresWorkWithoutInvokingProcess() throws Exception {
- try (CoordinationEngine engine = CoordinationEngine.inMemory()) {
+ try (CoordinationEngine engine = CoordinationEngine.legacyInMemory()) {
+ // given
Timeline alice = engine.registerTimeline(
ALICE_TIMELINE, "alice");
engine.startDocument(
@@ -265,18 +284,23 @@ void appendStoresWorkWithoutInvokingProcess() throws Exception {
long processBefore = engine.metrics().counter(
CoordinationMetrics.Counter.EXTERNAL_PROCESS_CALLS);
+ // when
TimelineEntry entry = engine.append(
alice,
Operation.yaml(
"increment", "aliceChannel", "amount: 3"));
-
- assertEquals(1, engine.metrics().journalEntryCount());
- assertEquals(0L, counter(engine));
- assertEquals(0L, engine.document(COUNTER).epoch());
- assertEquals(processBefore, engine.metrics().counter(
- CoordinationMetrics.Counter.EXTERNAL_PROCESS_CALLS));
-
+ int journalEntriesAfterAppend = engine.metrics().journalEntryCount();
+ long counterAfterAppend = counter(engine);
+ long epochAfterAppend = engine.document(COUNTER).epoch();
+ long processAfterAppend = engine.metrics().counter(
+ CoordinationMetrics.Counter.EXTERNAL_PROCESS_CALLS);
ProcessingDrainReceipt drained = engine.drain();
+
+ // then
+ assertEquals(1, journalEntriesAfterAppend);
+ assertEquals(0L, counterAfterAppend);
+ assertEquals(0L, epochAfterAppend);
+ assertEquals(processBefore, processAfterAppend);
assertEquals(List.of(entry.blueId()), entryIds(
drained.processedEntries()));
assertEquals(1, drained.outcomesFor(entry.blueId()).size());
@@ -291,8 +315,9 @@ void appendStoresWorkWithoutInvokingProcess() throws Exception {
@Test
void exactDocumentTargetUsesCurrentOrAnyRetainedEpochWithoutProcessingOnAppend()
throws Exception {
- try (CoordinationEngine source = CoordinationEngine.inMemory();
- CoordinationEngine target = CoordinationEngine.inMemory()) {
+ try (CoordinationEngine source = CoordinationEngine.legacyInMemory();
+ CoordinationEngine target = CoordinationEngine.legacyInMemory()) {
+ // given
Timeline sourceAlice = source.registerTimeline(
ALICE_TIMELINE, "alice");
TimelineEntry template1 = source.appendAt(sourceAlice,
@@ -316,6 +341,7 @@ void exactDocumentTargetUsesCurrentOrAnyRetainedEpochWithoutProcessingOnAppend()
long processBefore = target.metrics().counter(
CoordinationMetrics.Counter.EXTERNAL_PROCESS_CALLS);
+ // when
TimelineAppendReceipt current = target.appendTimelineEntry(
targeted(template1, initialA, true, null));
TimelineAppendReceipt staleExact = target.appendTimelineEntry(
@@ -327,9 +353,12 @@ void exactDocumentTargetUsesCurrentOrAnyRetainedEpochWithoutProcessingOnAppend()
TimelineAppendReceipt implicitRetained = target.appendTimelineEntry(
targeted(template4, initialA, null,
retained.entry().blueId()));
+ long processAfterAppend = target.metrics().counter(
+ CoordinationMetrics.Counter.EXTERNAL_PROCESS_CALLS);
+ ProcessingDrainReceipt drained = target.drain();
- assertEquals(processBefore, target.metrics().counter(
- CoordinationMetrics.Counter.EXTERNAL_PROCESS_CALLS));
+ // then
+ assertEquals(processBefore, processAfterAppend);
assertEquals("external-provider", current.entry().exactEvent()
.canonicalAt("/source").getValue());
assertEquals(initialA.blueId(), current.entry().exactEvent()
@@ -338,7 +367,6 @@ void exactDocumentTargetUsesCurrentOrAnyRetainedEpochWithoutProcessingOnAppend()
.canonicalAt("/message/requireExactDocumentVersion")
.getValue());
- ProcessingDrainReceipt drained = target.drain();
assertEquals(List.of(current.entry().blueId(),
staleExact.entry().blueId(),
retained.entry().blueId(),
@@ -364,6 +392,7 @@ void exactDocumentTargetUsesCurrentOrAnyRetainedEpochWithoutProcessingOnAppend()
void drainSelectsShuffledCrossTimelineEntriesByExternalOrder()
throws Exception {
try (CoordinationEngine engine = counterEngine()) {
+ // given
Timeline alice = engine.registerTimeline(
ALICE_TIMELINE, "alice");
Timeline bob = engine.registerTimeline(BOB_TIMELINE, "bob");
@@ -391,8 +420,10 @@ void drainSelectsShuffledCrossTimelineEntriesByExternalOrder()
early.globalSequence(),
late.globalSequence()));
+ // when
ProcessingDrainReceipt drained = engine.drain();
+ // then
assertEquals(
List.of(early.blueId(), middle.blueId(), late.blueId()),
entryIds(drained.processedEntries()));
@@ -408,6 +439,7 @@ void drainSelectsShuffledCrossTimelineEntriesByExternalOrder()
void drainThroughProcessesEveryEarlierEntryAndIsIdempotent()
throws Exception {
try (CoordinationEngine engine = counterEngine()) {
+ // given
Timeline alice = engine.registerTimeline(
ALICE_TIMELINE, "alice");
Timeline bob = engine.registerTimeline(BOB_TIMELINE, "bob");
@@ -428,6 +460,7 @@ void drainThroughProcessesEveryEarlierEntryAndIsIdempotent()
"increment", "aliceChannel", "amount: 10"),
T0 + 300L);
+ // when
ProcessingDrainReceipt first = engine.drainThrough(
middle.sourceOrderKey());
assertEquals(
@@ -450,6 +483,8 @@ void drainThroughProcessesEveryEarlierEntryAndIsIdempotent()
ProcessingDrainReceipt remainder = engine.drainThrough(
late.sourceOrderKey());
+
+ // then
assertEquals(
List.of(late.blueId()),
entryIds(remainder.processedEntries()));
@@ -460,7 +495,7 @@ void drainThroughProcessesEveryEarlierEntryAndIsIdempotent()
}
private static CoordinationEngine counterEngine() throws Exception {
- CoordinationEngine engine = CoordinationEngine.inMemory();
+ CoordinationEngine engine = CoordinationEngine.legacyInMemory();
try {
engine.startDocument(
COUNTER, resource("examples/clean/counter.yaml"));
diff --git a/src/integrationTest/java/blue/coordination/integration/RemovalCycleAndReattachmentTest.java b/src/integrationTest/java/blue/coordination/integration/RemovalCycleAndReattachmentTest.java
index 81b0136..232c370 100644
--- a/src/integrationTest/java/blue/coordination/integration/RemovalCycleAndReattachmentTest.java
+++ b/src/integrationTest/java/blue/coordination/integration/RemovalCycleAndReattachmentTest.java
@@ -18,6 +18,7 @@ final class RemovalCycleAndReattachmentTest {
void detachedParentStopsMovingAndReattachUsesFreshCursorWithoutReplay()
throws Exception {
try (TestEngine engine = TestEngine.create()) {
+ // given
String childInitial = resource(
"examples/clean/embedded-counter.yaml");
Timeline childTimeline = engine.timeline(
@@ -58,6 +59,8 @@ void detachedParentStopsMovingAndReattachUsesFreshCursorWithoutReplay()
"embedded-counter-A").size();
EngineMetrics.MetricsSnapshot before = engine.metricsSnapshot();
+
+ // when
engine.appendAndDispatch(
parentTimeline,
Operation.exact(
@@ -66,6 +69,8 @@ void detachedParentStopsMovingAndReattachUsesFreshCursorWithoutReplay()
engine.embeddedDocumentRequest(childInitial)));
EngineTestSupport.MetricDelta work = delta(
before, engine.metricsSnapshot());
+
+ // then
assertEquals(3L, integer(
engine, "embedded-state-parent", "/child/counter"));
assertEquals(3L, work.counter("childRevisionApplications"),
@@ -86,6 +91,7 @@ void detachedParentStopsMovingAndReattachUsesFreshCursorWithoutReplay()
void directCycleFailsBeforeAnySessionLinkCursorOrReceiptPublishes()
throws Exception {
try (TestEngine engine = TestEngine.create()) {
+ // given
String parentInitial = resource(
"examples/clean/embedded-state-parent.yaml");
Timeline parentTimeline = engine.timeline(
@@ -98,9 +104,12 @@ void directCycleFailsBeforeAnySessionLinkCursorOrReceiptPublishes()
"ownerChannel",
engine.embeddedDocumentRequest(parentInitial)));
+ // when
assertThrows(
IllegalStateException.class,
() -> engine.dispatch(cycle));
+
+ // then
assertEquals(0L, engine.session(
"embedded-state-parent").epoch());
assertEquals(SessionStatus.READY, engine.session(
@@ -114,6 +123,7 @@ void directCycleFailsBeforeAnySessionLinkCursorOrReceiptPublishes()
void threeRootCycleFailsBeforeAttemptedEdgeOrReceiptPublishes()
throws Exception {
try (TestEngine engine = TestEngine.create()) {
+ // given
String template = resource(
"examples/clean/embedded-state-parent.yaml");
String first = parent(template, "a");
@@ -141,11 +151,14 @@ void threeRootCycleFailsBeforeAttemptedEdgeOrReceiptPublishes()
"attachChild", "ownerChannel",
engine.embeddedDocumentRequest(first)));
EngineMetrics.MetricsSnapshot before = engine.metricsSnapshot();
+
+ // when
assertThrows(IllegalStateException.class,
() -> engine.dispatch(closingEdge));
EngineTestSupport.MetricDelta work = delta(
before, engine.metricsSnapshot());
+ // then
assertTrue(engine.embeddedDocuments(
"embedded-state-parent-c").isEmpty());
assertEquals(SessionStatus.READY, engine.session(
diff --git a/src/integrationTest/java/blue/coordination/integration/RetryStructuralCountersIntegrationTest.java b/src/integrationTest/java/blue/coordination/integration/RetryStructuralCountersIntegrationTest.java
index 090e4be..39c9b4f 100644
--- a/src/integrationTest/java/blue/coordination/integration/RetryStructuralCountersIntegrationTest.java
+++ b/src/integrationTest/java/blue/coordination/integration/RetryStructuralCountersIntegrationTest.java
@@ -18,6 +18,7 @@ final class RetryStructuralCountersIntegrationTest {
void graphRetryReconcilesACommittedParentWithoutAnotherProcess()
throws Exception {
try (TestEngine engine = TestEngine.create()) {
+ // given
engine.start(
"embedded-state-parent",
resource("examples/clean/embedded-state-parent.yaml"));
@@ -44,10 +45,13 @@ void graphRetryReconcilesACommittedParentWithoutAnotherProcess()
engine.clearFailureInjection();
EngineMetrics.MetricsSnapshot beforeRetry =
engine.metricsSnapshot();
+
+ // when
engine.dispatch(attachment);
EngineTestSupport.MetricDelta retry = delta(
beforeRetry, engine.metricsSnapshot());
+ // then
assertTrue(retry.counters().containsKey(
"temporal.parentProcessRerunsOnGraphRetry"),
"the graph-retry monitor must produce its raw source");
@@ -67,6 +71,7 @@ void graphRetryReconcilesACommittedParentWithoutAnotherProcess()
void parentRetryReconcilesACommittedChildWithoutAnotherProcess()
throws Exception {
try (TestEngine engine = TestEngine.create()) {
+ // given
String childSource = resource(
"examples/clean/embedded-counter.yaml");
engine.start("embedded-counter-A", childSource);
@@ -101,10 +106,13 @@ void parentRetryReconcilesACommittedChildWithoutAnotherProcess()
engine.clearFailureInjection();
EngineMetrics.MetricsSnapshot beforeRetry =
engine.metricsSnapshot();
+
+ // when
engine.dispatch(childEntry);
EngineTestSupport.MetricDelta retry = delta(
beforeRetry, engine.metricsSnapshot());
+ // then
assertTrue(retry.counters().containsKey(
"temporal.childProcessRerunsOnParentRetry"),
"the parent-retry monitor must produce its raw source");
diff --git a/src/integrationTest/java/blue/coordination/integration/Round10InitializationIdentityIntegrationTest.java b/src/integrationTest/java/blue/coordination/integration/Round10InitializationIdentityIntegrationTest.java
index 7d9be4e..34e8989 100644
--- a/src/integrationTest/java/blue/coordination/integration/Round10InitializationIdentityIntegrationTest.java
+++ b/src/integrationTest/java/blue/coordination/integration/Round10InitializationIdentityIntegrationTest.java
@@ -28,9 +28,14 @@ final class Round10InitializationIdentityIntegrationTest {
void authoredManagedChildGetsItsOwnEpochZeroBeforeParentApplication()
throws Exception {
try (TestEngine engine = TestEngine.create()) {
- engine.start("initial-embedded-parent", resource(
- "examples/clean/initial-embedded-parent.yaml"));
+ // given
+ String authored = resource(
+ "examples/clean/initial-embedded-parent.yaml");
+ // when
+ engine.start("initial-embedded-parent", authored);
+
+ // then
List parent = engine.history(
"initial-embedded-parent");
List child = engine.history(
@@ -54,6 +59,7 @@ void authoredManagedChildGetsItsOwnEpochZeroBeforeParentApplication()
void unavailableInitialChildHistoryCannotPublishReadyState()
throws Exception {
try (TestEngine engine = TestEngine.create()) {
+ // given
Timeline rootTimeline = engine.timeline(
"examples/embedded/initial-parent", "bob");
engine.appendAt(
@@ -73,8 +79,13 @@ void unavailableInitialChildHistoryCannotPublishReadyState()
engine.restartFromStores();
assertEquals(SessionStatus.CATCHING_UP,
engine.session("initial-embedded-parent").status());
+
+ // when
engine.makeHistoricalAvailable();
- assertTrue(engine.drain().quiescent());
+ boolean quiescent = engine.drain().quiescent();
+
+ // then
+ assertTrue(quiescent);
assertEquals(SessionStatus.READY,
engine.session("initial-embedded-parent").status());
assertEquals(4L, integer(
@@ -86,6 +97,7 @@ void unavailableInitialChildHistoryCannotPublishReadyState()
void initialChildAndRootHistoryMergeByGlobalSourceOrder()
throws Exception {
try (TestEngine engine = TestEngine.create()) {
+ // given
Timeline rootTimeline = engine.timeline(
"examples/embedded/initial-parent", "bob");
Timeline childTimeline = engine.timeline(
@@ -101,12 +113,14 @@ void initialChildAndRootHistoryMergeByGlobalSourceOrder()
"increment", "ownerChannel", "amount: 7"),
T0 + 200L);
+ // when
engine.start(
"initial-embedded-parent",
resource("examples/clean/initial-embedded-parent.yaml"),
CoordinationEngine.AdmissionPolicy.FULL_HISTORY,
null);
+ // then
List history = engine.history(
"initial-embedded-parent");
assertEquals(List.of(
@@ -136,6 +150,7 @@ void initialChildAndRootHistoryMergeByGlobalSourceOrder()
void failedHistoricalStartRetainsCommittedAdmissionAndRestartResumes()
throws Exception {
try (TestEngine engine = TestEngine.create()) {
+ // given
Timeline rootTimeline = engine.timeline(
"examples/embedded/initial-parent", "bob");
engine.appendAt(
@@ -156,9 +171,13 @@ void failedHistoricalStartRetainsCommittedAdmissionAndRestartResumes()
assertEquals(SessionStatus.CATCHING_UP,
engine.session("initial-embedded-parent").status());
+ // when
engine.clearFailureInjection();
engine.restartFromStores();
- assertTrue(engine.drain().quiescent());
+ boolean quiescent = engine.drain().quiescent();
+
+ // then
+ assertTrue(quiescent);
assertEquals(SessionStatus.READY,
engine.session("initial-embedded-parent").status());
assertEquals(4L, integer(
@@ -173,6 +192,7 @@ void failedHistoricalStartRetainsCommittedAdmissionAndRestartResumes()
@Test
void failedHistoricalStartBeforeFirstCommitIsDeltaCleanAndRetryable()
throws Exception {
+ // given
String authored = resource(
"examples/clean/initial-embedded-parent.yaml");
try (TestEngine engine = TestEngine.create()) {
@@ -188,12 +208,15 @@ void failedHistoricalStartBeforeFirstCommitIsDeltaCleanAndRetryable()
"an admission with no committed transition must vanish");
assertEquals(0, engine.routeRowCount());
+ // when
engine.clearFailureInjection();
engine.start(
"initial-embedded-parent",
authored,
CoordinationEngine.AdmissionPolicy.FULL_HISTORY,
null);
+
+ // then
assertEquals(SessionStatus.READY,
engine.session("initial-embedded-parent").status());
assertEquals(2, engine.documentCount());
@@ -204,6 +227,7 @@ void failedHistoricalStartBeforeFirstCommitIsDeltaCleanAndRetryable()
void invalidTopLevelCompletenessEvidenceBlocksPendingAdmission()
throws Exception {
try (TestEngine engine = TestEngine.create()) {
+ // given
Timeline rootTimeline = engine.timeline(
"examples/embedded/initial-parent", "bob");
engine.appendAt(
@@ -218,6 +242,7 @@ void invalidTopLevelCompletenessEvidenceBlocksPendingAdmission()
CoordinationEngine.AdmissionPolicy.FULL_HISTORY,
null);
+ // when
engine.invalidateHistoricalEvidence("invalid provider cursor");
assertThrows(RuntimeException.class, engine::drain);
assertEquals(SessionStatus.BLOCKED,
@@ -225,6 +250,8 @@ void invalidTopLevelCompletenessEvidenceBlocksPendingAdmission()
engine.restartFromStores();
assertEquals(SessionStatus.BLOCKED,
engine.session("initial-embedded-parent").status());
+
+ // then
assertThrows(RuntimeException.class, engine::drain);
}
}
@@ -233,6 +260,7 @@ void invalidTopLevelCompletenessEvidenceBlocksPendingAdmission()
void initializationEpochPrecedesHistoricalProcessEvenWhenHistoryIsEarlier()
throws Exception {
try (TestEngine engine = TestEngine.create()) {
+ // given
String childInitial = resource(
"examples/clean/embedded-counter.yaml");
Timeline childTimeline = engine.timeline(
@@ -265,10 +293,13 @@ void initializationEpochPrecedesHistoricalProcessEvenWhenHistoryIsEarlier()
"the child history must be strictly before attachment");
EngineMetrics.MetricsSnapshot before = engine.metricsSnapshot();
+
+ // when
engine.dispatch(attachment);
EngineTestSupport.MetricDelta work = delta(
before, engine.metricsSnapshot());
+ // then
List applications = embeddedApplications(
engine, "embedded-state-parent");
assertEquals(List.of(0L, 1L, 3L), applications.stream()
@@ -291,6 +322,7 @@ void initializationEpochPrecedesHistoricalProcessEvenWhenHistoryIsEarlier()
void topLevelHistoryAdvancesNewChildBeforeTheRootsNextEntry()
throws Exception {
try (TestEngine engine = TestEngine.create()) {
+ // given
String childInitial = resource(
"examples/clean/embedded-counter.yaml");
Timeline rootTimeline = engine.timeline(
@@ -320,12 +352,14 @@ void topLevelHistoryAdvancesNewChildBeforeTheRootsNextEntry()
assertTrue(c1.sourceOrderKey().compareTo(
r2.sourceOrderKey()) < 0);
+ // when
engine.start(
"embedded-state-parent",
resource("examples/clean/embedded-state-parent.yaml"),
CoordinationEngine.AdmissionPolicy.FULL_HISTORY,
null);
+ // then
List rootHistory = engine.history(
"embedded-state-parent");
assertEquals(List.of(
@@ -368,6 +402,7 @@ void topLevelHistoryAdvancesNewChildBeforeTheRootsNextEntry()
void knownHistoricalStateAppliesOnlyEpochsAfterTheSuppliedState()
throws Exception {
try (TestEngine engine = TestEngine.create()) {
+ // given
String childInitial = resource(
"examples/clean/embedded-counter.yaml");
Timeline childTimeline = engine.timeline(
@@ -387,6 +422,8 @@ void knownHistoricalStateAppliesOnlyEpochsAfterTheSuppliedState()
Timeline parentTimeline = engine.timeline(
"examples/embedded/state-parent", "bob");
EngineMetrics.MetricsSnapshot before = engine.metricsSnapshot();
+
+ // when
engine.dispatch(engine.appendAt(
parentTimeline,
Operation.exact(
@@ -397,6 +434,7 @@ void knownHistoricalStateAppliesOnlyEpochsAfterTheSuppliedState()
EngineTestSupport.MetricDelta work = delta(
before, engine.metricsSnapshot());
+ // then
List parentHistory = engine.history(
"embedded-state-parent");
assertEquals(1L, childCounter(parentHistory.get(1)),
@@ -420,6 +458,7 @@ void knownHistoricalStateAppliesOnlyEpochsAfterTheSuppliedState()
void unknownDivergentStateRejectsBeforeParentOrTopologyCommit()
throws Exception {
try (TestEngine engine = TestEngine.create()) {
+ // given
String childInitial = resource(
"examples/clean/embedded-counter.yaml");
Timeline childTimeline = engine.timeline(
@@ -446,6 +485,8 @@ void unknownDivergentStateRejectsBeforeParentOrTopologyCommit()
String divergent = childInitial.replace(
"counter: 0", "counter: 99");
+
+ // when
IllegalStateException failure = assertThrows(
IllegalStateException.class,
() -> engine.appendAndDispatch(
@@ -457,6 +498,7 @@ void unknownDivergentStateRejectsBeforeParentOrTopologyCommit()
EngineTestSupport.MetricDelta work = delta(
before, engine.metricsSnapshot());
+ // then
assertTrue(failure.getMessage().contains(
"Invalid admission evidence: unknown state"),
failure::getMessage);
diff --git a/src/integrationTest/java/blue/coordination/integration/SameDocumentInitialIdentityTest.java b/src/integrationTest/java/blue/coordination/integration/SameDocumentInitialIdentityTest.java
index a2d0eb5..ad4bd85 100644
--- a/src/integrationTest/java/blue/coordination/integration/SameDocumentInitialIdentityTest.java
+++ b/src/integrationTest/java/blue/coordination/integration/SameDocumentInitialIdentityTest.java
@@ -19,6 +19,7 @@ final class SameDocumentInitialIdentityTest {
void equalExactStatesWithDifferentDocumentIdsKeepIndependentHistories()
throws Exception {
try (TestEngine engine = TestEngine.create()) {
+ // given
String identityFreeCounter = resource(
"examples/clean/embedded-counter.yaml")
.replace("documentId: embedded-counter-A\n", "");
@@ -32,11 +33,13 @@ void equalExactStatesWithDifferentDocumentIdsKeepIndependentHistories()
engine.session("counter-lineage-one").current().blueId(),
engine.session("counter-lineage-two").current().blueId());
+ // when
engine.appendAndDispatch(
timeline,
Operation.yaml(
"increment", "ownerChannel", "amount: 4"));
+ // then
assertEquals(4L, integer(
engine, "counter-lineage-one", "/counter"));
assertEquals(4L, integer(
@@ -54,6 +57,7 @@ void equalExactStatesWithDifferentDocumentIdsKeepIndependentHistories()
void sameDocumentIsReusedButUnknownDivergentStateIsRejectedAtomically()
throws Exception {
try (TestEngine engine = TestEngine.create()) {
+ // given
String childInitial = resource(
"examples/clean/embedded-counter.yaml");
Timeline childTimeline = engine.timeline(
@@ -101,6 +105,8 @@ void sameDocumentIsReusedButUnknownDivergentStateIsRejectedAtomically()
String conflictingInitial = childInitial.replace(
"counter: 0", "counter: 99");
+
+ // when
IllegalStateException failure = assertThrows(
IllegalStateException.class,
() -> engine.appendAndDispatch(
@@ -111,6 +117,7 @@ void sameDocumentIsReusedButUnknownDivergentStateIsRejectedAtomically()
engine.embeddedDocumentRequest(
conflictingInitial))));
+ // then
assertTrue(failure.getMessage().contains(
"Invalid admission evidence: unknown state"),
failure::getMessage);
diff --git a/src/integrationTest/java/blue/coordination/integration/SharedManagedChildTwoOccurrencesTest.java b/src/integrationTest/java/blue/coordination/integration/SharedManagedChildTwoOccurrencesTest.java
index 9988846..c30f588 100644
--- a/src/integrationTest/java/blue/coordination/integration/SharedManagedChildTwoOccurrencesTest.java
+++ b/src/integrationTest/java/blue/coordination/integration/SharedManagedChildTwoOccurrencesTest.java
@@ -18,6 +18,7 @@ final class SharedManagedChildTwoOccurrencesTest {
void oneDirectChildProcessAdvancesBothOccurrenceCursors()
throws Exception {
try (TestEngine engine = TestEngine.create()) {
+ // given
String child = resource("examples/clean/embedded-counter.yaml");
Timeline childTimeline = engine.timeline(
"examples/embedded/A", "alice");
@@ -61,6 +62,7 @@ void oneDirectChildProcessAdvancesBothOccurrenceCursors()
assertOccurrenceCursors(engine, 0L);
EngineMetrics.MetricsSnapshot before = engine.metricsSnapshot();
+ // when
engine.appendAndDispatch(
childTimeline,
Operation.yaml(
@@ -68,6 +70,7 @@ void oneDirectChildProcessAdvancesBothOccurrenceCursors()
EngineTestSupport.MetricDelta work = delta(
before, engine.metricsSnapshot());
+ // then
assertEquals(3L, integer(
engine, "embedded-counter-A", "/counter"));
assertEquals(3L, integer(
diff --git a/src/integrationTest/java/blue/coordination/integration/SharedManagedChildTwoParentsTest.java b/src/integrationTest/java/blue/coordination/integration/SharedManagedChildTwoParentsTest.java
index 042da91..3dbba7a 100644
--- a/src/integrationTest/java/blue/coordination/integration/SharedManagedChildTwoParentsTest.java
+++ b/src/integrationTest/java/blue/coordination/integration/SharedManagedChildTwoParentsTest.java
@@ -21,6 +21,7 @@ final class SharedManagedChildTwoParentsTest {
void oneChildRevisionConvergesTwoParentsWithoutReprocessingTheChild()
throws Exception {
try (TestEngine engine = TestEngine.create()) {
+ // given
String childInitial = resource(
"examples/clean/embedded-counter.yaml");
Timeline childTimeline = engine.timeline(
@@ -77,6 +78,7 @@ void oneChildRevisionConvergesTwoParentsWithoutReprocessingTheChild()
"embedded-parent-two").size();
EngineMetrics.MetricsSnapshot before = engine.metricsSnapshot();
+ // when
engine.appendAndDispatch(
childTimeline,
Operation.yaml(
@@ -84,6 +86,7 @@ void oneChildRevisionConvergesTwoParentsWithoutReprocessingTheChild()
EngineTestSupport.MetricDelta work = delta(
before, engine.metricsSnapshot());
+ // then
assertEquals(7L, integer(
engine, "embedded-counter-A", "/counter"));
assertEquals(7L, integer(
@@ -114,6 +117,7 @@ void oneChildRevisionConvergesTwoParentsWithoutReprocessingTheChild()
void failedSharedParentRetryRunsOnlyItsMissingApplication()
throws Exception {
try (TestEngine engine = TestEngine.create()) {
+ // given
String childInitial = resource(
"examples/clean/embedded-counter.yaml");
Timeline childTimeline = engine.timeline(
@@ -206,10 +210,13 @@ void failedSharedParentRetryRunsOnlyItsMissingApplication()
engine.clearFailureInjection();
EngineMetrics.MetricsSnapshot beforeRetry =
engine.metricsSnapshot();
+
+ // when
assertTrue(engine.drain().quiescent());
EngineTestSupport.MetricDelta retry = delta(
beforeRetry, engine.metricsSnapshot());
+ // then
assertEquals(1L, retry.counter("frozenProcessCalls"),
"retry runs only the failed second-parent application");
assertEquals(0L, retry.counter("temporal.externalProcessCalls"));
diff --git a/src/integrationTest/java/blue/coordination/integration/SourceSurfaceIdentityIntegrationTest.java b/src/integrationTest/java/blue/coordination/integration/SourceSurfaceIdentityIntegrationTest.java
index 8a0e761..e35ab98 100644
--- a/src/integrationTest/java/blue/coordination/integration/SourceSurfaceIdentityIntegrationTest.java
+++ b/src/integrationTest/java/blue/coordination/integration/SourceSurfaceIdentityIntegrationTest.java
@@ -28,6 +28,7 @@ final class SourceSurfaceIdentityIntegrationTest {
void businessOnlyTransitionReusesIdentityAndRouteChangeInvalidatesIt()
throws Exception {
try (TestEngine engine = TestEngine.create()) {
+ // given
Timeline owner = engine.timeline(OWNER_TIMELINE, "owner");
Timeline dynamic = engine.timeline(
DYNAMIC_TIMELINE, "dynamic-owner");
@@ -100,12 +101,16 @@ void businessOnlyTransitionReusesIdentityAndRouteChangeInvalidatesIt()
long beforeFreshWindow = counter(engine.metricsSnapshot(),
"journal.sourceSurfaceIdentitiesResolved");
+
+ // when
engine.dispatch(engine.appendAt(parent, Operation.yaml(
"detachChild", "ownerChannel", "{}"), T0 + 500L));
engine.dispatch(engine.appendAt(parent, Operation.exact(
"attachChild", "ownerChannel",
engine.embeddedDocumentRequest(
engine.session(CHILD).current())), T0 + 600L));
+
+ // then
assertTrue(counter(engine.metricsSnapshot(),
"journal.sourceSurfaceIdentitiesResolved")
> beforeFreshWindow,
diff --git a/src/integrationTest/java/blue/coordination/integration/StartAdmissionAtomicityTest.java b/src/integrationTest/java/blue/coordination/integration/StartAdmissionAtomicityTest.java
index 712cc9a..6f294f6 100644
--- a/src/integrationTest/java/blue/coordination/integration/StartAdmissionAtomicityTest.java
+++ b/src/integrationTest/java/blue/coordination/integration/StartAdmissionAtomicityTest.java
@@ -12,6 +12,7 @@ final class StartAdmissionAtomicityTest {
@Test
void rejectedTopLevelSelfCyclePublishesNothingAndRetryMatchesFresh()
throws Exception {
+ // given
String parent = resource(
"examples/clean/root-isolation-parent.yaml");
String child = resource(
@@ -30,6 +31,7 @@ void rejectedTopLevelSelfCyclePublishesNothingAndRetryMatchesFresh()
EngineMetrics.MetricsSnapshot metricsBefore =
engine.metricsSnapshot();
+ // when
assertThrows(
IllegalStateException.class,
() -> engine.start(
@@ -48,6 +50,8 @@ void rejectedTopLevelSelfCyclePublishesNothingAndRetryMatchesFresh()
var retry = engine.start("root-isolation-parent", parent);
var expected = fresh.start("root-isolation-parent", parent);
+
+ // then
assertEquals(expected.authoredInitialBlueId(),
retry.authoredInitialBlueId());
assertEquals(expected.layout().rootBlueId(),
diff --git a/src/integrationTest/java/blue/coordination/integration/TemporalAdmissionPolicyIntegrationTest.java b/src/integrationTest/java/blue/coordination/integration/TemporalAdmissionPolicyIntegrationTest.java
index 3b33995..41e332c 100644
--- a/src/integrationTest/java/blue/coordination/integration/TemporalAdmissionPolicyIntegrationTest.java
+++ b/src/integrationTest/java/blue/coordination/integration/TemporalAdmissionPolicyIntegrationTest.java
@@ -27,15 +27,19 @@ final class TemporalAdmissionPolicyIntegrationTest {
@Test
void rejectsFrontiersWithoutExactJournalEvidence() throws Exception {
try (TestEngine engine = TestEngine.create()) {
+ // given
ExternalOrderKey forged = ExternalOrderKey.of(
List.of(T0 + 999L, "forged-frontier"));
String counter = resource("examples/clean/counter.yaml");
+ // when
assertThrows(IllegalArgumentException.class, () -> engine.start(
"counter-forged",
document(counter, "counter-forged"),
CoordinationEngine.AdmissionPolicy.FROM_FRONTIER,
forged));
+
+ // then
assertThrows(IllegalArgumentException.class,
() -> engine.configureEmbeddedAdmission(
"child-forged",
@@ -48,6 +52,7 @@ void rejectsFrontiersWithoutExactJournalEvidence() throws Exception {
void topLevelHistoryPoliciesUseExclusiveVerifiedFrontiers()
throws Exception {
try (TestEngine engine = TestEngine.create()) {
+ // given
Timeline alice = engine.timeline(
"examples/clean-counter/alice", "alice");
var one = engine.appendAt(alice, counterIncrement(1), T0 + 100L);
@@ -55,6 +60,7 @@ void topLevelHistoryPoliciesUseExclusiveVerifiedFrontiers()
engine.appendAt(alice, counterIncrement(3), T0 + 300L);
String counter = resource("examples/clean/counter.yaml");
+ // when
engine.start(
"counter-full",
document(counter, "counter-full"),
@@ -71,6 +77,7 @@ void topLevelHistoryPoliciesUseExclusiveVerifiedFrontiers()
CoordinationEngine.AdmissionPolicy.FROM_NOW,
null);
+ // then
assertEquals(6L, integer(engine, "counter-full", "/counter"));
assertEquals(5L, integer(
engine, "counter-frontier", "/counter"));
@@ -85,11 +92,13 @@ void topLevelHistoryPoliciesUseExclusiveVerifiedFrontiers()
void embeddedBirthFrontierFullAndPassivePoliciesAreHostMetadata()
throws Exception {
try (TestEngine engine = TestEngine.create()) {
+ // given
Timeline feed = engine.timeline("examples/embedded/A", "alice");
var one = engine.appendAt(feed, increment(1), T0 + 100L);
engine.appendAt(feed, increment(2), T0 + 200L);
engine.appendAt(feed, increment(3), T0 + 300L);
+ // when
engine.configureEmbeddedAdmission(
"child-birth", ActivationMode.BIRTH_AT_ATTACHMENT, null);
attachVariant(engine, "birth", "child-birth", 0L,
@@ -110,6 +119,7 @@ void embeddedBirthFrontierFullAndPassivePoliciesAreHostMetadata()
attachVariant(engine, "passive", "child-passive", 0L,
T0 + 1_300L);
+ // then
assertEquals(0L, integer(
engine, "parent-birth", "/child/counter"));
assertEquals(6L, integer(
@@ -132,6 +142,7 @@ void embeddedBirthFrontierFullAndPassivePoliciesAreHostMetadata()
void attachCurrentRequiresExistingCurrentStateAndCompletenessThroughT()
throws Exception {
try (TestEngine engine = TestEngine.create()) {
+ // given
Timeline feed = engine.timeline("examples/embedded/A", "alice");
engine.appendAt(feed, increment(1), T0 + 100L);
engine.appendAt(feed, increment(2), T0 + 200L);
@@ -161,8 +172,11 @@ void attachCurrentRequiresExistingCurrentStateAndCompletenessThroughT()
"child-current",
ActivationMode.ATTACH_CURRENT_STATE,
attachment.sourceOrderKey());
+
+ // when
engine.dispatch(attachment);
+ // then
assertEquals(3L, integer(
engine, parentId, "/child/counter"));
assertEquals(1L, engine.session(parentId).epoch());
@@ -174,6 +188,7 @@ void attachCurrentRequiresExistingCurrentStateAndCompletenessThroughT()
void exactOccurrencePlansSelectIndependentEpochs()
throws Exception {
try (TestEngine engine = TestEngine.create()) {
+ // given
Timeline feed = engine.timeline("examples/embedded/A", "alice");
engine.appendAt(feed, increment(1), T0 + 100L);
engine.appendAt(feed, increment(-1), T0 + 200L);
@@ -184,11 +199,13 @@ void exactOccurrencePlansSelectIndependentEpochs()
ExactValue first = engine.history(childId).get(1).after();
ExactValue latest = engine.history(childId).get(3).after();
+ // when
attachAtEpoch(engine, "first", childId, first, 1L,
T0 + 1_000L, true);
attachAtEpoch(engine, "latest", childId, latest, 3L,
T0 + 1_100L, true);
+ // then
assertEquals(3L, engine.session("parent-first").epoch());
assertEquals(1L, engine.session("parent-latest").epoch());
assertEquals(1L, integer(engine, "parent-first", "/child/counter"));
@@ -199,6 +216,7 @@ void exactOccurrencePlansSelectIndependentEpochs()
@Test
void failedPublicationRetainsExactOccurrencePlan() throws Exception {
try (TestEngine engine = TestEngine.create()) {
+ // given
String childId = "child-retry-plan";
ExactValue child = engine.registerType(child(childId, 0L));
TimelineEntry attachment = attachAtEpoch(
@@ -216,10 +234,12 @@ void failedPublicationRetainsExactOccurrencePlan() throws Exception {
notReady.code());
engine.clearFailureInjection();
+ // when
engine.dispatch(attachment);
+
+ // then
assertEquals(SessionStatus.READY,
engine.session("parent-retry-plan").status());
-
assertEquals(2L, engine.session("parent-retry-plan").epoch());
assertEquals(0L, integer(
engine, "parent-retry-plan", "/child/counter"));
diff --git a/src/integrationTest/java/blue/coordination/integration/TestEngine.java b/src/integrationTest/java/blue/coordination/integration/TestEngine.java
index 15be27c..19b10ab 100644
--- a/src/integrationTest/java/blue/coordination/integration/TestEngine.java
+++ b/src/integrationTest/java/blue/coordination/integration/TestEngine.java
@@ -38,7 +38,7 @@ private TestEngine(CoordinationEngine engine) {
}
static TestEngine create() {
- return new TestEngine(CoordinationEngine.inMemory());
+ return new TestEngine(CoordinationEngine.legacyInMemory());
}
Timeline timeline(String timelineId, String actorId) {
diff --git a/src/integrationTest/java/blue/coordination/integration/WholeObjectFailureHygieneTest.java b/src/integrationTest/java/blue/coordination/integration/WholeObjectFailureHygieneTest.java
index 6ace182..25e3882 100644
--- a/src/integrationTest/java/blue/coordination/integration/WholeObjectFailureHygieneTest.java
+++ b/src/integrationTest/java/blue/coordination/integration/WholeObjectFailureHygieneTest.java
@@ -22,6 +22,7 @@ final class WholeObjectFailureHygieneTest {
void identicalPrePublicationFailuresReachAStableWholeObjectCount()
throws Exception {
try (TestEngine engine = TestEngine.create()) {
+ // given
Timeline alice = engine.timeline(
"examples/clean-counter/alice", "alice");
engine.start("counter", resource("examples/clean/counter.yaml"));
@@ -35,6 +36,7 @@ void identicalPrePublicationFailuresReachAStableWholeObjectCount()
engine.metricsSnapshot();
List retainedCounts = new ArrayList<>(ATTEMPTS);
+ // when
for (int attempt = 0; attempt < ATTEMPTS; attempt++) {
engine.failOnceAt(TestEngine.FailurePoint
.AFTER_FROZEN_BEFORE_STAGE);
@@ -74,6 +76,8 @@ void identicalPrePublicationFailuresReachAStableWholeObjectCount()
var next = engine.append(
alice,
Operation.yaml("ignored", "aliceChannel", "{}"));
+
+ // then
assertEquals(entry.timestampMicros() + 1L,
next.timestampMicros());
assertEquals(entry.globalSequence() + 1L,
diff --git a/src/integrationTest/java/blue/coordination/internal/ApplicationReadinessProofIntegrationTest.java b/src/integrationTest/java/blue/coordination/internal/ApplicationReadinessProofIntegrationTest.java
index 0a78f19..3886aab 100644
--- a/src/integrationTest/java/blue/coordination/internal/ApplicationReadinessProofIntegrationTest.java
+++ b/src/integrationTest/java/blue/coordination/internal/ApplicationReadinessProofIntegrationTest.java
@@ -34,10 +34,14 @@ final class ApplicationReadinessProofIntegrationTest {
void rejectsCurrentReadyMarkerWhileAnEmbeddedBarrierIsOpen()
throws Exception {
try (ReadyFixture fixture = readyFixture()) {
+ // given
Map openBarriers = mapField(
fixture.coordinator(), "openBarrierByParent");
+
+ // when
openBarriers.put(PARENT, "test-open-barrier");
+ // then
assertRejectedButAuditable(
fixture, "an embedded catch-up barrier remains open");
}
@@ -47,11 +51,15 @@ void rejectsCurrentReadyMarkerWhileAnEmbeddedBarrierIsOpen()
void rejectsCurrentReadyMarkerWhenTheCommittedOccurrenceHasNoBinding()
throws Exception {
try (ReadyFixture fixture = readyFixture()) {
+ // given
ProcessEmbeddedGraphSnapshot withoutBinding = fixture
.coordinator().graphSnapshot().reconcileParent(
PARENT, List.of());
+
+ // when
setField(fixture.coordinator(), "graph", withoutBinding);
+ // then
assertRejectedButAuditable(
fixture, "published graph does not match current "
+ "Process Embedded occurrence count");
@@ -62,10 +70,14 @@ void rejectsCurrentReadyMarkerWhenTheCommittedOccurrenceHasNoBinding()
void rejectsCurrentReadyMarkerWhenTheBindingHasNoCursor()
throws Exception {
try (ReadyFixture fixture = readyFixture()) {
+ // given
Map cursors = mapField(
fixture.coordinator(), "cursors");
+
+ // when
cursors.remove(fixture.binding().bindingId());
+ // then
assertRejectedButAuditable(
fixture, "missing embedded epoch cursor");
}
@@ -75,14 +87,18 @@ void rejectsCurrentReadyMarkerWhenTheBindingHasNoCursor()
void rejectsCurrentReadyMarkerWhenTheCursorIsBehindItsChild()
throws Exception {
try (ReadyFixture fixture = readyFixture()) {
+ // given
Map cursors = mapField(
fixture.coordinator(), "cursors");
+ assertEquals(0L, fixture.child().epoch());
+
+ // when
cursors.put(
fixture.binding().bindingId(),
new EmbeddedEpochCursor(
fixture.binding().bindingId(), -1L));
- assertEquals(0L, fixture.child().epoch());
+ // then
assertRejectedButAuditable(
fixture, "parent cursor -1 is behind child epoch 0");
}
@@ -92,8 +108,13 @@ void rejectsCurrentReadyMarkerWhenTheCursorIsBehindItsChild()
void rejectsCurrentReadyMarkerWhenParentStateDiffersFromCursorState()
throws Exception {
try (ReadyFixture fixture = readyFixture()) {
- replaceCurrentParentStateWithMismatch(fixture.parent());
+ // given
+ DocumentSession parent = fixture.parent();
+
+ // when
+ replaceCurrentParentStateWithMismatch(parent);
+ // then
assertRejectedButAuditable(
fixture, "parent state/cursor mismatch at /child");
}
diff --git a/src/integrationTest/java/blue/coordination/internal/EmbeddedReceiptRetryIdentityIntegrationTest.java b/src/integrationTest/java/blue/coordination/internal/EmbeddedReceiptRetryIdentityIntegrationTest.java
index e40f60b..a372d34 100644
--- a/src/integrationTest/java/blue/coordination/internal/EmbeddedReceiptRetryIdentityIntegrationTest.java
+++ b/src/integrationTest/java/blue/coordination/internal/EmbeddedReceiptRetryIdentityIntegrationTest.java
@@ -25,9 +25,15 @@ final class EmbeddedReceiptRetryIdentityIntegrationTest {
@Test
void committedReceiptRetryDoesNotConsumeClockOrCreatePhantomInput()
throws Exception {
- Scenario uninterrupted = run(false);
- Scenario retried = run(true);
+ // given
+ boolean uninterruptedRun = false;
+ boolean retryingRun = true;
+ // when
+ Scenario uninterrupted = run(uninterruptedRun);
+ Scenario retried = run(retryingRun);
+
+ // then
assertEquals(uninterrupted.objectsAfterRecovery(),
retried.objectsAfterRecovery(),
"receipt recovery cannot add a whole object");
diff --git a/src/integrationTest/resources/examples/clean/large-paynote.yaml b/src/integrationTest/resources/examples/clean/large-paynote.yaml
index 0d89aa3..ee2da3a 100644
--- a/src/integrationTest/resources/examples/clean/large-paynote.yaml
+++ b/src/integrationTest/resources/examples/clean/large-paynote.yaml
@@ -629,10 +629,8 @@ contracts:
then:
- $appendChange:
op: replace
- path: /attachedConditions
- val:
- hotel: true
- restaurant: {$document: /restaurantConditionAttachedState}
+ path: /attachedConditions/hotel
+ val: true
- $appendChange: {op: replace, path: /hotelConditionAttachedState, val: true}
- $appendChange: {op: replace, path: /status, val: Awaiting Product Confirmations}
- $appendEvent:
@@ -668,10 +666,8 @@ contracts:
then:
- $appendChange:
op: replace
- path: /attachedConditions
- val:
- hotel: {$document: /hotelConditionAttachedState}
- restaurant: true
+ path: /attachedConditions/restaurant
+ val: true
- $appendChange: {op: replace, path: /restaurantConditionAttachedState, val: true}
- $appendChange: {op: replace, path: /status, val: Awaiting Product Confirmations}
- $appendEvent:
@@ -1005,14 +1001,12 @@ contracts:
then:
- $appendChange:
op: replace
- path: /refund
- val:
- requested: true
- requestId: {$document: /refundRequestIdState}
- amountMinor: {$document: /refundAmountMinorState}
- reason: {$document: /refundReasonState}
- completed: true
- completedAt: {$binding: event/timestamp}
+ path: /refund/completed
+ val: true
+ - $appendChange:
+ op: replace
+ path: /refund/completedAt
+ val: {$binding: event/timestamp}
- $appendChange: {op: replace, path: /refundCompletedState, val: true}
- $appendChange:
op: replace
diff --git a/src/integrationTest/resources/examples/clean/package-paynote.yaml b/src/integrationTest/resources/examples/clean/package-paynote.yaml
index 4fef1d4..1e9a518 100644
--- a/src/integrationTest/resources/examples/clean/package-paynote.yaml
+++ b/src/integrationTest/resources/examples/clean/package-paynote.yaml
@@ -411,10 +411,8 @@ contracts:
then:
- $appendChange:
op: replace
- path: /attachedConditions
- val:
- hotel: true
- restaurant: {$document: /restaurantConditionAttachedState}
+ path: /attachedConditions/hotel
+ val: true
- $appendChange: {op: replace, path: /hotelConditionAttachedState, val: true}
- $appendChange: {op: replace, path: /status, val: Awaiting Product Confirmations}
- $appendEvent:
@@ -450,10 +448,8 @@ contracts:
then:
- $appendChange:
op: replace
- path: /attachedConditions
- val:
- hotel: {$document: /hotelConditionAttachedState}
- restaurant: true
+ path: /attachedConditions/restaurant
+ val: true
- $appendChange: {op: replace, path: /restaurantConditionAttachedState, val: true}
- $appendChange: {op: replace, path: /status, val: Awaiting Product Confirmations}
- $appendEvent:
@@ -787,14 +783,12 @@ contracts:
then:
- $appendChange:
op: replace
- path: /refund
- val:
- requested: true
- requestId: {$document: /refundRequestIdState}
- amountMinor: {$document: /refundAmountMinorState}
- reason: {$document: /refundReasonState}
- completed: true
- completedAt: {$binding: event/timestamp}
+ path: /refund/completed
+ val: true
+ - $appendChange:
+ op: replace
+ path: /refund/completedAt
+ val: {$binding: event/timestamp}
- $appendChange: {op: replace, path: /refundCompletedState, val: true}
- $appendChange:
op: replace
diff --git a/src/main/java/blue/coordination/api/Contracts10Configuration.java b/src/main/java/blue/coordination/api/Contracts10Configuration.java
new file mode 100644
index 0000000..2b1581d
--- /dev/null
+++ b/src/main/java/blue/coordination/api/Contracts10Configuration.java
@@ -0,0 +1,73 @@
+package blue.coordination.api;
+
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Objects;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.regex.Pattern;
+
+/**
+ * Explicit artifact and public-Root inputs for Contracts 1.0 Coordination.
+ *
+ * The engine never invents release identities. A closure-enabled factory
+ * accepts this value only after the caller binds the exact final Language and
+ * Contracts specification artifacts it intends to execute.
+ *
+ * @param blueLanguageSpecificationIdentity exact final Language artifact ID
+ * @param contractsSpecificationIdentity exact final Contracts artifact ID
+ * @param publicRootDocumentIds public feeder Root lineages for this engine
+ */
+public record Contracts10Configuration(
+ String blueLanguageSpecificationIdentity,
+ String contractsSpecificationIdentity,
+ Set publicRootDocumentIds) {
+ private static final Pattern SHA_256_IDENTITY = Pattern.compile(
+ "^sha256:[0-9a-f]{64}$");
+
+ /** Validates exact artifact identities and canonical public Root order. */
+ public Contracts10Configuration {
+ blueLanguageSpecificationIdentity = requireIdentity(
+ blueLanguageSpecificationIdentity,
+ "blueLanguageSpecificationIdentity");
+ contractsSpecificationIdentity = requireIdentity(
+ contractsSpecificationIdentity,
+ "contractsSpecificationIdentity");
+ TreeSet canonical = new TreeSet<>((left, right) ->
+ comparePortableText(left.value(), right.value()));
+ Objects.requireNonNull(
+ publicRootDocumentIds, "publicRootDocumentIds")
+ .forEach(root -> canonical.add(Objects.requireNonNull(
+ root, "publicRootDocumentId")));
+ if (canonical.isEmpty()) {
+ throw new IllegalArgumentException(
+ "Contracts 1.0 requires at least one public Root");
+ }
+ publicRootDocumentIds = Collections.unmodifiableSet(
+ new LinkedHashSet<>(canonical));
+ }
+
+ private static String requireIdentity(String value, String label) {
+ String checked = Objects.requireNonNull(value, label);
+ if (!SHA_256_IDENTITY.matcher(checked).matches()) {
+ throw new IllegalArgumentException(
+ label + " must be a lowercase sha256 identity");
+ }
+ return checked;
+ }
+
+ private static int comparePortableText(String left, String right) {
+ int leftOffset = 0;
+ int rightOffset = 0;
+ while (leftOffset < left.length() && rightOffset < right.length()) {
+ int leftPoint = left.codePointAt(leftOffset);
+ int rightPoint = right.codePointAt(rightOffset);
+ if (leftPoint != rightPoint) {
+ return Integer.compare(leftPoint, rightPoint);
+ }
+ leftOffset += Character.charCount(leftPoint);
+ rightOffset += Character.charCount(rightPoint);
+ }
+ return Integer.compare(left.length(), right.length());
+ }
+}
diff --git a/src/main/java/blue/coordination/api/ContractsClosureAdmissionReceipt.java b/src/main/java/blue/coordination/api/ContractsClosureAdmissionReceipt.java
new file mode 100644
index 0000000..a557a71
--- /dev/null
+++ b/src/main/java/blue/coordination/api/ContractsClosureAdmissionReceipt.java
@@ -0,0 +1,84 @@
+package blue.coordination.api;
+
+import blue.language.processor.closure.ClosureAttemptResult;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.TreeSet;
+
+/**
+ * Exact attempt and durable-publication evidence for one Contracts admission.
+ *
+ * A resource suspension or semantic rejection has
+ * {@link PublicationOutcome#NOT_PUBLISHED} and leaves Coordination state
+ * unchanged. A retry of an already durable publication returns the original
+ * immutable Contracts attempt with
+ * {@link PublicationOutcome#ALREADY_PUBLISHED}; Contracts is not executed a
+ * second time.
+ *
+ * @param attempt exact completed result or exact resource suspension
+ * @param publicationIdentity stable host publication identity
+ * @param publicationOutcome whether this call published or replayed a receipt
+ * @param documentIds canonical admitted document lineages
+ */
+public record ContractsClosureAdmissionReceipt(
+ ClosureAttemptResult attempt,
+ String publicationIdentity,
+ PublicationOutcome publicationOutcome,
+ List documentIds) {
+
+ /** Validates the relationship between the attempt and publication state. */
+ public ContractsClosureAdmissionReceipt {
+ attempt = Objects.requireNonNull(attempt, "attempt");
+ publicationIdentity = requireText(
+ publicationIdentity, "publicationIdentity");
+ publicationOutcome = Objects.requireNonNull(
+ publicationOutcome, "publicationOutcome");
+ List supplied = List.copyOf(Objects.requireNonNull(
+ documentIds, "documentIds"));
+ TreeSet canonical = new TreeSet<>();
+ for (DocumentId documentId : supplied) {
+ if (!canonical.add(Objects.requireNonNull(
+ documentId, "documentId"))) {
+ throw new IllegalArgumentException(
+ "Admission receipt repeats document " + documentId);
+ }
+ }
+ documentIds = List.copyOf(new ArrayList<>(canonical));
+ boolean commits = attempt.isComplete()
+ && attempt.processResult().commits();
+ if (publicationOutcome == PublicationOutcome.NOT_PUBLISHED) {
+ if (commits || !documentIds.isEmpty()) {
+ throw new IllegalArgumentException(
+ "An unpublished admission cannot retain committed documents");
+ }
+ } else if (!commits || documentIds.isEmpty()) {
+ throw new IllegalArgumentException(
+ "A published admission requires a committing result and documents");
+ }
+ }
+
+ /** Returns whether durable state exists for this admission. */
+ public boolean published() {
+ return publicationOutcome != PublicationOutcome.NOT_PUBLISHED;
+ }
+
+ /** Closed durable-publication outcome. */
+ public enum PublicationOutcome {
+ /** The attempt suspended or rejected and made no durable mutation. */
+ NOT_PUBLISHED,
+ /** This call atomically published the successful admission. */
+ PUBLISHED,
+ /** A prior call published it and this call reconciled that receipt. */
+ ALREADY_PUBLISHED
+ }
+
+ private static String requireText(String value, String label) {
+ String checked = Objects.requireNonNull(value, label);
+ if (checked.isBlank()) {
+ throw new IllegalArgumentException(label + " must not be blank");
+ }
+ return checked;
+ }
+}
diff --git a/src/main/java/blue/coordination/api/ContractsClosureDispatchAttempt.java b/src/main/java/blue/coordination/api/ContractsClosureDispatchAttempt.java
new file mode 100644
index 0000000..3c81785
--- /dev/null
+++ b/src/main/java/blue/coordination/api/ContractsClosureDispatchAttempt.java
@@ -0,0 +1,57 @@
+package blue.coordination.api;
+
+import blue.language.processor.closure.ClosureAttemptResult;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Advanced exact Contracts evidence retained for one dispatched cohort.
+ *
+ * The stable SDK translates this low-level attempt into application-facing
+ * closure results. Existing Coordination callers may ignore it.
+ *
+ * @param entryBlueId exact external Timeline Entry identity
+ * @param documentIds canonical affected cohort members
+ * @param attempt exact completed or suspended Contracts attempt
+ * @param published whether durable state was published
+ * @param publicationIdentity durable publication identity, when available
+ * @param replayed whether an existing publication receipt was reconciled
+ */
+public record ContractsClosureDispatchAttempt(
+ String entryBlueId,
+ List documentIds,
+ ClosureAttemptResult attempt,
+ boolean published,
+ String publicationIdentity,
+ boolean replayed) {
+
+ /** Validates immutable cohort evidence. */
+ public ContractsClosureDispatchAttempt {
+ entryBlueId = requireText(entryBlueId, "entryBlueId");
+ documentIds = List.copyOf(Objects.requireNonNull(
+ documentIds, "documentIds"));
+ attempt = Objects.requireNonNull(attempt, "attempt");
+ if (publicationIdentity != null && publicationIdentity.isBlank()) {
+ throw new IllegalArgumentException(
+ "publicationIdentity must not be blank");
+ }
+ if (published && (!attempt.isComplete()
+ || !attempt.processResult().commits())) {
+ throw new IllegalArgumentException(
+ "Only a committing attempt can be published");
+ }
+ if (replayed && publicationIdentity == null) {
+ throw new IllegalArgumentException(
+ "A replayed attempt requires a publication identity");
+ }
+ }
+
+ private static String requireText(String value, String label) {
+ String checked = Objects.requireNonNull(value, label);
+ if (checked.isBlank()) {
+ throw new IllegalArgumentException(label + " must not be blank");
+ }
+ return checked;
+ }
+}
diff --git a/src/main/java/blue/coordination/api/CoordinationEngine.java b/src/main/java/blue/coordination/api/CoordinationEngine.java
index e21490c..0688abd 100644
--- a/src/main/java/blue/coordination/api/CoordinationEngine.java
+++ b/src/main/java/blue/coordination/api/CoordinationEngine.java
@@ -4,6 +4,7 @@
import blue.language.model.Node;
import blue.language.processor.ExternalOrderKey;
+import blue.language.processor.closure.ClosureInvocationInput;
import java.util.List;
import java.util.Set;
@@ -15,11 +16,35 @@
* the engine releases its borrowed Language, Contracts, and BEX runtimes.
*/
public interface CoordinationEngine extends AutoCloseable {
- /** Creates the supported single-process in-memory engine. */
+ /**
+ * Creates the legacy single-document in-memory engine.
+ *
+ * @deprecated normal applications should use
+ * {@link blue.coordination.sdk.BlueCoordination#inMemory()};
+ * advanced compatibility callers should name
+ * {@link #legacyInMemory()} explicitly
+ */
+ @Deprecated(since = "3.0.0-rc.2", forRemoval = false)
static CoordinationEngine inMemory() {
+ return legacyInMemory();
+ }
+
+ /** Creates the explicitly named legacy in-memory compatibility engine. */
+ static CoordinationEngine legacyInMemory() {
return builder().inMemory().build();
}
+ /**
+ * Creates the in-memory Contracts 1.0 engine for exact release artifacts.
+ *
+ * @param configuration final artifact identities and public Root lineages
+ * @return a new Contracts 1.0 engine
+ */
+ static CoordinationEngine inMemoryContracts10(
+ Contracts10Configuration configuration) {
+ return DefaultCoordinationEngine.createContracts10(configuration);
+ }
+
/** Starts configuration of a Coordination engine. */
static Builder builder() {
return new Builder();
@@ -41,6 +66,26 @@ DocumentSnapshot startDocument(
AdmissionPolicy policy,
ExternalOrderKey verifiedFrontier);
+ /**
+ * Verifies and atomically admits one complete Contracts 1.0 closure.
+ *
+ * This explicit multi-document boundary is available only on an engine
+ * created by {@link #inMemoryContracts10(Contracts10Configuration)}.
+ * Cyclic member bodies remain authenticated by the supplied complete
+ * closure proof; this method never degrades them into independent legacy
+ * document starts.
+ *
+ * @param input exact typed {@code ADMIT_CLOSURE} invocation
+ * @param policy host temporal admission policy for every new member
+ * @param verifiedFrontier retained frontier required by
+ * {@link AdmissionPolicy#FROM_FRONTIER}, otherwise {@code null}
+ * @return exact Contracts attempt and publication receipt
+ */
+ ContractsClosureAdmissionReceipt admitContractsClosure(
+ ClosureInvocationInput input,
+ AdmissionPolicy policy,
+ ExternalOrderKey verifiedFrontier);
+
/**
* Registers host-owned temporal admission evidence for future occurrences
* of one embedded DocumentId. Process Embedded itself remains limited to
diff --git a/src/main/java/blue/coordination/api/ExactValue.java b/src/main/java/blue/coordination/api/ExactValue.java
index ed1d39c..0dcfee7 100644
--- a/src/main/java/blue/coordination/api/ExactValue.java
+++ b/src/main/java/blue/coordination/api/ExactValue.java
@@ -3,6 +3,12 @@
import blue.language.merge.ResolvedSnapshot;
import blue.language.model.Node;
import blue.language.model.wire.JsonPointer;
+import blue.language.processor.closure.ClosureProcessResult;
+import blue.language.processor.closure.ClosureInvocationInput;
+import blue.language.processor.closure.ComponentKind;
+import blue.language.processor.closure.ComponentSnapshot;
+import blue.language.processor.closure.ManagedDocumentSnapshot;
+import blue.language.processor.closure.ResultingDocument;
import blue.language.snapshot.FrozenNode;
import java.util.Objects;
@@ -20,18 +26,36 @@ public final class ExactValue {
private final String blueId;
private final FrozenNode frozen;
private final ResolvedSnapshot snapshot;
+ private final boolean cyclicMember;
private ExactValue(
String blueId,
FrozenNode frozen,
ResolvedSnapshot snapshot) {
+ this(blueId, frozen, snapshot, false);
+ }
+
+ private ExactValue(
+ String blueId,
+ FrozenNode frozen,
+ ResolvedSnapshot snapshot,
+ boolean cyclicMember) {
this.blueId = requireText(blueId, "blueId");
this.frozen = Objects.requireNonNull(frozen, "frozen");
this.snapshot = snapshot;
- if (!this.blueId.equals(this.frozen.blueId())) {
+ this.cyclicMember = cyclicMember;
+ if (!cyclicMember && !this.blueId.equals(this.frozen.blueId())) {
throw new IllegalArgumentException(
"Frozen value does not match supplied BlueId");
}
+ if (cyclicMember && !this.blueId.contains("#")) {
+ throw new IllegalArgumentException(
+ "Cyclic member identity requires a numeric member suffix");
+ }
+ if (cyclicMember && snapshot != null) {
+ throw new IllegalArgumentException(
+ "Cyclic member state cannot carry an acyclic resolver snapshot");
+ }
if (snapshot != null && !this.blueId.equals(snapshot.blueId())) {
throw new IllegalArgumentException(
"Snapshot does not match supplied BlueId");
@@ -72,6 +96,139 @@ public static ExactValue fromFrozen(FrozenNode frozen) {
return new ExactValue(exact.blueId(), exact, null);
}
+ /**
+ * Retains one document from an already verified successful closure result.
+ *
+ * This is the only Coordination boundary that may associate a local
+ * cyclic member body with its {@code MASTER#n} identity. The supplied
+ * Contracts result has already verified the complete component proof and
+ * every resulting document together; callers cannot inject a claimed
+ * cyclic identity independently of that evidence.
+ *
+ * @param result verified successful Contracts closure result
+ * @param documentId selected managed document lineage
+ * @return exact durable value with its authoritative closure identity
+ */
+ public static ExactValue fromVerifiedClosureResult(
+ ClosureProcessResult result,
+ DocumentId documentId) {
+ ClosureProcessResult verified = Objects.requireNonNull(result, "result");
+ DocumentId selected = Objects.requireNonNull(documentId, "documentId");
+ if (!verified.commits()) {
+ throw new IllegalArgumentException(
+ "Only a successful closure result can publish document state");
+ }
+ ResultingDocument document = verified.resultingDocuments().stream()
+ .filter(candidate -> candidate.documentId().value()
+ .equals(selected.value()))
+ .findFirst()
+ .orElseThrow(() -> new IllegalArgumentException(
+ "Closure result has no document " + selected));
+ FrozenNode body = FrozenNode.fromNode(document.document());
+ if (document.memberIndex() == null) {
+ if (!document.afterBlueId().equals(body.blueId())) {
+ throw new IllegalArgumentException(
+ "Acyclic closure document identity does not match its body");
+ }
+ return new ExactValue(document.afterBlueId(), body, null);
+ }
+ return new ExactValue(document.afterBlueId(), body, null, true);
+ }
+
+ /**
+ * Retains an admission input body only after the matching successful
+ * Contracts invocation has authenticated the complete closure.
+ *
+ * This is deliberately stricter than {@link #verified(String, Node)}:
+ * a standalone {@code MASTER#n} claim is never accepted. The exact input
+ * closure identity, invocation identity, commit-companion head fence, and
+ * complete cyclic component record must all agree with the successful
+ * result before the local member body can be retained.
+ *
+ * @param input exact {@code ADMIT_CLOSURE} input which was executed
+ * @param result verified successful result produced from {@code input}
+ * @param documentId selected managed document lineage
+ * @return exact authenticated input value for the initial history record
+ */
+ public static ExactValue fromVerifiedClosureAdmissionInput(
+ ClosureInvocationInput input,
+ ClosureProcessResult result,
+ DocumentId documentId) {
+ ClosureInvocationInput admission = Objects.requireNonNull(
+ input, "input");
+ ClosureProcessResult verified = Objects.requireNonNull(
+ result, "result");
+ DocumentId selected = Objects.requireNonNull(documentId, "documentId");
+ if (admission.operation()
+ != ClosureInvocationInput.Operation.ADMIT_CLOSURE) {
+ throw new IllegalArgumentException(
+ "Only an ADMIT_CLOSURE input can retain admission state");
+ }
+ if (!verified.commits() || verified.platformCommitCompanion() == null) {
+ throw new IllegalArgumentException(
+ "Only a successful closure admission can retain input state");
+ }
+ if (!verified.invocationIdentity().equals(
+ admission.invocationIdentity())
+ || !verified.inputClosureIdentity().equals(
+ admission.snapshot().closureIdentity())) {
+ throw new IllegalArgumentException(
+ "Closure result does not authenticate the admission input");
+ }
+ ManagedDocumentSnapshot document = admission.snapshot()
+ .managedDocuments().stream()
+ .filter(candidate -> candidate.documentId().value()
+ .equals(selected.value()))
+ .findFirst()
+ .orElseThrow(() -> new IllegalArgumentException(
+ "Admission input has no document " + selected));
+ boolean companionFence = verified.platformCommitCompanion()
+ .expectedInputDocuments().stream()
+ .anyMatch(candidate -> candidate.documentId().value()
+ .equals(selected.value())
+ && candidate.blueId().equals(document.blueId()));
+ if (!companionFence) {
+ throw new IllegalArgumentException(
+ "Commit companion does not fence admission input "
+ + selected);
+ }
+
+ FrozenNode body = FrozenNode.fromNode(document.document());
+ ComponentSnapshot component = admission.snapshot().components()
+ .stream()
+ .filter(candidate -> candidate.orderedMemberDocumentIds()
+ .stream().anyMatch(member -> member.value()
+ .equals(selected.value())))
+ .findFirst()
+ .orElseThrow(() -> new IllegalArgumentException(
+ "Admission input has no component for " + selected));
+ if (component.kind() == ComponentKind.ACYCLIC) {
+ if (!document.blueId().equals(body.blueId())) {
+ throw new IllegalArgumentException(
+ "Acyclic admission identity does not match its body");
+ }
+ return new ExactValue(document.blueId(), body, null);
+ }
+ int memberIndex = -1;
+ for (int index = 0;
+ index < component.orderedMemberDocumentIds().size(); index++) {
+ if (component.orderedMemberDocumentIds().get(index).value()
+ .equals(selected.value())) {
+ memberIndex = index;
+ break;
+ }
+ }
+ if (memberIndex < 0
+ || component.completeCyclicProof() == null
+ || !component.orderedMemberBlueIds().get(memberIndex)
+ .equals(document.blueId())) {
+ throw new IllegalArgumentException(
+ "Cyclic admission component does not authenticate "
+ + selected);
+ }
+ return new ExactValue(document.blueId(), body, null, true);
+ }
+
/** Returns the content-addressed identity of the whole exact value. */
public String blueId() {
return blueId;
@@ -87,11 +244,22 @@ public Node referenceNode() {
return new Node().blueId(blueId);
}
- /** Returns the shareable immutable frozen representation. */
+ /**
+ * Returns the shareable immutable local body.
+ *
+ * For an authenticated cyclic member, {@link #blueId()} is the
+ * authoritative {@code MASTER#n} identity while this frozen value is the
+ * corresponding local member body.
+ */
public FrozenNode frozen() {
return frozen;
}
+ /** Returns whether the authoritative identity is a cyclic member suffix. */
+ public boolean isCyclicMember() {
+ return cyclicMember;
+ }
+
/** Returns the retained resolver snapshot when one was available. */
public Optional snapshot() {
return Optional.ofNullable(snapshot);
diff --git a/src/main/java/blue/coordination/api/Operation.java b/src/main/java/blue/coordination/api/Operation.java
index 702fe4e..01b473f 100644
--- a/src/main/java/blue/coordination/api/Operation.java
+++ b/src/main/java/blue/coordination/api/Operation.java
@@ -9,12 +9,16 @@ public final class Operation {
private final String channel;
private final String requestYaml;
private final ExactValue exactRequest;
+ private final ExactValue targetDocument;
+ private final boolean requireExactDocumentVersion;
private Operation(
String operation,
String channel,
String requestYaml,
- ExactValue exactRequest) {
+ ExactValue exactRequest,
+ ExactValue targetDocument,
+ boolean requireExactDocumentVersion) {
this.operation = requireText(operation, "operation");
this.channel = requireText(channel, "channel");
if ((requestYaml == null) == (exactRequest == null)) {
@@ -25,6 +29,12 @@ private Operation(
? null
: normalizeYaml(requestYaml);
this.exactRequest = exactRequest;
+ this.targetDocument = targetDocument;
+ this.requireExactDocumentVersion = requireExactDocumentVersion;
+ if (requireExactDocumentVersion && targetDocument == null) {
+ throw new IllegalArgumentException(
+ "An exact-version requirement needs a document target");
+ }
}
/** Creates an operation whose request is resolved from source YAML. */
@@ -32,7 +42,8 @@ public static Operation yaml(
String operation,
String channel,
String requestYaml) {
- return new Operation(operation, channel, requestYaml, null);
+ return new Operation(
+ operation, channel, requestYaml, null, null, false);
}
/** Creates an operation that reuses an already retained exact request. */
@@ -44,7 +55,32 @@ public static Operation exact(
operation,
channel,
null,
- Objects.requireNonNull(request, "request"));
+ Objects.requireNonNull(request, "request"),
+ null,
+ false);
+ }
+
+ /**
+ * Returns an operation constrained to one managed document state.
+ *
+ * The target remains environment-verified routing evidence. It does not
+ * supply a recipient set: the route index still derives the one accepting
+ * document from the selected profile and exact Timeline Entry.
+ *
+ * @param document retained document state used for lineage targeting
+ * @param requireExactVersion whether processing requires this exact head
+ * @return a new immutable targeted operation
+ */
+ public Operation targeting(
+ ExactValue document,
+ boolean requireExactVersion) {
+ return new Operation(
+ operation,
+ channel,
+ requestYaml,
+ exactRequest,
+ Objects.requireNonNull(document, "document"),
+ requireExactVersion);
}
/** Returns the authored operation name used by exact route matching. */
@@ -63,6 +99,16 @@ public Optional exactRequest() {
return Optional.ofNullable(exactRequest);
}
+ /** Exact managed state used to constrain routing, when targeted. */
+ public Optional targetDocument() {
+ return Optional.ofNullable(targetDocument);
+ }
+
+ /** Whether the target must still be the document's current exact head. */
+ public boolean requireExactDocumentVersion() {
+ return requireExactDocumentVersion;
+ }
+
private static String normalizeYaml(String value) {
String checked = Objects.requireNonNull(value, "requestYaml").strip();
return checked.isEmpty() ? "{}" : checked;
diff --git a/src/main/java/blue/coordination/api/ProcessingDrainReceipt.java b/src/main/java/blue/coordination/api/ProcessingDrainReceipt.java
index 22e7fe0..8a5d18a 100644
--- a/src/main/java/blue/coordination/api/ProcessingDrainReceipt.java
+++ b/src/main/java/blue/coordination/api/ProcessingDrainReceipt.java
@@ -14,6 +14,8 @@
public final class ProcessingDrainReceipt {
private final List processedEntries;
private final Map> outcomesByEntry;
+ private final Map>
+ contractsAttemptsByEntry;
private final ExternalOrderKey processedThrough;
private final boolean quiescent;
private final boolean paused;
@@ -29,6 +31,23 @@ public ProcessingDrainReceipt(
boolean paused,
long committedProcessTransitions,
long elapsedNanos) {
+ this(processedEntries, outcomesByEntry, Map.of(), processedThrough,
+ quiescent, paused, committedProcessTransitions, elapsedNanos);
+ }
+
+ /**
+ * Creates bounded-drain evidence including advanced Contracts attempts.
+ */
+ public ProcessingDrainReceipt(
+ List processedEntries,
+ Map> outcomesByEntry,
+ Map>
+ contractsAttemptsByEntry,
+ ExternalOrderKey processedThrough,
+ boolean quiescent,
+ boolean paused,
+ long committedProcessTransitions,
+ long elapsedNanos) {
this.processedEntries = List.copyOf(Objects.requireNonNull(
processedEntries, "processedEntries"));
Map> copied =
@@ -39,6 +58,15 @@ public ProcessingDrainReceipt(
List.copyOf(Objects.requireNonNull(
outcomes, "outcomes"))));
this.outcomesByEntry = Collections.unmodifiableMap(copied);
+ Map> attempts =
+ new LinkedHashMap<>();
+ Objects.requireNonNull(
+ contractsAttemptsByEntry, "contractsAttemptsByEntry")
+ .forEach((entryBlueId, values) -> attempts.put(
+ requireText(entryBlueId, "entryBlueId"),
+ List.copyOf(Objects.requireNonNull(
+ values, "contractsAttempts"))));
+ this.contractsAttemptsByEntry = Collections.unmodifiableMap(attempts);
this.processedThrough = processedThrough;
this.quiescent = quiescent;
this.paused = paused;
@@ -88,6 +116,19 @@ public Map> outcomesByEntry() {
return outcomesByEntry;
}
+ /** Advanced exact Contracts cohort attempts for one Timeline Entry. */
+ public List contractsAttemptsFor(
+ String entryBlueId) {
+ return contractsAttemptsByEntry.getOrDefault(
+ requireText(entryBlueId, "entryBlueId"), List.of());
+ }
+
+ /** Advanced immutable Contracts attempts indexed by Timeline Entry. */
+ public Map>
+ contractsAttemptsByEntry() {
+ return contractsAttemptsByEntry;
+ }
+
/** Highest canonical external order completed by this environment. */
public Optional processedThrough() {
return Optional.ofNullable(processedThrough);
diff --git a/src/main/java/blue/coordination/internal/BlueRuntime.java b/src/main/java/blue/coordination/internal/BlueRuntime.java
index 66d4be3..6329350 100644
--- a/src/main/java/blue/coordination/internal/BlueRuntime.java
+++ b/src/main/java/blue/coordination/internal/BlueRuntime.java
@@ -12,6 +12,8 @@
import blue.language.model.Node;
import blue.language.model.NodeWireForm;
import blue.language.processor.BlueContracts;
+import blue.language.processor.ContractProcessorRegistry;
+import blue.language.processor.ContractProcessorRegistryBuilder;
import blue.language.processor.DocumentProcessingResult;
import blue.language.processor.DocumentProcessor;
import blue.language.processor.EffectiveFragmentationCatalog;
@@ -22,7 +24,10 @@
import blue.language.processor.SubscriptionDelta;
import blue.language.processor.registry.BlueRuntimeTypeRegistry;
import blue.language.processor.registry.RuntimeTypeAliases;
+import blue.language.provider.CyclicAwareNodeProvider;
+import blue.language.provider.CyclicSetProofResult;
import blue.language.provider.NodeProvider;
+import blue.language.provider.NodeProviderResult;
import blue.language.provider.SequentialNodeProvider;
import blue.language.runtime.BlueLanguage;
import blue.language.snapshot.FrozenNode;
@@ -46,6 +51,9 @@
* current generated Repository.
*/
final class BlueRuntime implements AutoCloseable {
+ static final String PROVIDER_EXACT_NODE_READS =
+ "provider.exactNodeReads";
+
private final NodeProvider nodeProvider;
private final BlueLanguage language;
private final BlueContracts contracts;
@@ -76,11 +84,14 @@ static BlueRuntime create(
EngineMetrics metrics) {
BlueRepository repository = BlueRepository.current();
List providers = new ArrayList<>();
- providers.add(Objects.requireNonNull(wholeObjects, "wholeObjects"));
- providers.add(BlueRuntimeTypeRegistry.getDefault()
- .asProcessorSnapshotProvider());
- providers.add(repository.nodeProvider());
- providers.add(new RepositoryExactNodeProvider(repository));
+ providers.add(metered(
+ Objects.requireNonNull(wholeObjects, "wholeObjects"),
+ metrics));
+ providers.add(metered(BlueRuntimeTypeRegistry.getDefault()
+ .asProcessorSnapshotProvider(), metrics));
+ providers.add(metered(repository.nodeProvider(), metrics));
+ providers.add(metered(
+ new RepositoryExactNodeProvider(repository), metrics));
NodeProvider nodeProvider = new SequentialNodeProvider(providers);
Map imports = new LinkedHashMap<>();
@@ -96,14 +107,22 @@ static BlueRuntime create(
CoordinationProcessorOptions.builder()
.language(language)
.build();
- BlueContracts contracts = CoordinationProcessors.contracts(
- language, options);
- DocumentProcessor processor = CoordinationProcessors.configure(
- DocumentProcessor.builder()
- .runtimeAccess(contracts.runtimeAccess()),
+ ContractProcessorRegistry runtimeRegistry =
+ CoordinationProcessors.configure(
+ ContractProcessorRegistryBuilder.create()
+ .registerDefaults(),
options)
- .runtimeRegistryIdentity(
- "blue.coordination/in-memory-runtime/3.0")
+ .build();
+ String runtimeRegistryIdentity =
+ runtimeRegistry.generationIdentity();
+ BlueContracts contracts = BlueContracts.builder(
+ language.processing())
+ .runtimeRegistry(runtimeRegistry)
+ .build();
+ DocumentProcessor processor = DocumentProcessor.builder()
+ .runtimeAccess(contracts.runtimeAccess())
+ .runtimeRegistry(runtimeRegistry)
+ .runtimeRegistryIdentity(runtimeRegistryIdentity)
.build();
return new BlueRuntime(
nodeProvider, language, contracts, processor, metrics);
@@ -255,6 +274,17 @@ NodeProvider nodeProvider() {
return nodeProvider;
}
+ /** Returns the exact configured processor borrowed by closure execution. */
+ DocumentProcessor documentProcessor() {
+ ensureOpen();
+ return processor;
+ }
+
+ EngineMetrics metrics() {
+ ensureOpen();
+ return metrics;
+ }
+
@Override
public void close() {
if (closed) {
@@ -283,6 +313,65 @@ private static void close(AutoCloseable resource) {
}
}
+ private static NodeProvider metered(
+ NodeProvider delegate,
+ EngineMetrics metrics) {
+ return delegate instanceof CyclicAwareNodeProvider cyclic
+ ? new MeteredCyclicAwareNodeProvider(
+ delegate, cyclic, metrics)
+ : new MeteredNodeProvider(delegate, metrics);
+ }
+
+ /** Transparent leaf meter preserving the provider graph seen by Language. */
+ private static class MeteredNodeProvider implements NodeProvider {
+ private final NodeProvider delegate;
+ private final EngineMetrics metrics;
+
+ private MeteredNodeProvider(
+ NodeProvider delegate,
+ EngineMetrics metrics) {
+ this.delegate = Objects.requireNonNull(delegate, "delegate");
+ this.metrics = Objects.requireNonNull(metrics, "metrics");
+ }
+
+ @Override
+ public List fetchByBlueId(String blueId) {
+ metrics.increment(PROVIDER_EXACT_NODE_READS);
+ return delegate.fetchByBlueId(blueId);
+ }
+
+ @Override
+ public NodeProviderResult fetchResultByBlueId(String blueId) {
+ metrics.increment(PROVIDER_EXACT_NODE_READS);
+ return delegate.fetchResultByBlueId(blueId);
+ }
+ }
+
+ /** Leaf meter retaining complete cyclic-set proof capability. */
+ private static final class MeteredCyclicAwareNodeProvider
+ extends MeteredNodeProvider implements CyclicAwareNodeProvider {
+ private final CyclicAwareNodeProvider cyclicDelegate;
+
+ private MeteredCyclicAwareNodeProvider(
+ NodeProvider delegate,
+ CyclicAwareNodeProvider cyclicDelegate,
+ EngineMetrics metrics) {
+ super(delegate, metrics);
+ this.cyclicDelegate = Objects.requireNonNull(
+ cyclicDelegate, "cyclicDelegate");
+ }
+
+ @Override
+ public boolean hasVerifiedContentForBlueId(String blueId) {
+ return cyclicDelegate.hasVerifiedContentForBlueId(blueId);
+ }
+
+ @Override
+ public CyclicSetProofResult cyclicSetProofFor(String blueId) {
+ return cyclicDelegate.cyclicSetProofFor(blueId);
+ }
+ }
+
/** Lazy exact index for inherited inline Repository contributions. */
private static final class RepositoryExactNodeProvider
implements NodeProvider {
diff --git a/src/main/java/blue/coordination/internal/BundledContracts10Release.java b/src/main/java/blue/coordination/internal/BundledContracts10Release.java
new file mode 100644
index 0000000..c9547e3
--- /dev/null
+++ b/src/main/java/blue/coordination/internal/BundledContracts10Release.java
@@ -0,0 +1,105 @@
+package blue.coordination.internal;
+
+import blue.coordination.api.Contracts10Configuration;
+import blue.coordination.api.DocumentId;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Objects;
+import java.util.Properties;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+/** Exact immutable Contracts 1.0 release manifest bundled with the SDK. */
+public final class BundledContracts10Release {
+ private static final String RESOURCE =
+ "/blue/coordination/sdk/contracts-1.0-release.properties";
+ private static final Pattern SHA_256 = Pattern.compile(
+ "^sha256:[0-9a-f]{64}$");
+ private static final Manifest MANIFEST = load();
+
+ private BundledContracts10Release() {
+ }
+
+ /** Returns the verified bundled release identities. */
+ public static Manifest manifest() {
+ return MANIFEST;
+ }
+
+ /** Creates exact low-level configuration for the supplied public Roots. */
+ public static Contracts10Configuration configuration(
+ Set publicRoots) {
+ return new Contracts10Configuration(
+ MANIFEST.blueLanguageSpecification(),
+ MANIFEST.contractsSpecification(),
+ Objects.requireNonNull(publicRoots, "publicRoots"));
+ }
+
+ private static Manifest load() {
+ Properties properties = new Properties();
+ try (InputStream input = BundledContracts10Release.class
+ .getResourceAsStream(RESOURCE)) {
+ if (input == null) {
+ throw new IllegalStateException(
+ "Missing bundled Contracts release manifest "
+ + RESOURCE);
+ }
+ properties.load(input);
+ } catch (IOException failure) {
+ throw new ExceptionInInitializerError(failure);
+ }
+ return new Manifest(
+ identity(properties, "blueLanguageSpecification"),
+ identity(properties, "contractsSpecification"),
+ identity(properties, "contractsRelease"),
+ identity(properties, "fixturePackage"),
+ identity(properties, "gasManifest"),
+ identity(properties, "cyclicFinalizer"),
+ identity(properties, "cyclicProofVerifier"));
+ }
+
+ private static String identity(Properties properties, String key) {
+ String value = properties.getProperty(key);
+ if (value == null || !SHA_256.matcher(value).matches()) {
+ throw new IllegalStateException(
+ "Bundled Contracts release has invalid " + key);
+ }
+ return value;
+ }
+
+ /** Exact identities bound by the locally bundled Contracts 1.0 release. */
+ public record Manifest(
+ String blueLanguageSpecification,
+ String contractsSpecification,
+ String contractsRelease,
+ String fixturePackage,
+ String gasManifest,
+ String cyclicFinalizer,
+ String cyclicProofVerifier) {
+ /** Revalidates values even when constructed by reflective tooling. */
+ public Manifest {
+ blueLanguageSpecification = requireIdentity(
+ blueLanguageSpecification, "blueLanguageSpecification");
+ contractsSpecification = requireIdentity(
+ contractsSpecification, "contractsSpecification");
+ contractsRelease = requireIdentity(
+ contractsRelease, "contractsRelease");
+ fixturePackage = requireIdentity(
+ fixturePackage, "fixturePackage");
+ gasManifest = requireIdentity(gasManifest, "gasManifest");
+ cyclicFinalizer = requireIdentity(
+ cyclicFinalizer, "cyclicFinalizer");
+ cyclicProofVerifier = requireIdentity(
+ cyclicProofVerifier, "cyclicProofVerifier");
+ }
+
+ private static String requireIdentity(String value, String label) {
+ String checked = Objects.requireNonNull(value, label);
+ if (!SHA_256.matcher(checked).matches()) {
+ throw new IllegalArgumentException(
+ label + " must be a lowercase sha256 identity");
+ }
+ return checked;
+ }
+ }
+}
diff --git a/src/main/java/blue/coordination/internal/ClosureGraphGenerationInventory.java b/src/main/java/blue/coordination/internal/ClosureGraphGenerationInventory.java
new file mode 100644
index 0000000..7ef3e83
--- /dev/null
+++ b/src/main/java/blue/coordination/internal/ClosureGraphGenerationInventory.java
@@ -0,0 +1,277 @@
+package blue.coordination.internal;
+
+import blue.coordination.api.DocumentId;
+import blue.language.processor.closure.ClosureCommitCompanion;
+import blue.language.processor.closure.ClosureProcessResult;
+import blue.language.processor.closure.ResultingDocument;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.TreeMap;
+
+/** Durable cohort-local Contracts graph generations, keyed by document. */
+final class ClosureGraphGenerationInventory {
+ private final Map generations;
+
+ private ClosureGraphGenerationInventory(Map values) {
+ TreeMap canonical = new TreeMap<>(
+ EmbeddingBinding.DOCUMENT_ORDER);
+ Objects.requireNonNull(values, "values").forEach((documentId,
+ generation) -> canonical.put(
+ Objects.requireNonNull(documentId, "documentId"),
+ MultiDocumentPublicationTransaction.requireSafeInteger(
+ Objects.requireNonNull(
+ generation, "graphGeneration"),
+ "graphGeneration")));
+ this.generations = Collections.unmodifiableMap(
+ new LinkedHashMap<>(canonical));
+ }
+
+ static ClosureGraphGenerationInventory empty() {
+ return new ClosureGraphGenerationInventory(Map.of());
+ }
+
+ /** Retains known lineages and initializes newly admitted documents at zero. */
+ ClosureGraphGenerationInventory retainingDocuments(
+ Collection documentIds) {
+ TreeMap retained = new TreeMap<>(
+ EmbeddingBinding.DOCUMENT_ORDER);
+ for (DocumentId documentId : Objects.requireNonNull(
+ documentIds, "documentIds")) {
+ DocumentId exact = Objects.requireNonNull(
+ documentId, "documentId");
+ retained.put(exact, generations.getOrDefault(exact, 0L));
+ }
+ return new ClosureGraphGenerationInventory(retained);
+ }
+
+ long require(DocumentId documentId) {
+ DocumentId selected = Objects.requireNonNull(
+ documentId, "documentId");
+ Long generation = generations.get(selected);
+ if (generation == null) {
+ throw new IllegalArgumentException(
+ "No durable graph generation for " + selected);
+ }
+ return generation.longValue();
+ }
+
+ /** Requires one connected cohort to share exactly one durable generation. */
+ long requireCohortGeneration(Collection members) {
+ ArrayList exact = new ArrayList<>(Objects.requireNonNull(
+ members, "members"));
+ if (exact.isEmpty()) {
+ throw new IllegalArgumentException(
+ "A graph-generation cohort must not be empty");
+ }
+ long generation = require(exact.get(0));
+ for (int index = 1; index < exact.size(); index++) {
+ DocumentId member = exact.get(index);
+ long candidate = require(member);
+ if (candidate != generation) {
+ throw new IllegalStateException(
+ "Connected cohort has divergent durable graph "
+ + "generations: " + exact);
+ }
+ }
+ return generation;
+ }
+
+ /**
+ * Applies one already-validated committing closure result. Every resulting
+ * member receives the result generation, including members separated by a
+ * split; disconnected lineages remain untouched.
+ */
+ ClosureGraphGenerationInventory apply(ClosureProcessResult result) {
+ ClosureProcessResult selected = Objects.requireNonNull(
+ result, "result");
+ if (!selected.commits()
+ || selected.platformCommitCompanion() == null) {
+ throw new IllegalArgumentException(
+ "Only a committing closure result can advance graph state");
+ }
+ ClosureCommitCompanion companion =
+ selected.platformCommitCompanion();
+ Set expectedMembers = new LinkedHashSet<>();
+ companion.expectedInputDocuments().forEach(document -> {
+ DocumentId member = DocumentId.of(document.documentId().value());
+ if (!expectedMembers.add(member)) {
+ throw new IllegalArgumentException(
+ "Duplicate expected graph-generation member "
+ + member);
+ }
+ long actual = require(member);
+ if (actual != companion.expectedInputGraphGeneration()) {
+ throw new MultiDocumentPublicationTransaction
+ .AtomicPublicationCasException(
+ "Stale graph generation for " + member
+ + ": expected "
+ + companion
+ .expectedInputGraphGeneration()
+ + " but found " + actual);
+ }
+ });
+ Set resultingMembers = new LinkedHashSet<>();
+ for (ResultingDocument document : selected.resultingDocuments()) {
+ resultingMembers.add(DocumentId.of(
+ document.documentId().value()));
+ }
+ if (!resultingMembers.equals(expectedMembers)) {
+ throw new IllegalArgumentException(
+ "Closure result graph members differ from its input "
+ + "generation cohort");
+ }
+
+ TreeMap replacement = new TreeMap<>(
+ EmbeddingBinding.DOCUMENT_ORDER);
+ replacement.putAll(generations);
+ for (DocumentId member : resultingMembers) {
+ replacement.put(member, selected.graphGeneration());
+ }
+ return new ClosureGraphGenerationInventory(replacement);
+ }
+
+ /**
+ * Installs graph generations for one verified all-new closure admission.
+ * Existing lineages are rejected rather than silently treated as updates.
+ */
+ ClosureGraphGenerationInventory admit(
+ ClosureProcessResult result,
+ Collection expectedAbsent) {
+ ClosureProcessResult selected = Objects.requireNonNull(
+ result, "result");
+ if (!selected.commits()
+ || selected.platformCommitCompanion() == null) {
+ throw new IllegalArgumentException(
+ "Only a committing closure admission can install graph state");
+ }
+ LinkedHashSet admitted = new LinkedHashSet<>(
+ Objects.requireNonNull(expectedAbsent, "expectedAbsent"));
+ if (admitted.isEmpty()) {
+ throw new IllegalArgumentException(
+ "Closure admission must contain a new document");
+ }
+ for (DocumentId documentId : admitted) {
+ if (generations.containsKey(documentId)) {
+ throw new MultiDocumentPublicationTransaction
+ .AtomicPublicationCasException(
+ "Closure admission graph lineage already exists "
+ + documentId);
+ }
+ }
+ LinkedHashSet companionMembers = new LinkedHashSet<>();
+ selected.platformCommitCompanion().expectedInputDocuments()
+ .forEach(document -> companionMembers.add(DocumentId.of(
+ document.documentId().value())));
+ LinkedHashSet resultingMembers = new LinkedHashSet<>();
+ selected.resultingDocuments().forEach(document ->
+ resultingMembers.add(DocumentId.of(
+ document.documentId().value())));
+ if (!admitted.equals(companionMembers)
+ || !admitted.equals(resultingMembers)) {
+ throw new IllegalArgumentException(
+ "Closure admission graph members are incomplete");
+ }
+ TreeMap replacement = new TreeMap<>(
+ EmbeddingBinding.DOCUMENT_ORDER);
+ replacement.putAll(generations);
+ admitted.forEach(documentId -> replacement.put(
+ documentId, selected.graphGeneration()));
+ return new ClosureGraphGenerationInventory(replacement);
+ }
+
+ /**
+ * Applies one verified PROCESS result that atomically expands an existing
+ * cohort with lineages which were absent at capture time.
+ *
+ * Existing members retain the ordinary exact graph-generation CAS.
+ * New members have no durable predecessor generation; they are installed
+ * only when the same result and transaction also prove their absence.
+ */
+ ClosureGraphGenerationInventory applyExpansion(
+ ClosureProcessResult result,
+ Collection expectedPresent,
+ Collection expectedAbsent) {
+ ClosureProcessResult selected = Objects.requireNonNull(
+ result, "result");
+ if (!selected.commits()
+ || selected.platformCommitCompanion() == null) {
+ throw new IllegalArgumentException(
+ "Only a committing closure result can expand graph state");
+ }
+ LinkedHashSet present = new LinkedHashSet<>(
+ Objects.requireNonNull(expectedPresent, "expectedPresent"));
+ LinkedHashSet absent = new LinkedHashSet<>(
+ Objects.requireNonNull(expectedAbsent, "expectedAbsent"));
+ if (present.isEmpty() || absent.isEmpty()) {
+ throw new IllegalArgumentException(
+ "A closure expansion requires present and absent members");
+ }
+ LinkedHashSet overlap = new LinkedHashSet<>(present);
+ overlap.retainAll(absent);
+ if (!overlap.isEmpty()) {
+ throw new IllegalArgumentException(
+ "Closure expansion fences overlap " + overlap);
+ }
+ long expectedGeneration = selected.platformCommitCompanion()
+ .expectedInputGraphGeneration();
+ for (DocumentId documentId : present) {
+ long actual = require(documentId);
+ if (actual != expectedGeneration) {
+ throw new MultiDocumentPublicationTransaction
+ .AtomicPublicationCasException(
+ "Stale graph generation for " + documentId
+ + ": expected "
+ + expectedGeneration
+ + " but found " + actual);
+ }
+ }
+ for (DocumentId documentId : absent) {
+ if (generations.containsKey(documentId)) {
+ throw new MultiDocumentPublicationTransaction
+ .AtomicPublicationCasException(
+ "Closure expansion graph lineage already exists "
+ + documentId);
+ }
+ }
+
+ LinkedHashSet expectedMembers = new LinkedHashSet<>(
+ present);
+ expectedMembers.addAll(absent);
+ LinkedHashSet companionMembers = new LinkedHashSet<>();
+ selected.platformCommitCompanion().expectedInputDocuments()
+ .forEach(document -> companionMembers.add(DocumentId.of(
+ document.documentId().value())));
+ LinkedHashSet resultingMembers = new LinkedHashSet<>();
+ selected.resultingDocuments().forEach(document ->
+ resultingMembers.add(DocumentId.of(
+ document.documentId().value())));
+ if (!expectedMembers.equals(companionMembers)
+ || !expectedMembers.equals(resultingMembers)) {
+ throw new IllegalArgumentException(
+ "Closure expansion graph members are incomplete");
+ }
+
+ TreeMap replacement = new TreeMap<>(
+ EmbeddingBinding.DOCUMENT_ORDER);
+ replacement.putAll(generations);
+ expectedMembers.forEach(documentId -> replacement.put(
+ documentId, selected.graphGeneration()));
+ return new ClosureGraphGenerationInventory(replacement);
+ }
+
+ Map generations() {
+ return generations;
+ }
+
+ List documents() {
+ return List.copyOf(generations.keySet());
+ }
+}
diff --git a/src/main/java/blue/coordination/internal/ClosureSubscriptionInventory.java b/src/main/java/blue/coordination/internal/ClosureSubscriptionInventory.java
new file mode 100644
index 0000000..36f77a9
--- /dev/null
+++ b/src/main/java/blue/coordination/internal/ClosureSubscriptionInventory.java
@@ -0,0 +1,241 @@
+package blue.coordination.internal;
+
+import blue.coordination.api.DocumentId;
+import blue.language.processor.closure.ClosureCommitCompanion;
+import blue.language.processor.closure.ClosureProcessResult;
+import blue.language.processor.closure.ResultingDocument;
+import blue.language.processor.closure.SubscriptionDelta;
+import blue.language.processor.closure.SubscriptionState;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/** Complete durable Contracts subscription state, independent of legacy rows. */
+final class ClosureSubscriptionInventory {
+ private static final Comparator SLOT_ORDER = Comparator
+ .comparing(Slot::documentId, EmbeddingBinding.TEXT_ORDER)
+ .thenComparing(Slot::rawChannelKey, EmbeddingBinding.TEXT_ORDER);
+
+ private final Map bySlot;
+ private final List states;
+
+ private ClosureSubscriptionInventory(
+ Map rows) {
+ List> canonical =
+ new ArrayList<>(rows.entrySet());
+ canonical.sort(Map.Entry.comparingByKey(SLOT_ORDER));
+ LinkedHashMap ordered = new LinkedHashMap<>();
+ LinkedHashSet identities = new LinkedHashSet<>();
+ for (Map.Entry entry : canonical) {
+ SubscriptionState state = Objects.requireNonNull(
+ entry.getValue(), "subscription state");
+ Slot actual = Slot.from(state);
+ if (!entry.getKey().equals(actual)) {
+ throw new IllegalArgumentException(
+ "Subscription state is stored under the wrong slot");
+ }
+ if (!identities.add(state.subscriptionIdentity())) {
+ throw new IllegalArgumentException(
+ "Duplicate closure subscription identity "
+ + state.subscriptionIdentity());
+ }
+ ordered.put(entry.getKey(), state);
+ }
+ this.bySlot = Map.copyOf(ordered);
+ this.states = List.copyOf(ordered.values());
+ }
+
+ static ClosureSubscriptionInventory empty() {
+ return new ClosureSubscriptionInventory(Map.of());
+ }
+
+ static ClosureSubscriptionInventory of(
+ Collection states) {
+ LinkedHashMap rows = new LinkedHashMap<>();
+ for (SubscriptionState state : Objects.requireNonNull(
+ states, "states")) {
+ SubscriptionState exact = Objects.requireNonNull(
+ state, "subscription state");
+ Slot slot = Slot.from(exact);
+ if (rows.putIfAbsent(slot, exact) != null) {
+ throw new IllegalArgumentException(
+ "Duplicate closure subscription slot " + slot);
+ }
+ }
+ return new ClosureSubscriptionInventory(rows);
+ }
+
+ /** Applies one already verified successful result to the durable inventory. */
+ ClosureSubscriptionInventory apply(ClosureProcessResult result) {
+ ClosureProcessResult verified = Objects.requireNonNull(result, "result");
+ if (!verified.commits()) {
+ throw new IllegalArgumentException(
+ "Non-success closure result cannot change subscriptions");
+ }
+ Map expectedHeads = expectedInputHeads(
+ verified.platformCommitCompanion());
+ Map resultingDocuments =
+ resultingDocuments(verified.resultingDocuments());
+ LinkedHashMap next =
+ new LinkedHashMap<>(bySlot);
+ for (SubscriptionDelta delta : verified.subscriptionDeltas()) {
+ SubscriptionState before = delta.beforeSubscription();
+ SubscriptionState after = delta.afterSubscription();
+ SubscriptionState representative = after != null ? after : before;
+ Slot slot = Slot.from(Objects.requireNonNull(
+ representative, "subscription delta side"));
+ if (before != null) {
+ requireExpectedInputState(before, expectedHeads);
+ }
+ SubscriptionState current = next.get(slot);
+ if (delta.operation() == SubscriptionDelta.Operation.ADD) {
+ if (current != null) {
+ throw new IllegalStateException(
+ "Closure subscription ADD targets a present slot "
+ + slot);
+ }
+ next.put(slot, after);
+ } else {
+ if (current == null) {
+ // Migration bootstrap is safe because the verified before
+ // state is bound to the exact CAS-fenced input head.
+ current = before;
+ }
+ if (!current.subscriptionIdentity().equals(
+ before.subscriptionIdentity())) {
+ throw new IllegalStateException(
+ "Closure subscription before-state CAS mismatch at "
+ + slot);
+ }
+ if (delta.operation() == SubscriptionDelta.Operation.REMOVE) {
+ next.remove(slot);
+ } else {
+ next.put(slot, after);
+ }
+ }
+ }
+ ClosureSubscriptionInventory applied =
+ new ClosureSubscriptionInventory(next);
+ applied.requireResultingStates(
+ resultingDocuments, verified.graphGeneration());
+ return applied;
+ }
+
+ ClosureSubscriptionInventory retainingDocuments(
+ Collection documents) {
+ Set retained = new LinkedHashSet<>();
+ for (DocumentId document : Objects.requireNonNull(
+ documents, "documents")) {
+ retained.add(Objects.requireNonNull(
+ document, "document").value());
+ }
+ LinkedHashMap selected = new LinkedHashMap<>();
+ bySlot.forEach((slot, state) -> {
+ if (retained.contains(slot.documentId())) {
+ selected.put(slot, state);
+ }
+ });
+ return new ClosureSubscriptionInventory(selected);
+ }
+
+ List states() {
+ return states;
+ }
+
+ List statesFor(DocumentId documentId) {
+ String selected = Objects.requireNonNull(
+ documentId, "documentId").value();
+ return states.stream()
+ .filter(state -> state.channelOccurrence().managedDocumentId()
+ .value().equals(selected))
+ .toList();
+ }
+
+ private void requireResultingStates(
+ Map resultingDocuments,
+ long graphGeneration) {
+ for (SubscriptionState state : states) {
+ String documentId = state.channelOccurrence()
+ .managedDocumentId().value();
+ ResultingDocument resulting = resultingDocuments.get(documentId);
+ if (resulting == null) {
+ continue;
+ }
+ if (!state.documentBlueId().equals(resulting.afterBlueId())
+ || state.graphGeneration() != graphGeneration
+ || state.componentGeneration()
+ != resulting.componentGeneration()) {
+ throw new IllegalStateException(
+ "Closure subscription state is stale after publication "
+ + documentId + "/"
+ + state.channelOccurrence().rawChannelKey());
+ }
+ }
+ }
+
+ private static void requireExpectedInputState(
+ SubscriptionState before,
+ Map expectedHeads) {
+ String documentId = before.channelOccurrence()
+ .managedDocumentId().value();
+ String expected = expectedHeads.get(documentId);
+ if (expected == null || !expected.equals(before.documentBlueId())) {
+ throw new IllegalStateException(
+ "Closure subscription before state is not bound to the "
+ + "input head for " + documentId);
+ }
+ }
+
+ private static Map expectedInputHeads(
+ ClosureCommitCompanion companion) {
+ LinkedHashMap result = new LinkedHashMap<>();
+ for (ClosureCommitCompanion.InputDocument document
+ : Objects.requireNonNull(
+ companion, "platformCommitCompanion")
+ .expectedInputDocuments()) {
+ result.put(document.documentId().value(), document.blueId());
+ }
+ return result;
+ }
+
+ private static Map resultingDocuments(
+ Collection documents) {
+ LinkedHashMap result = new LinkedHashMap<>();
+ for (ResultingDocument document : documents) {
+ if (result.put(document.documentId().value(), document) != null) {
+ throw new IllegalArgumentException(
+ "Duplicate resulting document "
+ + document.documentId().value());
+ }
+ }
+ return result;
+ }
+
+ private record Slot(String documentId, String rawChannelKey) {
+ private Slot {
+ documentId = requireText(documentId, "documentId");
+ rawChannelKey = requireText(rawChannelKey, "rawChannelKey");
+ }
+
+ static Slot from(SubscriptionState state) {
+ return new Slot(
+ state.channelOccurrence().managedDocumentId().value(),
+ state.channelOccurrence().rawChannelKey());
+ }
+ }
+
+ private static String requireText(String value, String label) {
+ String checked = Objects.requireNonNull(value, label);
+ if (checked.isBlank()) {
+ throw new IllegalArgumentException(label + " must not be blank");
+ }
+ return checked;
+ }
+}
diff --git a/src/main/java/blue/coordination/internal/Contracts10AuthoredClosureCompiler.java b/src/main/java/blue/coordination/internal/Contracts10AuthoredClosureCompiler.java
new file mode 100644
index 0000000..1559a76
--- /dev/null
+++ b/src/main/java/blue/coordination/internal/Contracts10AuthoredClosureCompiler.java
@@ -0,0 +1,1067 @@
+package blue.coordination.internal;
+
+import blue.coordination.api.CoordinationEngine;
+import blue.coordination.api.DocumentId;
+import blue.coordination.api.ExactValue;
+import blue.language.identity.BlueIds;
+import blue.language.identity.CircularSetIdentityCalculator;
+import blue.language.identity.DirectBlueIdCalculator;
+import blue.language.model.Node;
+import blue.language.model.NodePathEditor;
+import blue.language.model.wire.JsonPointer;
+import blue.language.processor.EffectiveFragmentationCatalog;
+import blue.language.processor.EmbeddedScopePlanView;
+import blue.language.processor.ExternalOrderKey;
+import blue.language.processor.closure.AdmissionKind;
+import blue.language.processor.closure.AffectedClosureSnapshot;
+import blue.language.processor.closure.ClosureEnvironment;
+import blue.language.processor.closure.ClosureEvidenceFactory;
+import blue.language.processor.closure.ClosureInvocationInput;
+import blue.language.processor.closure.ComponentFinalizationInput;
+import blue.language.processor.closure.ComponentFinalizationKernel;
+import blue.language.processor.closure.ComponentFinalizationResult;
+import blue.language.processor.closure.ComponentKind;
+import blue.language.processor.closure.ComponentSnapshot;
+import blue.language.processor.closure.ExecutionPolicy;
+import blue.language.processor.closure.FinalizedComponentEvidence;
+import blue.language.processor.closure.FinalizedDocumentEvidence;
+import blue.language.processor.closure.ManagedDocumentGraph;
+import blue.language.processor.closure.ManagedDocumentSnapshot;
+import blue.language.processor.closure.ManagedOccurrenceBinding;
+import blue.language.processor.closure.ScopeAddress;
+import blue.language.processor.util.PointerUtils;
+import blue.language.provider.CyclicAwareNodeProvider;
+import blue.language.provider.CyclicSetProof;
+import blue.language.provider.CyclicSetProofResult;
+import blue.language.provider.NodeProvider;
+import blue.language.provider.VerifyingNodeProvider;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Compiles authored Contracts 1.0 documents into verified closure admission
+ * evidence.
+ *
+ * This is the production counterpart of the old scenario fixture. Callers
+ * supply stable managed lineages and occurrence declarations, never a graph,
+ * component partition, BlueId, cyclic proof, or closure snapshot. The
+ * compiler resolves effective Process Embedded declarations with the shipped
+ * runtime, derives the graph solely from the declared occurrences, and asks
+ * Language's finalization and proof-verification kernels to establish the
+ * complete closure.
+ *
+ * The class is public only as an internal cross-package bridge. A public
+ * SDK translates its immutable values into these DTOs without making this
+ * compiler or any low-level closure type part of the ordinary SDK surface.
+ */
+public final class Contracts10AuthoredClosureCompiler {
+ private static final String ADMISSION_POLICY =
+ "contracts-top-level-admission-v1";
+ private static final String DEFAULT_ADMISSION_LABEL =
+ "contracts10-sdk-authored-closure";
+ private static final long INITIAL_GENERATION = 1L;
+
+ private final DefaultCoordinationEngine engine;
+
+ public Contracts10AuthoredClosureCompiler(
+ DefaultCoordinationEngine engine) {
+ this.engine = Objects.requireNonNull(engine, "engine");
+ }
+
+ /** Compiles one complete immutable authored request. */
+ public CompiledClosure compile(CompilationRequest request) {
+ CompilationRequest input = Objects.requireNonNull(request, "request");
+ RequestIndex index = RequestIndex.from(input);
+
+ EngineMetrics verificationMetrics = new EngineMetrics();
+ WholeObjectStore verificationObjects =
+ new WholeObjectStore(verificationMetrics);
+ try (BlueRuntime verificationRuntime = BlueRuntime.create(
+ verificationObjects, verificationMetrics)) {
+ LinkedHashMap resolved = resolveDocuments(
+ input.documents(), verificationRuntime,
+ verificationObjects);
+ validateDeclarationsAndCoverage(
+ index, resolved, verificationRuntime,
+ verificationObjects);
+ LinkedHashMap authored =
+ installAndVerifyPreliminaryReferences(index, resolved);
+ return finalizeClosure(input, index, authored);
+ }
+ }
+
+ private static LinkedHashMap resolveDocuments(
+ List documents,
+ BlueRuntime runtime,
+ WholeObjectStore objects) {
+ LinkedHashMap result = new LinkedHashMap<>();
+ for (AuthoredDocument document : documents) {
+ ExactValue exact = runtime.exactSource(
+ document.authoredYaml(),
+ objects,
+ "contracts10-authored-compiler-source");
+ Node body = exact.copyNode();
+ requireOrWriteDocumentId(document.documentId(), body);
+ result.put(document.documentId(), body);
+ }
+ return result;
+ }
+
+ private static void validateDeclarationsAndCoverage(
+ RequestIndex index,
+ Map resolved,
+ BlueRuntime runtime,
+ WholeObjectStore objects) {
+ Map> bySource =
+ occurrencesBySource(index.occurrences());
+ for (Map.Entry entry : resolved.entrySet()) {
+ DocumentId source = entry.getKey();
+ Node declarationOnly = entry.getValue().clone();
+ List declared = bySource.getOrDefault(
+ source, List.of());
+ ArrayList paths = new ArrayList<>(declared.stream()
+ .map(ResolvedOccurrence::path)
+ .toList());
+ paths.sort(Contracts10AuthoredClosureCompiler
+ ::compareRemovalPaths);
+ for (String path : paths) {
+ removeAt(declarationOnly, path);
+ }
+
+ ExactValue declarationExact = objects.put(
+ declarationOnly,
+ "contracts10-authored-compiler-declarations");
+ EffectiveFragmentationCatalog catalog =
+ runtime.effectiveFragmentationCatalog(
+ declarationExact.blueId());
+ for (ResolvedOccurrence occurrence : declared) {
+ requireOneEffectiveDeclaration(catalog, occurrence);
+ }
+ List unbound = catalog.scopePlansByScope().values()
+ .stream()
+ .flatMap(plan -> plan.concreteChildPaths().stream())
+ .distinct()
+ .sorted()
+ .toList();
+ if (!unbound.isEmpty()) {
+ throw new IllegalArgumentException(
+ "Authored document " + source
+ + " contains Process Embedded occurrences "
+ + "without managed bindings: " + unbound);
+ }
+ }
+ }
+
+ private static void requireOneEffectiveDeclaration(
+ EffectiveFragmentationCatalog catalog,
+ ResolvedOccurrence occurrence) {
+ ArrayList matches = new ArrayList<>();
+ for (EmbeddedScopePlanView plan
+ : catalog.scopePlansByScope().values()) {
+ for (String declaration : plan.explicitDeclarationPaths()) {
+ String absolute = PointerUtils.resolvePointer(
+ plan.scopePath(), declaration);
+ if (absolute.equals(occurrence.path())) {
+ matches.add("path " + absolute);
+ }
+ }
+ for (String declaration : plan.collectionDeclarationPaths()) {
+ String absolute = PointerUtils.resolvePointer(
+ plan.scopePath(), declaration);
+ if (isDirectCollectionMember(
+ absolute, occurrence.path())) {
+ matches.add("collectionPath " + absolute);
+ }
+ }
+ }
+ if (matches.isEmpty()) {
+ throw new IllegalArgumentException(
+ "Managed occurrence " + occurrence.sourceDocumentId()
+ + occurrence.path()
+ + " is not declared by the effective Process "
+ + "Embedded paths or collectionPaths catalog");
+ }
+ if (matches.size() != 1) {
+ throw new IllegalArgumentException(
+ "Managed occurrence " + occurrence.sourceDocumentId()
+ + occurrence.path()
+ + " is ambiguously declared by " + matches);
+ }
+ }
+
+ private static LinkedHashMap
+ installAndVerifyPreliminaryReferences(
+ RequestIndex index,
+ Map resolved) {
+ LinkedHashMap referenceBodies = cloneBodies(
+ resolved);
+ for (ResolvedOccurrence occurrence : index.occurrences()) {
+ NodePathEditor.put(
+ referenceBodies.get(occurrence.sourceDocumentId()),
+ occurrence.path(),
+ preliminaryReference(occurrence.targetDocumentId()));
+ }
+
+ LinkedHashMap authored = cloneBodies(resolved);
+ for (ResolvedOccurrence occurrence : index.occurrences()) {
+ Node current = NodePathEditor.getOrNull(
+ resolved.get(occurrence.sourceDocumentId()),
+ occurrence.path());
+ Node replacement;
+ if (current == null) {
+ replacement = preliminaryReference(
+ occurrence.targetDocumentId());
+ } else if (current.isReferenceOnly()) {
+ requireExpectedPreliminaryIdentity(current, occurrence);
+ replacement = preliminaryReference(
+ occurrence.targetDocumentId());
+ } else {
+ replacement = expectedMaterializedTarget(
+ referenceBodies, occurrence.targetDocumentId());
+ requireExpectedMaterializedTarget(
+ current, replacement, occurrence);
+ }
+ NodePathEditor.put(
+ authored.get(occurrence.sourceDocumentId()),
+ occurrence.path(), replacement);
+ }
+ return authored;
+ }
+
+ private CompiledClosure finalizeClosure(
+ CompilationRequest request,
+ RequestIndex index,
+ LinkedHashMap authored) {
+ ContractsClosureAdmissionAdapter adapter =
+ engine.contractsClosureAdmissionAdapter();
+ ClosureEnvironment environment = adapter.environment();
+ ExecutionPolicy policy = adapter.executionPolicy();
+
+ List closureIds =
+ request.documents().stream()
+ .map(AuthoredDocument::documentId)
+ .map(Contracts10AuthoredClosureCompiler::closureId)
+ .toList();
+ List bindings = bindings(
+ index.occurrences(), authored, environment);
+ ManagedDocumentGraph graph = ManagedDocumentGraph.fromBindings(
+ closureIds, bindings);
+ ComponentFinalizationResult finalization =
+ new ComponentFinalizationKernel().finalizeComponents(
+ new ComponentFinalizationInput(
+ graph,
+ generations(closureIds),
+ closureBodies(authored),
+ bindings));
+
+ List canonicalBindings =
+ verifiedCanonicalBindings(finalization);
+ LinkedHashMap
+ representedBodies = representedBodies(finalization);
+ Map independentlyVerifiedMasters =
+ verifyLanguageEvidence(finalization, representedBodies);
+ List snapshots = snapshots(
+ finalization,
+ representedBodies,
+ request.publicRootDocumentIds());
+ List components = finalization.components()
+ .stream()
+ .map(FinalizedComponentEvidence::component)
+ .toList();
+ List roots =
+ request.publicRootDocumentIds().stream()
+ .sorted()
+ .map(Contracts10AuthoredClosureCompiler::closureId)
+ .toList();
+ AffectedClosureSnapshot snapshot = ClosureEvidenceFactory
+ .affectedClosure(
+ INITIAL_GENERATION,
+ snapshots,
+ canonicalBindings,
+ components,
+ roots);
+ ClosureInvocationInput invocation = ClosureEvidenceFactory
+ .admitClosure(
+ snapshot,
+ ClosureEvidenceFactory.admissionCause(
+ AdmissionKind.TOP_LEVEL_ADMISSION,
+ request.admissionLabel(),
+ null,
+ null,
+ ADMISSION_POLICY),
+ null,
+ policy,
+ environment);
+ return new CompiledClosure(
+ invocation,
+ request.activationInputs(),
+ authored,
+ representedBodies,
+ finalization,
+ canonicalBindings,
+ independentlyVerifiedMasters);
+ }
+
+ private static List bindings(
+ List occurrences,
+ Map authored,
+ ClosureEnvironment environment) {
+ ArrayList result = new ArrayList<>();
+ for (ResolvedOccurrence occurrence : occurrences) {
+ Node exactReference = NodePathEditor.getOrNull(
+ authored.get(occurrence.sourceDocumentId()),
+ occurrence.path());
+ String expected = preliminaryBlueId(
+ occurrence.targetDocumentId());
+ if (exactReference == null
+ || !expected.equals(exactReference.getBlueId())) {
+ throw new IllegalStateException(
+ "Managed occurrence no longer carries the verified "
+ + "target identity at "
+ + occurrence.sourceDocumentId()
+ + occurrence.path());
+ }
+ ManagedOccurrenceBinding derived =
+ ManagedOccurrenceBinding.derived(
+ environment.managedBindingPolicyIdentity(),
+ closureId(occurrence.sourceDocumentId()),
+ ScopeAddress.embedded(
+ occurrence.path(), INITIAL_GENERATION),
+ closureId(occurrence.targetDocumentId()),
+ expected,
+ true,
+ null);
+ result.add(ManagedOccurrenceBinding.verified(
+ derived.occurrenceIdentity(),
+ derived.bindingIdentity(),
+ derived.bindingPolicyIdentity(),
+ derived.sourceDocumentId(),
+ derived.sourceAddress(),
+ derived.targetDocumentId(),
+ derived.expectedTargetBlueId(),
+ derived.active(),
+ derived.pendingHistoricalEpoch()));
+ }
+ return List.copyOf(result);
+ }
+
+ private static List verifiedCanonicalBindings(
+ ComponentFinalizationResult finalization) {
+ List rows =
+ finalization.finalizedGraph().bindings();
+ ArrayList sorted = new ArrayList<>(rows);
+ Collections.sort(sorted);
+ if (!bindingIdentitySequence(rows).equals(
+ bindingIdentitySequence(sorted))) {
+ throw new IllegalStateException(
+ "Finalized occurrence rows are not canonical");
+ }
+ for (ManagedOccurrenceBinding row : rows) {
+ ManagedOccurrenceBinding.verified(
+ row.occurrenceIdentity(),
+ row.bindingIdentity(),
+ row.bindingPolicyIdentity(),
+ row.sourceDocumentId(),
+ row.sourceAddress(),
+ row.targetDocumentId(),
+ row.expectedTargetBlueId(),
+ row.active(),
+ row.pendingHistoricalEpoch());
+ }
+ return List.copyOf(rows);
+ }
+
+ private static LinkedHashMap representedBodies(ComponentFinalizationResult finalization) {
+ LinkedHashMap
+ result = new LinkedHashMap<>();
+ finalization.documents().forEach((documentId, evidence) ->
+ result.put(documentId, evidence.document()));
+ return result;
+ }
+
+ private static Map verifyLanguageEvidence(
+ ComponentFinalizationResult finalization,
+ Map bodies) {
+ CompilerProofProvider evidence = new CompilerProofProvider();
+ for (FinalizedDocumentEvidence document
+ : finalization.documents().values()) {
+ evidence.addDocument(
+ document.blueId(), bodies.get(document.documentId()));
+ }
+ LinkedHashMap verifiedMasters =
+ new LinkedHashMap<>();
+ for (FinalizedComponentEvidence finalizedComponent
+ : finalization.components()) {
+ ComponentSnapshot component = finalizedComponent.component();
+ if (component.kind() != ComponentKind.CYCLIC) {
+ continue;
+ }
+ CyclicSetProof proof = component.completeCyclicProof();
+ for (String memberBlueId : component.orderedMemberBlueIds()) {
+ evidence.addProof(memberBlueId, proof);
+ }
+ List independentlyCalculated =
+ CircularSetIdentityCalculator
+ .calculateCircularSetBlueIds(
+ proof.declaredPlaceholderSet());
+ String verifiedMaster = BlueIds.cyclicSetMasterBlueId(
+ independentlyCalculated.get(0));
+ if (!verifiedMaster.equals(component.masterBlueId())
+ || !new HashSet<>(independentlyCalculated).equals(
+ new HashSet<>(
+ component.orderedMemberBlueIds()))) {
+ throw new IllegalArgumentException(
+ "Complete Language proof does not independently "
+ + "verify the finalized cyclic component");
+ }
+ verifiedMasters.put(
+ component.componentIdentity(), verifiedMaster);
+ }
+ VerifyingNodeProvider verifier = new VerifyingNodeProvider(evidence);
+ for (FinalizedDocumentEvidence document
+ : finalization.documents().values()) {
+ List verified = verifier.fetchByBlueId(document.blueId());
+ if (verified == null || verified.size() != 1) {
+ throw new IllegalArgumentException(
+ "Language proof verifier did not return one exact "
+ + "document for " + document.documentId());
+ }
+ }
+ return Collections.unmodifiableMap(verifiedMasters);
+ }
+
+ private static List snapshots(
+ ComponentFinalizationResult finalization,
+ Map bodies,
+ Set publicRoots) {
+ ArrayList result = new ArrayList<>();
+ for (FinalizedDocumentEvidence document
+ : finalization.documents().values()) {
+ result.add(new ManagedDocumentSnapshot(
+ document.documentId(),
+ document.blueId(),
+ bodies.get(document.documentId()),
+ false,
+ false,
+ publicRoots.contains(apiId(document.documentId())),
+ 0L,
+ document.componentGeneration()));
+ }
+ Collections.sort(result);
+ return List.copyOf(result);
+ }
+
+ private static LinkedHashMap generations(
+ Collection
+ documentIds) {
+ LinkedHashMap
+ result = new LinkedHashMap<>();
+ for (blue.language.processor.closure.DocumentId documentId
+ : documentIds) {
+ result.put(documentId, INITIAL_GENERATION);
+ }
+ return result;
+ }
+
+ private static LinkedHashMap closureBodies(Map authoredBodies) {
+ LinkedHashMap
+ result = new LinkedHashMap<>();
+ authoredBodies.forEach((documentId, document) ->
+ result.put(closureId(documentId), document.clone()));
+ return result;
+ }
+
+ private static Map>
+ occurrencesBySource(List occurrences) {
+ LinkedHashMap> result =
+ new LinkedHashMap<>();
+ for (ResolvedOccurrence occurrence : occurrences) {
+ result.computeIfAbsent(
+ occurrence.sourceDocumentId(),
+ ignored -> new ArrayList<>()).add(occurrence);
+ }
+ return result;
+ }
+
+ private static void requireExpectedPreliminaryIdentity(
+ Node current,
+ ResolvedOccurrence occurrence) {
+ String expected = preliminaryBlueId(
+ occurrence.targetDocumentId());
+ if (!expected.equals(current.getBlueId())) {
+ throw new IllegalArgumentException(
+ "Managed occurrence " + occurrence.sourceDocumentId()
+ + occurrence.path()
+ + " contains conflicting reference "
+ + current.getBlueId() + "; expected " + expected
+ + " for target "
+ + occurrence.targetDocumentId());
+ }
+ }
+
+ private static void requireExpectedMaterializedTarget(
+ Node current,
+ Node expected,
+ ResolvedOccurrence occurrence) {
+ if (!preliminaryBlueId(occurrence.targetDocumentId()).equals(
+ current.getBlueId())
+ || !unclaimedBlueId(current).equals(
+ unclaimedBlueId(expected))) {
+ throw new IllegalArgumentException(
+ "Managed occurrence " + occurrence.sourceDocumentId()
+ + occurrence.path()
+ + " contains materialized state for the wrong "
+ + "target; expected exact authored member "
+ + occurrence.targetDocumentId());
+ }
+ }
+
+ private static String unclaimedBlueId(Node value) {
+ Node unclaimed = Objects.requireNonNull(value, "value").clone();
+ unclaimed.blueId(null);
+ return DirectBlueIdCalculator.calculateBlueId(unclaimed);
+ }
+
+ private static Node expectedMaterializedTarget(
+ Map referenceBodies,
+ DocumentId target) {
+ Node body = referenceBodies.get(target);
+ if (body == null) {
+ throw new IllegalStateException(
+ "Missing resolved target body " + target);
+ }
+ return body.clone().blueId(preliminaryBlueId(target));
+ }
+
+ private static Node preliminaryReference(DocumentId target) {
+ return new Node().blueId(preliminaryBlueId(target));
+ }
+
+ private static String preliminaryBlueId(DocumentId target) {
+ return DirectBlueIdCalculator.calculateBlueId(
+ new Node().value("scenario-target:" + target.value()));
+ }
+
+ private static boolean isDirectCollectionMember(
+ String collectionPath,
+ String candidatePath) {
+ List collection = JsonPointer.split(collectionPath);
+ List candidate = JsonPointer.split(candidatePath);
+ return candidate.size() == collection.size() + 1
+ && candidate.subList(0, collection.size())
+ .equals(collection);
+ }
+
+ private static boolean overlaps(String left, String right) {
+ return left.equals(right)
+ || left.startsWith(right + "/")
+ || right.startsWith(left + "/");
+ }
+
+ private static int compareRemovalPaths(String left, String right) {
+ List leftSegments = JsonPointer.split(left);
+ List rightSegments = JsonPointer.split(right);
+ int depth = Integer.compare(
+ rightSegments.size(), leftSegments.size());
+ if (depth != 0) {
+ return depth;
+ }
+ int leaf = leftSegments.size() - 1;
+ if (leftSegments.subList(0, leaf).equals(
+ rightSegments.subList(0, leaf))
+ && JsonPointer.isArrayIndexSegment(leftSegments.get(leaf))
+ && JsonPointer.isArrayIndexSegment(
+ rightSegments.get(leaf))) {
+ return Integer.compare(
+ Integer.parseInt(rightSegments.get(leaf)),
+ Integer.parseInt(leftSegments.get(leaf)));
+ }
+ return right.compareTo(left);
+ }
+
+ private static void removeAt(Node root, String pointer) {
+ List segments = JsonPointer.split(pointer);
+ if (segments.isEmpty()) {
+ throw new IllegalArgumentException(
+ "A managed occurrence cannot replace the document Root");
+ }
+ Node parent = root;
+ for (int index = 0; index < segments.size() - 1; index++) {
+ parent = NodePathEditor.getOrNull(
+ parent, "/" + escapePointerToken(
+ segments.get(index)));
+ if (parent == null) {
+ return;
+ }
+ }
+ String leaf = segments.get(segments.size() - 1);
+ if ("type".equals(leaf)) {
+ parent.type((Node) null);
+ } else if ("itemType".equals(leaf)) {
+ parent.itemType((Node) null);
+ } else if ("keyType".equals(leaf)) {
+ parent.keyType((Node) null);
+ } else if ("valueType".equals(leaf)) {
+ parent.valueType((Node) null);
+ } else if ("blue".equals(leaf)) {
+ parent.blue(null);
+ } else if ("contracts".equals(leaf)) {
+ parent.contracts(null);
+ } else if (JsonPointer.isArrayIndexSegment(leaf)
+ && parent.getItems() != null) {
+ int item = Integer.parseInt(leaf);
+ if (item < parent.getItems().size()) {
+ parent.getItems().remove(item);
+ }
+ } else if (parent.getProperties() != null) {
+ parent.getProperties().remove(leaf);
+ }
+ }
+
+ private static String escapePointerToken(String token) {
+ return token.replace("~", "~0").replace("/", "~1");
+ }
+
+ private static void requireOrWriteDocumentId(
+ DocumentId documentId,
+ Node document) {
+ Node declared = NodePathEditor.getOrNull(document, "/documentId");
+ if (declared == null) {
+ NodePathEditor.put(
+ document,
+ "/documentId",
+ new Node().value(documentId.value()));
+ return;
+ }
+ if (!documentId.value().equals(declared.getValue())) {
+ throw new IllegalArgumentException(
+ "Authored documentId does not match managed lineage "
+ + documentId);
+ }
+ }
+
+ private static LinkedHashMap cloneBodies(
+ Map source) {
+ LinkedHashMap result = new LinkedHashMap<>();
+ source.forEach((documentId, document) ->
+ result.put(documentId, document.clone()));
+ return result;
+ }
+
+ private static List bindingIdentitySequence(
+ List bindings) {
+ return bindings.stream()
+ .map(row -> row.occurrenceIdentity()
+ + ":" + row.bindingIdentity())
+ .toList();
+ }
+
+ private static blue.language.processor.closure.DocumentId closureId(
+ DocumentId documentId) {
+ return new blue.language.processor.closure.DocumentId(
+ documentId.value());
+ }
+
+ private static DocumentId apiId(
+ blue.language.processor.closure.DocumentId documentId) {
+ return DocumentId.of(documentId.value());
+ }
+
+ /** One managed document authored as source YAML. */
+ public record AuthoredDocument(
+ DocumentId documentId,
+ String authoredYaml) {
+ public AuthoredDocument {
+ documentId = Objects.requireNonNull(documentId, "documentId");
+ authoredYaml = requireText(authoredYaml, "authoredYaml");
+ }
+ }
+
+ /** One source-path-to-target managed lineage declaration. */
+ public record OccurrenceBinding(
+ String sourceAlias,
+ String path,
+ String targetAlias) {
+ public OccurrenceBinding {
+ sourceAlias = requireText(sourceAlias, "sourceAlias");
+ targetAlias = requireText(targetAlias, "targetAlias");
+ path = JsonPointer.canonicalize(
+ Objects.requireNonNull(path, "path"));
+ if (path.isEmpty()) {
+ throw new IllegalArgumentException(
+ "A managed occurrence path cannot be the document Root");
+ }
+ }
+ }
+
+ /** Temporal publication inputs applied after compilation. */
+ public record ActivationInputs(
+ CoordinationEngine.AdmissionPolicy policy,
+ ExternalOrderKey verifiedFrontier) {
+ public ActivationInputs {
+ policy = Objects.requireNonNull(policy, "policy");
+ if (policy == CoordinationEngine.AdmissionPolicy.FROM_FRONTIER
+ && verifiedFrontier == null) {
+ throw new IllegalArgumentException(
+ "FROM_FRONTIER requires verified frontier evidence");
+ }
+ if (policy != CoordinationEngine.AdmissionPolicy.FROM_FRONTIER
+ && verifiedFrontier != null) {
+ throw new IllegalArgumentException(
+ policy + " does not accept frontier evidence");
+ }
+ }
+
+ public static ActivationInputs fromNow() {
+ return new ActivationInputs(
+ CoordinationEngine.AdmissionPolicy.FROM_NOW, null);
+ }
+
+ public static ActivationInputs fullHistory() {
+ return new ActivationInputs(
+ CoordinationEngine.AdmissionPolicy.FULL_HISTORY, null);
+ }
+
+ public static ActivationInputs fromFrontier(
+ ExternalOrderKey frontier) {
+ return new ActivationInputs(
+ CoordinationEngine.AdmissionPolicy.FROM_FRONTIER,
+ Objects.requireNonNull(frontier, "frontier"));
+ }
+ }
+
+ /** Complete high-level input. It contains no caller-authored graph. */
+ public record CompilationRequest(
+ List documents,
+ Map aliases,
+ List occurrenceBindings,
+ Set publicRootDocumentIds,
+ ActivationInputs activationInputs,
+ String admissionLabel) {
+ public CompilationRequest {
+ documents = List.copyOf(Objects.requireNonNull(
+ documents, "documents"));
+ aliases = Collections.unmodifiableMap(new LinkedHashMap<>(
+ Objects.requireNonNull(aliases, "aliases")));
+ occurrenceBindings = List.copyOf(Objects.requireNonNull(
+ occurrenceBindings, "occurrenceBindings"));
+ publicRootDocumentIds = Collections.unmodifiableSet(
+ new LinkedHashSet<>(Objects.requireNonNull(
+ publicRootDocumentIds,
+ "publicRootDocumentIds")));
+ activationInputs = Objects.requireNonNull(
+ activationInputs, "activationInputs");
+ admissionLabel = requireText(
+ admissionLabel, "admissionLabel");
+ }
+
+ public CompilationRequest(
+ List documents,
+ Map aliases,
+ List occurrenceBindings,
+ Set publicRootDocumentIds,
+ ActivationInputs activationInputs) {
+ this(
+ documents,
+ aliases,
+ occurrenceBindings,
+ publicRootDocumentIds,
+ activationInputs,
+ DEFAULT_ADMISSION_LABEL);
+ }
+ }
+
+ /** Immutable result retained by the future SDK runtime bridge. */
+ public static final class CompiledClosure {
+ private final ClosureInvocationInput invocation;
+ private final ActivationInputs activationInputs;
+ private final Map authoredDocuments;
+ private final Map finalizedDocuments;
+ private final Map blueIds;
+ private final List bindings;
+ private final List components;
+ private final Map> adjacency;
+ private final Map independentlyVerifiedMasters;
+
+ private CompiledClosure(
+ ClosureInvocationInput invocation,
+ ActivationInputs activationInputs,
+ Map authored,
+ Map bodies,
+ ComponentFinalizationResult finalization,
+ List bindings,
+ Map verifiedMasters) {
+ this.invocation = Objects.requireNonNull(
+ invocation, "invocation");
+ this.activationInputs = Objects.requireNonNull(
+ activationInputs, "activationInputs");
+ this.authoredDocuments = Collections.unmodifiableMap(
+ cloneBodies(authored));
+ LinkedHashMap retainedDocuments =
+ new LinkedHashMap<>();
+ LinkedHashMap retainedBlueIds =
+ new LinkedHashMap<>();
+ finalization.documents().forEach((documentId, evidence) -> {
+ DocumentId apiDocumentId = apiId(documentId);
+ retainedDocuments.put(
+ apiDocumentId, bodies.get(documentId).clone());
+ retainedBlueIds.put(apiDocumentId, evidence.blueId());
+ });
+ this.finalizedDocuments = Collections.unmodifiableMap(
+ retainedDocuments);
+ this.blueIds = Collections.unmodifiableMap(retainedBlueIds);
+ this.bindings = List.copyOf(bindings);
+ this.components = finalization.components().stream()
+ .map(FinalizedComponentEvidence::component)
+ .toList();
+ LinkedHashMap> retainedAdjacency =
+ new LinkedHashMap<>();
+ finalization.finalizedGraph().adjacency().forEach(
+ (source, targets) -> retainedAdjacency.put(
+ apiId(source),
+ targets.stream()
+ .map(Contracts10AuthoredClosureCompiler
+ ::apiId)
+ .toList()));
+ this.adjacency = Collections.unmodifiableMap(retainedAdjacency);
+ LinkedHashMap masters =
+ new LinkedHashMap<>();
+ for (ComponentSnapshot component : components) {
+ String verified = verifiedMasters.get(
+ component.componentIdentity());
+ if (verified == null) {
+ continue;
+ }
+ for (blue.language.processor.closure.DocumentId member
+ : component.orderedMemberDocumentIds()) {
+ masters.put(apiId(member), verified);
+ }
+ }
+ this.independentlyVerifiedMasters =
+ Collections.unmodifiableMap(masters);
+ }
+
+ public ClosureInvocationInput invocation() {
+ return invocation;
+ }
+
+ public ActivationInputs activationInputs() {
+ return activationInputs;
+ }
+
+ public Node authoredDocument(DocumentId documentId) {
+ return requireDocument(authoredDocuments, documentId).clone();
+ }
+
+ public Node finalizedDocument(DocumentId documentId) {
+ return requireDocument(finalizedDocuments, documentId).clone();
+ }
+
+ public Map blueIds() {
+ return blueIds;
+ }
+
+ public String blueId(DocumentId documentId) {
+ String blueId = blueIds.get(Objects.requireNonNull(
+ documentId, "documentId"));
+ if (blueId == null) {
+ throw new IllegalArgumentException(
+ "Unknown compiled document " + documentId);
+ }
+ return blueId;
+ }
+
+ public List bindings() {
+ return bindings;
+ }
+
+ public List components() {
+ return components;
+ }
+
+ public List> componentMembers() {
+ return components.stream()
+ .map(ComponentSnapshot::orderedMemberDocumentIds)
+ .map(members -> members.stream()
+ .map(Contracts10AuthoredClosureCompiler::apiId)
+ .toList())
+ .toList();
+ }
+
+ public Map> adjacency() {
+ return adjacency;
+ }
+
+ public String independentlyVerifiedMaster(DocumentId documentId) {
+ return independentlyVerifiedMasters.get(Objects.requireNonNull(
+ documentId, "documentId"));
+ }
+
+ private static Node requireDocument(
+ Map documents,
+ DocumentId documentId) {
+ Node document = documents.get(Objects.requireNonNull(
+ documentId, "documentId"));
+ if (document == null) {
+ throw new IllegalArgumentException(
+ "Unknown compiled document " + documentId);
+ }
+ return document;
+ }
+ }
+
+ private record ResolvedOccurrence(
+ DocumentId sourceDocumentId,
+ String path,
+ DocumentId targetDocumentId) {
+ }
+
+ private record RequestIndex(List occurrences) {
+ private static RequestIndex from(CompilationRequest request) {
+ if (request.documents().isEmpty()) {
+ throw new IllegalArgumentException(
+ "A closure requires at least one authored document");
+ }
+ LinkedHashSet documentIds = new LinkedHashSet<>();
+ for (AuthoredDocument document : request.documents()) {
+ Objects.requireNonNull(document, "document");
+ if (!documentIds.add(document.documentId())) {
+ throw new IllegalArgumentException(
+ "Duplicate managed document identity "
+ + document.documentId());
+ }
+ }
+ if (request.aliases().isEmpty()) {
+ throw new IllegalArgumentException(
+ "A closure requires document aliases");
+ }
+ LinkedHashSet aliased = new LinkedHashSet<>();
+ for (Map.Entry alias
+ : request.aliases().entrySet()) {
+ requireText(alias.getKey(), "alias");
+ DocumentId documentId = Objects.requireNonNull(
+ alias.getValue(), "alias documentId");
+ if (!documentIds.contains(documentId)) {
+ throw new IllegalArgumentException(
+ "Alias " + alias.getKey()
+ + " names unknown document " + documentId);
+ }
+ if (!aliased.add(documentId)) {
+ throw new IllegalArgumentException(
+ "Managed document has more than one alias: "
+ + documentId);
+ }
+ }
+ if (!aliased.equals(documentIds)) {
+ throw new IllegalArgumentException(
+ "Aliases must name every authored document exactly "
+ + "once");
+ }
+ if (request.publicRootDocumentIds().isEmpty()) {
+ throw new IllegalArgumentException(
+ "A closure requires at least one public Root");
+ }
+ for (DocumentId root : request.publicRootDocumentIds()) {
+ if (!documentIds.contains(Objects.requireNonNull(
+ root, "publicRootDocumentId"))) {
+ throw new IllegalArgumentException(
+ "Public Root is not an authored document: "
+ + root);
+ }
+ }
+
+ ArrayList occurrences = new ArrayList<>();
+ LinkedHashMap> pathsBySource =
+ new LinkedHashMap<>();
+ for (OccurrenceBinding binding
+ : request.occurrenceBindings()) {
+ Objects.requireNonNull(binding, "occurrenceBinding");
+ DocumentId source = request.aliases().get(
+ binding.sourceAlias());
+ DocumentId target = request.aliases().get(
+ binding.targetAlias());
+ if (source == null) {
+ throw new IllegalArgumentException(
+ "Unknown occurrence source alias "
+ + binding.sourceAlias());
+ }
+ if (target == null) {
+ throw new IllegalArgumentException(
+ "Unknown occurrence target alias "
+ + binding.targetAlias());
+ }
+ List paths = pathsBySource.computeIfAbsent(
+ source, ignored -> new ArrayList<>());
+ for (String existing : paths) {
+ if (overlaps(existing, binding.path())) {
+ throw new IllegalArgumentException(
+ "Managed occurrence paths overlap in "
+ + source + ": " + existing + " and "
+ + binding.path());
+ }
+ }
+ paths.add(binding.path());
+ occurrences.add(new ResolvedOccurrence(
+ source, binding.path(), target));
+ }
+ return new RequestIndex(List.copyOf(occurrences));
+ }
+ }
+
+ private static final class CompilerProofProvider
+ implements NodeProvider, CyclicAwareNodeProvider {
+ private final Map documents = new LinkedHashMap<>();
+ private final Map proofs =
+ new LinkedHashMap<>();
+
+ private void addDocument(String blueId, Node document) {
+ documents.put(
+ Objects.requireNonNull(blueId, "blueId"),
+ Objects.requireNonNull(document, "document").clone());
+ }
+
+ private void addProof(String blueId, CyclicSetProof proof) {
+ proofs.put(
+ Objects.requireNonNull(blueId, "blueId"),
+ CyclicSetProof.fromDeclaredPlaceholderSet(
+ Objects.requireNonNull(proof, "proof")
+ .declaredPlaceholderSet()));
+ }
+
+ @Override
+ public List fetchByBlueId(String blueId) {
+ Node document = documents.get(blueId);
+ return document == null
+ ? List.of()
+ : List.of(document.clone());
+ }
+
+ @Override
+ public CyclicSetProofResult cyclicSetProofFor(String blueId) {
+ CyclicSetProof proof = proofs.get(blueId);
+ return proof == null
+ ? CyclicSetProofResult.notFound()
+ : CyclicSetProofResult.found(proof);
+ }
+ }
+
+ private static String requireText(String value, String label) {
+ String checked = Objects.requireNonNull(value, label).trim();
+ if (checked.isEmpty()) {
+ throw new IllegalArgumentException(label + " must not be blank");
+ }
+ return checked;
+ }
+}
diff --git a/src/main/java/blue/coordination/internal/ContractsActiveSourceTimelineIndex.java b/src/main/java/blue/coordination/internal/ContractsActiveSourceTimelineIndex.java
new file mode 100644
index 0000000..30fce27
--- /dev/null
+++ b/src/main/java/blue/coordination/internal/ContractsActiveSourceTimelineIndex.java
@@ -0,0 +1,98 @@
+package blue.coordination.internal;
+
+import blue.coordination.api.DocumentId;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+/**
+ * Disposable exact Timeline union for the configured public Root surfaces.
+ *
+ * Successful publications refresh only configured Roots in the affected
+ * connected cohort. The durable occurrence inventory remains authoritative;
+ * restart may rebuild every configured Root contribution from it.
+ */
+final class ContractsActiveSourceTimelineIndex {
+ private final TreeSet publicRoots;
+ private final Map
+ surfacesByRoot = new TreeMap<>(EmbeddingBinding.DOCUMENT_ORDER);
+ private Set timelineIds = Set.of();
+
+ ContractsActiveSourceTimelineIndex(Collection publicRoots) {
+ TreeSet canonical = new TreeSet<>(
+ EmbeddingBinding.DOCUMENT_ORDER);
+ Objects.requireNonNull(publicRoots, "publicRoots").forEach(root ->
+ canonical.add(Objects.requireNonNull(root, "publicRoot")));
+ this.publicRoots = canonical;
+ }
+
+ synchronized void addPublicRoots(Collection roots) {
+ Objects.requireNonNull(roots, "roots").forEach(root ->
+ publicRoots.add(Objects.requireNonNull(root, "publicRoot")));
+ }
+
+ /** Refreshes configured Roots present in one newly published cohort. */
+ synchronized void refresh(
+ Collection affectedDocuments,
+ InMemoryDocumentStore documents) {
+ Objects.requireNonNull(affectedDocuments, "affectedDocuments");
+ InMemoryDocumentStore store = Objects.requireNonNull(
+ documents, "documents");
+ TreeSet affectedRoots = new TreeSet<>(
+ EmbeddingBinding.DOCUMENT_ORDER);
+ for (DocumentId documentId : affectedDocuments) {
+ DocumentId checked = Objects.requireNonNull(
+ documentId, "affectedDocument");
+ if (publicRoots.contains(checked)) {
+ affectedRoots.add(checked);
+ }
+ }
+ if (affectedRoots.isEmpty()) {
+ return;
+ }
+ ManagedOccurrenceInventory occurrences = store.occurrenceInventory();
+ for (DocumentId root : affectedRoots) {
+ surfacesByRoot.put(root, resolve(root, occurrences, store));
+ }
+ rebuildTimelineUnion();
+ }
+
+ /** Rebuilds the entire disposable index after a process restart. */
+ synchronized void rebuild(InMemoryDocumentStore documents) {
+ surfacesByRoot.clear();
+ refresh(List.copyOf(publicRoots), documents);
+ }
+
+ /** Immutable O(1) snapshot used for journal entry filtering. */
+ synchronized Set timelineIds() {
+ return timelineIds;
+ }
+
+ private void rebuildTimelineUnion() {
+ TreeSet canonical = new TreeSet<>(EmbeddingBinding.TEXT_ORDER);
+ surfacesByRoot.values().forEach(surface ->
+ canonical.addAll(surface.timelineIds()));
+ timelineIds = Collections.unmodifiableSet(
+ new LinkedHashSet<>(canonical));
+ }
+
+ private static ContractsRootSourceSurface.Surface resolve(
+ DocumentId root,
+ ManagedOccurrenceInventory occurrences,
+ InMemoryDocumentStore documents) {
+ return ContractsRootSourceSurface.resolve(
+ ContractsRootFeederWindow.LaneId.publicRoots(List.of(root)),
+ occurrences,
+ documentId -> documents.find(documentId)
+ .map(session -> session.layout().routingSurface()
+ .externalTimelineIds())
+ .orElse(List.of()));
+ }
+}
diff --git a/src/main/java/blue/coordination/internal/ContractsClosureAdapter.java b/src/main/java/blue/coordination/internal/ContractsClosureAdapter.java
new file mode 100644
index 0000000..44399c8
--- /dev/null
+++ b/src/main/java/blue/coordination/internal/ContractsClosureAdapter.java
@@ -0,0 +1,2337 @@
+package blue.coordination.internal;
+
+import blue.coordination.api.DocumentId;
+import blue.coordination.api.DocumentRevision;
+import blue.coordination.api.ExactValue;
+import blue.coordination.api.SessionStatus;
+import blue.coordination.api.TimelineEntry;
+import blue.language.model.Node;
+import blue.language.model.NodePathEditor;
+import blue.language.model.wire.JsonPointer;
+import blue.language.processor.EffectiveFragmentationCatalog;
+import blue.language.processor.EmbeddedScopePlanView;
+import blue.language.processor.ProcessorStatus;
+import blue.language.processor.SubscriptionDelta;
+import blue.language.processor.ManagedRootChannelOccurrence;
+import blue.language.processor.ManagedRootSubscriptionSurface;
+import blue.language.processor.closure.AffectedClosureSnapshot;
+import blue.language.processor.closure.BlueClosureContracts;
+import blue.language.processor.closure.ClosureAttemptResult;
+import blue.language.processor.closure.ClosureCommitCompanion;
+import blue.language.processor.closure.ClosureEnvironment;
+import blue.language.processor.closure.ClosureEvidenceFactory;
+import blue.language.processor.closure.ClosureImplementationEvidence;
+import blue.language.processor.closure.ClosureInvocationInput;
+import blue.language.processor.closure.ClosureProcessResult;
+import blue.language.processor.closure.ComponentKind;
+import blue.language.processor.closure.ComponentFinalizationInput;
+import blue.language.processor.closure.ComponentFinalizationKernel;
+import blue.language.processor.closure.ComponentFinalizationResult;
+import blue.language.processor.closure.ComponentSnapshot;
+import blue.language.processor.closure.DirectLogicalDelivery;
+import blue.language.processor.closure.ExternalEventCause;
+import blue.language.processor.closure.GasTraceEntry;
+import blue.language.processor.closure.ManagedDocumentSnapshot;
+import blue.language.processor.closure.ManagedDocumentGraph;
+import blue.language.processor.closure.ManagedOccurrenceBinding;
+import blue.language.processor.closure.PublicEventOccurrence;
+import blue.language.processor.closure.ResultingDocument;
+import blue.language.processor.closure.ScopeAddress;
+import blue.language.processor.closure.SubscriptionState;
+import blue.language.processor.util.PointerUtils;
+
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Deque;
+import java.util.HexFormat;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.function.Consumer;
+
+/**
+ * Package-internal Contracts 1.0 execution and atomic-publication boundary.
+ *
+ * One frozen Root route selection may select several disconnected managed
+ * cohorts. Each cohort gets its own exact invocation and publication attempt.
+ * Every ordinary work occurrence is still executed by Contracts against its
+ * target document as Root; this adapter never supplies a containing document
+ * or reverse-containment context.
+ *
+ * The caller must serialize capture with route, session, and journal
+ * publication, as {@link DefaultCoordinationEngine} already does. The adapter
+ * additionally rechecks the frozen route generation and lets the store enforce
+ * every document-head and topology generation fence at the final swap.
+ */
+final class ContractsClosureAdapter implements AutoCloseable {
+ static final String PLAN_CONSTRUCTIONS =
+ "contracts.closure.planConstructions";
+ static final String COHORTS_SELECTED =
+ "contracts.closure.cohortsSelected";
+ static final String DOCUMENT_OPENS =
+ "contracts.closure.documentOpens";
+ static final String UNRELATED_DOCUMENT_OPENS =
+ "contracts.closure.unrelatedDocumentOpens";
+ static final String OCCURRENCE_ROWS_EXAMINED =
+ "contracts.closure.occurrenceRowsExamined";
+ static final String COMPONENT_STATES_READ =
+ "contracts.closure.componentStatesRead";
+ static final String RESULTING_COMPONENTS =
+ "contracts.closure.resultingComponents";
+ static final String PLAN_CONSTRUCTION_PHASE =
+ "contracts.closure.planConstruction";
+ static final String PROCESSOR_PHASE =
+ "contracts.closure.processor";
+ static final String RESULT_VALIDATION_PHASE =
+ "contracts.closure.resultValidation";
+ static final String PUBLICATION_PHASE =
+ "contracts.closure.publication";
+
+ enum PublicationFailurePoint {
+ AFTER_STORE_COMMIT_BEFORE_ROUTE_PUBLISH
+ }
+
+ private final BlueRuntime runtime;
+ private final WholeObjectStore objects;
+ private final EmbeddedOnlyLayoutBuilder layoutBuilder;
+ private final InMemoryDocumentStore documents;
+ private final OperationRouteIndex routes;
+ private final ContractsClosureProfile profile;
+ private final ContractsActiveSourceTimelineIndex activeSourceTimelines;
+ private final ClosureEnvironment environment;
+ private final ContractsClosureExecutionMetricsObserver executionObserver;
+ private final BlueClosureContracts contracts;
+ private final Map managedDraftPlans =
+ new LinkedHashMap<>();
+ private Consumer publicationFailureInjector =
+ ignored -> { };
+ private boolean closed;
+
+ ContractsClosureAdapter(
+ BlueRuntime runtime,
+ WholeObjectStore objects,
+ EmbeddedOnlyLayoutBuilder layoutBuilder,
+ InMemoryDocumentStore documents,
+ OperationRouteIndex routes,
+ ContractsClosureProfile profile) {
+ this(
+ runtime,
+ objects,
+ layoutBuilder,
+ documents,
+ routes,
+ profile,
+ new ContractsActiveSourceTimelineIndex(
+ profile.publicRoots()));
+ }
+
+ ContractsClosureAdapter(
+ BlueRuntime runtime,
+ WholeObjectStore objects,
+ EmbeddedOnlyLayoutBuilder layoutBuilder,
+ InMemoryDocumentStore documents,
+ OperationRouteIndex routes,
+ ContractsClosureProfile profile,
+ ContractsActiveSourceTimelineIndex activeSourceTimelines) {
+ this.runtime = Objects.requireNonNull(runtime, "runtime");
+ this.objects = Objects.requireNonNull(objects, "objects");
+ this.layoutBuilder = Objects.requireNonNull(
+ layoutBuilder, "layoutBuilder");
+ this.documents = Objects.requireNonNull(documents, "documents");
+ this.routes = Objects.requireNonNull(routes, "routes");
+ this.profile = Objects.requireNonNull(profile, "profile");
+ this.activeSourceTimelines = Objects.requireNonNull(
+ activeSourceTimelines, "activeSourceTimelines");
+ this.environment = profile.environment(runtime.documentProcessor());
+ this.executionObserver =
+ new ContractsClosureExecutionMetricsObserver(
+ runtime.metrics());
+ this.contracts = new BlueClosureContracts(
+ runtime.documentProcessor(), executionObserver);
+ }
+
+ /** Captures all exact inputs selected by one immutable Root feeder event. */
+ synchronized FrozenBatch capture(TimelineEntry entry) {
+ ensureOpen();
+ TimelineEntry selectedEntry = Objects.requireNonNull(entry, "entry");
+ OperationRouteIndex.FrozenDirectDeliverySelection selection =
+ routes.selectDirectDeliveries(selectedEntry);
+ if (selection.deliveries().isEmpty()) {
+ return new FrozenBatch(
+ selectedEntry, selection.routeGeneration(), List.of());
+ }
+ return runtime.metrics().timed(PLAN_CONSTRUCTION_PHASE, () -> {
+ InMemoryDocumentStore.ClosureTopologySnapshot topology =
+ documents.closureTopologySnapshot();
+ List selectedCohorts = partitionSelection(
+ topology.componentIndex(),
+ topology.occurrenceInventory(),
+ selection,
+ runtime.metrics());
+ LinkedHashSet selectedMembers = new LinkedHashSet<>();
+ selectedCohorts.forEach(cohort -> selectedMembers.addAll(
+ cohort.members()));
+ InMemoryDocumentStore.ClosureSnapshot publication =
+ documents.closureSnapshot(selectedMembers, topology);
+ List invocations = new ArrayList<>();
+ for (CohortSelection selectedCohort : selectedCohorts) {
+ invocations.add(captureInvocation(
+ selectedEntry,
+ publication,
+ selectedCohort));
+ }
+ invocations = applyManagedDraftPlan(
+ selectedEntry, invocations);
+ runtime.metrics().add(COHORTS_SELECTED, invocations.size());
+ runtime.metrics().increment(PLAN_CONSTRUCTIONS);
+ return new FrozenBatch(
+ selectedEntry,
+ selection.routeGeneration(),
+ invocations);
+ });
+ }
+
+ /** Registers exact SDK host evidence before the entry can be drained. */
+ synchronized boolean registerManagedDraftPlan(
+ String entryBlueId,
+ ContractsManagedDraftPlan plan) {
+ ensureOpen();
+ String identity = Objects.requireNonNull(
+ entryBlueId, "entryBlueId");
+ if (identity.isBlank()) {
+ throw new IllegalArgumentException(
+ "entryBlueId must not be blank");
+ }
+ ContractsManagedDraftPlan selected = Objects.requireNonNull(
+ plan, "plan");
+ ContractsManagedDraftPlan prior = managedDraftPlans.putIfAbsent(
+ identity, selected);
+ if (prior != null && prior != selected) {
+ throw new IllegalStateException(
+ "A managed draft plan is already registered for "
+ + identity);
+ }
+ return prior == null;
+ }
+
+ /** Removes only a plan inserted by a journal append that is rolling back. */
+ synchronized void unregisterManagedDraftPlan(
+ String entryBlueId,
+ ContractsManagedDraftPlan plan) {
+ ensureOpen();
+ if (!managedDraftPlans.remove(
+ Objects.requireNonNull(entryBlueId, "entryBlueId"),
+ Objects.requireNonNull(plan, "plan"))) {
+ throw new IllegalStateException(
+ "Managed draft rollback lost its exact plan");
+ }
+ }
+
+ /** Forgets disposable host evidence after its journal entry is terminal. */
+ synchronized void completeManagedDraftPlan(String entryBlueId) {
+ ensureOpen();
+ managedDraftPlans.remove(Objects.requireNonNull(
+ entryBlueId, "entryBlueId"));
+ }
+
+ /** Package-internal append-atomicity observation. */
+ synchronized boolean hasManagedDraftPlan(String entryBlueId) {
+ ensureOpen();
+ return managedDraftPlans.containsKey(Objects.requireNonNull(
+ entryBlueId, "entryBlueId"));
+ }
+
+ /**
+ * Rejects a managed-draft plan against the exact current target before its
+ * Timeline Entry can consume journal order.
+ */
+ synchronized void preflightManagedDraftPlan(
+ ContractsManagedDraftPlan plan) {
+ ensureOpen();
+ ContractsManagedDraftPlan selected = Objects.requireNonNull(
+ plan, "plan");
+ DocumentSession session = documents.require(
+ selected.targetDocumentId());
+ ExactValue target;
+ synchronized (session) {
+ target = session.currentRevision().after();
+ if (session.epoch() != selected.targetEpoch()
+ || !target.blueId().equals(selected.targetBlueId())) {
+ throw stale("Managed expansion target head changed before "
+ + "append " + selected.targetDocumentId());
+ }
+ }
+ validateManagedDraftExpectationPaths(selected, target);
+ }
+
+ /** Executes and independently publishes every disconnected cohort. */
+ synchronized List processAndPublish(FrozenBatch batch) {
+ ensureOpen();
+ FrozenBatch frozen = Objects.requireNonNull(batch, "batch");
+ List outcomes = new ArrayList<>();
+ for (CohortInvocation invocation : frozen.invocations()) {
+ outcomes.add(executeAndPublish(frozen, invocation));
+ }
+ return List.copyOf(outcomes);
+ }
+
+ /** Executes and independently publishes exactly one frozen cohort lane. */
+ synchronized CohortOutcome executeAndPublish(
+ FrozenBatch batch,
+ CohortInvocation cohort) {
+ ensureOpen();
+ FrozenBatch frozen = Objects.requireNonNull(batch, "batch");
+ CohortInvocation selected = Objects.requireNonNull(cohort, "cohort");
+ if (!frozen.invocations().contains(selected)) {
+ throw new IllegalArgumentException(
+ "Cohort invocation does not belong to the frozen batch");
+ }
+ Optional prior =
+ publicationReceipt(frozen, selected);
+ if (prior.isPresent()) {
+ ContractsClosurePublicationReceipt receipt = prior.get();
+ if (receipt.commits()) {
+ reconcilePublication(frozen, selected);
+ }
+ return outcome(receipt, true);
+ }
+ requireRouteSelectionCurrent(frozen, selected);
+ executionObserver.beginAttempt(selected.members().stream()
+ .map(DocumentId::value)
+ .toList());
+ ClosureAttemptResult attempt = runtime.metrics().timed(
+ PROCESSOR_PHASE,
+ () -> contracts.processClosure(selected.input()));
+ long validationStarted = System.nanoTime();
+ String identity;
+ ContractsClosurePublicationReceipt receipt;
+ try {
+ if (attempt.isComplete()) {
+ runtime.metrics().add(
+ RESULTING_COMPONENTS,
+ attempt.processResult()
+ .resultingComponents().size());
+ }
+ identity = publicationIdentity(frozen, selected);
+ if (!attempt.isComplete()) {
+ return new CohortOutcome(
+ selected.members(), attempt, false, identity, false);
+ }
+ if (!isDurablyTerminalStatus(
+ attempt.processResult().status())) {
+ throw new ProjectionUnavailableException(
+ "Contracts capability failure is not a durable feeder "
+ + "disposition and must be retried after the "
+ + "capability is available");
+ }
+ receipt = new ContractsClosurePublicationReceipt(
+ identity,
+ selected.members(),
+ attempt);
+ } finally {
+ runtime.metrics().addNanos(
+ RESULT_VALIDATION_PHASE,
+ System.nanoTime() - validationStarted);
+ }
+ runtime.metrics().timed(PUBLICATION_PHASE, () -> {
+ if (receipt.commits()) {
+ publish(frozen, selected, receipt);
+ } else {
+ publishNonCommit(frozen, selected, receipt);
+ }
+ });
+ return outcome(receipt, false);
+ }
+
+ /** Exact implementation evidence from the latest completed execution. */
+ synchronized Optional
+ lastExecutionEvidence() {
+ ensureOpen();
+ return executionObserver.lastEvidence();
+ }
+
+ static boolean isDurablyTerminalStatus(ProcessorStatus status) {
+ return Objects.requireNonNull(status, "status")
+ != ProcessorStatus.CAPABILITY_FAILURE;
+ }
+
+ private static CohortOutcome outcome(
+ ContractsClosurePublicationReceipt receipt,
+ boolean replayed) {
+ return new CohortOutcome(
+ receipt.documentIds(),
+ receipt.attempt(),
+ receipt.commits(),
+ receipt.publicationIdentity(),
+ replayed);
+ }
+
+ synchronized Optional
+ publicationReceipt(
+ FrozenBatch batch,
+ CohortInvocation cohort) {
+ ensureOpen();
+ FrozenBatch frozen = requireCohortHandle(batch, cohort);
+ String identity = publicationIdentity(frozen, cohort);
+ ContractsClosurePublicationReceipt receipt = documents
+ .closurePublicationReceipt(identity)
+ .orElse(null);
+ if (receipt == null) {
+ if (documents.hasPublicationReceipt(identity)) {
+ throw new IllegalStateException(
+ "Closure publication has no typed replay receipt "
+ + identity);
+ }
+ return Optional.empty();
+ }
+ if (!receipt.documentIds().equals(cohort.members())) {
+ throw new IllegalStateException(
+ "Typed closure receipt does not belong to the frozen "
+ + "cohort " + identity);
+ }
+ return Optional.of(receipt);
+ }
+
+ /** Stable pre-execution idempotency key for one frozen cohort handle. */
+ synchronized String publicationIdentityFor(
+ FrozenBatch batch,
+ CohortInvocation cohort) {
+ ensureOpen();
+ FrozenBatch frozen = requireCohortHandle(batch, cohort);
+ return publicationReceipt(frozen, cohort)
+ .map(ContractsClosurePublicationReceipt::publicationIdentity)
+ .orElseGet(() -> publicationIdentity(frozen, cohort));
+ }
+
+ synchronized void onPublicationFailurePoint(
+ Consumer injector) {
+ ensureOpen();
+ publicationFailureInjector = Objects.requireNonNull(
+ injector, "injector");
+ }
+
+ private void publishNonCommit(
+ FrozenBatch batch,
+ CohortInvocation invocation,
+ ContractsClosurePublicationReceipt receipt) {
+ ClosureProcessResult result = receipt.attempt().processResult();
+ if (!receipt.publicationIdentity().equals(
+ publicationIdentity(batch, invocation))) {
+ throw new IllegalArgumentException(
+ "Process receipt identity does not identify this cohort");
+ }
+ requireTerminalResult(invocation, result);
+ if (result.commits()) {
+ throw new IllegalArgumentException(
+ "Receipt-only publication requires a non-commit result");
+ }
+ InMemoryDocumentStore.ClosureSnapshot current =
+ documents.closureSnapshot(invocation.existingMemberSet());
+ requireCohortStillCurrent(invocation, current);
+ MultiDocumentPublicationTransaction transaction = documents
+ .beginAtomicPublication(
+ receipt.publicationIdentity(),
+ current.occurrenceInventoryGeneration(),
+ current.componentIndexGeneration());
+ for (CapturedDocument document : invocation.documents().values()) {
+ transaction.expectHead(
+ document.documentId(),
+ document.head().epoch(),
+ document.head().blueId());
+ }
+ if (invocation.managedExpansion()) {
+ invocation.newMemberSet().forEach(transaction::expectAbsent);
+ transaction.stageManagedExpansionInput(invocation.input());
+ }
+ invocation.input().snapshot().components().forEach(
+ component -> {
+ boolean existing = component.orderedMemberDocumentIds()
+ .stream().allMatch(member -> invocation
+ .existingMemberSet().contains(
+ coordinationId(member)));
+ if (existing) {
+ transaction.expectComponentState(component);
+ }
+ });
+ transaction.stageClosurePublicationReceipt(receipt);
+ requireRouteSelectionCurrent(batch, invocation);
+ transaction.commit();
+ }
+
+ private static void requireTerminalResult(
+ CohortInvocation invocation,
+ ClosureProcessResult result) {
+ if (!result.invocationIdentity().equals(
+ invocation.input().invocationIdentity())
+ || !result.inputClosureIdentity().equals(
+ invocation.input().snapshot().closureIdentity())) {
+ throw new IllegalStateException(
+ "Closure result does not belong to the captured input");
+ }
+ resultingDocuments(result, invocation.memberSet());
+ }
+
+ /** Captures, executes, and publishes one event under caller serialization. */
+ synchronized List processAndPublish(TimelineEntry entry) {
+ return processAndPublish(capture(entry));
+ }
+
+ synchronized boolean hasPublicationReceipt(
+ FrozenBatch batch,
+ CohortInvocation cohort) {
+ ensureOpen();
+ FrozenBatch frozen = requireCohortHandle(batch, cohort);
+ return publicationReceipt(frozen, cohort).isPresent();
+ }
+
+ /**
+ * Rebuilds the disposable route cache from durable post-commit sessions
+ * when a receipt proves that this cohort was already atomically published.
+ */
+ synchronized boolean reconcilePublication(
+ FrozenBatch batch,
+ CohortInvocation cohort) {
+ ensureOpen();
+ FrozenBatch frozen = requireCohortHandle(batch, cohort);
+ Optional receipt =
+ publicationReceipt(frozen, cohort);
+ if (receipt.isEmpty()) {
+ return false;
+ }
+ if (!receipt.get().commits()) {
+ return true;
+ }
+ List replacements =
+ new ArrayList<>();
+ for (DocumentId documentId : cohort.members()) {
+ DocumentSession session = documents.require(documentId);
+ synchronized (session) {
+ replacements.add(new OperationRouteIndex.Replacement(
+ documentId,
+ session.layout().routingSurface(),
+ session.activeSubscriptions()));
+ }
+ }
+ routes.prepareReplacement(replacements).publish();
+ activeSourceTimelines.refresh(cohort.members(), documents);
+ return true;
+ }
+
+ @Override
+ public synchronized void close() {
+ if (!closed) {
+ closed = true;
+ managedDraftPlans.clear();
+ contracts.close();
+ }
+ }
+
+ static List partitionSelection(
+ ProcessEmbeddedComponentIndex componentIndex,
+ ManagedOccurrenceInventory occurrenceInventory,
+ OperationRouteIndex.FrozenDirectDeliverySelection selection) {
+ return partitionSelection(
+ componentIndex, occurrenceInventory, selection, null);
+ }
+
+ private static List partitionSelection(
+ ProcessEmbeddedComponentIndex componentIndex,
+ ManagedOccurrenceInventory occurrenceInventory,
+ OperationRouteIndex.FrozenDirectDeliverySelection selection,
+ EngineMetrics metrics) {
+ ProcessEmbeddedComponentIndex index = Objects.requireNonNull(
+ componentIndex, "componentIndex");
+ ManagedOccurrenceInventory inventory = Objects.requireNonNull(
+ occurrenceInventory, "occurrenceInventory");
+ OperationRouteIndex.FrozenDirectDeliverySelection frozen =
+ Objects.requireNonNull(selection, "selection");
+ TreeMap groups = new TreeMap<>(
+ EmbeddingBinding.DOCUMENT_ORDER);
+ Set alreadySelected = new LinkedHashSet<>();
+ long occurrenceRowsExamined = 0L;
+ List directTargets = frozen.documentIds().stream()
+ .sorted(EmbeddingBinding.DOCUMENT_ORDER)
+ .toList();
+ for (DocumentId directTarget : directTargets) {
+ if (alreadySelected.contains(directTarget)) {
+ continue;
+ }
+ ConnectedSelection connected = connectedSelection(
+ index, inventory, directTarget);
+ groups.put(connected.members().get(0), connected);
+ alreadySelected.addAll(connected.members());
+ occurrenceRowsExamined = Math.addExact(
+ occurrenceRowsExamined, connected.rowsExamined());
+ }
+ if (metrics != null) {
+ metrics.add(OCCURRENCE_ROWS_EXAMINED, occurrenceRowsExamined);
+ }
+ List result = new ArrayList<>();
+ for (ConnectedSelection connected : groups.values()) {
+ List members = connected.members();
+ Set memberSet = new LinkedHashSet<>(members);
+ TreeMap
+ activeCohorts = new TreeMap<>(
+ EmbeddingBinding.DOCUMENT_ORDER);
+ for (DocumentId member : members) {
+ ProcessEmbeddedComponentIndex.Cohort active =
+ index.cohort(member);
+ activeCohorts.putIfAbsent(
+ active.members().get(0), active);
+ }
+ List components =
+ activeCohorts.values().stream()
+ .flatMap(active -> active.components().stream())
+ .toList();
+ List deliveries =
+ frozen.deliveries().stream()
+ .filter(delivery -> memberSet.contains(
+ delivery.documentId()))
+ .toList();
+ result.add(new CohortSelection(
+ members,
+ components,
+ connected.occurrences(),
+ deliveries));
+ }
+ return List.copyOf(result);
+ }
+
+ private static ConnectedSelection connectedSelection(
+ ProcessEmbeddedComponentIndex index,
+ ManagedOccurrenceInventory inventory,
+ DocumentId start) {
+ TreeMap discovered = new TreeMap<>(
+ EmbeddingBinding.DOCUMENT_ORDER);
+ Deque pending = new ArrayDeque<>();
+ discovered.put(Objects.requireNonNull(start, "start"), Boolean.TRUE);
+ pending.addLast(start);
+ Map occurrences =
+ new LinkedHashMap<>();
+ long rowsExamined = 0L;
+ while (!pending.isEmpty()) {
+ DocumentId current = pending.removeFirst();
+ for (DocumentId activeMember : index.cohort(current).members()) {
+ if (discovered.putIfAbsent(
+ activeMember, Boolean.TRUE) == null) {
+ pending.addLast(activeMember);
+ }
+ }
+ for (ManagedOccurrenceBinding row
+ : inventory.rowsTouching(current)) {
+ if (occurrences.putIfAbsent(
+ row.occurrenceIdentity(), row) != null) {
+ continue;
+ }
+ rowsExamined = Math.addExact(rowsExamined, 1L);
+ DocumentId source = coordinationId(row.sourceDocumentId());
+ DocumentId target = coordinationId(row.targetDocumentId());
+ if (discovered.putIfAbsent(source, Boolean.TRUE) == null) {
+ pending.addLast(source);
+ }
+ if (discovered.putIfAbsent(target, Boolean.TRUE) == null) {
+ pending.addLast(target);
+ }
+ }
+ }
+ ArrayList canonicalOccurrences =
+ new ArrayList<>(occurrences.values());
+ canonicalOccurrences.sort(Comparator.naturalOrder());
+ return new ConnectedSelection(
+ new ArrayList<>(discovered.keySet()),
+ canonicalOccurrences,
+ rowsExamined);
+ }
+
+ private CohortInvocation captureInvocation(
+ TimelineEntry entry,
+ InMemoryDocumentStore.ClosureSnapshot publication,
+ CohortSelection selection) {
+ TreeMap captured = new TreeMap<>(
+ EmbeddingBinding.DOCUMENT_ORDER);
+ Set allowedMembers = new LinkedHashSet<>(
+ selection.members());
+ for (DocumentId documentId : selection.members()) {
+ captured.put(documentId, captureDocument(
+ documentId,
+ publication.requireHead(documentId),
+ allowedMembers));
+ }
+
+ List components = captureComponents(
+ publication, selection.components(), captured);
+ Map componentGenerations = new LinkedHashMap<>();
+ for (ComponentSnapshot component : components) {
+ component.orderedMemberDocumentIds().forEach(member ->
+ componentGenerations.put(
+ coordinationId(member),
+ component.componentGeneration()));
+ }
+
+ List managedDocuments = new ArrayList<>();
+ List publicRoots =
+ new ArrayList<>();
+ for (CapturedDocument document : captured.values()) {
+ boolean publicRoot = profile.isPublicRoot(document.documentId());
+ blue.language.processor.closure.DocumentId closureDocumentId =
+ closureId(document.documentId());
+ managedDocuments.add(new ManagedDocumentSnapshot(
+ closureDocumentId,
+ document.head().blueId(),
+ document.current().copyNode(),
+ document.initialized(),
+ document.terminated(),
+ publicRoot,
+ document.head().epoch(),
+ requireComponentGeneration(
+ componentGenerations, document.documentId())));
+ if (publicRoot) {
+ publicRoots.add(closureDocumentId);
+ }
+ }
+
+ List occurrences = selection.occurrences();
+ long graphGeneration = publication.graphGenerations()
+ .requireCohortGeneration(captured.keySet());
+ AffectedClosureSnapshot snapshot = ClosureEvidenceFactory
+ .affectedClosure(
+ graphGeneration,
+ managedDocuments,
+ occurrences,
+ components,
+ publicRoots);
+ ExternalEventCause cause = ClosureEvidenceFactory.externalCause(
+ entry.exactEvent().copyNode(),
+ entry.blueId(),
+ entry.sourceOrderKey(),
+ environment.externalOrderPolicyIdentity());
+ List deliveries = selection.deliveries()
+ .stream()
+ .map(OperationRouteIndex.FrozenDirectDelivery
+ ::toContractsEvidence)
+ .toList();
+ ClosureInvocationInput input = ClosureEvidenceFactory.processClosure(
+ snapshot,
+ cause,
+ deliveries,
+ profile.executionPolicy(),
+ environment);
+ return new CohortInvocation(
+ selection.members(),
+ deliveries,
+ input,
+ captured,
+ null);
+ }
+
+ private List applyManagedDraftPlan(
+ TimelineEntry entry,
+ List invocations) {
+ ContractsManagedDraftPlan plan = managedDraftPlans.get(
+ entry.blueId());
+ if (plan == null) {
+ return invocations;
+ }
+ ArrayList result = new ArrayList<>(invocations);
+ int selected = -1;
+ for (int index = 0; index < result.size(); index++) {
+ if (result.get(index).memberSet().contains(
+ plan.targetDocumentId())) {
+ if (selected >= 0) {
+ throw new IllegalStateException(
+ "Managed expansion target belongs to more than "
+ + "one captured cohort");
+ }
+ selected = index;
+ }
+ }
+ if (selected < 0) {
+ throw new IllegalStateException(
+ "Managed expansion target was not selected by its exact "
+ + "operation " + plan.targetDocumentId());
+ }
+ CohortInvocation base = result.get(selected);
+ long presentDrafts = plan.drafts().keySet().stream()
+ .filter(documentId -> documents.find(documentId).isPresent())
+ .count();
+ if (presentDrafts == plan.drafts().size()) {
+ TreeMap replayMembers = new TreeMap<>(
+ EmbeddingBinding.DOCUMENT_ORDER);
+ base.members().forEach(member -> replayMembers.put(
+ member, Boolean.TRUE));
+ plan.drafts().keySet().forEach(member -> replayMembers.put(
+ member, Boolean.TRUE));
+ CohortInvocation replay = new CohortInvocation(
+ List.copyOf(replayMembers.keySet()),
+ base.directDeliveries(),
+ base.input(),
+ base.documents(),
+ plan);
+ String identity = publicationIdentity(
+ new FrozenBatch(entry, 0L, List.of(replay)), replay);
+ ContractsClosurePublicationReceipt receipt = documents
+ .closurePublicationReceipt(identity)
+ .orElse(null);
+ if (receipt != null && receipt.documentIds().equals(
+ replay.members())) {
+ // The store swap completed before feeder progress was
+ // recorded. Ordinary recapture plus the typed receipt now
+ // drives route-cache reconciliation.
+ return invocations;
+ }
+ throw stale("Managed draft lineage already exists before "
+ + entry.blueId());
+ }
+ if (presentDrafts != 0L) {
+ throw stale("Managed expansion is only partially durable for "
+ + entry.blueId());
+ }
+ CapturedDocument target = base.documents().get(
+ plan.targetDocumentId());
+ if (target == null
+ || target.head().epoch() != plan.targetEpoch()
+ || !target.head().blueId().equals(plan.targetBlueId())) {
+ throw stale("Managed expansion target head changed before capture "
+ + plan.targetDocumentId());
+ }
+ result.set(selected, augmentWithManagedDrafts(
+ base, plan, entry.exactRequest()));
+ return List.copyOf(result);
+ }
+
+ private CohortInvocation augmentWithManagedDrafts(
+ CohortInvocation base,
+ ContractsManagedDraftPlan plan,
+ ExactValue exactRequest) {
+ ClosureInvocationInput original = base.input();
+ validateManagedDraftDeclarations(base, plan, exactRequest);
+
+ ArrayList members =
+ new ArrayList<>(original.snapshot().managedDocuments()
+ .stream()
+ .map(ManagedDocumentSnapshot::documentId)
+ .toList());
+ plan.drafts().keySet().forEach(documentId -> members.add(
+ closureId(documentId)));
+
+ ArrayList rows = new ArrayList<>(
+ original.snapshot().occurrences());
+ for (ContractsManagedDraftPlan.ExpectedOccurrence expectation
+ : plan.expectedOccurrences()) {
+ ContractsManagedDraftPlan.ManagedDraft draft = plan.drafts().get(
+ expectation.targetDocumentId());
+ rows.add(ManagedOccurrenceBinding.derived(
+ original.environment().managedBindingPolicyIdentity(),
+ closureId(plan.targetDocumentId()),
+ ScopeAddress.embedded(expectation.path(), 1L),
+ closureId(expectation.targetDocumentId()),
+ draft.initial().blueId(),
+ false,
+ null));
+ }
+
+ LinkedHashMap
+ bodies = new LinkedHashMap<>();
+ LinkedHashMap
+ generations = new LinkedHashMap<>();
+ LinkedHashMap existing = new LinkedHashMap<>();
+ for (ManagedDocumentSnapshot document
+ : original.snapshot().managedDocuments()) {
+ bodies.put(document.documentId(), document.document());
+ generations.put(
+ document.documentId(), document.componentGeneration());
+ existing.put(document.documentId(), document);
+ }
+ plan.drafts().forEach((documentId, draft) -> {
+ blue.language.processor.closure.DocumentId draftId = closureId(
+ documentId);
+ bodies.put(draftId, draft.initial().copyNode());
+ generations.put(draftId, 1L);
+ });
+
+ ManagedDocumentGraph graph = ManagedDocumentGraph.fromBindings(
+ members, rows);
+ ComponentFinalizationResult finalization =
+ new ComponentFinalizationKernel().finalizeComponents(
+ new ComponentFinalizationInput(
+ graph, generations, bodies, rows));
+
+ ArrayList compiledDocuments =
+ new ArrayList<>();
+ finalization.documents().forEach((documentId, exact) -> {
+ ManagedDocumentSnapshot prior = existing.get(documentId);
+ if (prior != null) {
+ if (!prior.blueId().equals(exact.blueId())) {
+ throw new IllegalStateException(
+ "Managed expansion changed an existing input head "
+ + documentId);
+ }
+ compiledDocuments.add(new ManagedDocumentSnapshot(
+ documentId,
+ exact.blueId(),
+ exact.document(),
+ prior.initialized(),
+ prior.terminated(),
+ prior.publicRoot(),
+ prior.epoch(),
+ exact.componentGeneration()));
+ return;
+ }
+ ContractsManagedDraftPlan.ManagedDraft draft = plan.drafts().get(
+ coordinationId(documentId));
+ if (draft == null
+ || !draft.initial().blueId().equals(exact.blueId())) {
+ throw new IllegalStateException(
+ "Managed draft input is not an exact isolated Root "
+ + documentId);
+ }
+ compiledDocuments.add(new ManagedDocumentSnapshot(
+ documentId,
+ exact.blueId(),
+ exact.document(),
+ false,
+ false,
+ false,
+ 0L,
+ exact.componentGeneration()));
+ });
+ List components = finalization.components()
+ .stream()
+ .map(component -> component.component())
+ .toList();
+ AffectedClosureSnapshot snapshot = ClosureEvidenceFactory
+ .affectedClosure(
+ original.snapshot().graphGeneration(),
+ compiledDocuments,
+ finalization.finalizedGraph().bindings(),
+ components,
+ original.snapshot().publicRootDocumentIds());
+ ClosureInvocationInput expanded = ClosureEvidenceFactory
+ .processClosure(
+ snapshot,
+ original.cause(),
+ original.directDeliveries(),
+ original.executionPolicy(),
+ original.environment());
+ return new CohortInvocation(
+ coordinationIds(graph.documentIds()),
+ base.directDeliveries(),
+ expanded,
+ base.documents(),
+ plan);
+ }
+
+ private void validateManagedDraftDeclarations(
+ CohortInvocation base,
+ ContractsManagedDraftPlan plan,
+ ExactValue exactRequest) {
+ CapturedDocument target = base.documents().get(
+ plan.targetDocumentId());
+ for (DocumentId documentId : plan.drafts().keySet()) {
+ if (profile.isPublicRoot(documentId)) {
+ throw new IllegalArgumentException(
+ "Managed draft expansion cannot create a public Root "
+ + documentId);
+ }
+ }
+ plan.managedRequestFields().forEach((field, documentId) -> {
+ String requestBlueId = exactRequest.canonicalBlueIdAt(
+ PointerUtils.appendPointer("/", field));
+ String draftBlueId = plan.drafts().get(documentId)
+ .initial().blueId();
+ if (!draftBlueId.equals(requestBlueId)) {
+ throw new IllegalArgumentException(
+ "Managed request field " + field
+ + " does not retain exact draft "
+ + documentId);
+ }
+ });
+ validateManagedDraftExpectationPaths(plan, target.current());
+ }
+
+ private void validateManagedDraftExpectationPaths(
+ ContractsManagedDraftPlan plan,
+ ExactValue target) {
+ EffectiveFragmentationCatalog catalog = runtime
+ .effectiveFragmentationCatalog(Objects.requireNonNull(
+ target, "target").blueId());
+ for (ContractsManagedDraftPlan.ExpectedOccurrence expectation
+ : plan.expectedOccurrences()) {
+ ArrayList matches = new ArrayList<>();
+ for (EmbeddedScopePlanView scope
+ : catalog.scopePlansByScope().values()) {
+ for (String declaration
+ : scope.explicitDeclarationPaths()) {
+ String absolute = PointerUtils.resolvePointer(
+ scope.scopePath(), declaration);
+ if (absolute.equals(expectation.path())) {
+ matches.add("path " + absolute);
+ }
+ }
+ for (String declaration
+ : scope.collectionDeclarationPaths()) {
+ String absolute = PointerUtils.resolvePointer(
+ scope.scopePath(), declaration);
+ if (isDirectCollectionMember(
+ absolute, expectation.path())) {
+ matches.add("collectionPath " + absolute);
+ }
+ }
+ }
+ if (matches.size() != 1) {
+ throw new IllegalArgumentException(
+ "Expected managed occurrence "
+ + plan.targetDocumentId()
+ + expectation.path()
+ + " must match exactly one effective Process "
+ + "Embedded declaration; found " + matches);
+ }
+ }
+ }
+
+ private static boolean isDirectCollectionMember(
+ String collectionPath,
+ String candidatePath) {
+ List collection = JsonPointer.split(collectionPath);
+ List candidate = JsonPointer.split(candidatePath);
+ return candidate.size() == collection.size() + 1
+ && candidate.subList(0, collection.size()).equals(collection);
+ }
+
+ private CapturedDocument captureDocument(
+ DocumentId documentId,
+ InMemoryDocumentStore.DocumentHead expectedHead,
+ Set allowedMembers) {
+ runtime.metrics().increment(DOCUMENT_OPENS);
+ if (!Objects.requireNonNull(allowedMembers, "allowedMembers")
+ .contains(documentId)) {
+ runtime.metrics().increment(UNRELATED_DOCUMENT_OPENS);
+ runtime.metrics().increment("temporal.unrelatedDocumentReads");
+ }
+ DocumentSession session = documents.require(documentId);
+ synchronized (session) {
+ InMemoryDocumentStore.DocumentHead actualHead =
+ new InMemoryDocumentStore.DocumentHead(
+ session.epoch(),
+ session.currentRevision().after().blueId());
+ if (!expectedHead.equals(actualHead)) {
+ throw stale("Document head changed during closure capture for "
+ + documentId);
+ }
+ ExactValue current = session.currentRevision().after();
+ if (!current.blueId().equals(session.layout().rootBlueId())) {
+ throw new IllegalStateException(
+ "Session layout disagrees with the durable head for "
+ + documentId);
+ }
+ boolean initialized = runtime.documentProcessor().isInitialized(
+ current.copyNode());
+ boolean terminated = session.status() == SessionStatus.TERMINATED;
+ return new CapturedDocument(
+ documentId,
+ actualHead,
+ current,
+ session.layout(),
+ session.activeSubscriptions(),
+ session.nextApplicationOrder(),
+ initialized,
+ terminated);
+ }
+ }
+
+ private List captureComponents(
+ InMemoryDocumentStore.ClosureSnapshot publication,
+ List indexedComponents,
+ Map documentsById) {
+ Map, ComponentSnapshot> byMembers =
+ new LinkedHashMap<>();
+ for (ComponentSnapshot component : publication.componentStates()) {
+ runtime.metrics().increment(COMPONENT_STATES_READ);
+ List members = coordinationIds(
+ component.orderedMemberDocumentIds());
+ if (byMembers.putIfAbsent(members, component) != null) {
+ throw new IllegalStateException(
+ "Duplicate durable component state for " + members);
+ }
+ }
+ List result = new ArrayList<>();
+ for (ProcessEmbeddedComponentIndex.Component indexed
+ : indexedComponents) {
+ ComponentSnapshot state = byMembers.get(indexed.members());
+ if (state == null) {
+ throw new ProjectionUnavailableException(
+ "No Contracts component state is durable for "
+ + indexed.members());
+ }
+ ComponentKind expectedKind = indexed.cyclic()
+ ? ComponentKind.CYCLIC : ComponentKind.ACYCLIC;
+ if (state.kind() != expectedKind) {
+ throw new IllegalStateException(
+ "Durable component kind disagrees with topology for "
+ + indexed.members());
+ }
+ for (int index = 0; index < indexed.members().size(); index++) {
+ DocumentId member = indexed.members().get(index);
+ CapturedDocument document = documentsById.get(member);
+ if (document == null
+ || !document.head().blueId().equals(
+ state.orderedMemberBlueIds().get(index))) {
+ throw stale("Durable component state is stale for "
+ + member);
+ }
+ }
+ result.add(state);
+ }
+ return List.copyOf(result);
+ }
+
+ private void publish(
+ FrozenBatch batch,
+ CohortInvocation invocation,
+ ContractsClosurePublicationReceipt receipt) {
+ ClosureProcessResult result = receipt.attempt().processResult();
+ if (!receipt.publicationIdentity().equals(
+ publicationIdentity(batch, invocation))) {
+ throw new IllegalArgumentException(
+ "Process receipt identity does not identify this cohort");
+ }
+ requirePublishableResult(batch, invocation, result);
+ InMemoryDocumentStore.ClosureSnapshot current =
+ documents.closureSnapshot(invocation.existingMemberSet());
+ requireCohortStillCurrent(invocation, current);
+ ManagedOccurrenceInventory resultingInventory = mergeInventory(
+ current.occurrenceInventory(),
+ invocation.memberSet(),
+ result.occurrenceBindings());
+ long resultingInventoryGeneration = transitionGeneration(
+ current.occurrenceInventoryGeneration(),
+ !sameInventory(
+ current.occurrenceInventory(),
+ resultingInventory),
+ "occurrence inventory generation");
+ boolean topologyChanged = !sameActiveTopology(
+ current.occurrenceInventory(),
+ resultingInventory);
+ long resultingComponentIndexGeneration = transitionGeneration(
+ current.componentIndexGeneration(),
+ topologyChanged,
+ "component index generation");
+ String publicationIdentity = receipt.publicationIdentity();
+ MultiDocumentPublicationTransaction transaction = documents
+ .beginAtomicPublication(
+ publicationIdentity,
+ current.occurrenceInventoryGeneration(),
+ current.componentIndexGeneration());
+ for (CapturedDocument document
+ : invocation.documents().values()) {
+ transaction.expectHead(
+ document.documentId(),
+ document.head().epoch(),
+ document.head().blueId());
+ }
+ if (invocation.managedExpansion()) {
+ invocation.newMemberSet().forEach(transaction::expectAbsent);
+ }
+ invocation.input().snapshot().components().forEach(
+ component -> {
+ boolean existing = component.orderedMemberDocumentIds()
+ .stream().allMatch(member -> invocation
+ .existingMemberSet().contains(
+ coordinationId(member)));
+ if (existing) {
+ transaction.expectComponentState(component);
+ }
+ });
+ if (invocation.managedExpansion() || !sameInventory(
+ current.occurrenceInventory(),
+ resultingInventory)) {
+ transaction.stageOccurrenceInventory(
+ resultingInventory,
+ resultingInventoryGeneration,
+ resultingComponentIndexGeneration);
+ }
+ transaction.stageComponentStates(result.resultingComponents());
+ if (invocation.managedExpansion()) {
+ transaction.stageManagedExpansionResult(
+ invocation.input(), result);
+ } else {
+ transaction.stageClosureGraphGeneration(result);
+ transaction.stageClosureSubscriptionDeltas(result);
+ }
+ transaction.stageOutbox(result.publicEvents());
+ transaction.stageCheckpointEvidence(result.checkpointWrites());
+ transaction.stageClosurePublicationReceipt(receipt);
+
+ Map resultingDocuments =
+ resultingDocuments(result, invocation.memberSet());
+ Map gasByDocument = gasByDocument(
+ result, resultingDocuments.keySet());
+ ContractsStructuralWorkMetrics.recordGlobalPasses(
+ runtime.metrics(),
+ ContractsStructuralWorkMetrics
+ .GLOBAL_SUBSCRIPTION_ENTRIES_TRAVERSED,
+ 3L,
+ Math.multiplyExact(
+ current.closureSubscriptions().states().size(),
+ 3L));
+ ClosureSubscriptionInventory resultingClosureSubscriptions =
+ current.closureSubscriptions().apply(result);
+ WholeObjectStore.Mark objectMark = objects.mark();
+ boolean storeCommitted = false;
+ try {
+ List routeReplacements =
+ new ArrayList<>();
+ for (Map.Entry entry
+ : resultingDocuments.entrySet()) {
+ CapturedDocument before = invocation.documents().get(
+ entry.getKey());
+ ResultingDocument after = entry.getValue();
+ if (before == null) {
+ ContractsManagedDraftPlan.ManagedDraft draft = invocation
+ .managedDraftPlan().drafts().get(entry.getKey());
+ if (draft == null || after.epoch() != 0L
+ || !after.initialized()) {
+ throw new ProjectionUnavailableException(
+ "Managed expansion did not initialize new Root "
+ + entry.getKey());
+ }
+ ManagedRootSubscriptionSurface projected = contracts
+ .projectRootSubscriptionSurface(after.document());
+ RoutingSurface routingSurface = RoutingSurface
+ .fromManagedRootContracts(
+ projected.effectiveRootContracts());
+ EmbeddedOnlyLayout layout = layoutBuilder
+ .retainVerifiedClosureRoot(
+ result,
+ entry.getKey(),
+ routingSurface);
+ requireExactRootSubscriptionSurface(
+ entry.getKey(),
+ projected,
+ subscriptionStatesFor(
+ resultingClosureSubscriptions,
+ entry.getKey()));
+ List activeSubscriptions =
+ activateInitialSubscriptions(
+ projected.externalSubscriptions(),
+ batch.entry().sourceOrderKey());
+ CheckpointDomainEvidence.retainAll(
+ activeSubscriptions, objects);
+ ExactValue authored = objects.put(
+ draft.initial(),
+ "verified-managed-expansion-input");
+ List emitted = result.publicEvents().stream()
+ .filter(event -> event.publicRootDocumentId()
+ .value().equals(entry.getKey().value()))
+ .map(PublicEventOccurrence::event)
+ .toList();
+ DocumentRevision revision = new DocumentRevision(
+ entry.getKey(),
+ 0L,
+ 0L,
+ DocumentRevision.Kind.INITIALIZATION,
+ authored,
+ layout.semanticRoot(),
+ null,
+ batch.entry().sourceOrderKey(),
+ batch.entry().blueId(),
+ null,
+ emitted,
+ gasByDocument.getOrDefault(
+ entry.getKey(), 0L));
+ DocumentSession session = new DocumentSession(
+ entry.getKey(),
+ authored,
+ layout,
+ activeSubscriptions,
+ batch.entry().sourceOrderKey(),
+ revision);
+ session.restoreCoordinationState(
+ after.terminated()
+ ? SessionStatus.TERMINATED
+ : SessionStatus.READY,
+ batch.entry().sourceOrderKey(),
+ 0L,
+ 0L);
+ transaction.stageNewSession(session);
+ routeReplacements.add(
+ new OperationRouteIndex.Replacement(
+ entry.getKey(),
+ layout.routingSurface(),
+ activeSubscriptions));
+ continue;
+ }
+ boolean changed = requiresDocumentPublication(before, after);
+ ManagedRootSubscriptionSurface projected = contracts
+ .projectRootSubscriptionSurface(after.document());
+ RoutingSurface routingSurface = RoutingSurface
+ .fromManagedRootContracts(
+ projected.effectiveRootContracts());
+ EmbeddedOnlyLayout layout = changed
+ ? layoutBuilder.retainVerifiedClosureRoot(
+ result,
+ entry.getKey(),
+ before.layout(),
+ routingSurface)
+ : before.layout();
+ requireExactRootSubscriptionSurface(
+ entry.getKey(),
+ projected,
+ subscriptionStatesFor(
+ resultingClosureSubscriptions,
+ entry.getKey()));
+ List activeSubscriptionsAfter;
+ if (changed) {
+ SubscriptionDelta routeDelta = routeDelta(
+ before.activeSubscriptions(),
+ projected.externalSubscriptions(),
+ after.epoch(),
+ batch.entry().sourceOrderKey());
+ activeSubscriptionsAfter = DocumentTransitionProcessor
+ .applyManagedRootSubscriptionDelta(
+ before.activeSubscriptions(),
+ routeDelta,
+ after.epoch(),
+ batch.entry().sourceOrderKey(),
+ runtime.metrics());
+ } else {
+ requireUnchangedRouteSurface(
+ entry.getKey(),
+ before.activeSubscriptions(),
+ projected.externalSubscriptions());
+ activeSubscriptionsAfter = before.activeSubscriptions();
+ }
+ CheckpointDomainEvidence.retainAll(
+ activeSubscriptionsAfter, objects);
+ routeReplacements.add(new OperationRouteIndex.Replacement(
+ entry.getKey(),
+ layout.routingSurface(),
+ activeSubscriptionsAfter));
+ if (!changed) {
+ continue;
+ }
+ ExactValue exact = objects.put(
+ layout.semanticRoot(),
+ "closure-document-revision");
+ List emitted = result.publicEvents()
+ .stream()
+ .filter(event -> event.publicRootDocumentId().value()
+ .equals(entry.getKey().value()))
+ .map(PublicEventOccurrence::event)
+ .toList();
+ DocumentRevision revision = new DocumentRevision(
+ entry.getKey(),
+ after.epoch(),
+ before.nextApplicationOrder(),
+ DocumentRevision.Kind.TIMELINE_ENTRY,
+ before.current(),
+ exact,
+ batch.entry(),
+ null,
+ emitted,
+ gasByDocument.getOrDefault(entry.getKey(), 0L));
+ transaction.stageDocument(
+ revision,
+ layout,
+ batch.entry().sourceOrderKey(),
+ activeSubscriptionsAfter,
+ publicationIdentity + "|"
+ + entry.getKey().value());
+ }
+ requireRouteSelectionCurrent(batch, invocation);
+ OperationRouteIndex.PreparedReplacement preparedRoutes =
+ routes.prepareReplacement(routeReplacements);
+ transaction.commit();
+ storeCommitted = true;
+ publicationFailureInjector.accept(
+ PublicationFailurePoint
+ .AFTER_STORE_COMMIT_BEFORE_ROUTE_PUBLISH);
+ preparedRoutes.publish();
+ activeSourceTimelines.refresh(invocation.members(), documents);
+ objects.commit(objectMark);
+ } catch (RuntimeException failure) {
+ if (storeCommitted) {
+ // Durable sessions may already reference these exact values.
+ // Preserve them so receipt reconciliation can safely rebuild
+ // an interrupted route-cache publication.
+ objects.commit(objectMark);
+ } else {
+ objects.rollbackTo(objectMark);
+ }
+ throw failure;
+ }
+ }
+
+ private static void requirePublishableResult(
+ FrozenBatch batch,
+ CohortInvocation invocation,
+ ClosureProcessResult result) {
+ if (!result.commits()
+ || result.platformCommitCompanion() == null) {
+ throw new IllegalArgumentException(
+ "Only a committing result can be published");
+ }
+ if (!result.invocationIdentity().equals(
+ invocation.input().invocationIdentity())
+ || !result.inputClosureIdentity().equals(
+ invocation.input().snapshot().closureIdentity())) {
+ throw new IllegalStateException(
+ "Closure result does not belong to the captured input");
+ }
+ ClosureCommitCompanion companion = result.platformCommitCompanion();
+ if (companion.expectedInputGraphGeneration()
+ != invocation.input().snapshot().graphGeneration()) {
+ throw new IllegalStateException(
+ "Commit companion graph fence is stale");
+ }
+ Map expectedDocuments = new TreeMap<>(
+ EmbeddingBinding.DOCUMENT_ORDER);
+ companion.expectedInputDocuments().forEach(document ->
+ expectedDocuments.put(
+ coordinationId(document.documentId()),
+ document.blueId()));
+ Map inputDocuments = new TreeMap<>(
+ EmbeddingBinding.DOCUMENT_ORDER);
+ invocation.input().snapshot().managedDocuments().forEach(document ->
+ inputDocuments.put(
+ coordinationId(document.documentId()),
+ document.blueId()));
+ if (!expectedDocuments.equals(inputDocuments)) {
+ throw new IllegalStateException(
+ "Commit companion input-document fences are incomplete");
+ }
+ invocation.documents().forEach((documentId, document) ->
+ {
+ if (!document.head().blueId().equals(
+ inputDocuments.get(documentId))) {
+ throw new IllegalStateException(
+ "Captured durable head differs from managed "
+ + "expansion input " + documentId);
+ }
+ });
+ if (invocation.managedExpansion()) {
+ requireManagedExpansionResult(invocation, result);
+ }
+ }
+
+ private static void requireManagedExpansionResult(
+ CohortInvocation invocation,
+ ClosureProcessResult result) {
+ Map documents = resultingDocuments(
+ result, invocation.memberSet());
+ for (DocumentId documentId : invocation.newMemberSet()) {
+ ContractsManagedDraftPlan.ManagedDraft draft = invocation
+ .managedDraftPlan().drafts().get(documentId);
+ ResultingDocument initialized = documents.get(documentId);
+ if (draft == null
+ || !draft.initial().blueId().equals(
+ initialized.beforeBlueId())
+ || initialized.epoch() != 0L
+ || !initialized.initialized()) {
+ throw new IllegalStateException(
+ "Managed draft was not initialized exactly once "
+ + documentId);
+ }
+ }
+ ResultingDocument source = documents.get(
+ invocation.managedDraftPlan().targetDocumentId());
+ for (ContractsManagedDraftPlan.ExpectedOccurrence expectation
+ : invocation.managedDraftPlan().expectedOccurrences()) {
+ ResultingDocument target = documents.get(
+ expectation.targetDocumentId());
+ List prospectiveRows = invocation.input()
+ .snapshot().occurrences().stream()
+ .filter(row -> !row.active()
+ && row.sourceDocumentId().value().equals(
+ source.documentId().value())
+ && row.sourcePath().equals(expectation.path())
+ && row.targetDocumentId().value().equals(
+ expectation.targetDocumentId().value()))
+ .toList();
+ if (prospectiveRows.size() != 1) {
+ throw new IllegalStateException(
+ "Managed expansion input has no unique prospective "
+ + "occurrence at " + expectation.path());
+ }
+ ManagedOccurrenceBinding prospective = prospectiveRows.get(0);
+ List matches = result
+ .occurrenceBindings().stream()
+ .filter(row -> row.sourceDocumentId().value().equals(
+ source.documentId().value())
+ && row.sourcePath().equals(expectation.path()))
+ .toList();
+ Node exact = NodePathEditor.getOrNull(
+ source.document(), expectation.path());
+ if (matches.size() != 1
+ || !matches.get(0).active()
+ || !matches.get(0).occurrenceIdentity().equals(
+ prospective.occurrenceIdentity())
+ || !matches.get(0).targetDocumentId().value().equals(
+ expectation.targetDocumentId().value())
+ || !matches.get(0).expectedTargetBlueId().equals(
+ target.afterBlueId())
+ || exact == null
+ || !target.afterBlueId().equals(exact.getBlueId())) {
+ throw new IllegalStateException(
+ "Managed occurrence was not established exactly at "
+ + expectation.path());
+ }
+ }
+ }
+
+ private static void requireExactRootSubscriptionSurface(
+ DocumentId documentId,
+ ManagedRootSubscriptionSurface projected,
+ List