# The Dawn semantic surface

**Status:** current development contract, 2026-09-19. This document owns Dawn's
observable semantics. [HTTP.md](HTTP.md) and [WS.md](WS.md) bind the same
operations. This revision replaces, in one landing, the 2026-09-17 production
surface and the 2026-09-18 subscription frame, and is not additive against
either. Against the 2026-09-17 surface it renames *production* to *application*
and the verb `produce` to `apply` — the application surface of
[decision 0005](../docs/decisions/0005-hosted-production.md) — the fact profile
becomes `dawn-facts-v2`, and both worlds restart empty; nothing migrates.
Against the 2026-09-18 frame it replaces the event shape with a journal's
advance, one per journal, coalesced, with no cutoffs at open, no backfill and no
kinds, per
[decision 0006](../docs/decisions/0006-subscriptions-as-cursor-advances.md).
A server never answers 2026-09-18, a contract a client may already have been
built against, because `produce` is gone.
[STATUS.md](../STATUS.md) records implementation progress, including
which operations of this contract the current source still leaves unimplemented,
and [ACCEPTANCE.md](../ACCEPTANCE.md) records evidence for specific builds;
neither a contract change nor an older passing result establishes deployment of
this revision. The 2026-09-11 contract was replaced in place: its storage was
Registry, and its public vocabulary was Registry's model
([order 0003](../../docs/orders/0003-dawn-on-journal.md), decision D-A).
The Registry vocabulary is removed with no alias: forking has no successor
(order 0002 D2); listing, history and views are `children`, `entries` and
`snapshot`; coordinates with a global revision, publications, bindings and the
contribution profile are gone. A request naming a removed verb is `not_found`.

Dawn gives workers a shared place to inspect artifacts and append attributed
findings. Coordinators retain inputs, grant authority, select findings into views
and run campaigns. Workers use Dawn; Dawn consumes the external
[Journal](../../ulab-journal/api/SURFACE.md) and Bytes services. Journal owns
spaces, append-only journals of canonical facts, occurrence coordinates and
retry receipts. Bytes owns retained content. Dawn owns authentication,
authorization, semantic admission and the provenance Journal does not supply.

## Values and identity

| Term | Meaning |
|---|---|
| World | One Dawn deployment's subtree `/worlds/<world>` of one Journal root, with its configured name, root key and Bytes endpoint |
| Principal | An Archon Ed25519 public key authenticated for one request |
| Path | Labels below the world, one per entry; the world itself is the empty path |
| Space | A space's exact Journal identity `{root_id, space_id}` with its Dawn path |
| Occurrence | An `EntryRef` `{root_id, space_id, position}`: one entry of one journal; positions are per journal, never a global revision |
| Artifact | Exact retained content with a selected interpretation, `{bytes, type}`; the type selects a reading of the bytes, not an exclusive classification or a second identity |
| Content | The one `content` fact of an artifact space |
| Subject | A qualified `space#local` identity within an interpretation of an artifact; distinct from a path or a worker |
| Declaration | One immutable assertion occurrence about an artifact's exact content and optional subject, with its retained signed envelope and frame |
| Record | One admitted record occurrence with its profile, retained payload, metadata and Dawn's admission provenance |
| Grant | A Thesmos fact authorizing a principal's operation kind on a subtree until a time |
| Manifest | An explicit selection of journal prefixes with their cutoffs, retained in Bytes and cited by digest |
| Campaign / assignment | Coordination records binding a worker to pinned inputs, intended outputs and a brief |
| Tool | A configured provider Dawn runs; its logical name is an organizer under the world's `tools`, and each configured release is an artifact space under `tools/<name>/releases/<digest>` |
| Release | One configured executable of a tool; its `content` fact is the tool's description — name, version, class, input and output roles, effects, whether it is deterministic, its egress and its executable identity |
| Application | One run of a tool Dawn performed on a valuation, admitted as an attempt occurrence A, one `bound` fact per required input role, and completed as a sealed batch of produced outputs; attested by Dawn, never reported by a caller |

Artifact content, subject identity, assertion identity, authenticated publisher
and occurrence are separate. Two workers can assert different statements about
the same artifact and subject. Both statements remain available; arrival order
does not choose which one a consumer accepts. Publishing a statement does not
establish its truth or adopt it for a view.

## The world's spaces

Journal enforces unique child keys under a parent, one parent per space and
no removal. Dawn assigns meaning to the layout:

