# Shared surfaces

> When more than one party writes to one tree, the surfaces they share are shared mutable state.

Page: PAG · Orchestration
Canonical: https://banes-lab.com/pag/orchestration#shared-surfaces

This section is stop 83 of 102 in the learning route. Previous: [02 - Composing a collaboration](https://banes-lab.com/pag/orchestration/composing-a-workflow.md). Next: [04 - Phase binding](https://banes-lab.com/pag/orchestration/phase-binding.md). It builds on [02 - Composing a collaboration](https://banes-lab.com/pag/orchestration/composing-a-workflow.md).

When more than one party writes to one tree, the surfaces they share are [shared mutable state](https://banes-lab.com/records/arch/shared-mutable-state.md). A document expresses four things about them, the schema, the records, the items and their lifetime, as written in [C1·a surface declared](https://banes-lab.com/pag/orchestration#shared-surfaces-panel-a) and shown in [C1·d surface to state](https://banes-lab.com/pag/orchestration#shared-surfaces-panel-d). The definitions and the reasons are given in [coordination is software](https://banes-lab.com/disciplined-methodology/collaborate/coordination-is-software.md) and in [the board and the venue](https://banes-lab.com/disciplined-methodology/collaborate/the-board-and-the-venue.md); this section declares the shape. [C1·b state as function](https://banes-lab.com/pag/orchestration#shared-surfaces-panel-b) shows how a state is read and a write is fenced, [C1·e a write lands](https://banes-lab.com/pag/orchestration#shared-surfaces-panel-e) shows where a write lands or is refused, and [C1·c lifetime axes](https://banes-lab.com/pag/orchestration#shared-surfaces-panel-c) shows the declaration a mechanism reads.

### Records, items, derived states

A shared document with no declared writer per span is one that every party rewrites whole. Two parties revise their own records by rewriting the file, each correctly, and the second write is a [lost update](https://banes-lab.com/records/arch/lost-update.md) for the first party, with no error anywhere. A file offers no span a party can anchor on unless the document declares one, so the only edit available is the whole file.

For this reason a shared surface holds records with one writer each, and every state is a query over those records. The surface is declared as a schema a tool can refuse against, rather than described in prose the parties have to keep in mind. In practice, a shared surface is declared as a schema: its key in the header, one record per writer with the writer named on the record, and a fence around each record so that an edit has a span to anchor on. An item is declared with an id the surface allocates, a kind that selects its closure, and the readers it is addressed to. Open, blocked and absorbed are derived by a function over the edges, an absorbed item's durable half is extracted and the item deleted in the same change, and each surface's lifetime is declared on retention, mutability and removal.

To check this, take the last write to a shared surface and name the span it was anchored on. A write with no span was a whole-file write, and the neighbour it overwrote is the finding. An outcome surface written jointly has no per-party unit for the one-writer rule to range over, so the invariant is declared inapplicable there, with its reason. A clash of meaning on such a surface is caught by announcing the intended write, with each author removing its own duplicate.

The document states the one writer per record on the record itself. The act node that writes carries the mechanism as its contract: a witness read, an anchor on its own fence, and a refusal when the surface has moved. An edit against a moved surface is therefore refused with the diff, and a whole-file write is never the available path.

The document writes no state. Every state is a function over the edges, declared once and evaluated on every read, as described in [derived state](https://banes-lab.com/disciplined-methodology/verify/derived-state.md), and an item whose citation resolves is extracted and deleted in the same change rather than left resting in a state.

A lifetime is declared on the three axes derived in [stating an invariant](https://banes-lab.com/disciplined-methodology/collaborate/stating-an-invariant.md), each drawn from a closed set, so the declaration is a value a mechanism can join on rather than a sentence. A mechanism decides what it may do to a surface from that declaration, never from the shape of the surface's path.

C1·a surface declared

```pag
# the four things a document declares about a shared surface · a structure declaration, never prose
SURFACE <key>:                                   # declared in the header, never derived from the path
RECORD <key>-1 subject: <what it is about>   # one writer, named on the record · the fence an edit anchors on
ITEM <key>-1-1 TO <reader>: <a claim>    # an addressed span · its id allocated once, never reused
SATISFIED_BY <artifact>              # an edge · an id in a field · resolves or does not
BLOCKS <key>-2-1
RECORD <key>-2 subject: <what it is about>
ITEM <key>-2-1 TO <reader>: <a claim>
ANSWERS <key>-1-1

# the states are derived from the edges, never written
state: OPEN | BLOCKED | ABSORBED

# the lifetime, declared on three axes a mechanism can join on
DECLARE lifetime: object
SET lifetime = {retention: <what ends a piece of content>, mutability: <who may rewrite a landed statement>, removal: <who may take content out>}
```

C1·b state as function

```pag
# no party writes a state · every state is a function over the edges, evaluated on every read
FUNCTION state_of(item):
IF resolves(item.edges.satisfied_by): RETURN <absorbed>       # a transition · extract, then delete in the same change
FOR EACH edge IN inbound(item, <blocks>):
IF state_of(edge.from) == <open>: RETURN <blocked>
RETURN <open>

# NODE 6 — ACT   [epistemic · formalisation · computation · yields: procedures]
CONTRACT:
input:        <my record> + <the surface as it stands>
transform:    read the surface whole → anchor on my own fence → land the edit inside it
constraints:  a write to a path not read this turn is an edit to unknown contents; a whole-file write reports success to the one who overwrote and nothing to the one overwritten
output:       <my record, revised>
handoff:      the edit landed inside my fence and the surface had not moved, or the edit was refused with the diff (yields: boolean)

HANDOFF GATE (evidence-bearing):
rule_id: "ACT"   yields: boolean
[check] nothing outside my fence changed (evidence: the diff of the surface) over: the surface's records measured: <untouched> / <records>
[check] the surface was read whole immediately before the write (evidence: the witness read)
[check] a moved surface refused the write, or the write commuted and replayed (evidence: the compare against my own span)
refuse: the surface moved inside my span since the witness read before PERSIST_ARTIFACT
standing: moved-set <the records that moved outside my span>
result: pass → NODE 7 | a write outside my fence → REPAIR (owner: NODE 6) | unknown → BLOCKED
```

C1·c lifetime axes

```pag
# a lifetime is three independent axes · one word for it drops the axis a reader assumes follows
DECLARE lifetimes: array
SET lifetimes = [
{surface: <a coordination surface>, retention: <current-truth>,  mutability: <owner-rewritable>, removal: <the handler of an item>},
{surface: <an argument>,            retention: <accumulating>,   mutability: <append-only>,      removal: <none while open · moved whole when settled>},
{surface: <an archive>,             retention: <accumulating>,   mutability: <frozen>,           removal: <none>}
]

FUNCTION may_remove(party, content, surface):
# decided from the declaration, never from the shape of the path
SET lifetime = lifetimes[surface]
RETURN lifetime.removal == party.role_on(content)
```

C1·d surface to state

```mermaid
flowchart TB
surface["A surface · a file the parties read and write"]
r1["Record · one writer, declared on the record"]
r2["Record · one writer"]
item["Item · an allocated id, a kind, its readers"]
ref["An edge · an id in a field · resolves or does not"]
state["State · a function over the edges, written by no party"]
surface --> r1
surface --> r2
r1 --> item --> ref --> state
```

C1·e a write lands

```mermaid
flowchart TB
intent["A party intends a write"]
read["Read the surface whole"]
span["Anchor on its own fence"]
moved{"Surface moved since the read?"}
land["Land inside the span"]
overlap{"Overlap with its own span?"}
replay["Replay · the writes commute"]
refuse["Refuse · with the diff of the span"]
intent --> read --> span --> moved
moved -- no --> land
moved -- yes --> overlap
overlap -- no --> replay --> land
overlap -- yes --> refuse
```

## Links to

- [Shared Mutable State](https://banes-lab.com/records/arch/shared-mutable-state.md)
- [Coordination is software](https://banes-lab.com/disciplined-methodology/collaborate/coordination-is-software.md)
- [The board and the venue](https://banes-lab.com/disciplined-methodology/collaborate/the-board-and-the-venue.md)
- [Lost Update](https://banes-lab.com/records/arch/lost-update.md)
- [Derived state](https://banes-lab.com/disciplined-methodology/verify/derived-state.md)
- [Stating an invariant](https://banes-lab.com/disciplined-methodology/collaborate/stating-an-invariant.md)

## Linked from

- [Orchestration invariants](https://banes-lab.com/pag/orchestration/orchestration-invariants.md)
- [Keywords](https://banes-lab.com/pag/keywords/keyword-ontology.md)
- [BNF grammar](https://banes-lab.com/pag/grammar/bnf-grammar.md)
- [Coordination templates](https://banes-lab.com/pag/templates/templates-coordination.md)
- [Where a rule lives](https://banes-lab.com/disciplined-methodology/start/from-chat-to-tree.md)
- [Coordination is software](https://banes-lab.com/disciplined-methodology/collaborate/coordination-is-software.md)
- [Scale follows from structure](https://banes-lab.com/disciplined-methodology/ship/scale-follows-from-structure.md)
- [A concern is a component](https://banes-lab.com/software-architecture/scale/a-concern-is-a-component.md)
