## Summary Introduce a document graph architecture for Assets resources so structured documents can reference one another while keeping full document payloads on CDN-backed storage. The content database will retain the searchable index and dependency graph. At runtime, the server will select a root document, determine its complete dependency set before performing I/O, fetch each unique document concurrently, and assemble the final result server-side. ## Implementation todos ### Document graph foundation - [x] Add normalized internal reference contracts for whole documents, JSON paths, Markdown body, and Markdown frontmatter. - [x] Add an immutable graph abstraction with deterministic ordering, dependency traversal, revision validation, and cycle detection. - [x] Add a storage-independent resolver with dependency closure, bounded parallel loading, shared-content deduplication, representation selection, assembly hooks, cancellation, and structured errors. - [x] Add focused unit coverage for the isolated document-graph abstraction. - [x] Define and validate the external remote-document reference syntax. - [x] Add a pure graph compiler that resolves canonical document URLs into IDs and revisions and validates missing, duplicate, and cyclic dependencies. ### Build-time graph - [x] Add a storage-independent source-catalog compiler for bounded parallel reference discovery and deterministic graph compilation. - [x] Add a versioned deterministic document graph artifact with canonical serialization and integrity verification. - [x] Discover and normalize statically declared references while Assets resources are processed. - [x] Resolve document identities and revisions and report missing, stale, or cyclic dependencies with document context. - [x] Persist document nodes and dependency edges alongside searchable resource metadata. - [x] Include persisted graph data in artifact integrity and size validation. ### Document representations - [x] Add a standalone JSON document adapter for exact reference markers, compiler occurrences, JSON path selection, and immutable assembly. - [x] Add a standalone Markdown document adapter for bounded decoding, frontmatter references, immutable assembly, and source, body, and frontmatter selection. - [x] Parse fetched JSON and Markdown documents through format-specific adapters. - [x] Select complete documents, JSON fragments, Markdown body, and Markdown frontmatter through the shared reference model. - [x] Assemble nested and shared references into the final server-side value. ### Runtime resolution - [x] Enforce reachable-document, per-document byte, and aggregate-byte limits before parsing and assembly. - [x] Add a revision-keyed bounded source-cache wrapper with replayable bytes, stale-entry fallback, and in-flight deduplication. - [x] Add a storage-neutral loaded-source contract that rejects revision mismatches before parsing. - [x] Resolve graph-backed property paths before filtering, sorting, pagination, and projection; skip graph loading for unrelated queries. - [x] Add an injected HTTP/CDN document loader with revision metadata validation. - [x] Apply runtime payload limits, bounded concurrency, caching, and structured failure mapping. - [x] Ensure shared documents are fetched and parsed once per resolution. ### Product integration - [x] Integrate equivalent behavior in local preview, SSR, SSG, and published runtime paths. - [x] Preserve existing embedded Assets resource behavior without requiring migration. - [x] Add observability for graph loading, document fetching, caching, and resolution failures. - [x] Document the public reference and resolution behavior. ### Review hardening - [x] Enforce graph, artifact, cache, and published-storage identity consistency. - [x] Compile only graph sources reachable from selected build entries. - [x] Resolve graph-backed fields before query evaluation and avoid unnecessary graph loading. - [x] Consolidate duplicated graph schemas, utilities, and document content-type detection. - [x] Correct runtime documentation and use structural evaluation validation. ### Verification - [x] Keep a deterministic high-impact agent evaluation for Markdown-to-JSON references across the dynamic blog overview and article routes. - [x] Add a separate discoverability evaluation that must learn the reference workflow from MCP guidance without prompt-supplied syntax, queries, bindings, or call order. - [ ] Re-run the complete local agent evaluation suite after the discoverability fixture change and compare every fixture with its accepted baseline. - [x] Add integration coverage for CDN-backed hydration, parallel fetching, caching, and shared dependencies. - [x] Add integration coverage for missing references, cycles, revision changes, limits, and cancellation. - [x] Verify equivalent behavior across preview, SSR, SSG, and published deployments. - [x] Verify that existing embedded Assets resources retain their current behavior. ## Intended outcome - Storage-backed documents do not require their complete payloads to be embedded in the content database. - The server knows the complete statically discoverable dependency set before fetching document content. - Shared dependencies are fetched once per resolution and independent resources can be fetched in parallel. - JSON fragments and Markdown body/frontmatter representations use the same document-resolution system. - Reference and payload revisions cannot be mixed silently. - Existing Assets resources continue to work without migration. ## Verification results - `@webstudio-is/content-engine`: 37 files / 244 tests passed; typecheck passed. - `@webstudio-is/asset-uploader`: 22 files / 182 tests passed; typecheck passed. - CLI: 57 files / 925 tests passed, including generated SSR and SSG build/prerender coverage; typecheck passed. - Repository-wide `pnpm oxfmt --check` passed across 2,757 files. - Package-boundary validation passed. - Previous `pnpm evaluations` run: all 4 original high-impact fixtures passed. The complete 5-fixture rerun is pending explicit approval after adding the discoverability fixture.
Content engine
The content engine compiles file-based Assets into a portable query database. JSON and Markdown documents can reference other Assets without embedding their complete source in that database.
Document references
A reference is an object with exactly one $ref property whose value is a URI
reference:
{
"author": { "$ref": "./authors/ada.json" }
}
References can appear at any depth in a JSON document. In Markdown, references can appear at any depth in YAML frontmatter:
---
title: Building with Markdown
author:
$ref: ./authors/ada.json
---
Article body.
An object with additional properties is ordinary content, not a reference.
The $ref spelling is familiar from JSON Schema, but this syntax does not
implement JSON Schema resolution, identifiers, anchors, or schema evaluation.
Reference paths use standard URI-reference resolution relative to the source document. The fragment selects which representation of the target to insert:
| Reference | Selected target value |
|---|---|
./author.json |
Complete JSON value |
./author.json#/profile/name |
Value at the JSON Pointer /profile/name |
./article.md |
Original Markdown source, including frontmatter |
./article.md#body |
Markdown body without frontmatter |
./article.md#frontmatter |
Markdown frontmatter as an object |
JSON Pointer escaping follows RFC 6901. For example, #/a~1b selects the
property named a/b. JSON Pointer fragments apply only to JSON documents;
#body and #frontmatter apply only to Markdown documents.
Authored URLs identify Assets during compilation. They do not permit arbitrary runtime network requests. Every target must match a document in the compiled Asset catalog.
Resolution behavior
At build time, the content engine:
- Parses bounded JSON sources and Markdown frontmatter.
- Resolves each static URI reference to a document identity and revision.
- Rejects missing targets, duplicate identities, revision conflicts, and dependency cycles.
- Stores the validated dependency graph with searchable metadata while source payloads remain in Asset storage.
At runtime, the server compares the query's filter, sort, and output property
paths with the graph's reference locations. If a reference can affect the
query, the server resolves the relevant source documents before executing the
query; otherwise it uses the normal query path without loading graph sources.
Non-reference filters first narrow the candidate sources. The server follows
only root references that intersect the query, preserves unselected sibling
markers, and computes the selected references' complete downstream dependency
closure. It loads independent documents concurrently, loads shared storage
content once, validates storage identities, revisions, formats, and payload
limits, and assembles dependencies before their consumers. A resolved value
replaces its exact { "$ref": "..." } marker.
Filtering, sorting, pagination, and projection therefore operate on resolved
values when their property paths intersect a reference. Only properties
selected by the Assets query are returned. Resolution cannot expose an
unselected property merely because it exists in a fetched document.
markdown-body content selection is also graph-backed. Compilation retains the
Markdown document identity instead of its source bytes, and runtime hydration
fetches only the documents selected after filtering, sorting, and pagination.
The resolved query result continues to expose the body at content.text.
Whole-document and range content modes retain their embedded-content behavior.
Document source caches are keyed by storage reference, revision, and format, so content from one identity cannot be combined silently with different graph metadata. Published runtime assets must also point at the graph node's exact storage reference before the source is fetched. Resolution is bounded by document-count, per-document byte, aggregate-byte, and concurrency limits. A missing document, invalid representation, stale identity, limit violation, or cancellation fails the query instead of returning a partially assembled graph.
Assets without a document graph keep the existing embedded-content behavior and do not require migration.