```text
/worlds/<world>
  /tools/<tool>                    organizer (the world's roots)
    /releases/<digest>             artifact space: content = the release's description; declarations child
      /declarations                one fact per declaration (leaf)
  /projects/<project>
    /artifacts/<artifact>          staged(A) first when an application created it; content; declarations child
      /declarations                one fact per declaration (leaf)
    /applications/<name>           leaf: attempt · refusal records · bound* · produced* + result (one batch) · reuse
    /snapshots/<name>              record journal (leaf)
    /inventories/<name>            record journal (leaf)
    /views/<name>                  record journal (leaf)
    /workers/<worker>
      /state                       record journal (leaf)
      /notes/<name>                record journal (leaf)
  /campaigns/<campaign>
    /state                         record journal (leaf)
    /assignments/<name>            record journal (leaf)
    /evidence/<name>               record journal (leaf)
```

A **leaf** is an individual record or declarations space under which Dawn never
creates children, so its Dawn-created entries have origin `append`. The
collection spaces `tools`, `releases`, `applications`, `snapshots`, `inventories`, `views`,
`notes`, `assignments` and `evidence` are organizers, not leaves; two named views have independent
histories and current records. Organizers have their own journals and are
included in exports. Workers remain under their project: a worker's `state` or
`notes` describes that worker, and declarations about an artifact remain in that
artifact's `declarations` journal regardless of which worker submitted them. The
publisher occurs in provenance, never in the placement rule.

Dawn ensures `/worlds`, the selected world and its organizing spaces with
stable scoped operation IDs and resumes creation through Journal receipts after
a partial setup or an uncertain response; it ensures one artifact space under
`tools` per configured tool the same way. The labels `tools`, `releases`,
`artifacts`, `declarations`, `applications`, `snapshots`, `inventories`, `views`, `workers`,
`notes`, `state`, `assignments` and `evidence` are structural where the layout
places them. A
user label cannot shadow a structural label at its reserved position. The
`content` of an artifact is a fact of its space, not a child.

## The fact profile

Every fact Dawn writes is a canonical Ontos tuple headed by an atom under
Journal's `ulab-journal-facts-v1` profile. Dawn's profile is `dawn-facts-v2`,
owned by the API's `facts` package; generated schemas describe it and do not
define it.

| Predicate | Arguments | Written in |
|---|---|---|
| `content` | `artifact` | an artifact space, exactly once |
| `record` | `profile`, `hash`, `metadata`, `publisher`, `request_id`, `request_digest`, `accepted_at` | an individual record's leaf space |
| `declaration` | `assertion_id`, `envelope`, `artifact`, `subject`, `publisher`, `application` | a `declarations` space |
| `attempt` | `release`, `request`, `publisher`, `request_id`, `request_digest`, `accepted_at` | an application leaf, once, heading its batch |
| `bound` | `application`, `role`, `entry` | an application leaf, one per bound input role, after the attempt |
| `produced` | `application`, `role`, `entry` | an application leaf, one per output role, in the completion batch |
| `result` | `application`, `outcome`, `manifest`, `resolution`, `attestation` | an application leaf, once, closing the completion batch |
| `staged` | `application` | an artifact space, once, before its content fact |
| `justifies` | `assertion_id`, `group`, `premise`, `method` | a `declarations` space, one per premise |
| `relation` | `assertion_id`, `kind`, `target` | a `declarations` space |

Text arguments are exact Ontos text values, so Journal's argument filters
select them; a position is an int; metadata is a map of text pairs. The
`application` argument of `declaration`, `bound`, `produced`, `result` and
`staged` is the admission occurrence A, an entry value, or `dawn.none.v1` —
the atom alone — for a declaration a principal wrote itself with no hosted
application. `release` and `entry` are occurrence reference values, so a filter
on `bound`'s third argument selects every application that consumed one
occurrence; `request`, `envelope`, `manifest`, `resolution` and `attestation`
are hash values. `outcome` is text, one of `completed`, `failed` or
`interrupted`; `kind` is text, one of `supersedes`, `challenges` or
`withdraws`. Several membership claims may hold as independent facts. The
profile fixes the encoding of every argument and the request-identity domains;
independent fixtures, not round trips, establish it.

### Record provenance and retries

Journal supplies occurrence coordinates, origins and batch boundaries. It does
not supply a publisher or an acceptance clock, so Dawn persists both in the
record fact. `publisher` is the authenticated principal. `accepted_at` is
Dawn's clock for the first admitted candidate that commits, never a Journal
timestamp. `request_id` is derived from the authenticated publisher and the
caller's operation ID, scoped to the exact destination journal (`RequestID`).
`request_digest` covers the operation name, the destination identity and the
normalized semantic request, including the caller's explicit precondition; it
excludes transport proof bytes and server-generated values such as the
acceptance clock (`RequestDigest`). The normalized semantic request of an
operation is the canonical JSON of its request type as the caller sent it,
and the operation name in the digest is the table name (`publish`,
`compose`, `provision_worker`, `retire_worker`, `create_campaign`, `assign`).
A reader after a fresh Dawn process reconstructs all of them from the fact
alone; the [requirements](../conformance/requirements/README.md) restate
both identities independently of the service.

