Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 31 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,25 +132,39 @@ Install an application-owned key provider at the runtime boundary:

```java
byte[] activeKey = loadThirtyTwoByteKeyFromSecretManager();
EntityReferenceCodec codec = new AeadEntityReferenceCodec(
2, Map.of(2, activeKey));
userContext.withEntityReferenceCodec(codec);

String token = userContext.encodeEntityReference(
"OrderItem", 42L, 7L, "edit-order", Duration.ofMinutes(15));
EntityReferenceClaims claims = userContext.decodeEntityReference(
token, "OrderItem", "edit-order");
var provider = AeadRoundTripReferenceProvider.fromProcessEnvironment(
DeploymentProfile.PRODUCTION,
"order-service",
"production",
new StaticRoundTripReferenceKeyProvider(
new RoundTripReferenceKey("k2", activeKey)),
currentAuthorizationPolicy);
var runtime = TeaQLRuntime.builder()
.roundTripReferenceProvider(provider)
.build();
userContext.withTrustedReferencePrincipal(
new TrustedReferencePrincipal("oidc", "alice", "Platform", 7));
var scope = new ReferenceDocumentScope(
"order-editor-100", "edit-order", "Order", 100, 9);

Object wire = userContext.referenceFor(orderItem, scope, Duration.ofMinutes(15));
ResolvedRoundTripReference resolved = userContext.resolveReference(
wire, scope, "OrderItem");
```

