Pipeline Apps

Collaborative board walkthrough

A developer-preview portable pipeline app spanning release distribution, rendering, durable replay, and causal entity merge.
This is the golden developer-preview fixture for the portable Pipeline Apps contract. It covers strict authoring, managed signed publication, tenant installation, verified remote loading, the shared Vue host, durable Rust/WASM replay, schema-descriptor-driven board mutations, and schema-descriptor-bound causal entity merge, owner/grantee sharing, and operator-reviewed catalog publication. Arbitrary schema-descriptor-generated subscriptions, custom client transforms, and public self-service publication remain planned.

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

  1. A user installs a pinned board release into a workspace.
  2. The shared client renders the release's board and card-detail surfaces.
  3. Queries and subscriptions resolve through named pipeline revisions.
  4. Edits become typed operations and enter the durable device queue.
  5. Offline operations replay after reconnect or app resume.
  6. The server authorizes operations, merges them, and materializes cheap read datasets.
  7. Other clients can read the converged server materialization.
  8. An upgrade displays schema, pipeline, surface, dependency, and permission changes before moving the installation pin.

Artifact map

ArtifactBoard responsibilityStatus
Rust serde schemas and runtime descriptorsBoard, Column, Card, and Comment recordsExact schema-descriptor and dataset pins are validated and registered with installed releases
Pipeline manifestsStrict row-shape list/write/move/comment/event fixturesResolved to exact PipelineSpec hashes with declared capability checks
Custom transformDomain-specific move validation or derived activity rowsTrusted native server transforms current; portable client UDF planned
Entity operation policyField clocks, sets, ordered references, and tombstonesDescriptor-checked causal reducer with durable dots/version vectors and server materialization
Surface treeBoard and card-detail pages with bindings and actionsVerified installed-package source and shared Vue/Capacitor host with create/edit/move/comment/label adapters
Application releaseExact membership, hashes, capabilities, and provenanceLocal unsigned candidates plus managed signing, strict verification, receipt, and immutable registry storage
InstallationTenant pin, accepted permissions, and upgrade historyTenant-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:

FieldPolicyReason
title, column_id, assignee_idHLC-ordered LWW registerOne current scalar value
labelsCausal add-wins setConcurrent add/remove events retain their causal frontiers before policy resolution
commentsIndependent immutable recordsChild operations participate in durable ordered replay
deleted stateClocked tombstoneOld updates cannot revive a deleted card
descriptionLWW initially; text CRDT opt-inCharacter-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-list filters archived rows, sorts by column_id and title, and projects the card columns;
  • board-upsert-card trims titles, rejects empty ids/titles, and projects a complete row into the card dataset;
  • board-move-card rejects empty card/column ids and projects a complete card row;
  • board-add-comment trims bodies, rejects empty ids/card ids/bodies, and projects a comment row;
  • board-events subscribes 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:

  1. Validate the proposed Card change against the pinned Rust schema descriptor.
  2. Create a typed operation with an id, site id, and HLC stamp.
  3. Persist the operation before exposing the queued result.
  4. Replay after reconnect in deterministic order.
  5. Record an applied, duplicate, retry, or permanent-rejection disposition.
  6. Merge accepted operations and any concurrent remote operations using the field's declared policy.
  7. 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.

Copyright © 2026