The shared record writer applies one protocol to `publish`, `compose` and every
provisioning record:

1. Resolve and authorize the exact destination. Search its journal for the
   request identity at a pinned cutoff through every required page. An
   incomplete lookup is not absence; conflicting occurrences are a profile
   violation, never a reason to select one silently.
2. If found, validate the fact and the required retained payload, compare the
   semantic request digest and fields, and return the original occurrence and
   admission clock. Changed content under the same identity is
   `idempotency_mismatch`; unavailable recovery data is `unavailable`.
3. If absent, construct and retain the required payload once, choose the
   candidate's admission clock, and append the exact fact with a stable scoped
   Journal operation ID. An uncertain result is retried with identical fact
   bytes and the identical precondition; a new Dawn process searches before
   constructing another candidate.
4. On an append mismatch caused by a concurrent winner, refresh the selected
   cutoff and recover that winner: an equal semantic request returns the
   original result, a different request is refused. An implicit current-head
   precondition is never recomputed as a substitute for recovering an accepted
   result.

These rules add no Journal uniqueness predicate, publisher field or clock. They
preserve caller-supplied optimistic concurrency and cover a lost acknowledgement
followed by later writes to the same journal.

### Reading rules

- **Content** is the sole `append` entry of an artifact space whose predicate
  is `content`, selected by rule, never by position. A second such entry is a
  Dawn invariant violation, refused by the filtered read that precedes every
  `declare`.
- **The current record** of a leaf is the last `append` entry with predicate
  `record`, never the raw head: a head can be a creation event elsewhere in
  the tree.
- **A declaration** is found by a filtered read of the `declarations` space at
  a pinned cutoff, including every continuation. The same completeness rule
  applies to record request lookup; an incomplete lookup is not absence.
- **An occurrence reference** is an `EntryRef`. An entry inside a batch is a
  valid reference but not a valid `through` cutoff; a reader uses the batch end
  or a pinned later head and selects the requested position.
- **Authorization** resolves a submitted space or occurrence identity to its
  actual path and verifies the configured Journal root and Dawn world before
  applying path grants. A caller-supplied path is not evidence that an
  `EntryRef` belongs to that path; foreign roots and worlds never bypass read,
  declaration, evidence or compose authorization.

## Authentication, authorization and admission

Every surface call carries a proof signed by its principal. Dawn checks key
admissibility, the exact signing record, audience, freshness and replay
protection, then proves a grant chain from the world's root for the actual
operation and path. A request path maps to the Thesmos space
`["dawn", <world>, <path segments…>]`; a grant on a space covers its subtree.

| Kind | Permits |
|---|---|
| `read` | Resolve, children, entries, get, fetch, snapshot, query, explain and subscribe within scope |
| `publish` | Retain bytes, create artifacts, publish records, compose views and apply tools within scope |
| `declare` | Append an attributed declaration to an artifact within scope |
| `provision` | Create projects, campaigns and worker spaces, issue grants, assign, retire and export |

A `declare` grant is sufficient for the declaration write; it does not grant
ordinary publishing, artifact replacement or adoption. Artifact and evidence
reads must also be authorized. General `publish` cannot write an artifact
space's content or `declarations` journal. This structural rule applies to
every principal, including the root. A broader grant does not bypass record
immutability.

A valid proof identifies the submitting key. Dawn records that authenticated
publisher itself; a statement's own author labels are claims inside the
statement. Acceptance records the authority Dawn checked. It does not certify
independent authorship, the truth of a statement, or the reliability of its
evidence. Journal is identity-blind: every guarantee this surface makes holds
for facts that entered through Dawn and for no others.

The development profile uses a configured root key, expiring grants and replay
protection. Production backend isolation, current-authority/revocation ordering
and key-management requirements remain tracked in [STATUS.md](../STATUS.md).

## Artifact creation

`create artifact` accepts `operation_id`, an artifact `path` shaped
`projects/<project>/artifacts/<artifact>`, and an already retained `artifact`.
It needs `publish` at that path and an existing project. Dawn verifies that the
content is retained, creates the artifact space, appends its one `content` fact
with `expected_cutoff` 0, and creates the `declarations` child. The result is
the artifact space, its content occurrence and its declarations space.

An artifact's interpretation must itself be retained: Dawn retains the
descriptors of its own record profiles beside their payloads, and any other
interpretation's descriptor is the caller's to retain, under whatever type,
before `create artifact` names it. Unretained bytes or an unretained
interpretation are `unresolved`, and nothing is created.

A retry with the same operation ID and request recovers the same outcome; a
different operation cannot replace the content. Initialization is resumable:
after an uncertain content append, Dawn recovers and validates the one content
fact before ensuring the declarations child, and never appends a second content
fact on restart. Clients must not treat an artifact space without a content
fact as a created artifact.

