Collaborative board walkthrough
The collaborative board is deliberately ordinary: columns contain cards, cards have labels and comments, several people can edit, and the application works on web and mobile. That makes it a useful proof of the complete contract. It exercises typed storage, reads, writes, subscriptions, page layout, local state, conflict resolution, permissions, installation, and upgrades without requiring a separate service architecture.
Target experience
- A user installs a pinned board release into a workspace.
- The shared client renders the release's board and card-detail surfaces.
- Queries and subscriptions resolve through named pipeline revisions.
- Edits become typed operations and enter the durable device queue.
- Offline operations replay after reconnect or app resume.
- The server authorizes operations, merges them, and materializes cheap read datasets.
- Other clients can read the converged server materialization.
- An upgrade displays schema, pipeline, surface, dependency, and permission changes before moving the installation pin.
Artifact map
| Artifact | Board responsibility | Status |
|---|---|---|
| Rust serde schemas and runtime descriptors | Board, Column, Card, and Comment records | Exact schema-descriptor and dataset pins are validated and registered with installed releases |
| Pipeline manifests | Strict row-shape list/write/move/comment/event fixtures | Resolved to exact PipelineSpec hashes with declared capability checks |
| Custom transform | Domain-specific move validation or derived activity rows | Trusted native server transforms current; portable client UDF planned |
| Entity operation policy | Field clocks, sets, ordered references, and tombstones | Descriptor-checked causal reducer with durable dots/version vectors and server materialization |
| Surface tree | Board and card-detail pages with bindings and actions | Verified installed-package source and shared Vue/Capacitor host with create/edit/move/comment/label adapters |
| Application release | Exact membership, hashes, capabilities, and provenance | Local unsigned candidates plus managed signing, strict verification, receipt, and immutable registry storage |
| Installation | Tenant pin, accepted permissions, and upgrade history | Tenant-scoped install, consent, upgrade, rollback, revocation projection, and write enforcement |
Try the local release path
From the repository root:
rac app validate apps/collaborative-board/app.toml
rac app build apps/collaborative-board/app.toml
rac app inspect apps/collaborative-board/build/app-release.pb
Validation reports valid=true for the local application graph. Building
strictly parses all five referenced pipeline files, resolves their canonical
PipelineSpec hashes, and writes deterministic CBOR and JSON artifacts.
The CLI retains the app-release.pb filename for compatibility; the local
candidate payload is deterministic CBOR, with a JSON companion. Inspection
reports publish-eligible=false because the local fixture has no
registry-resolved schema-descriptor/dataset proofs or publisher signature. Those
warnings are part of the publication boundary rather than skipped checks.
1. Define records
The card entity in apps/collaborative-board/app.toml declares its fields
directly, each with the merge policy that governs it:
[[entities]]
id = "card"
type_url = "app_example.collaborative_board.Card"
dataset = "app_example.collaborative_board.Card"
identity_fields = [1]
display_fields = [3]
[[entities.fields]]
tag = 1
name = "id"
kind = "string"
cardinality = "required"
merge = "immutable"
[[entities.fields]]
tag = 2
name = "column_id"
kind = "entity_reference"
cardinality = "required"
references = "column"
merge = "last_writer_wins"
The remaining fields follow the same shape: title (searchable, max length
200), description, labels (repeated, add_wins_set, tombstone-retaining),
assignee_id, and archived (bool, defaulting to false). Field tags are
stable identity for the merge reducer, so a field keeps its tag across
revisions.
The final stored records also follow the platform's owner scope and primary key conventions. Schema registration gives each type URL an exact schema hash. In this walkthrough, a descriptor is the runtime metadata derived from that checked-in Rust serde schema: field tags, kinds, identity fields, and merge policy. It is runtime schema metadata, rather than a code-generation artifact. Surface fields and transforms refer to this registered schema metadata rather than recreating a TypeScript-only model.
The merge policy belongs beside the schema contract:
| Field | Policy | Reason |
|---|---|---|
title, column_id, assignee_id | HLC-ordered LWW register | One current scalar value |
labels | Causal add-wins set | Concurrent add/remove events retain their causal frontiers before policy resolution |
| comments | Independent immutable records | Child operations participate in durable ordered replay |
| deleted state | Clocked tombstone | Old updates cannot revive a deleted card |
description | LWW initially; text CRDT opt-in | Character-level merging adds cost and should be explicit |
The entity merge crate validates manifest field declarations against an exact pinned schema-descriptor set. Its preview reducer applies immutable and LWW registers, causal add/remove sets, ordered references, and delete/update frontiers to dynamic messages. Installation-qualified client sites allocate durable dots; the DES oplog validates dot collisions against the exact tenant, entity reference, and canonical payload before materializing the result.
2. Declare server pipelines
The current manifest path can express the board's backend routes as small graphs:
board-listfilters archived rows, sorts bycolumn_idandtitle, and projects the card columns;board-upsert-cardtrims titles, rejects empty ids/titles, and projects a complete row into the card dataset;board-move-cardrejects empty card/column ids and projects a complete card row;board-add-commenttrims bodies, rejects empty ids/card ids/bodies, and projects a comment row;board-eventssubscribes to non-archived card rows with a bounded projection.
These are executable row-shape manifests. Portable entity writes use the
installation-bound CBOR EntityOp transport and require an active,
verified DATASET_WRITE grant for the release-pinned dataset. The
schema-descriptor-driven action adapter covers card creation and editing, moves,
label-set membership, and comment creation. A future custom transform could
handle a rule such as “a closed card cannot move into an active sprint without
reopening,” with pinned input/output schemas and an exact artifact hash.
The public pipeline manifest preview covers the
referenced execution graphs. The application compiler separately parses the
outer manifest and resolves every source path through that strict pipeline
parser.
3. Group a release
The checked-in fixture uses accepted application TOML. This is a shortened
excerpt from apps/collaborative-board/app.toml:
[app]
id = "collaborative-board"
name = "Collaborative Board"
version = "0.1.0"
minimum_runtime_version = "1.0.0"
default_view = "board"
[[entities]]
id = "card"
type_url = "app_example.collaborative_board.Card"
dataset = "app_example.collaborative_board.Card"
[[pipelines]]
name = "board-list"
source = "pipelines/board-list.toml"
[[views]]
id = "board"
name = "Board"
initial_page = "board"
rac app build apps/collaborative-board/app.toml resolves every pipeline,
computes the publisher-independent canonical package hash, and writes
deterministic CBOR and JSON candidates under the fixture's ignored
build/ directory. The local candidate is deliberately unsigned. Managed
publication binds the exact authored source and normalized manifest to that
digest, rejects unknown field tags, verifies the separate publisher
attestation, stores the immutable release, and returns a signed verification
receipt.
4. Describe the board surface
Surface declarations live in the same accepted manifest:
[[views.pages]]
id = "board"
route = "/"
title = "Collaborative Board"
[views.pages.root]
id = "board-shell"
type = "stack"
[[views.pages.root.children]]
id = "board-columns"
type = "board"
The component and action names come from an allowlisted registry. The shared validator checks v1 route, component, action-target, binding-shape, execution site, capability, and schema-descriptor rules. The current adapter derives safe create, edit, move, label, and comment operations from signed entity schema descriptors; arbitrary schema-descriptor-generated live subscriptions remain planned.
5. Execute on web and mobile
The canonical Vue application already builds into the Capacitor Android and iOS shells. The local fixture and remote installed-package source run through that shared Vue code. Remote loading resolves the authenticated tenant installation, verifies the immutable receipt and signed mutable installation projection, checks the authenticated response sequence, requires an active installation, and then projects the release:
AppRelease
-> verify hashes and signature
-> register schema descriptors and components
-> resolve installed capabilities
-> build routes and surfaces
-> open query/subscription bindings
-> render with the shared Vue host
Native actions such as notifications, camera input, file selection, or location will use separately declared host capabilities. Arbitrary schema-descriptor-generated subscriptions and native effect adapters are still planned.
6. Work offline and converge
The portable Rust/WASM runtime binds state to the installation, tenant, user, and an installation-qualified device site. It persists one bounded canonical CBOR envelope containing HLC state, entity cache, causal context, queue sequence, retry metadata, acknowledgements, dead letters, and verified package trust state through Capacitor Preferences. Mutations reload the latest durable snapshot under a cross-tab lock, and reconnect/resume hooks replay the ordered queue through the installation-bound CBOR entity endpoint.
An offline move follows this path:
- Validate the proposed
Cardchange against the pinned Rust schema descriptor. - Create a typed operation with an id, site id, and HLC stamp.
- Persist the operation before exposing the queued result.
- Replay after reconnect in deterministic order.
- Record an applied, duplicate, retry, or permanent-rejection disposition.
- Merge accepted operations and any concurrent remote operations using the field's declared policy.
- Refresh the rendered board through the same query/subscription interface.
Authentication and dataset grants remain server-authoritative. Optimistic state can be rejected, in which case the client records the error and re-materializes from acknowledged operations.
7. Share, upgrade, and fork
- Share the application: grant a principal access to the private catalog entry; the grantee sees it under Shared with me and can install the pinned release into an authorized workspace.
- Share board data: create a separate dataset grant, potentially read-only.
- Upgrade: inspect changes to schemas, pipelines, surfaces, dependencies, migrations, and capabilities, then move the installation pin.
- Rollback: return to a compatible prior release while preserving the append-only operation history.
- Fork: create a new app identity and release lineage. Source data stays in place unless the user explicitly copies or grants it.
Preview verification checklist
The developer-preview implementation exposes each lifecycle boundary for targeted verification. A deployment or device-conformance result should be claimed only after its corresponding check has run:
- publish and install a signed release, then load it without a hard-coded app route;
- render the same installed surface through the shared web and Capacitor host;
- inspect every exact schema, pipeline, artifact, surface, and dependency pin;
- replay concurrent offline edits from installation-qualified sites and inspect the converged materialization;
- reject an undeclared capability, inactive installation, and tampered bundle;
- inspect an upgrade diff, preserve pending operations across the rebind, and exercise rollback.
Continue with release and security lifecycle or return to the Pipeline Apps mental model.