The token uses AES-256-GCM, carries a key version for rotation, expires, and is
bound to both entity type and purpose. Invalid, expired, substituted, or
tampered tokens all fail with `ENTITY_REFERENCE_INVALID`; a missing codec fails
with `ENTITY_REFERENCE_CODEC_REQUIRED`. Raw `tqr0.` references are available
only after setting the deliberately long local-development acknowledgement
documented in the canonical
[opaque entity reference contract](https://github.com/teaql/teaql-conformance/blob/main/design/opaque-entity-references.md).
Tokens complement authorization; they do not replace tenant, ownership, role,
or optimistic-lock checks.
The `tqr1` token uses AES-256-GCM with HKDF-SHA-256, carries a key ID for
rotation, expires, and binds Actor, Domain Root, service, environment, purpose,
document, Aggregate identity/revision, entity type/ID/version. Current
authorization runs both when issuing and consuming the reference. Java and
Rust retain the same deterministic golden vector.

For local diagnosis only, the exact
`TEAQL_UNSAFE_EXPOSE_RAW_ENTITY_IDS=I_UNDERSTAND_THIS_EXPOSES_INTERNAL_ENTITY_IDS_FOR_LOCAL_DEBUGGING_ONLY`
acknowledgement changes the startup-selected wire shape to `{id, version}` in
development/test. Production fails closed; raw mode does not bypass current
authorization, type, version, projection, Checker/Fix, audit, or Mutation
Ledger enforcement. See the canonical
[context-bound reference contract](https://github.com/teaql/teaql-conformance/blob/main/design/context-bound-round-trip-references.md).

## Choose Modules

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Java context-bound Round-Trip Reference evidence

Date: 2026-09-27

Tracking: [teaql-java#175](https://github.com/teaql/teaql-java/issues/175)

Baseline: `origin/main` at `9ca9e261a7b8c5f39292374ac7eab2c7cab303d6`.

Scope: the bounded `tqr1` independent-reference core. This evidence does not
claim a complete encrypted document manifest or generated `openDocument` /
`acceptDocument` adapters.

## Implemented boundary

- high-level `UserContext::referenceFor` / `resolveReference` boundary;
- trusted authentication realm, subject and Domain Root identity supplied by
runtime assembly, never accepted from the wire;
- service/environment, purpose, document, Aggregate type/ID/revision and
entity type/ID/version binding;
- AES-256-GCM, 96-bit CSPRNG nonce, HKDF-SHA-256 and unpadded Base64URL;
- current encryption key plus decode-only previous keys;
- expiry, service/environment isolation, tamper rejection, fresh nonces and
restart/rotation continuity;
- current authorization at issue and consume time;
- one runtime-selected diagnostic raw mode with an exact long acknowledgement,
production failure, explicit ERROR notice, telemetry and response metadata;
- removal of the earlier weaker codec/raw-fallback path so there is one public
Round-Trip Reference contract.

## Cross-language vector

The fixed inputs used by Rust and Java are identical: service `order-service`,
environment `test-a`, current key `k2` filled with `0x22`, timestamp
`2026-09-27T00:00:00Z`, nonce filled with `0x33`, principal
`oidc/alice/Platform#7`, scope `doc-100/edit-order/Order#100@9`, identity
`OrderItem#42@3`, and lifetime 600 seconds.

Both runtimes assert this exact token:

```text
tqr1.k2.MzMzMzMzMzMzMzMzmhCET15vBdSc1BbFJcrvMMnRP1wAyjunShYlFPnDZL7Gk5V1LuptWyrbiQ8LQSDHFUmN3o6vb-MEgXZuh3N-YkeO55CYLeVgiyBBYslzuSu-fvWC2KMZcnZ5D8BRmWpGYA2Ok5sWe1Fg1aIyadTjVoKB-H2vPW_0okj-
```

## RAW-01 through RAW-09

`ContextBoundRoundTripReferenceTest` contains individually named deterministic
tests for all nine cases: default governed mode, exact development/test opt-in,
near-match rejection, production failure, current authorization after actor
change, mode/wire-shape mismatch, identity/version preservation, and safe
downgrade metadata.

## Commands

```text
mvn -pl teaql-runtime -am \
-Dtest=ContextBoundRoundTripReferenceTest \
-Dsurefire.failIfNoSpecifiedTests=false test

mvn -pl examples/round-trip-reference-runtime -am test
mvn clean verify
```

The focused runtime suite reports 12 passed tests. The full clean reactor
reports 39/39 modules successful, including 197 core tests, 48 runtime tests,
the migrated security-foundations example, the standalone Round-Trip Reference
example, all database dialect modules, TFP, telemetry, and generated
conformance workspaces. One external OTLP smoke test is skipped because its
external endpoint is not configured. No release is produced by this work.
21 changes: 21 additions & 0 deletions examples/round-trip-reference-runtime/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Round-Trip Reference runtime example

This example proves that the reusable runtime serialization boundary can be
called by any Web adapter without depending on Spring, JAX-RS, Servlet, or a
specific JSON library.

It covers governed issue/resolve through `UserContext`, trusted Actor and
Domain Root binding, purpose/document/Aggregate scope, tamper rejection,
expected-type validation, expiry, key rotation, current authorization at issue
and consume time, and the explicit development/test-only raw diagnostic shape.

The core deliberately does not define tenant or role semantics. Runtime
assembly installs the trusted principal and application-owned authorization
policy. The focused runtime tests retain the same deterministic `tqr1` golden
vector as TeaQL Rust and enumerate RAW-01 through RAW-09.

Run locally against repository sources:

```bash
mvn -pl examples/round-trip-reference-runtime -am test
```
25 changes: 25 additions & 0 deletions examples/round-trip-reference-runtime/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.teaql</groupId>
<artifactId>teaql-java-parent</artifactId>
<version>1.550-RELEASE</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>teaql-round-trip-reference-example</artifactId>
<name>TeaQL Round-Trip Reference focused example</name>
<dependencies>
<dependency>
<groupId>io.teaql</groupId>
<artifactId>teaql-runtime</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package io.teaql.examples.reference;

import io.teaql.core.BaseEntity;
import io.teaql.core.UserContext;
import io.teaql.core.meta.SimpleEntityMetaFactory;
import io.teaql.core.reference.ReferenceDocumentScope;
import io.teaql.core.reference.RoundTripReferenceErrorCode;
import io.teaql.core.reference.RoundTripReferenceException;
import io.teaql.core.reference.TrustedReferencePrincipal;
import io.teaql.runtime.DefaultUserContext;
import io.teaql.runtime.TeaQLRuntime;
import io.teaql.runtime.reference.AeadRoundTripReferenceProvider;
import io.teaql.runtime.reference.DeploymentProfile;
import io.teaql.runtime.reference.RoundTripReferenceKey;
import io.teaql.runtime.reference.StaticRoundTripReferenceKeyProvider;
import java.time.Duration;
import java.util.Arrays;
import org.junit.Assert;
import org.junit.Test;

public class RoundTripReferenceRuntimeExampleTest {
private static final class OrderItem extends BaseEntity {
OrderItem(long id, long version) {
__internalSet("id", id);
__internalSet("version", version);
}

@Override public String typeName() { return "OrderItem"; }
}

@Test
public void contextApiIssuesAndConsumesAnOpaqueReference() {
byte[] current = new byte[32];
byte[] previous = new byte[32];
Arrays.fill(current, (byte) 0x22);
Arrays.fill(previous, (byte) 0x11);
AeadRoundTripReferenceProvider provider = AeadRoundTripReferenceProvider.governed(
DeploymentProfile.DEVELOPMENT,
"order-service",
"local",
new StaticRoundTripReferenceKeyProvider(
new RoundTripReferenceKey("k2", current),
new RoundTripReferenceKey("k1", previous)),
(context, principal, scope, identity) -> {
if (!"alice".equals(principal.subject())
|| !"OrderItem".equals(identity.entityType())) {
throw new RoundTripReferenceException(
RoundTripReferenceErrorCode.AUTHORIZATION_REQUIRED,
"current authorization rejected the entity reference");
}
});
TeaQLRuntime runtime = TeaQLRuntime.builder()
.metadata(new SimpleEntityMetaFactory())
.roundTripReferenceProvider(provider)
.build();
UserContext context = new DefaultUserContext(runtime).withTrustedReferencePrincipal(
new TrustedReferencePrincipal("oidc", "alice", "Platform", 7));
ReferenceDocumentScope scope = new ReferenceDocumentScope(
"order-editor-100", "edit-order", "Order", 100, 9);

Object wire = context.referenceFor(new OrderItem(42, 3), scope, Duration.ofMinutes(15));
Assert.assertTrue(wire instanceof String);
Assert.assertTrue(wire.toString().startsWith("tqr1.k2."));
Assert.assertFalse(wire.toString().contains("OrderItem"));
Assert.assertEquals(42, context.resolveReference(wire, scope, "OrderItem").id());

ReferenceDocumentScope anotherDocument = new ReferenceDocumentScope(
"order-editor-101", "edit-order", "Order", 100, 9);
Assert.assertThrows(
RoundTripReferenceException.class,
() -> context.resolveReference(wire, anotherDocument, "OrderItem"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,58 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;

import io.teaql.core.EntityReferenceTokenException;
import io.teaql.core.BaseEntity;
import io.teaql.core.UserContext;
import io.teaql.core.meta.SimpleEntityMetaFactory;
import io.teaql.core.reference.ReferenceDocumentScope;
import io.teaql.core.reference.RoundTripReferenceException;
import io.teaql.core.reference.TrustedReferencePrincipal;
import io.teaql.runtime.DefaultUserContext;
import io.teaql.runtime.security.AeadEntityReferenceCodec;
import java.time.Clock;
import io.teaql.runtime.TeaQLRuntime;
import io.teaql.runtime.reference.AeadRoundTripReferenceProvider;
import io.teaql.runtime.reference.DeploymentProfile;
import io.teaql.runtime.reference.RoundTripReferenceKey;
import io.teaql.runtime.reference.StaticRoundTripReferenceKeyProvider;
import java.time.Duration;
import java.time.Instant;
import java.time.ZoneOffset;
import java.util.Arrays;
import java.util.Map;
import org.junit.Test;

public class SecurityFoundationsExampleTest {
private static final class OrderItem extends BaseEntity {
OrderItem(long id, long version) {
__internalSet("id", id);
__internalSet("version", version);
}

@Override public String typeName() { return "OrderItem"; }
}

@Test
public void userContextProtectsBoundaryReferences() {
byte[] key = new byte[32];
byte[] nonce = new byte[12];
Arrays.fill(key, (byte) 0x22);
Arrays.fill(nonce, (byte) 0x33);
AeadEntityReferenceCodec codec = new AeadEntityReferenceCodec(2, Map.of(2, key))
.withClock(Clock.fixed(Instant.parse("2026-09-26T12:00:00Z"), ZoneOffset.UTC))
.withNonceSource(() -> nonce.clone());
DefaultUserContext context = new DefaultUserContext(null);
context.withEntityReferenceCodec(codec);
String token = context.encodeEntityReference("OrderItem", 42, 7, "edit-order", Duration.ofHours(1));
assertEquals(42, context.decodeEntityReference(token, "OrderItem", "edit-order").id());
assertThrows(EntityReferenceTokenException.class,
() -> context.decodeEntityReference(token, "OrderItem", "view-order"));
System.out.println("PASS Java security foundations: opaque reference is portable and purpose-bound");
AeadRoundTripReferenceProvider provider = AeadRoundTripReferenceProvider.governed(
DeploymentProfile.TEST,
"order-service",
"test-a",
new StaticRoundTripReferenceKeyProvider(new RoundTripReferenceKey("k2", key)),
(context, principal, scope, identity) -> {});
TeaQLRuntime runtime = TeaQLRuntime.builder()
.metadata(new SimpleEntityMetaFactory())
.roundTripReferenceProvider(provider)
.build();
UserContext context = new DefaultUserContext(runtime).withTrustedReferencePrincipal(
new TrustedReferencePrincipal("oidc", "alice", "Platform", 7));
ReferenceDocumentScope scope = new ReferenceDocumentScope(
"doc-100", "edit-order", "Order", 100, 9);

Object token = context.referenceFor(new OrderItem(42, 7), scope, Duration.ofHours(1));
assertEquals(42, context.resolveReference(token, scope, "OrderItem").id());
assertThrows(RoundTripReferenceException.class,
() -> context.resolveReference(
token,
new ReferenceDocumentScope("doc-100", "view-order", "Order", 100, 9),
"OrderItem"));
System.out.println("PASS Java security foundations: context-bound reference is opaque and purpose-bound");
}
}
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
<module>teaql-business-id-jdbc</module>
<module>examples/business-id-runtime</module>
<module>examples/security-foundations</module>
<module>examples/round-trip-reference-runtime</module>
<module>teaql-tfp-endpoint</module>
<module>teaql-opentelemetry</module>
<module>examples/conformance/lib</module>
Expand Down
4 changes: 3 additions & 1 deletion scripts/verify-examples.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ set -euo pipefail
repo="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
verification_dir="$(mktemp -d)"
trap 'rm -rf -- "$verification_dir"' EXIT
expected=(business-id-runtime conformance order-management school-management security-foundations)
expected=(business-id-runtime conformance order-management round-trip-reference-runtime school-management security-foundations)
mapfile -t actual < <(find "$repo/examples" -mindepth 1 -maxdepth 1 -type d -printf '%f\n' | sort)
if [[ "${actual[*]}" != "${expected[*]}" ]]; then
echo "example inventory changed; update scripts/verify-examples.sh: ${actual[*]}" >&2
Expand All @@ -17,6 +17,8 @@ mvn -q -pl examples/business-id-runtime \
-Dtest=BusinessIdRuntimeExampleTest test
mvn -q -pl examples/security-foundations \
-Dtest=SecurityFoundationsExampleTest test
mvn -q -pl examples/round-trip-reference-runtime \
-Dtest=RoundTripReferenceRuntimeExampleTest test
mvn -q -f examples/conformance/lib/pom.xml install -DskipTests
mvn -q -f examples/conformance/pom.xml spring-boot:run \
-Dspring-boot.run.arguments="--spring.main.web-application-type=none --spring.datasource.url=jdbc:sqlite:$verification_dir/conformance.db"
Expand Down
13 changes: 0 additions & 13 deletions teaql-core/src/main/java/io/teaql/core/EntityReferenceClaims.java

This file was deleted.

10 changes: 0 additions & 10 deletions teaql-core/src/main/java/io/teaql/core/EntityReferenceCodec.java

This file was deleted.

This file was deleted.

Loading
Loading