## Applications

An application is Dawn running a tool ([decision 0005](../docs/decisions/0005-hosted-production.md)).
`apply` accepts an `operation_id`, the leaf path
`projects/<project>/applications/<name>`, the `tool` by logical name, an
optional pinned `release`, `inputs` binding roles to occurrence references, the
`recipe` as a JSON value, and `wait`. It needs `publish` on the project and
`read` on every input occurrence. The tool must be one the world configures;
the valuation must bind every role the tool's description requires and no role
it does not declare. Each input is resolved to its actual path, authorized, and
verified: a content occurrence's retained bytes must exist, a record
occurrence's payload must verify. A foreign root or world is `not_found`; an
unconfigured tool or an unbound required role is `invalid`; unretained material
is `unresolved`.

Three identities order an application. The **retry identity** is the publisher,
the retry key and the destination leaf, exactly as for every other record: the
same `operation_id` under the same principal recovers the admitted application,
and a different semantic request under that key is `idempotency_mismatch`. The
**application identity** is A, the occurrence of the `attempt` fact heading the
leaf's batch. The **computation identity** is the resolved release occurrence,
the set of bound input occurrences and the recipe hash — the key a reuse looks
up, and two applications with the same computation identity are the same
computation. A reuse is reported as `reused`, naming an earlier application
whose outputs answered this request; the new application is still admitted and
its batch still records the attempt, the bounds and the result.

Dawn then retains the recipe, canonically, and the normalized request, and
appends the leaf's admission. The **normalization** is `dawn/normalization/1`:
the normalized request is the canonical JSON of the `ApplicationRequest`, with
`wait` absent, the recipe canonicalized (RFC 8785, `{}` for absent), the inputs
a map so order is irrelevant, and the release resolved to an occurrence before
normalization. `RequestDigest` covers this normalized request, so toggling
`wait` under one retry key replays. The admission fact is
`attempt(release, request, publisher, request_id, request_digest, accepted_at)`
at A: the release's content occurrence, the hash of the retained normalized
request, and the attempt's admission provenance. With `wait`, the service holds
the call up to its own bound and answers the terminal state if reached;
otherwise the caller follows the leaf through `entries` or a subscription.

The run proceeds under Dawn alone. The tool receives exactly the prepared
inputs, a staging destination that retains through Dawn's content adapter, and,
when its description declares egress, a client confined to those hosts; it has
no other access. On completion Dawn appends one `bound` fact per input role
naming A and the bound occurrence, then one completion batch: one `produced`
fact per output role naming the output's content occurrence, and one `result`
fact — the seal — carrying the outcome, the retained output manifest, the
retained resolution (or `dawn.none.v1` when there is none) and the retained,
service-signed seal envelope. An output artifact space an application creates
opens with a `staged(A)` fact before its content fact; such an artifact is
public only once A has a `result` with outcome `completed` whose manifest lists
it, and until then it is staging material, not a created artifact. An advance
for that space is delivered when it is staged, and the read answers `not_found`
until the seal: an advance reports that a journal changed and does not promise
the subscriber may read it; a read after an advance is authorized and resolved
as any other, and a refusal there is the subscriber's to tolerate. A terminal
attempt with outcome `failed` or `interrupted` carries the reason; no `result`
is appended and nothing the tool staged is named. The batch answers "what
consumed this occurrence" and "what did this tool produce" as filtered reads
over a project's applications; no pointer is copied into an output artifact.

An attempt admitted by a Dawn process that ended before completing it is
terminated as `interrupted` when next observed, and the caller submits a new
operation ID: no application is ever completed by a process that did not run
it. A request that fails validation or a contract check against the tool's
`accepts`, `yields` or `effects` is recorded under the leaf as a signed refusal
record of profile `dawn/refusal/1`, not admitted as an attempt; the caller is
told the reason and the leaf keeps the record.

**Tool releases.** Each configured tool is an organizer `tools/<name>`; each
configured release is an artifact space `tools/<name>/releases/<digest>` whose
`content` fact is the release's `dawn/tool/1` description — name, version,
class, input and output roles, effects, whether it is deterministic, its
egress, and its executable identity (the service binary's hash, the build
revision and the canonical configuration hash). A pinned `release` names the
release's content occurrence exactly; an absent one resolves to the newest
configured release. The executable identity is part of the release digest, so a
release whose binary or configuration changed is another release.

**A describer's declarations** carry the `application` argument of the
`declaration` fact: the admission occurrence A of the hosted application that
emitted the declaration, or `dawn.none.v1` for a declaration a principal wrote
itself. The `relation` fact carries supersession, challenge and withdrawal as
kinds of one explicit relationship; `justifies` joins the premises that jointly
justify an assertion. The describer's publication protocol — how a describer
publishes through its application, the candidate-selection record, and how the
selection is admitted — is not in this revision.

