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 resolve through exact named pipeline revisions; the foreground host refreshes declared entity collections from authorized snapshots.
- 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 visible, online clients refresh 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 six 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-listreads active and archived rows with nonempty ids, columns and titles, excluding legacy incomplete records from hydration without deleting them. It sorts active cards first and then bycolumn_idandtitle, and projects the card fields. The Board and Archive surfaces select their display from the same bounded collection;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-commentsreads workspace-authorized comments in creation order; the detail list filters by the selected card, and hydration merges durable pending creates into the same fixed collection used by optimistic writes;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.3"
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.
Card lifecycle
The create form accepts a title and description. Card edits, moves and label
changes update the selected entity after the durable operation succeeds, so the
next action uses the resulting values. Archive and restore change only the
existing archived field through the same offline-capable entity path; the card
and its comments remain stored. The Archive page opens archived cards for
restoration to their previous column.
The board component accepts a static archiveFilter prop with active,
archived, or all. Omission preserves the existing all behavior. Filtering
and display projection run in the shared Rust core/WASM, before the per-column
display limit. Display excerpts never replace the full entity carried by a
selection action, including long descriptions, labels and identifiers. Cached
archived records can therefore remain available for offline restoration without
appearing on the active board.
This remains a bounded preview: board-list reads at most 500 cards, and active
cards sort before archived cards within that window. The Archive page does not
provide pagination or promise a complete history beyond that limit. Version
0.1.3 must be published after deploying the host and publication validator that
support archiveFilter; existing installations must upgrade their signed pin.
Runtime version 1 alone does not negotiate this additive component property.
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 signed query and entity-read interfaces.
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.
Foreground synchronization
The signed release opts into foreground refresh with root config
runtime_entity_refresh = "foreground_snapshot_v1". This is the author's
declaration that all entity hydration pipelines are read-only and safe to repeat.
The board's card and comment queries satisfy that contract. The execution API
does not enforce a read-only mode, so existing releases without the opt-in keep
startup-only hydration and must be explicitly upgraded to enable refresh.
The installed host refreshes the opted-in collections declared by signed entity hydration actions, including cards and comments. It runs at most one refresh pass at a time, with at least 15 seconds between starts while the app is visible and online. Reconnect, returning to the app, replay completion and settled local actions request a refresh through the same bounded scheduler. Failures retain the current display, show an error and retry with backoff up to two minutes.
Each pass invokes exact release-pinned pipelines with the installation identity, then reads authorized entity values and folds pending durable operations. A local action or installation/workspace change invalidates an overlapping display result. Only declared collections are replaced; drafts, selected-card form values and navigation remain local. Open another card to load its refreshed values into the editor. Hidden and offline clients refresh after returning online and visible.
This is periodic snapshot convergence, not a live subscription guarantee. The
server does not yet accept installed application specs on its subscription
endpoint; the board-events fixture alone does not create a browser subscription.
Refresh retains the existing bounded query windows, so it does not establish
pagination, arbitrary board/column editing or native background synchronization.
Two independent browser contexts can verify this shared host behavior; native
device conformance still requires its own device run.
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.
Comment visibility and bounds
Comment display uses the generic list filterField and bound filterEquals
properties. Exact matching happens before the display limit; an empty or missing
selection renders no rows. This display filter does not authorize data access:
installation grants and workspace scope remain enforced by the server. The
reference query reads at most 200 comments and the selected-card list shows at
most 100. Pagination and live comment subscriptions remain future work.
Causal list contract
Ordered reference fields use unique, nonempty string members and causal snapshot frontiers. A snapshot replaces only snapshots its causal context observes. Concurrent snapshots retain their union: traverse maximal snapshots in ascending (site id, counter) order and append each member at its first occurrence. This resolves conflicting reorders deterministically without using wall clocks. An observed later snapshot can reorder or remove any member. A concurrent snapshot that still contains a removed member retains it until a later edit observes both. This is a snapshot sequence contract; it does not promise character-level editing or positional insertion intent across concurrent reorders.
The server persists the maximal snapshots with the entity clock rollup; the portable runtime retains this rollup and uses its version vector on the next queued operation. Portable list and set mutations require causal dots. The current board surfaces mutate cards and comments; arbitrary list-editing controls are not yet exposed by the host action contract.
Publication requires a trusted signed receipt naming
redgold.causal-snapshot-lists-add-wins-sets.v1, bound to the exact release.
The verifier first compiles every entity with the installed-write descriptor
mapping. Missing or unknown attestations and unsupported merge strategies remain
ineligible. Local unsigned candidates cannot assert their own compatibility.
For UI deployment evidence, read the redgold-ui-revision comment in the served
HTML index. For UI prefixes, the proxy derives /build-revision.txt from that
same index stamp rather than the legacy storage object. The endpoint is
non-cacheable and fails closed when the index has no valid revision; docs
prefixes retain their separate marker protocol. Compare the marker with the
loaded application and service worker when verifying browser delivery.