Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
234b4ad
Add E2E tests with caveat support and Docker Compose infrastructure
raz-shlomo-frontegg Mar 15, 2026
970807b
build(deps-dev): Bump org.apache.maven.plugins:maven-source-plugin
dependabot[bot] Mar 11, 2026
c5b472b
build(deps-dev): Bump mockito.version from 5.14.2 to 5.22.0
dependabot[bot] Mar 11, 2026
5126254
build(deps): Bump com.google.api.grpc:grpc-google-common-protos
dependabot[bot] Mar 11, 2026
d27abac
build(deps-dev): Bump org.apache.maven.plugins:maven-compiler-plugin
dependabot[bot] Mar 11, 2026
d3879b9
build(deps): Bump slf4j.version from 2.0.16 to 2.0.17
dependabot[bot] Mar 11, 2026
374f0a3
build(deps): Bump com.github.ben-manes.caffeine:caffeine
dependabot[bot] Mar 11, 2026
550334e
build(deps-dev): Bump org.apache.maven.plugins:maven-gpg-plugin
dependabot[bot] Mar 11, 2026
4f5015e
build(deps): Bump actions/checkout from 4 to 6
dependabot[bot] Mar 11, 2026
da1e27d
build(deps-dev): Bump org.apache.maven.plugins:maven-javadoc-plugin
dependabot[bot] Mar 15, 2026
5212489
build(deps-dev): Bump org.apache.maven.plugins:maven-surefire-plugin
dependabot[bot] Mar 15, 2026
06aced6
build(deps): Bump spring-boot.version
dependabot[bot] Mar 11, 2026
e6934dd
test: treat CONDITIONAL_PERMISSION as allowed and improve SpiceDB hea…
raz-shlomo-frontegg Mar 15, 2026
bc190ad
fix(e2e): add restart policy to SpiceDB container for CI reliability
raz-shlomo-frontegg Mar 15, 2026
0e4e0f0
fix(e2e): drop --wait flag and rely on readiness loop for SpiceDB sta…
raz-shlomo-frontegg Mar 15, 2026
690446e
fix(e2e): pin CockroachDB to v24.2.6 for SpiceDB v1.42.1 compatibility
raz-shlomo-frontegg Mar 15, 2026
b46e335
debug(e2e): add setup logging to diagnose schema write failures in CI
raz-shlomo-frontegg Mar 15, 2026
409efc1
debug(e2e): enable console output for failsafe to see setup logs
raz-shlomo-frontegg Mar 15, 2026
1f66415
fix(e2e): disable revision quantization to prevent stale schema reads
raz-shlomo-frontegg Mar 15, 2026
a05a1fc
fix(e2e): switch SpiceDB to in-memory datastore for E2E tests
raz-shlomo-frontegg Mar 15, 2026
ce4471e
fix(e2e): remove debug logging and clean up test configuration
raz-shlomo-frontegg Mar 15, 2026
8df21e6
debug(e2e): add diagnostic logging to identify CI test failures
raz-shlomo-frontegg Mar 15, 2026
6e34a04
debug(e2e): add detailed gRPC response logging to diagnose CI failures
raz-shlomo-frontegg Mar 15, 2026
87c0cad
chore: remove accidentally committed bmad doc
raz-shlomo-frontegg Mar 15, 2026
0f9564a
debug(e2e): add direct CheckPermission bypass to diagnose SDK vs SpiceDB
raz-shlomo-frontegg Mar 15, 2026
eb2a12e
fix: use fully_consistent reads for all SpiceDB gRPC requests
raz-shlomo-frontegg Mar 15, 2026
9167a92
feat: add configurable consistency policy for SpiceDB reads
raz-shlomo-frontegg Mar 15, 2026
e2d855b
fix(e2e): explicitly set null caveat params to fix CONDITIONAL_PERMIS…
raz-shlomo-frontegg Mar 15, 2026
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
38 changes: 37 additions & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
matrix:
java: ['17', '21']
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Set up JDK ${{ matrix.java }}
uses: actions/setup-java@v5
with:
Expand All @@ -22,6 +22,42 @@ jobs:
run: mvn verify --batch-mode
- name: Run integration tests
run: mvn verify -P integration --batch-mode
e2e:
runs-on: ubuntu-latest
strategy:
matrix:
java: ['17']
steps:
- uses: actions/checkout@v6
- name: Set up JDK ${{ matrix.java }}
uses: actions/setup-java@v5
with:
java-version: ${{ matrix.java }}
distribution: temurin
cache: maven
- name: Start SpiceDB
run: docker compose -f e2e/docker-compose.yml up -d
- name: Wait for SpiceDB readiness
run: |
for i in $(seq 1 60); do
if docker compose -f e2e/docker-compose.yml exec -T spicedb grpc_health_probe -addr=:50051 > /dev/null 2>&1; then
echo "SpiceDB is ready"
break
fi
if [ "$i" -eq 60 ]; then
echo "SpiceDB did not become ready"
docker compose -f e2e/docker-compose.yml logs spicedb
exit 1
fi
sleep 2
done
- name: Build SDK
run: mvn compile test-compile --batch-mode
- name: Run E2E tests
run: mvn verify -P e2e --batch-mode -Dspicedb.endpoint=localhost:50051 -Dspicedb.token=spicedb
- name: Teardown
if: always()
run: docker compose -f e2e/docker-compose.yml down -v
- name: Install core SDK to local repo
run: mvn install --batch-mode -DskipTests
- name: Compile Spring Boot Starter
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Set up JDK 17
uses: actions/setup-java@v5
with:
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ fields are validated in `build()`.
```java
import com.frontegg.sdk.entitlements.config.ClientConfiguration;
import com.frontegg.sdk.entitlements.config.CacheConfiguration;
import com.frontegg.sdk.entitlements.config.ConsistencyPolicy;
import com.frontegg.sdk.entitlements.fallback.StaticFallback;

import java.time.Duration;
Expand Down Expand Up @@ -219,6 +220,10 @@ ClientConfiguration config = ClientConfiguration.builder()
// Optional: enable monitoring mode (see Monitoring Mode section). Default: false.
.monitoring(false)

// Optional: SpiceDB read consistency. Default: MINIMIZE_LATENCY.
// Use FULLY_CONSISTENT when read-after-write consistency is required.
.consistencyPolicy(ConsistencyPolicy.MINIMIZE_LATENCY)

// Optional: enable in-memory result caching (see Caching section). Default: disabled.
.cacheConfiguration(CacheConfiguration.defaults())

Expand All @@ -237,6 +242,7 @@ ClientConfiguration config = ClientConfiguration.builder()
| `maxRetries` | `int` | `3` | No | Maximum retry attempts with exponential backoff before the fallback is invoked or the exception propagates. |
| `useTls` | `boolean` | `true` | No | Whether to use TLS on the gRPC channel. Disable only for local development. |
| `monitoring` | `boolean` | `false` | No | When `true`, checks are evaluated and logged but always return `allowed`. See [Monitoring Mode](#monitoring-mode). |
| `consistencyPolicy` | `ConsistencyPolicy` | `MINIMIZE_LATENCY` | No | SpiceDB read consistency: `MINIMIZE_LATENCY` (fastest, allows stale reads) or `FULLY_CONSISTENT` (linearizable). |
| `cacheConfiguration` | `CacheConfiguration` | `null` | No | When set, results are cached in memory. `null` disables caching. See [Caching](#caching). |

**Credential rotation** is supported by providing a `Supplier<String>` for the token. The
Expand Down Expand Up @@ -853,6 +859,7 @@ frontegg.entitlements.request-timeout=5s
frontegg.entitlements.bulk-request-timeout=15s
frontegg.entitlements.max-retries=3
frontegg.entitlements.monitoring=false
frontegg.entitlements.consistency-policy=minimize_latency

# Optional — static fallback result when the engine is unreachable.
# true = fail-open, false = fail-closed. Omit to propagate exceptions instead.
Expand All @@ -878,6 +885,7 @@ frontegg:
bulk-request-timeout: 15s
max-retries: 3
monitoring: false
consistency-policy: minimize_latency
fallback-result: false
cache:
max-size: 10000
Expand Down
50 changes: 41 additions & 9 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,10 @@ N/A — This is a client library published to Maven Central. No cloud infrastruc
| **gRPC** | grpc-java | 1.78.0 | gRPC transport (via authzed-java) | Transitive dep from authzed-java, uses shaded Netty |
| **Protobuf** | protobuf-java | 4.33.5 | Protocol Buffers (via authzed-java) | Transitive dep from authzed-java |
| **Logging** | SLF4J API | 2.0.x | Logging facade | Universal Java logging facade, no implementation forced |
| **Caching** | Caffeine | 3.1.x | In-memory caching | High-performance, thread-safe, Java 11+ compatible |
| **Caching** | Caffeine | 3.2.x | In-memory caching | High-performance, thread-safe, Java 11+ compatible |
| **Spring Integration** | Spring Boot (provided) | 3.2+ | Auto-configuration for Spring starter module | Provided scope — no forced Spring dependency for non-Spring consumers |
| **Testing** | JUnit 5 | 5.10.x | Test framework | Industry standard, parameterized tests, extensions |
| **Testing** | Mockito | 5.x | Mocking library | De facto standard for Java mocking |
| **Testing** | Mockito | 5.22.x | Mocking library | De facto standard for Java mocking |
| **Testing** | SLF4J Simple | 2.0.x | Test logging impl | Lightweight, test-scope only |
| **Publishing** | central-publishing-maven-plugin | 0.10.0 | Maven Central publishing | Sonatype Central Portal (replaces deprecated nexus-staging) |
| **Signing** | maven-gpg-plugin | 3.2.7 | Artifact signing | Required for Maven Central |
Expand Down Expand Up @@ -151,6 +151,15 @@ This SDK does not own data — it constructs gRPC requests and maps responses. T
- `bulkRequestTimeout: Duration` — Bulk check deadline (default: 15s)
- `maxRetries: int` — Retry count for transient errors (default: 3)
- `useTls: boolean` — Enable TLS (default: true)
- `consistencyPolicy: ConsistencyPolicy` — SpiceDB read consistency (default: `MINIMIZE_LATENCY`)

### ConsistencyPolicy (enum)

**Purpose:** Controls the consistency guarantee for SpiceDB reads.

**Values:**
- `MINIMIZE_LATENCY` — SpiceDB's default; fastest, allows stale reads
- `FULLY_CONSISTENT` — Linearizable reads; hits the primary datastore on every request. Use for read-after-write consistency (e.g. tests, post-relationship-write flows)

---

Expand Down Expand Up @@ -183,7 +192,7 @@ This SDK does not own data — it constructs gRPC requests and maps responses. T
**Key Interfaces:**
- `EntitlementsResult execute(SubjectContext, RequestContext)`

**Dependencies:** `FeatureSpiceDBQuery`, `PermissionSpiceDBQuery`, `FgaSpiceDBQuery`, authzed-java stubs
**Dependencies:** `FeatureSpiceDBQuery`, `PermissionSpiceDBQuery`, `FgaSpiceDBQuery`, `RouteSpiceDBQuery`, `LookupSpiceDBQuery`, `ConsistencyFactory`, authzed-java stubs

### Query Strategies (internal)

Expand All @@ -196,7 +205,18 @@ This SDK does not own data — it constructs gRPC requests and maps responses. T
- `RouteSpiceDBQuery` — Route matching with cached relationships
- `LookupSpiceDBQuery` — Dispatches `LookupResources` and `LookupSubjects` RPCs

**Dependencies:** authzed-java gRPC stubs, `Base64Utils`, `CaveatContextBuilder`
**Dependencies:** authzed-java gRPC stubs, `Base64Utils`, `CaveatContextBuilder`, `Supplier<Consistency>`

**Fail-closed behavior:** All query strategies treat `PERMISSIONSHIP_CONDITIONAL_PERMISSION` as denied (fail-closed) and emit a `WARN`-level log. This prevents incomplete caveat context from granting unintended access.

### ConsistencyFactory (internal)

**Responsibility:** Converts a `ConsistencyPolicy` enum into a `Supplier<Consistency>` that returns a pre-built protobuf `Consistency` instance. Zero allocation per call.

**Key Interfaces:**
- `static Supplier<Consistency> supplierFor(ConsistencyPolicy policy)`

**Dependencies:** `ConsistencyPolicy`, authzed-java `Consistency` protobuf

### RetryHandler (internal)

Expand Down Expand Up @@ -233,6 +253,7 @@ graph TB
B64[Base64Utils]
CVT[CaveatContextBuilder]
CCH[CaffeineCacheProvider]
CF[ConsistencyFactory]
end

subgraph "External (authzed-java)"
Expand All @@ -241,6 +262,7 @@ graph TB

ECF --> SDBC
SDBC --> QC
QC --> CF
QC --> FQ
QC --> PQ
QC --> EQ
Expand Down Expand Up @@ -415,7 +437,7 @@ tag → Publish workflow → Maven Central staging → validation → release
- **Format:** Structured key-value pairs in message: `"Checking entitlement subject={} resource={} relation={}"`
- **Levels:**
- `ERROR`: Unrecoverable failures (configuration errors, fallback failures)
- `WARN`: Fallback activated, retry exhausted, forced channel shutdown
- `WARN`: Fallback activated, retry exhausted, forced channel shutdown, `CONDITIONAL_PERMISSION` treated as denied
- `INFO`: Monitoring mode results, client creation/close
- `DEBUG`: Individual check inputs/outputs, cache hits/misses
- `TRACE`: Raw gRPC request/response (with token redacted)
Expand Down Expand Up @@ -501,12 +523,22 @@ tag → Publish workflow → Maven Central staging → validation → release

#### Integration Tests

- **Scope:** End-to-end tests against real SpiceDB instance
- **Scope:** Tests against a real SpiceDB instance via Testcontainers
- **Location:** `src/test/java/com/frontegg/sdk/entitlements/integration/`
- **Maven Profile:** `integration` (`mvn verify -Pintegration`)
- **Consistency:** Uses `FULLY_CONSISTENT` to ensure deterministic results
- **Test Infrastructure:**
- **SpiceDB:** Testcontainers with `authzed/spicedb` Docker image
- **Schema:** Test SpiceDB schema loaded via test fixtures

#### E2E Tests

- **Scope:** Full end-to-end tests against a real SpiceDB instance covering all query types
- **Location:** `src/test/java/com/frontegg/sdk/entitlements/e2e/`
- **Maven Profile:** `e2e` (`mvn verify -Pe2e`)
- **Consistency:** Uses `FULLY_CONSISTENT` to ensure deterministic results
- **Coverage:** Feature, Permission, Route, FGA, and Lookup operations

#### Cross-Language Compatibility Tests

- **Scope:** Verify Base64 encoding output matches TypeScript SDK for a known set of inputs
Expand Down Expand Up @@ -597,6 +629,6 @@ Rate limiting and DoS protection are the responsibility of the SpiceDB deploymen

## Next Steps

1. Add integration tests using Testcontainers against a real SpiceDB instance
2. Evaluate optional Micrometer companion module for metrics instrumentation
3. Consider Checkstyle or Spotless for automated style enforcement in CI
1. Evaluate optional Micrometer companion module for metrics instrumentation
2. Consider Checkstyle or Spotless for automated style enforcement in CI
3. Add `AT_LEAST_AS_FRESH` consistency policy with ZedToken support
3 changes: 2 additions & 1 deletion docs/architecture/coding-standards.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
- **Use `Optional` sparingly:** Only for truly optional return values. Never as method parameters.
- **Prefer `Supplier<String>` for tokens:** Enables credential rotation without client reinstantiation.
- **Unchecked exceptions only:** No checked exceptions in the public API.
- **Fail-closed on ambiguous permissions:** `PERMISSIONSHIP_CONDITIONAL_PERMISSION` from SpiceDB must be treated as denied. Log at `WARN` level with subject/resource context so operators can investigate incomplete caveat context.

## Error Handling

Expand All @@ -52,7 +53,7 @@
- **Library:** SLF4J 2.0.x
- **Levels:**
- `ERROR`: Unrecoverable failures
- `WARN`: Fallback activated, retry exhausted, forced shutdown
- `WARN`: Fallback activated, retry exhausted, forced shutdown, `CONDITIONAL_PERMISSION` treated as denied (fail-closed)
- `INFO`: Monitoring mode results, client creation/close
- `DEBUG`: Check inputs/outputs, cache hits/misses
- `TRACE`: Raw gRPC request/response (token redacted)
8 changes: 7 additions & 1 deletion docs/architecture/source-tree.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ entitlements-client-java/
│ │ └── LookupResult.java # Record
│ ├── config/
│ │ ├── ClientConfiguration.java # Builder pattern
│ │ └── CacheConfiguration.java # Record
│ │ ├── CacheConfiguration.java # Record
│ │ └── ConsistencyPolicy.java # Enum (MINIMIZE_LATENCY, FULLY_CONSISTENT)
│ ├── fallback/
│ │ ├── FallbackStrategy.java # Sealed interface
│ │ ├── StaticFallback.java # Record
Expand All @@ -75,6 +76,7 @@ entitlements-client-java/
│ ├── BearerTokenCallCredentials.java # gRPC call credentials
│ ├── CaveatContextBuilder.java # Builds protobuf Struct
│ ├── EntitlementsCacheKey.java # Cache key record
│ ├── ConsistencyFactory.java # Converts ConsistencyPolicy → Supplier<Consistency>
│ ├── BulkPermissionsExecutor.java # Executes CheckBulkPermissions RPCs
│ ├── CheckPermissionExecutor.java # Executes CheckPermission RPCs
│ ├── LookupResourcesExecutor.java # Executes LookupResources RPCs
Expand All @@ -86,6 +88,10 @@ entitlements-client-java/
│ └── LookupSpiceDBQuery.java # LookupResources / LookupSubjects dispatch
└── test/java/com/frontegg/sdk/entitlements/
├── EntitlementsClientFactoryTest.java
├── e2e/
│ └── SpiceDBE2ETest.java # Full E2E tests (mvn verify -Pe2e)
├── integration/
│ └── SpiceDBIntegrationTest.java # Integration tests (mvn verify -Pintegration)
├── cache/
│ └── CaffeineCacheProviderTest.java
├── config/
Expand Down
4 changes: 2 additions & 2 deletions docs/architecture/tech-stack.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
| **gRPC** | grpc-java | 1.78.0 | gRPC transport (via authzed-java) |
| **Protobuf** | protobuf-java | 4.33.5 | Protocol Buffers (via authzed-java) |
| **Logging** | SLF4J API | 2.0.x | Logging facade |
| **Caching** | Caffeine | 3.1.x | In-memory caching |
| **Caching** | Caffeine | 3.2.x | In-memory caching |
| **Spring Integration** | Spring Boot (provided) | 3.2+ | Auto-configuration for the starter module |
| **Testing** | JUnit 5 | 5.10.x | Test framework |
| **Testing** | Mockito | 5.x | Mocking library |
| **Testing** | Mockito | 5.22.x | Mocking library |
| **Publishing** | central-publishing-maven-plugin | 0.10.0 | Maven Central publishing |
| **Signing** | maven-gpg-plugin | 3.2.7 | Artifact signing |
| **CI/CD** | GitHub Actions | N/A | CI and deployment |
Expand Down
19 changes: 19 additions & 0 deletions e2e/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
services:
spicedb:
image: authzed/spicedb:v1.42.1
ports:
- '50051:50051'
command:
[
'serve',
'--datastore-engine=memory',
'--log-level=info'
]
environment:
- SPICEDB_GRPC_PRESHARED_KEY=${SPICEDB_GRPC_PRESHARED_KEY:-spicedb}
healthcheck:
test: ['CMD', 'grpc_health_probe', '-addr=:50051']
interval: 5s
timeout: 3s
retries: 5
start_period: 5s
46 changes: 46 additions & 0 deletions e2e/run-e2e.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#!/usr/bin/env bash
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"

cleanup() {
echo "Stopping SpiceDB infrastructure..."
docker compose -f "$SCRIPT_DIR/docker-compose.yml" down -v 2>/dev/null || true
}

trap cleanup EXIT

echo "Starting SpiceDB infrastructure..."
docker compose -f "$SCRIPT_DIR/docker-compose.yml" up -d

echo "Waiting for SpiceDB to be ready..."
for i in $(seq 1 60); do
if docker compose -f "$SCRIPT_DIR/docker-compose.yml" exec -T spicedb grpc_health_probe -addr=:50051 > /dev/null 2>&1; then
echo "SpiceDB is ready."
break
fi
if [ "$i" -eq 60 ]; then
echo "ERROR: SpiceDB did not become ready in time."
docker compose -f "$SCRIPT_DIR/docker-compose.yml" logs spicedb
exit 1
fi
sleep 2
done

# Seed schema and relationships if zed CLI is available
if command -v zed &> /dev/null; then
echo "Seeding schema and relationships via zed..."
zed import "$SCRIPT_DIR/schema-relationships.yaml"
else
echo "zed CLI not found — schema will be seeded by the Java test @BeforeAll setup."
echo "Install zed: https://authzed.com/docs/spicedb/getting-started/installing-zed"
fi

echo "Running E2E tests..."
cd "$PROJECT_DIR"
mvn verify -P e2e --batch-mode \
-Dspicedb.endpoint=localhost:50051 \
-Dspicedb.token=spicedb

echo "E2E tests completed successfully."
45 changes: 45 additions & 0 deletions e2e/schema-relationships.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
schema: |

caveat active_at(at timestamp, activeFrom any, activeUntil any) {
(activeFrom == null || at >= timestamp(activeFrom)) && (activeUntil == null || at <= timestamp(activeUntil))
}

definition frontegg_user {}

definition frontegg_tenant {}

definition frontegg_feature {
relation entitled: frontegg_user | frontegg_tenant
}

definition frontegg_permission {
relation entitled: frontegg_user | frontegg_tenant
}

definition frontegg_route {
relation entitled: frontegg_user | frontegg_tenant
}

definition document {
relation viewer: frontegg_user
relation editor: frontegg_user
relation parent: folder | folder with active_at
relation reader: frontegg_user | frontegg_user with active_at
permission view = viewer + editor
permission edit = editor
permission read_doc = parent->read_folder + reader
}

definition folder {
relation reader: frontegg_user | frontegg_user with active_at
permission read_folder = reader
}

relationships: |
folder:c2FsYXJpZXM#reader@frontegg_user:QWxpY2U
document:VGltJ3Nfc2FsYXJ5X0phbg#reader@frontegg_user:VGlt[active_at:{"activeFrom":"2026-01-01T00:00:00.000Z","activeUntil":null}]
document:VGltJ3Nfc2FsYXJ5X0ZlYg#reader@frontegg_user:VGlt[active_at:{"activeFrom":"2026-02-01T00:00:00.000Z","activeUntil":null}]
document:VGltJ3Nfc2FsYXJ5X01hcg#reader@frontegg_user:VGlt[active_at:{"activeFrom":"2026-03-01T00:00:00.000Z","activeUntil":null}]
document:VGltJ3Nfc2FsYXJ5X0phbg#parent@folder:c2FsYXJpZXM[active_at:{"activeFrom":"2026-01-01T00:00:00.000Z","activeUntil":null}]
document:VGltJ3Nfc2FsYXJ5X0ZlYg#parent@folder:c2FsYXJpZXM[active_at:{"activeFrom":"2026-02-01T00:00:00.000Z","activeUntil":null}]
document:VGltJ3Nfc2FsYXJ5X01hcg#parent@folder:c2FsYXJpZXM[active_at:{"activeFrom":"2026-03-01T00:00:00.000Z","activeUntil":null}]
Loading