Intake is an application whose input role is bound to a record of profile
`dawn/acquisition-target/1`: a locator, the publisher's expected hash when
there is one, the expected source the hash was read from, and the
interpretation the fetched bytes are retained under. The tool fetches through
its confined client, retains, and refuses a mismatch against the expected hash
as a failed application. Artifacts with no application are the world's roots:
its configured tool releases, and material a principal retained and named
through `create artifact`, which attests who retained it and nothing more.

## Declarations and admission evidence

`declare` accepts:

| Member | Meaning |
|---|---|
| `operation_id` | Stable identity for retries of this assertion request |
| `artifact` | The artifact space's path; Dawn selects its one content fact by rule |
| `subject` | Optional `space#local` subject; absent or empty means the artifact itself |
| `statement` | A JSON object carrying the assertion's domain-specific content |
| `evidence` | Optional exact occurrence references cited as support |
| `justifications` | Optional premises with a `group` and `method` that jointly justify the assertion |
| `relations` | Optional `{kind, target}` relationships to earlier declarations: `supersedes`, `challenges` or `withdraws` |

Dawn resolves the artifact's content and every cited occurrence under the
caller's read authority. The subject's shape is validated; interpreting a
domain subject and assessing a statement's evidence are model or consumer
responsibilities.

The retained envelope, a `DeclarationRecord`, keeps the shape
judgment(actor, claimed_time, frame_ref, evidence, body): its assertion ID, the
original request, the exact `content` artifact, the authenticated `publisher`
and the `admission`. Admission binds the world, the root, the destination
`declarations` space identity `{root_id, space_id}` and its path at admission,
the retained frame manifest by digest, Dawn's admission-policy version
`dawn.declaration.admission.v2`, the acceptance instant, the original request
proof and grants, and `request_body`, the exact request bytes whose digest the
publisher signed. The **frame** is retained in Bytes and named by digest: it
pins the artifact's content reference, the cited evidence occurrences, the
grants and proof relied on, and the policy version. The world is the storage
scope, not the frame. Dawn signs the canonical envelope with
`admission.signature` empty, prefixed by the policy name and one newline byte.
The occurrence position the fact later takes is not signed.

The declaration fact names the assertion ID, the envelope's digest, the exact
artifact, the subject, the publisher and the application: the admission
occurrence A of the hosted application that emitted it, or `dawn.none.v1` for a
principal's own declaration. Assertion
identity is allocated from the authenticated actor and operation ID before the
append. A retry compares the semantic declaration while retaining the first
envelope, admission time and exact signed proof; fresh transport proof never
justifies constructing a second envelope. Recovery verifies the original
signature, admission evidence, destination, artifact and repeated fact fields:
missing required evidence is `unavailable`, inconsistent occurrences are a
profile violation, and neither permits minting another assertion.

A verifier uses the expected world, trusted root, destination space and path,
verifies the admission signature, the frame digest and the original request
signature, and checks that the exact request bytes decode to the stored request.
The portable verifier does not rerun Thesmos or prove that the occurrence is in
a journal; trust in a root is supplied by the verifier. Verification shows what
Dawn admitted for that request. It does not make the statement true.

Each declaration is one occurrence. A later correction creates a new declaration
and names the earlier occurrence in a `relation` of kind `supersedes`,
`challenges` or `withdraws`. The earlier declaration remains retrievable.
Supersession targets must concern the same artifact and subject and belong to
the same authenticated publisher; a worker cannot withdraw another worker's
statement. A relation is an explicit relationship, not an overwrite or an
automatic consumer decision. Justifications cite the premises a describer
relied on, joined by a group and method, one fact per premise. Declaration IDs
are independent of the subject's identity, so disagreeing statements coexist
without competing for one global subject binding.

## Views, query and explanation

`compose` publishes a view record with exact `selections`, `rules` and an
`interpretation_profile`. Each selection is an `EntryRef`; the composer must be
authorized to read each selection and publish the view. Views select individual
declaration occurrences and inventory records. A selection references its
original occurrence; composition does not move it, change its publisher or
assert its content again.

`query` reads only the view's selected occurrences at the view's own
occurrence. Declaration rows have identity `assertion#<declaration-id>` and
expose the artifact, subject, statement, publisher, evidence and relation
links, with the original occurrence. Read that occurrence to retrieve the
complete signed envelope; Dawn verifies admission against the selected
destination, expected world and trusted root before projecting a declaration.
A selected view is an attributed choice of inputs; stored rules are not a
general truth evaluator.

