# Relay 1.3.0 Relay is a reusable work-history feature, not a separate identity service. Mock2 emits events; CPR packages the page and API. Every application keeps its own runtime, database, workspace permissions and release cadence. Installing the framework does not expose a public work feed. The complete shared interface is the implementation used by the [approved interactive demo](https://mock2.fractionate.ai/demos/relay/). The demo uses simulated events; an application uses its authorized workspace events through the same `Relay` component. Do not replace it with a simplified task list, download-only reference panel or separate mock UI. The canonical copy-pastable installation prompt is [`Relay-App-Integration-Prompt.md`](https://mock2.fractionate.ai/core/cpr/Relay-App-Integration-Prompt.md), distributed locally at `.mock2/standards/Relay-App-Integration-Prompt.md` with the Mock2/CPR 1.13.0 standards. Read this guide and the actual exported adapter interfaces with it. Publishing this shared package and public demo does not install or mount Relay in any existing application; each consuming app requires its own verified integration. ## Interface acceptance | Area | Required behavior in the consuming app | | --- | --- | | Workspace | Top tabs, left work-item column and right cards/activity/detail pane, independent content scrolling inside host-supplied height, mobile list/detail navigation, no initial document scrolling | | Metric cards | Running, today's work time, verified deployments and needs-input context; applicable cards filter the work list and All work resets it | | Activity | Date-selectable Git-style grid; blue imported history, green native activity, labeled mixed days; navigable earlier windows and all selected-day events | | Run costs | Executed/reused/prerequisite-blocked results, original source evidence, invalidation reason, queue and command durations; missing measurements remain unknown | | Tasks | Investigation/Build, measured running/stale clocks, outcome/summary, current checkpoint, next step, needs-input text, comments, requests and history/evidence | | Connections | Clickable task dependencies and design relationships shown as a graph, including linked work and the tasks a prerequisite unblocks | | Mockups | Reference gallery with linked-task context, image thumbnails and authenticated preview/download; unsupported inline formats remain safe downloadable evidence | | Execution | Actual parallel steps, animated running connectors, retained failure/repair/retry attempts, command/findings/revision/reference inspection and replay of recorded history | | Idea to outcome | Secondary view with visually connected origins, combined feature, contributing runs and verified delivery; distinct idea lead time and measured unique-run work time | | Daily reports | Separate structured reports with deployed, still-active and needs-input context, with links back to recorded tasks | | Theme and device | Host light/dark tokens, keyboard access, readable status contrast and desktop/mobile layouts preserving the same features | An integration is complete only after these behaviors are checked in the rendered host app. A successful build, an overflow check or a screenshot saved without comparison does not establish interface parity. Keep a feature matrix and compare screenshots of the host against the approved demo at desktop and mobile widths, including both host themes where supported. Record deliberate host theme differences and any feature gap; do not silently treat a reduced interface as complete. ## Install in an existing application 1. Inspect the application's actual framework, routes, auth/session middleware, workspace/RBAC, theme, PostgreSQL connection/migration owner, private storage, realtime and deployment process. Use an immutable release/commit-pinned copy of `core/relay` as `@cpr/relay` 1.3.0; this is a source package, not a promise of a public npm registry package. A vendored/file dependency or packaged release can use the application's existing dependency installation process. The checked-in `lib/` entry points are compiled ESM with declarations; `src/` remains editable source. Pin the exact source commit and package/archive SHA-256 in the application's records and dependency lock. Verify the published release hashes before using the files, and keep `src/`, `lib/`, styles and migrations from that one candidate. If source changes, rebuild `lib/` from the same source before packaging. Never mix a new source tree with an older compiled runtime or install from floating `main`. React, Express, Zod, Drizzle and Lucide are peer dependencies; reuse compatible host versions and document any small boundary adapter. Do not introduce a new auth service, independent database/backend or second ORM to install Relay. 2. Add `migrations/0000_jazzy_martin_li.sql` to the host's migration plan. It was generated from `src/db/schema.ts` using Drizzle. It creates only `relay_streams` and `relay_events`, with no existing-table rewrite. The host migration owner applies it before mounting the API. No user, session or credential tables are created. Disabling/removing the routes and package retains data. A reader downgrade is subject to the history compatibility requirements below. 3. Implement `RelayHost` in `src/routes/relay.ts`. Use the existing authorization resolver for every workspace and permission, the existing CSRF/session or scoped bearer validation in `checkMutation`, and private host storage for reference bytes. `authorize` returns null for anonymous users and forbidden workspaces. Use a transaction-capable PostgreSQL adapter; a transaction must run every statement on one checked-out connection and roll back on throw. 4. Mount the router **after** the host's session/bearer middleware: ```ts import { createRelayRouter } from '@cpr/relay/server'; import { postgresStore } from '@cpr/relay/store'; app.use('/api/workspaces/:workspaceId/relay', createRelayRouter({ events: postgresStore(hostDatabaseAdapter), authorize: hostRelayPermissionAdapter, checkMutation: hostCsrfOrScopedBearerAdapter, storage: hostPrivateReferenceAdapter, log: error => hostLogger.error(error), })); ``` The adapter names above are application-owned implementations of the exported interfaces, not claims that an existing SDK exports those names. See the executable PGlite adapter and fake host in `tests/relay.test.ts` for the complete contract exercised with real SQL and HTTP requests. Grant `relay.record` only to authenticated event-producing workers. Human pages need `relay.view` and their allowed comment/request/reference permissions. Grant `relay.history` separately to authorized workspace history operators for import/resume; do not grant human pages `relay.record` to enable that control. The server enforces these permissions independently of visible controls. SSE rechecks access on every poll and resumes with Last-Event-ID or `after`. 5. Mount the page in the host router/navigation under `/work`: ```tsx import { Relay, createRelayClient } from '@cpr/relay'; import '@cpr/relay/styles.css'; // Memoize this client per workspace; changing identity reloads and unsubscribes the old stream. const relay = createRelayClient(`/api/workspaces/${workspaceId}/relay`, { request: hostJsonRequest, // credentials + CSRF, parses JSON, throws user-facing HTTP errors subscribe: hostLiveStream, // parses SSE JSON, reconnects with cursor, returns an unsubscribe history: true, // only when the host has configured history endpoints canImportHistory: canHistory, // host grant for relay.history; server enforces it again }); return
; ``` Use a `useMemo`/module stable client, not a new object each render. Map the manifest theme tokens to the host palette in both light and dark modes; retain status accents and the component's information hierarchy. Keep the host page shell and navigation. `example` is false by default. The host supplies the available height; list and content panes scroll inside Relay. For a full-viewport route, the optional `observeRelayViewport(hostElement)` helper updates only the host element's `--relay-viewport-height` property. Call its returned cleanup function when the host unmounts; it does not lock or scroll the document. The bundled Vite example explicitly uses simulated events. Demo fixtures are opt-in development data, visibly labeled, never the default production workspace feed and never a fallback that hides an auth, connection or empty-data problem. The host transport must parse JSON failures, preserve same-origin credentials, supply CSRF for session-authenticated mutations (or validate the existing scoped bearer mechanism), parse SSE, reconnect from the last persisted cursor and unsubscribe on workspace/identity change. Do not put tokens in URLs or browser storage merely to enable the stream. Configure the existing reverse proxy to avoid buffering this authenticated SSE route. Test access revocation after the stream opens; the server rechecks access, and the client must reflect disconnection rather than claiming live work. Reference bytes stay in the host's private storage and are served through workspace-authorized routes. Gallery preview access has the same workspace boundary as downloads; public object URLs are not a shortcut. `RelayClient.preview(artifactId)` is optional; `createRelayClient` supplies the same-origin `/references/:artifactId/preview` URL. That route rechecks authorization and image signatures and only serves PNG/JPEG/WebP inline. PDF, text and Markdown remain attachment-only through `reference(artifactId)`. An `` request uses the host session cookie; a bearer-only app must use its existing authenticated proxy or fetch bytes with its authorized transport and expose managed blob URLs, revoking them on replacement/unmount. Never embed a token in an image URL or render Markdown/text as raw HTML. Check proxy/body size limits against the configured reference limit, including JSON/base64 expansion; test real storage, missing-object and denied-workspace cases in the app. ## Automatic initial Git lookback Configure the repository on its authorized backend checkout using its stable host repository ID: ```sh node .mock2/bin/relay.mjs init --workspace WORKSPACE_ID --repository-id REPOSITORY_ID node .mock2/bin/relay.mjs history-status node .mock2/bin/relay.mjs history --earlier ``` `init` (or `connect`) starts bounded background history work after saving the explicit source and workspace mapping. `--repository PATH` selects a known authorized checkout; the browser never supplies this path. `--foreground` is available for deterministic installation/test procedures. An optional `--ingest-url HTTPS_URL` binds delivery to the configured host. The existing `RELAY_INGEST_TOKEN` is supplied by the host secret mechanism and never persisted in configuration. With a configured endpoint and token, the worker delivers after importing; otherwise history stays local and status reports the delivery gap. Existing producer synchronization remains available. Initial import observes the previous six months at a pinned commit. Work is batched and resumable; `history --earlier` requests an earlier window. The background worker pauses after at most 100 batches (10,000 commits with its default batch size); resume the current window with `history`. The status reports running, paused, complete or failed and flags shallow history explicitly. After fetching deeper Git ancestry, run `history` again (or POST `/history/import` with `{"earlier":false}`); a completed shallow window is rechecked at its pinned commit and deduplicated. Do not claim complete Git history from a shallow checkout or from a window/cursor that has not finished. The host may inject the exported `createLocalHistoryAdapter` from `@cpr/relay/history`, mapping an authorized workspace to its configured producer root. The optional router history endpoints check workspace capabilities and mutation protection; they never accept arbitrary filesystem paths. Hosts without local Git can provide an equivalent adapter using their existing repository-provider connection. A history observation carries repository ID, full SHA, parents, subject, original author/committer timestamps and import time. Deduplication preserves a single observation per workspace/repository/ SHA. Blue imported squares and green native squares show origin; mixed days show both. Historical tests, deployment, intent and execution time are unknown until independent recorded evidence is linked. They do not contribute to measured work time or verified-deployment counts. ## Upgrade and retained-history compatibility Relay 1.3.0 adds three optional `schemaVersion:3` lifecycle observations with minimum reader 1.3.0: `record.added` (a request, interpretation, question, answer, feedback, proposed or approved decision, acceptance check, risk, plan revision, authorization, finding or observation, with its author, provenance and references), `work.state.changed` (one of nine lifecycle stages, a state, and a waiting reason when waiting) and `execution.recorded` (a Mock2 execution record, or its id and SHA-256 digest). The repository producer writes them only when `.mock2/checks.json` sets `"relay": {"lifecycleEvidence": true}`, off by default exactly like `checkEvidence` (`relay.mjs lifecycle` reads `{type, taskId, data}` on standard input and reports `emitted: false` with the reason while it is off). Upgrade every reader, ingestion validator and producer to 1.3.0 before turning it on; version-1 and version-2 history remains valid unchanged, and a rollback keeps a 1.3.0-compatible reader once version-3 observations exist. The event contract is `Work-Lifecycle.md` in the standards directory. Relay 1.2.0 adds `schemaVersion:2` `history.commit.observed` and `check.recorded` with minimum reader 1.2.0. Upgrade frontend/server/storage validators and repository producers together before enabling these observations. Existing version-1 records remain valid without rewriting. Apply the additive `migrations/0001_aspiring_morph.sql` migration through the host's migration owner. Rollback must retain a 1.2.0-compatible reader or tested adapter; never remove version-2 history to satisfy an old reader. The following version-1 metadata requirement still applies: Within `schemaVersion: 1` records, the 1.1.0 metadata vocabulary requires a **minimum reader version of 1.1.0 after enriched metadata events are written**. A 1.0.1 validator rejects `checkpoint`, `next` or `needsInput` in `task.created` as unexpected fields, and rejects `task.updated` as an unsupported event type. A preserved envelope version does not make those older validators forward-compatible. When upgrading an existing integration, update all frontend/server event readers, worker consumers, and repository emitter/export/sync/check-capture packages to the pinned 1.3.0 implementation before enabling producers that emit this metadata. Test reads of existing history and the first enriched event through every delivery path. An older producer emitting only the previous vocabulary does not need its old events rewritten, but any tool that reads a ledger containing enriched events needs the new reader. Retain enriched database and repository history during disabling or rollback. Returning to the 1.0.1 package alone cannot read that history. A rollback must keep a 1.2.0-compatible reader once version-2 observations exist, or provide a tested compatibility adapter that handles the new metadata/type while preserving stored events and lifecycle semantics. Do not erase or strip history to make the old validator pass. Record this minimum-reader requirement and the actual rollback path in the consuming app's release information; route/package removal and compatible software downgrade are different acts. ## Work recording The installed repository gets `.mock2/bin/relay.mjs`, the package under `.mock2/features/relay`, and automatic check-runner capture. The check runner emits actual started / heartbeat / finished records including the check's exit result and revision. Missing checks remain `missing`, failures remain failures; recording cannot turn a failing check green. Heartbeats say only that the process is running; they never invent a test count or a reviewer percentage. Raw command output is not captured automatically because it may contain credentials or private application data. For a task, instrument boundaries and meaningful checkpoints with JSON on stdin: ```sh node .mock2/bin/relay.mjs emit < event.json node .mock2/bin/relay.mjs export ``` ```json {"schemaVersion":1,"id":"event-unique-id","workspaceId":"my-workspace","taskId":"W42","runId":"run-unique-id","stepId":"implementation","attempt":1,"type":"step.started","at":"2026-10-02T16:00:00.000Z","data":{"title":"Implementation","revision":"exact-git-sha","dependencies":[]}} ``` Create `task.created` and `run.started` first. Follow with `step.activity` and `step.finished`, then `run.finished`. Use a distinct run ID per execution; retry a failed logical step with the same step ID and a higher attempt. Dependencies reference `stepId:attempt`; a repair may depend on a failed attempt and its new implementation may depend on the repair. All remain in history. The shared validator lists accepted event fields and never accepts executable event instructions. `task.created` can additionally record `checkpoint`, `next` and `needsInput`. Later `task.updated` events update the summary and those display fields without rewriting the original record. For example: ```json {"schemaVersion":1,"id":"checkpoint-unique-id","workspaceId":"my-workspace","taskId":"W42","type":"task.updated","at":"2026-10-02T16:04:00.000Z","data":{"checkpoint":"Export schema verified","next":"Build the download action","needsInput":"Confirm which columns should be exported"}} ``` Use `"needsInput":""` in a later `task.updated` to clear the displayed input request when it is resolved. `task.updated` requires at least one of `summary`, `checkpoint`, `next` or `needsInput`, each a string of at most 8,000 characters; an empty string clears that field. It targets a task that already exists in the same workspace and does not accept run/step context, status, revision or control fields. Metadata is not a run heartbeat and cannot start, finish, pause, move to backlog or prioritize a worker. Run lifecycle and accepted worker acknowledgments remain authoritative. Use meaningful checkpoint messages rather than repeated task titles; time-only heartbeats should not pretend to be findings. Daily reports summarize actual records and link to their tasks; a report is not a separate execution run. Set `MOCK2_WORKSPACE_ID`, `MOCK2_TASK_ID` and optionally `MOCK2_RUN_ID` for check capture. Without them checks use a local `repository` / `verification` task and a unique run. With an explicit `MOCK2_RUN_ID`, the runner records its checks but never closes the agent-owned run; its owner emits the finish. An agent supplies its own started/activity/finished events using the same emitter; installing this does not instrument unmodified external coding tools or reconstruct missing historical checkpoints. `state/relay/events` is an append-only directory of sequence-prefixed event files. Atomic rename and a short writer lock avoid interleaved writes; retries of the same ID are idempotent. Commit that directory beside the existing Markdown ledger. A crashed writer can leave `.writer-lock`; verify no writer is active before removing only that lock. No timeout steals a live lock. To send recorded work to a host, set `RELAY_INGEST_URL` to the HTTPS workspace `/events` endpoint and obtain `RELAY_INGEST_TOKEN` through the host credential mechanism, then run `node .mock2/bin/relay.mjs sync`. No token is committed or returned. Re-sending the ledger is idempotent; redirects are refused. The host token's scope must match `workspaceId`. Offline recording works without a host. `sync` is an explicit retry/delivery step; configured initial lookback also delivers through the same authenticated channel. There is no unconfigured export or continuously installed background service. Plan the app's actual producers and delivery path during installation. Connect its existing coding/check/deployment workflow to the emitter, record a real task through at least one check, then demonstrate explicit authenticated `sync` into the correct workspace. Preserve the Markdown run ledger and existing historical records. Older start/end records can establish only their recorded overall duration/status; do not manufacture intermediate steps, progress or idea dates. Where a producer has no instrumentation, show that gap rather than substituting demo events. ## Verification timing and reuse The runner writes one per-run JSON evidence record and emits `check.recorded` observations. Run costs distinguish executed results, unchanged-input reuse and prerequisites that could not run, including queue time and actual command duration. Original evidence time and identity remain visible. No new step execution is fabricated for reuse. Human work/handoff records reference the canonical JSON and add scope, decisions and review findings. See `Standards-Benchmark.md` for repeatable runner measurements and the separate fresh-agent build comparison procedure. ## Meaning and controls - A task is an Investigation or Build; reports are separate records. A deployment card counts only `deployment.recorded` with verified=true, category feature/update/patch and revision. - Work time sums distinct runs, not overlapping steps. Simultaneous runs count separately and are not billed inference time. Day boundaries use the selected timezone, including DST. - Idea lead time measures the recorded idea's inception to the recorded verified outcome (or the present while still open). It includes waiting. Work time sums each contributing run once even when multiple origins link to it. Neither metric estimates API billing or missing history. - Running clocks become stale after `staleAfterMs` (default 120 seconds); stale time stops at the last observed activity. An open page is not proof that a worker remains active. - Idea IDs survive combinations. `relationship.added` with `inspired` or `combines` points from source idea/task to outcome task. Lineage counts each contributing run once. `depends_on` points from a prerequisite to its dependent task. A `design` link goes from a recorded artifact ID to its destination task ID, with event.taskId equal to that destination. Both must exist in the same workspace. Linked references appear under Linked mockups on the destination task. - A browser sends only `control.requested`. Worker integration validates and acknowledges the request with `control.acknowledged`, requestId, action and accepted. Only accepted acknowledgment changes the effective task state. This package does not run shell commands or stop workers. - References accept PNG/JPEG/WebP/PDF/UTF-8 text/Markdown, validate extension and signature, and are served as private attachments with nosniff. No scripts or SVG. Maximum size is host configurable (default 5 MiB). Failed event persistence may leave an unreferenced host object; the host may reconcile orphan objects separately. Nothing automatically deletes references. - Redaction catches common bearer/token/password patterns, not arbitrary secrets. Producers must summarize activity and redact sensitive evidence before emitting it. The authenticated feed is not automatically suitable for external sharing. ## Verification and limits `npm ci`, `npm run build:package`, `npm run check:types`, `npm test`, `npm run build:example`, and `node scripts/browser-check.mjs` exercise the package locally. The browser check starts/stops its own loopback server. PGlite tests execute the generated PostgreSQL schema; a consuming host must also run its integration tests on its PostgreSQL version and real auth/realtime/storage adapters. Host acceptance includes authenticated/anonymous and cross-workspace access, permission-specific mutations, CSRF/scoped bearer rejection, SSE reconnect without duplicate events and access revocation, real upload/preview/download validation, pending/rejected/accepted control requests, event ingestion/retry idempotence and data retained after disabling. Run the app's normal checks as well as the interface matrix above. Save actual rendered comparisons with the candidate identity, viewport, theme, fixture/real-data source and result. Check the public demo and host against the same package identity. Record an unavailable integration check as unavailable, not passed; passing package tests alone does not prove host integration. Live causal transitions remain validated against the workspace stream. History imports use batched local append and a unique indexed history identity in the reference database, avoiding per-commit replay of the full live projection. Very large streams still require host capacity measurement. SSE uses a configurable database poll interval (default 1 second), not a required second message bus. Persisted sequence order, independent of client timestamps, is the reconnect cursor. The package does not claim automatic adoption by apps that have not mounted it or automatic execution control over workers without acknowledgments. ## Automatic producer delivery (1.2.1) See the distributed `Relay-Recording.md` for event semantics, command/session wrapping, automatic configured emitter/check delivery, retrying structured-event watch, delivery status and actual coding-tool coverage. `relay.mjs run --title "Coding session" -- COMMAND ARGS` records real process boundaries using version-1 lifecycle events; it does not prove feature acceptance or deployment. The host must acknowledge the same event ID/workspace with its committed sequence. Failed delivery retains local records. Existing 1.1.x hosts still need migration before version-2 observations.