APP-CONVENTION-SEMANTIC-CONTENT-SITE — content sites built on Embed — v0.5 DRAFT

Version: 0.5 Status: Draft {publisher_peer_id}/content/sites/{site_id}/_root placement (a layer violation: system/content/* is the CONTENT-extension namespace for capability-scoping the content-hash address space, where the leaf is always {hex(H)} per EXTENSION-CONTENT §6.4.2; an L5 application subgraph has no business there). Sites are now free subgraphs at publisher-chosen tree paths; the site's capability scope is its own subgraph root. Adds a new §11 URL projection prefix that registers sites as the SITE convention's reserved first-segment literal at the EXTENSION-NETWORK §6.5.6 demux layer (per Amendment 9's reserved-word extensibility hook and length-floor rule). Word-overloading rule: each word in the system carries one meaning; content is the CONTENT extension's, sites is the SITE convention's. Per the network-reserved-word-extensibility-and-site-prefix proposal.

v0.4.2: joint cross-team round folded per the joint embed/site convergence synthesis (spine locked three ways; .entsite pinned, .list discovery folded, nav/directive/tree-version fixes). v0.4.1: pages field REMOVED (arch scaffolding; redundant with nav; reintroduced the index anti-pattern — §4.2). v0.4.2: ordering tightened (entity-browser-rust) — pin one determinism floor (lexicographic-by-name presentation rule); frontmatter is optional local flavor not the contract; semantic feeds flagged OPEN / still-researching, deferred to a named post-v1 extension (L5 — guidance now, lock later). v1 primitives: manifest/page/nav/.list. Reconciled to APP-CONVENTION-EMBED v0.2.3. Supersedes the scattered design set (entity-browser-rust SPEC-SEMANTIC-CONTENT-SITE Rev 0.1/0.2 + the v1-lock synthesis). Next: cut joint conformance vectors → ratify (no further team cycle on the contracts — the vector sprint is the lock; folds recirculate as a confirm). Domain: applications/ (second member, first consumer of EMBED — see CHARTER.md). Charter class: FORMAT-only. Consumes: APP-CONVENTION-EMBED (the rich-content node + EmbedOutput + the two-level registry + the fallback ladder).

What this is. The convention for a content site — a verifiable, transferable, peer-hosted body of pages (docs, a blog, a wiki, a product site) — built as a thin L5 layer over already-shipped subgraph primitives (tree snapshot/extract, content blobs/chunks, the capability gradient, entity-native compute). It invents no site-specific versioning, transfer, or storage machinery, and no kernel feature. It defines the site entity vocabulary (manifest, page, signed root pin), the base-format + inline-embed grammar, and the bundle/closure + trust model — and it points at EMBED for everything about rich content.

The relationship to EMBED, stated once. EMBED is the passive content format (an authored node → a handler → a drawable EmbedOutput). This convention is where active code, packaging, and trust live — precisely the things EMBED deliberately does not answer. A site contains embeds; an embed never contains a site. If a question is "what bytes does this node carry / how does it render," it's EMBED. If it's "how is this shipped, made complete, and trusted on arrival," it's here.


1. The anatomy of a site [LOCKED — read this first; it answers "where does the compute subgraph go?"]

A site is three parts that travel together but are three different kinds of thing. Conflating them is the single recurring confusion (compute-vs-embed-vs-asset); separating them is what makes the model click.

PartWhat it isTrustTravels as
1. DocumentSiteManifest + SitePages — the base-format bodies (§3) with embed nodes insertedauthored data; no codetree entities in the site subtree
2. Content closurethe assets an embed points at — images, video, blobs. Passive bytes.none — just datainline tree entities (small) or content-store blobs reached by content-hash (large)
3. Compute closurethe bundled handlers active embeds dispatch to — entity-native compute subgraphs (or, future, WASM). Executable.G1 install-audit + prefix-scoped grant (§8)tree entities in the site subtree, if the site ships its own handlers

The embed is the seam between the three. An Embed (EMBED §3) is declarative data in Part 1 that points down into Part 2 (its asset payload) and out to a handler in Part 3 (by its media_type type tag). It is neither an asset nor a handler. This is the whole resolution of "is an embed a compute subgraph?": no — an embed may reference a compute subgraph as its handler; the subgraph is the engine, the embed is the declaration, the asset is the data.

Three layers, never collapsed (the recurring conflation, pinned):

  app/embed/{type}      ← INPUT TYPE   (open; a handler.  e.g. app/embed/applet, app/embed/live-chart)
        │  dispatch by media_type (EMBED §5.1)
        ▼
  HANDLER               ← THE TRANSFORM (pass-through | COMPUTE SUBGRAPH | WASM).  ← compute lives HERE
        │  produces
        ▼
  EmbedOutput kind      ← OUTPUT KIND  (closed vocab: text/image/box/raw/fallback; media/interactive reserved)

So "what kind of embed uses the compute subgraph — raw? media? interactive?" is a category error: those are output kinds (what the handler emits). The compute subgraph is the handler, one layer up from the output. A site's live chart is app/embed/live-chart (input type) → a bundled compute subgraph (handler) → a box/raw today (output) → drawn by the renderer. Nothing is interactive yet — that output kind is reserved (EMBED §4.0a), behind G1.

Most sites are Parts 1+2 only (passive: markdown + images + fallback — the v1 floor). A site adds Part 3 only when it carries active embeds. The passive floor ships now; Part 3 is G1-gated (§8) and DEFER for v1.

1.1 Bundled vs. resolved handlers (the "you transfer the compute subgraph" intuition) [LOCKED — load-bearing for G1 C3]

A handler reaches the rendering peer two ways:

Assets vs. compute, the trust asymmetry that makes them feel different: an asset (Part 2) is passive bytes and makes no trust decision on arrival. A bundled compute subgraph (Part 3) is executable and makes a G1 trust decision on arrival. They travel the same way (the bundle is closure-complete over both); they differ at the trust boundary, not the transport. That is the precise content of "you transfer the compute subgraph, you don't [just trust] the asset."

2. Site identity & the signed root pin [LOCKED — D1 / G-PIN-3]

A site's identity is the root hash of its subtree — a tree:snapshot root (an EXTENSION-TREE op; a core-only peer is a valid reader without it — site identity sits at the +tree-ext tier, not the floor §7). Two identical subtrees produce the identical root given two named agreements (the caveat the spec states rather than overclaiming portability — C-2):

A static publisher serves the verifiable root beside the manifest as a signed site-root pin — the "commit-wraps-tree" shape without a revision DAG:

site-root-pin = {                            ; type = app/site-root  (an ECF entity)
  root:     content-hash,                    ; the subtree root — self-describing (format_code, digest), V7 §1.2
  seq:      uint,                            ; monotonic; advance = resign with seq+1; gaps tolerated
  site_id:  tstr,
  ? passthrough_of: peer-id,                 ; G2 republish marker (F-3): set when a front-end re-publishes a site
                                             ; it did NOT author → names the origin peer. Rides the SIGNED pin so
                                             ; it cannot be stripped; downstream applies origin-trust policy (§8).
}
peer-id      = bstr                          ; a peer identity reference (V7 §1.2, self-describing)

Signing contract (the highest-priority invisible-failure surface — G-PIN-3):

A cross-impl vector ships a fixture pin signed by a known identity with expected signature bytes, so that at least two independent L5 implementations verify byte-identically before circulation closes. Independent means built over different cores — two front ends linking the same core cannot disagree about it and do not constitute two.

3. The base format & the inline-embed grammar [LOCKED — EMBED pushed this down to here]

3.1 Base format

A SitePage has a base format (EMBED §1.1 — the base-format choice is the consuming convention's, i.e. ours). v1:

Prose (paragraphs, links, emphasis, lists, GFM tables, headings, code) is base content, rendered by each front-end's own base renderer (web→DOM, Godot→BBCode/RichTextLabel, terminal→text). It is not an embed and not EmbedOutput (EMBED §1.1, §4). The site convention adds no prose vocabulary — that's the base format's standard.

3.2 The inline-embed directive (this convention owns it; EMBED §10 deferred it here)

Embeds enter a page body two ways:

v1 directive grammar (leaf form, CommonMark-directive style):

![fallback text](<path-or-content-hash>)

4. Site entities [LOCKED shape / CDDL]

Type-tag namespace (F-8, confirmed): the app/site-* tags are final — consistent with app/embed/* and the applications/ domain. (Impls shipping content/site/* migrate; bounded, tracked impl-side, not a spec change.)

; --- SiteManifest: the site's COVER — identity + ONE optional human menu. No collections (§4.2) ---
site-manifest = {                            ; type = app/site-manifest
  site_id:    tstr,
  title:      tstr,
  ? nav:      [* nav-node],                  ; OPTIONAL human navigation menu — a curated tree of pointers; NOT the
                                             ; discovery index, NOT exhaustive (§4.2). Holds no content.
  ? params:   { * tstr => any },             ; open attribute bag; string keys only (EMBED §3 discipline)
}
nav-node  = { label: tstr, ? target: link-ref, ? children: [* nav-node] }   ; tree; cycle rule §4.1
link-ref  = tstr                             ; F-2: optional (section headers have none); a link the renderer's
                                             ; classifier resolves relative to the site root — relative ("./about"),
                                             ; scheme ("site:labs/intro"), or absolute ("entity://…" / V7 §1.4 path).
                                             ; NOT narrowed to absolute-only (that would regress real authoring).

; --- SitePage: one page ----------------------------------------------------------------
site-page = {                                ; type = app/site-page
  format:     "markdown" / "html",           ; base format (§3.1); markdown is the recommended default
  body:       tstr,                           ; the base-format body (may carry ::embed directives, §3.2)
  ? frontmatter: { ? title: tstr, * tstr => any },   ; title-only is conformant; MAY derive title from first H1
  ? embeds:   [* (path / content-hash)],     ; sibling Embed entities this page transcludes (child mode)
}

Merge policy (S-9): v1 SiteManifest merge is last-write-wins on the whole manifest (simple-publisher posture). Field-level named strategies (nav = ordered-set union; root = conflict-error) are deferred with the propose-back/edit arc (itself [GRADIENT][DEFER build]).

4.1 Navigation safety [v1 renderer contract — promoted from post-v1; DoS surface]

nav is a tree and MAY contain authored cycles or pathological depth. A renderer walking nav MUST: maintain a visited-set (cycle detection), enforce a max depth (recommend 32), and on either limit stop cleanly (render what it has; never infinite-loop / stack-overflow). One-line contract; v1-blocking.

4.2 Discovery, ordering & why the manifest holds no page-collection [v0.4.2 — discovery+floor LOCKED; semantic feeds OPEN/researching]

Discovery is lazy, one-level-at-a-time .list over the site namespace — the scalable v1 default. A renderer walks the tree namespace level by level (like a filesystem) and never requires downloading a full index to render the first page. This avoids the "download the index" anti-pattern at scale (huge-index → index-of-indexes, our SCALE landscape; entity-browser-rust discovery.rs, cd283a5). Paging belongs to .list — it is inherently incremental — never to a manifest field.

The manifest carries identity + one optional human menu (nav) — and NO page-collection field. v0.4 sketched an optional pages: [* page-ref] ordered list; v0.4.1 removes it. Honest accounting: pages was arch's addition in the v0.3 assembly — not in any impl, design doc, or the three-team v1 lock — and on merit it does not earn a slot:

Ordering — one small pinned floor; the semantic layer is open research (L5, not locked). First the fact that governs it: the tree carries no semantic order. Under EXTENSION-TREE v4.0.2 .list enumerates in hash-bit order, random w.r.t. names (§enumeration: "callers needing lex-sorted output MUST sort at output"). So order is a renderer presentation choice, not a tree property — we are not giving tree location semantic meaning.

Semantic feeds are explicitly OPEN — flagged, not solved. "Newest-first," prev/next, RSS — an order that declares its meaning (date-descending, sequence-ascending) and must agree cross-impl — is the one place a real contract is genuinely owed, and there is something here we haven't fully found. v1 deliberately does not guess a field/format ahead of the use case; it pins only the determinism floor above and defers the semantic layer to a named feed/index extension authored when a concrete feed use case arrives. (This is L5 — guidance now, lock later.) Paging of any ordered view is .list limit/offset.

The principle (so it never creeps back): the manifest is a cover, not a collection store. Every "do we also need an ordered list / a set / a keyed map / paging of pages?" is answered no — collections of pages live in content (an index page lists links — human) or in .list/query (enumerate + client-sort — machine), never as proliferating manifest fields.

The complete v1 set: manifest (identity + optional nav) · page (content) · nav (menu) · .list (discovery). Human navigation = nav + index-pages-with-links; ordering floor = lexicographic-by-name (a renderer presentation rule; .list is hash-ordered); semantic feeds = open, named post-v1 extension.

5. Embeds in a site — pointer to EMBED [reconciled to EMBED v0.2.2]

Everything about an embed's bytes, handlers, output shape, fallback ladder, and reserved kinds is EMBED's contract, not restated here. This convention only fixes the site-level facts:

6. The bundle — one transferable, closure-complete artifact [LOCKED — A1/A2 blessed; A3 rejected]

A site transfers as a closure-complete subgraph. The closure rulings (arch first-pass §1, verified against shipped ops — nothing is invented):

CaseMechanismVerdict
Small asset (≤ ~16 KiB, §6.1)inline tree entity → travels free in tree:extract (A1)BLESSED — recommended for icons/thumbnails/SVG. This is "put it in the tree."
Large blob asset (full bundle)tree:extract + content:ensure_closure (A2 — both ship)BLESSED — the v1 pattern. Sequencing of existing ops, not a new primitive.
Large blob asset (lazy browse)on-demand content:get via namespace; cache under {publisher}/… (PRIMER §3)BLESSED — don't eager-bundle the browse path.
Bundled compute handler (Part 3)ships as a tree entity; tree:extract carries it; G1 on arrival (§8)BLESSED (DEFER build, G1-gated).
Bind a blob's chunks at tree pathsproject chunk refs into the trieNOT BLESSED — layering inversion (chunk structure is a content-store detail, not a namespace fact; chunk hashes are non-canonical chunker artifacts; cardinality blowup). Use A2.
One-shot "bundle-with-closure" core op (A3)new wrapper opREJECTED for v1 — two honest sequenced calls beat a tidy new primitive.

The blessed bundle helper (SDK/L5, not a core op). "One self-contained artifact, no second dereference" is met at the envelope layer: the envelope's included map carries the content-store entities (assets + bundled-compute), produced by tree:extract + content:ensure_closure + the A4 completeness check. Bless a standard closure-complete-bundle helper that sequences those and emits one transferable artifact — the .entsite single-file envelope (CBOR shape pinned in §6.0). Rationale: if completeness is load-bearing enough to add the A4 ingest guardrail, producing a complete bundle should not be an un-blessed two-step every L5 app re-implements.

Until A4 lands (the EXTENSION-CONTENT ingest-completeness erratum — proposal-first, co-authored entity-browser-rust+arch), publishers MUST use the transactional wrapper: extract into a staging sub-namespace → ensure_closurerename live (never ship the silent-success-now / 404-later path).

6.0 The .entsite bundle — pinned CBOR shape [LOCKED — C-1; convergent CRITICAL, wb-go + godot]

Both teams flagged: blessing the helper while leaving the bytes [OPEN] = three impls, three divergent bundles. Pinned now. .entsite is NOT a new envelope format — it is a naming + packaging convention over V7's existing MaterializedEnvelope (charter #3: improvise no protocol), with three site-specific constraints:

entsite = {                                  ; a single-file serialization; canonical CBOR (V7 §1.3 / ECF §4.1)
  v:        uint,                            ; entsite format version = 1
  envelope: materialized-envelope,           ; V7 system/envelope/v1 — UNMODIFIED
  ? pin:    site-root-pin,                   ; the SIGNED site-root pin (§2) travels for offline verification
}
materialized-envelope = {                    ; V7 shape, restated for completeness — not redefined
  envelope_hash: content-hash,               ; merkle over root + included (V7)
  root:          content-hash,               ; MUST equal pin.root when pin present (site identity)
  included:      { * content-hash => entity },
}

The three site-specific constraints (what makes an envelope a valid .entsite):

  1. root is a site subtree root (a tree:snapshot root, §2) — and MUST equal pin.root when a pin is carried, binding the signed identity to the bundled bytes.
  2. included is closure-complete over the site: every SitePage, every transcluded Embed, every asset blob and its chunk closure, and every bundled compute entity (Part 3) reachable from root. (This is the A2 output; the A4 check verifies it before the bundle is sealed.)
  3. Canonical CBOR only (V7 §1.3) — so the same site produces a byte-identical .entsite across impls (the cross-impl portability the helper exists for; a .entsite conformance vector pins it).

Encoding-agnostic throughout: every hash is the self-describing content-hash (no width lock). A consumer verifies envelope_hash, then (if pin) the publisher signature over pin, then pin.root == envelope.root.

6.1 Canonical chunking & reproducible publish [LOCKED — G-PIN-4]

"Same image → same site root" silently breaks if publishers chunk differently (no chunking-independent content hash — EXTENSION-CONTENT §2.1/§2.3). v1 publishers MUST use the canonical default chunk_size = 1 MiB FastCDC average (min/avg/max = 256 KiB / 1 MiB / 2 MiB — the shipped FastCDC params). A reproducible-publish conformance test ingests one fixture under two publishers and asserts an identical site root.

7. Tiers & the floor [LOCKED — capability adds, never assumed]

TierReadsNotes
Floor (core only)markdown bodies + inline passive embeds (≤16 KiB) + every embed's authored fallbackno handler tier → no active content reaches the floor by construction; the base markdown renderer is on the safe side of the ladder (S-11). A floor peer is a valid reader, not a degraded one.
+content extlarge blob assets (pointer payloads), renditionscontent:get / ensure_closure.
+tree extsite identity (verifiable root hash), bundle extracttree:snapshot/tree:extract are EXTENSION-TREE, not core.
+computeactive embeds (Part 3)requires EXTENSION-COMPUTE on the evaluating peer; G1-gated; DEFER v1. Active-compute embeds require COMPUTE — state the dependency precisely (not "compute embeds ship" unqualified).

Cap scope (v0.5). The site subgraph's own root path is blessed as a first-class capability scope — the site subgraph is the cap surface, derived structurally from the publisher-chosen placement (it does not need to be carried in the manifest). Cross-peer caching under /{other_id}/… is PRIMER §3 (the cache is the tree, partitioned by peer), not a new feature. The prior v0.4.2 wording naming {publisher_peer_id}/content/sites/{site_id}/ as the scope is dropped per the v0.5 placement erratum above (§4.X): a site is a free subgraph, and the scope is wherever the publisher put it.

8. Security [gates named with owners — security is never deferred, even when the build is]

Neither gate blocks the v1 passive floor (markdown + passive media + fallback — no active code, no raw HTML by default).

9. Conformance vectors [REQUIRED before ratification — ship JOINTLY with EMBED's]

A FORMAT convention is not validated until vectors exercise it (PRIMER meta-rule). This convention ships:

Cross-impl byte-equality on these + EMBED's is the joint v1 lock signal.

10. Open / deferred

11. URL projection prefix [v0.5 — registers sites at the §6.5.6 demux]

Sites are tree subgraphs. To make them addressable on the legacy web (and the static no-JS surface for permalinking / SSG-style consumption), the SITE convention claims a reserved first-segment URL literal at the EXTENSION-NETWORK §6.5.6 demux layer:

{base}/sites/{peer_id}/{site_id}/…

sites is the SITE convention's registered reserved word per EXTENSION-NETWORK §6.5.6 G4 (Amendment 9 — reserved-word table extensibility hook + length-floor rule): NETWORK exposes the mechanism; the SITE convention owns the entry. The literal sites is five characters, comfortably below the Ed25519 peer-id minimum length — it satisfies the length-floor rule and cannot collide with a parseable peer-id at the demux.

Projection semantics.

Word-overloading rule. Each word in the system carries one meaning. content is the CONTENT extension's namespace word per EXTENSION-CONTENT §6.4 — the SITE convention does NOT squat on it. The v0.4.2 content/sites/{site_id}/_root placement is the layer violation v0.5 corrects (§2 placement).

Other L5 conventions. The SITE convention registers sites only. Other L5 conventions (a future repos / spaces / etc.) register their own prefixes in their own specs under the same NETWORK §6.5.6 extensibility hook; this convention does not preemptively claim words on their behalf.

Adoption posture. The prefix is the SITE convention's claim, not a universal mandate. Adoption by other parties is social convergence (cf. CHARTER framing — L5 is convention-not-conformance for non-format axes); the convention is here to be analyzed and adopted on merit, not joined.

12. Provenance