The query filter object is a conjunction of `scope`, `key`, `key_prefix`,
`identity` (`space#local`), `from` (an exact occurrence) and `where` (top-level
member values compared as canonical JSON). `limit` and `cursor` page records in
selection then identity order. An unknown filter produces `unknown` with an
explanation; no matching records produces `known_empty`; an incomplete or
unsupported evaluation is never `known_empty`. When the selected occurrences
define one identity differently and the view's rules do not choose between
them, the outcome for that identity is `ambiguous` and every definition is
returned; the surface reports the disagreement rather than resolving it. A
view occurrence that is not a view record is `unresolved`. The `assertion` identity scope
is reserved for the verified projection; an inventory record defining
`assertion#…` is refused by query rather than presented as an admitted
declaration.

`explain` identifies an assertion's defining declaration occurrence, the
selection that adopted it and the selected occurrences that disagree within the
requested view. Any required selection the reader cannot access makes the
request `denied`; Dawn does not silently narrow a view. Removed with the
contribution profile is any history-wide record binding (order 0002 R2): a
record identity has no global owner, only occurrences.

## Snapshots and exports

`snapshot` captures an explicit manifest of a scope (the world or a path) and
its selected descendants to an optional depth, retains it in Bytes as canonical
JSON and returns it with its digest. Its cutoffs are fixed selections of
individual journals, not a simultaneous observation of the whole tree.
Continuation and resubmission reuse that selection; hitting a bound is
`limit_exceeded`, never a shortened success.

`export` follows the complete traversal of order 0002: for the scope and
recursively for every selected child, capture the space's cutoff once, read its
own journal through all pages, enumerate its children at that same cutoff and
recurse. Every selected space, its cutoff and its child observation are
retained exactly once, including the world, intermediate organizers and empty
journals. The retained export manifest may be chunked in Bytes and need not fit
one Journal manifest's 4,096-space or byte bound. The retained export is a
manifest of profile `dawn/export/1`, `{scope, complete, chunks}`, naming its
chunks by digest in traversal order; each chunk is `{spaces: [{space,
through, entries, children}]}`, consecutive selected spaces with the cutoff
each was captured at, their own journal prefix through that cutoff as
entries, and their child observation at that cutoff, within one manifest's
byte bound. The result reports the manifest, the chunks, the counts and the
coverage. An empty page with a
continuation is not completion; an unavailable page or a traversal limit leaves
explicit incomplete coverage; resumption recaptures no selected prefix. Query is
only an optimization when its complete selection covers the same prefixes.
Referenced byte payloads are a separate retention obligation, and public export
is not provider restoration.

## Subscriptions

A subscription reports which journals under a scope advanced, so a consumer
learns of a change instead of polling for it. It is carried by the socket
alone ([WS.md](WS.md)): a server-initiated frame is what that binding has and
HTTP does not. Journal places subscriptions above its own contract and offers
none, so this is Dawn's surface to provide, not a Journal gap
([decision 0004](../docs/decisions/0004-world-subscriptions.md), whose event
shape [decision 0006](../docs/decisions/0006-subscriptions-as-cursor-advances.md)
replaces).

`subscribe` accepts a `scope` path — the empty path is the world, as it is for
`children`, `snapshot` and `export` — and needs `read` on it. Its result names
the subscription, the scope's space and an expiry, and nothing else: no
cutoffs, because every event carries the cutoff of the journal it reports.
From the instant the reply is written, every advance of every journal under
the scope is delivered or reported as a gap. What happened before it is the
caller's to read, and needs no help to locate: the caller reads each journal
it cares about at the cutoff the read reports, and applies events after that.
`unsubscribe` closes a subscription this connection opened; its authority is
that ownership, never a path grant, because it confers and discloses nothing.

**An event is a journal's advance.** It names the space, with its path, and
the committed cutoff the journal is now at — a cursor, never a fact. The
subscriber reads `(its last position, cutoff]` of that journal through
`entries` under its own read authority, so authorization stays on the read, an
event cannot disclose a fact its receiver may not read, and the journal remains
the single truth for what a fact says. A child space appearing under the scope
is an advance of that child, from absent to its first cutoff, and of its
parent, whose enumeration changed; both are delivered.

**Advances of one journal supersede each other.** Two advances of a journal
are one fact: it is now at the later cutoff. An undelivered event for a journal
is replaced by a newer one, never queued behind it, so a subscription holds at
most one pending event per journal that changed, whatever the commit rate under
its scope. A subscription over a space with a million descendants costs nothing
to open and delivers at most one pending event per journal that moved.

**An advance does not promise readability.** It reports that a journal
changed. A read after it is authorized and resolved exactly as any other read,
and `not_found` or `denied` there is the subscriber's to tolerate — as it is
for a journal beyond its grants, since routing matches the scope the
subscriber proved `read` on and re-checks nothing in the subtree at delivery.
A space that exists but is not yet readable still advances, and so does the
organizer that gained it, because both journals changed; suppressing either
would make a cutoff lie.

**Order is per journal.** Each event names a cutoff later than the last event
for that journal. Journal provides no total order across spaces and no global
snapshot, so this surface promises none: an event carries no world-wide
sequence, because there is none to carry. A consumer that needs one coherent
cross-space picture takes a `snapshot`.

**A gap is the scope, when which journal moved was lost.** The pending table
is bounded by distinct journals; an advance arriving while it is full cannot
be held by name, so the subscription records once that something under the
scope moved and, after the table drains, reports a `gap` naming the scope.
The subscriber re-reads the scope. That is the only thing a subscription can
lose. Silence for a journal means it did not advance; an event names where it
is; a gap says the scope moved somewhere and to look. Exactly as an incomplete
lookup is never absence, a lost advance is never reported as no change.

**Delivery is immediate.** Dawn emits an event as it commits the append, and
nothing polls: not a consumer, which is what the socket is for, and not Dawn
itself. One Dawn process serves one world and is its only writer, so emission
at commit is complete as well as instant; there is no interval to configure
and no bounded-delay case to reason about.

A fact that did not enter through Dawn is not delivered. Journal is
identity-blind, so another holder of Journal access can write into the
subtree, and this surface's standing position already covers it: every
guarantee here holds for facts that entered through Dawn and for no others.
Subscription delivery is one of those guarantees. Were a second Dawn process
ever to serve one world, the answer would be a change feed in Journal under
order 0002, never a poll inside Dawn.

**A subscription is authorized once, at open, and expires.** The open is an
ordinary authenticated call — one proof, one attempt, the replay cache
unchanged — and the subscription ends at the earlier of the grant's validity
end and the service's configured maximum, reported as `expires_at` and
announced by a final `closed` frame. Events carry no proof of their own, which
is why the open must expire: an unbounded subscription would be authority
outliving the grant that created it. Renewal is a fresh `subscribe` with a
fresh proof; the subscriber reads the scope's journals at their current cutoffs
and continues from there, the same read it made on first sight.

A subscription is connection state and nothing else. It writes no fact, creates
no space, appears in no export or snapshot, and leaves no trace when it ends.
It is not evidence: citing something still means citing its occurrence, read
and authorized as ever. Retained content needs no subscription, being immutable.
There is no replay log and no backfill: Journal is the durable history, the
subscriber owns its positions, and resumption is the subscriber's read.

## Operations

All mutations carry an `operation_id`. Dawn derives request identity from the
authenticated principal, so workers cannot claim each other's request IDs.
Record attribution is supplied from that principal.

| Verb | Input | Result |
|---|---|---|
| `resolve` | path | The space, or null when unoccupied |
| `children` | optional path, cutoff, cursor, limit | Readable children at a fixed cutoff; without a path, the world's |
| `entries` | path, after, cutoff, limit | The journal's entries over a fixed prefix, ascending, decoded under the profile |
| `get` | occurrence reference | One exact entry, read at a valid covering cutoff, with its space |
| `fetch` | path and the bytes' hash | Retained bytes the journal names, verified against the hash |
| `snapshot` | optional path, depth | A captured manifest retained in Bytes with its digest |
| `retain` | scope path, bytes, interpretation hash | The artifact; nothing is named yet |
| `create artifact` | operation ID, artifact path, retained artifact | The artifact space, its content occurrence and its declarations space |
| `declare` | operation ID, artifact path, optional subject, statement, evidence and supersession targets | The declaration occurrence with its retained signed envelope |
| `publish` | operation ID, leaf path, profile, record, expected cutoff | The record occurrence |
| `compose` | operation ID, view path, pinned selections, rules, interpretation profile, expected cutoff | The view's record occurrence |
| `apply` | operation ID, application path, tool, optional release, inputs by role, recipe, wait | The admitted attempt and its outcome; on completion the sealed result and the outputs by role |
| `query` | view occurrence, filters, cursor, limit | Records with source, provenance, outcome, coverage and limits |
| `explain` | view occurrence, assertion ID | Defining declaration, adopting selection and disagreement |
| `create project` | operation ID, label | The project space |
| `provision worker` | project, worker label, principal, permissions, validity end | The worker space, its state record, grants and workspace configuration |
| `retire worker` | project, worker | The retired state record; the space and its records remain |
| `create campaign` | project, campaign label | The campaign's state record |
| `assign` | campaign, worker, pinned inputs, intended outputs, brief, expected cutoff | The assignment record; grants no authority |
| `export` | optional path, cursor | The retained export manifest with coverage |
| `subscribe` | scope | The subscription, its scope and its expiry; from the reply on, each journal under the scope that advances is reported once with its new cutoff |
| `unsubscribe` | subscription | The closed subscription and what it delivered |

Worker lifecycle state is published at `workers/<worker>/state` and requires
`provision` on the project; the principal cannot change. Campaign state requires
`provision` on its campaign and cannot change its project. An assignment
requires an existing worker and resolvable pinned inputs; declared outputs are
working material beneath that worker's space or a
`projects/<project>/artifacts/<artifact>/declarations` destination in the same
project, covered by the worker's declaration authority. The assignment itself
never issues a grant.

## Receipts, durability and uncertain outcomes

A replay returns the first admitted result. The same principal and operation ID
with changed content is `idempotency_mismatch`. A fresh proof is required for
each attempt; the operation ID and semantic request remain unchanged.

Record payloads, envelopes, frames and manifests are retained in Bytes before
Journal appends the fact that names them. A failed append may leave
unreferenced retained content. Journal commits each batch with its receipt
atomically; multi-step provisioning or artifact creation can leave intermediate
spaces and recovers through retry. An acknowledged append is committed under
Journal's contract; durability across a Journal restart is the durable
provider's guarantee (order 0002 D10), not this surface's.

Cancellation or a lost connection after dispatch leaves the caller uncertain.
Resend the same operation ID and request to recover the result. Choosing
another operation ID can create another occurrence. Read cancellation has no
effect; an interrupted fetch must not be accepted as a complete verified object.

## Limits and refusals

Journal's key grammar, position domain, page bounds (1..128, default 64),
batch, manifest and continuation bounds apply. Dawn's label grammar is
stricter than Journal's key grammar. Dawn accepts at most 64 MiB of JSON per
record request, except `declare`, whose complete original request is limited
to 16 MiB. Byte streams inherit Bytes' object limits. One connection holds at
most 16 subscriptions; one subscription holds a pending advance for at most
4,096 distinct journals at once, past which the scope is reported as a gap.
Exceeding the subscription bound is `limit_exceeded`; the pending bound is a
gap, not a refusal, because the subscription is still working.

Canonical JSON follows RFC 8785 over finite IEEE 754 numbers. Invalid UTF-8,
unpaired surrogates, duplicate object members, non-finite numbers and integer
literals outside ±(2^53−1) are refused. Names are compared without Unicode
normalization. Statement objects follow the same canonicalization rules.

| Refusal | Meaning |
|---|---|
| `not_found` | The path, space, occurrence, record, file or operation is not here; a foreign root or an identity outside the world is not here for a reader of the world |
| `invalid` | A value outside its grammar, limit or structural rule: a parameter, a label, a cutoff inside a batch or in the future, a body the binding cannot decode strictly |
| `invalid_cursor` | A malformed cursor, or one of another enumeration, traversal or query |
| `unverified` | Invalid key, signature, audience, freshness or replay proof; the operation was not evaluated |
| `denied` | Required authority is absent; the refusal does not disclose target existence |
| `invalid record` | Invalid profile content or a fact that violates `dawn-facts-v2`, with the failing field |
| `unresolved` | Required exact content or occurrence cannot be resolved |
| `structural` | The requested path or profile violates Dawn's layout or protected-space rules |
| `exists` / `conflict` | A new operation collides with an existing key or an expected cutoff |
| `idempotency_mismatch` | An operation ID was reused for a different semantic request |
| `limit_exceeded` | A declared bound of a value, batch, page, manifest or continuation would be exceeded |
| `unavailable` | A backend, a selected journal or required recovery data cannot currently be served; never reported as absence |

These thirteen codes are the refusal table both bindings carry. An internal
failure is not a refusal: the bindings report it as `internal` (HTTP `500`)
and disclose nothing else. The transitional code `not_implemented` stays
defined, and the bindings still map it, for an operation whose source is a
stub naming its owning item in the details — and this revision is a seam, so
`apply` answers it, naming `applications/anchoring` in the details. Every
other verb of the table is implemented, the subscription rows added on
2026-09-17 included, and the
[requirements](../conformance/requirements/README.md) hold each of them to
this contract rather than to a stub, the skipped application cases named with
their lanes. [STATUS.md](../STATUS.md) states what is implemented and
[ACCEPTANCE.md](../ACCEPTANCE.md) dates what was measured.

## Scope and maintenance

Scheduling, execution, review policy and consumer belief are outside the
service. Dawn records claims, their admitted publisher and exact selections. It
does not implement the meta-series membrane/cell kernel or turn shared spaces
into endorsement. The Journal contract does not move for Dawn's sake: a gap is
a Journal change under order 0002, not a Dawn workaround.

The [documentation index](../docs/README.md) identifies the owners of current
contracts and the superseded design documents. Update this surface, affected
types/table, bindings, clients and the [public requirements](../conformance/requirements/README.md)
together: the requirements are the executable form of this document, grouped
by the operation lane that implements them, and a claim here without a case
there is prose. Generated schemas and route-name checks cannot establish the
guarantees described here.
