Skip to content

Dashboard-backed extension project sync ​

This chapter defines the standard persistence contract for extension-owned projects that must be visible to multiple users or devices through a dashboard. ISA Quote is the reference implementation for dashboard-backed file transport.

The word sync is used carefully here. A file can be dashboard-backed without being opted into the host's offline file-sync queue.

20.1 Persistence modes ​

Extensions must distinguish these three modes:

ModeAPIShared through dashboardWorks offline after provisioning
Device-local statestorage_*NoYes
Dashboard-backed filefilesystem_* without metadata.syncableYes, immediately after a successful requestNo
Host-syncable filefilesystem_* with exact metadata.syncable = "true"Yes, after host reconciliationNative host only; browser writes remain online

Browser hosts advertise filesystem.persistence.mode = "dashboard-confirmed" and offlineWrites = false. A successful browser mutation means dashboard acceptance, even when the file is marked syncable for native interoperability. Browser atomic_json_apply.syncable is a server-side creation hint only and never enables a local operation journal. Browser account/extension KV can hold recovery drafts, but a failed or disconnected shared save must remain a visible failed/unconfirmed operation. Upload/delete/atomic-write availability must come from the current gateway command list; implementation and live-verification status are tracked in the cloud write plan.

Dashboard-backed project state uses the second mode by default. It is the portable baseline because it uses the ordinary authenticated dashboard file API and does not depend on the optional workspace-file-riblt-v1 protocol.

Use host-syncable files only when offline editing is an explicit product requirement. A locally accepted syncable write is durable on that device, but is not confirmation that the dashboard or another user has received it. Extensions must not build another offline queue on top of the host queue.

Atomic object-key updates use atomic_json_get and atomic_json_apply against the same ordinary canonical workspace files and permissions. atomic_json_apply.syncable is an optional boolean creation hint: exact true makes a missing file host-syncable, while omission or false creates it dashboard-first; it never changes an existing file's persisted mode. All valid JSON roots are accepted, although upsert and remove retain their documented object-key semantics. A syncable get overlays durable unacknowledged operations on the strictly committed RIBLT generation; a non-syncable get reads the dashboard. Callers must preserve explicit absent, unavailable, malformed, and permission-error outcomes instead of treating each as an empty object.

An acknowledged atomic batch is removed from the local journal immediately. The ordinary RIBLT cache can therefore lag the accepted dashboard value until reconciliation, and a read in that window can return the older committed generation or unavailable. filesystem-index-changed is the single scoped wake-up event for both pending journal and reconciled file changes. Atomic operations add no tombstone: delete removes the ordinary file, a later operation recreates an absent path from {}, and dashboard mutations and whole-file replacements take effect in arrival order.

storage_* is never the canonical source for shared project data. It may hold preferences, recovery drafts, conflict sidecars, and resumable migration journals.

20.2 Project identity is not project storage ​

There are two valid project identity models:

  1. A dashboard Shared Project Registry ID from projects_list, projects_create, and projects_set_archived. Use this when project identity must be shared across extensions.
  2. An extension-owned project ID stored in the canonical project document. ISA Quote uses this model.

Registry records contain group-scoped identity and lifecycle metadata only. Their system-owned registry folders are not writable project storage or sync roots. When an extension uses a registry project, it stores that ID as a foreign key in its own canonical files.

In both models, project contents live below the extension's authoritative group folder.

20.3 Capability and root discovery ​

An extension using dashboard-backed project files must:

  • request get_app_info, filesystem_read, and filesystem_write;
  • request filesystem_delete only when its lifecycle deletes shared files;
  • request projects_read or projects_write only when it uses the Shared Project Registry;
  • check the advertised filesystem commands before enabling shared writes;
  • call filesystem_ensure_group_folder once per mounted launch before reading or writing canonical data;
  • use the returned path exactly and never derive a path from a mutable group name; and
  • keep all access in the immutable workspace, group, and extension scope supplied by the host.

A cached root may be used as an offline read fallback, keyed by immutable launch scope. It must not suppress authoritative root discovery on a later online launch. Calling the ensure command also registers the current root with the host event bridge.

First-time group-folder provisioning is online-only.

20.4 Canonical layout ​

Each extension documents one deterministic layout. New extensions should use:

text
<extension-root>/
  projects/<project-id>/
    project-state/current.json
    assets/<immutable-id>-<sha256>.<ext>

Existing schemas may retain another documented layout. For example, ISA Scribe uses meetings/<meeting-id>/project.json.

The mutable project document is the commit point. Immutable transcripts, sources, exports, and other assets are uploaded before it. An optional manifest or aggregate index is a derived accelerator only; canonical inventory must be able to repair it.

For ISA Quote acceptance, projects/<project-id>/workflow/award/current.json is the only canonical commit point. workflow/export-state/current.json and the workspace file manifest are derived state: failure to update either one queues repair but does not roll back or invalidate a successfully committed award. Repeated writes use one stable operation ID. Every mutation must use the authoritative award entry on which the UI state was based as its CAS base; an internal fresh read may validate that base but must never silently rebase a stale full-document mutation. A remote CAS conflict is resolved by loading the newest award instead of issuing a compensating write. Manifest repair rereads the current canonical award and only repairs its index entry. Export repair is tied to the committed award revision and is discarded when that award has already been superseded.

Derived workflow repair journals are keyed by immutable launch context and project ID. The launch-context identity always contains the workspace ID, including launches without a group, and adds the group ID when present. A late canonical commit may journal a repair under its captured context after the UI has switched workspaces, but reconciliation may run only when that exact context is active. A resolved canonical upload acknowledgement remains valid for its captured context after a switch; derived index and export work is journaled for that old context instead of being run in the new one. A journal from workspace A must never read or write project data in workspace B, even when both workspaces contain the same project ID.

20.5 Writes and metadata ​

Dashboard-first canonical writes follow the ISA Quote transport pattern:

js
await bridge.invoke('filesystem_upload_files', {
  workspaceId,
  files: [{
    path: canonicalPath,
    filename: 'current.json',
    text: JSON.stringify(project),
    contentType: 'application/json; charset=utf-8',
    resourceType: 'example.project_state',
    replaceExisting: true,
    metadata: {
      kind: 'project_state',
      schemaVersion: '1',
      projectId,
      updatedAt: project.updatedAt
    }
  }]
});

All metadata values must be strings. kind, schemaVersion, the relevant project identity, and updatedAt should be present on canonical files. resourceType should be stable and extension-specific.

Do not add syncable: "true" to dashboard-first writes. That exact value selects a different transport: the optional local/offline reconciliation queue.

For writes:

  • upload immutable assets with replaceExisting: false;
  • upload the canonical project document last;
  • replace a mutable canonical path with replaceExisting: true;
  • pass expectedSyncRevision and expectedContentHash when the host returned them, while continuing to address the file by canonical path;
  • use a stable 64-hex metadata.client_operation_id when retrying one idempotent online create.

The Filesystem API exposes only filesystem_upload_files for writes, for one to 256 items. Dashboard-first and syncable items share the same request envelope; every syncable item must carry the exact metadata.syncable = "true" value, and every returned item is an independent locally durable pending result. A missing dashboard file ID is normal until reconciliation confirms a new path. Use the local path and content hash as provisional identity, observe filesystem-sync-state-changed, query filesystem_query_index, or inspect sync metadata returned by filesystem_list_dir and filesystem_get_files only when upload statistics matter to the extension, and do not block the local UI on dashboard acceptance. The host replays queued files with bounded concurrency and prioritizes smaller files.

A project is saved only after the canonical upload succeeds. A failed derived index update is a repair condition, not a failed canonical commit.

Canonical reads distinguish four explicit policies:

  • dashboard-required bypasses pending and clean caches, never falls back to cached bytes, and is mandatory before CAS writes or applying a remote canonical document;
  • dashboard-first preserves a pending local edit, otherwise reads the dashboard and uses a clean cache only when the dashboard is unavailable; and
  • cache-preferred may return a complete clean cache immediately and is reserved for non-authoritative presentation reads; and
  • cache-only reads pending or complete clean local bytes without requesting a dashboard link or download. A content miss returns an item-level status: "unavailable", authoritative: false, source: "cache", and reason: "cache-miss" result so callers can poll without treating the miss as an authoritative deletion.

The read surface consists of filesystem_list_dir and filesystem_get_files. Listing merges dashboard inventory with locally available syncable entries and returns whether the inventory is authoritative. With cachePolicy: "cache-only", listing performs no dashboard RPC and returns only files whose bytes are already completely readable from the local sync cache; an empty list is non-authoritative and suitable for polling. filesystem_get_files is the bounded, unified content command for one file or a batch. Its default local-first policy uses only complete clean host-cache entries for syncable files, then retrieves unresolved paths from the dashboard. It is selected solely by canonical paths and validates supplied revision/hash preconditions before exposing an item. A missing cache path or metadata mismatch is not evidence that a dashboard file was deleted. Use cache-only for local polling that must never cause network retrieval. Canonical project application, authoritative absence checks, remote inventory, and CAS preparation must specify cachePolicy: "dashboard-required" with canonical paths and every available revision/hash precondition.

An authoritative file response couples the downloaded bytes with the workspace file ID, canonical path, artifact ID, sync revision, optional server SHA-256, and update timestamp obtained for that same file generation. When a server hash is present, the host verifies the downloaded bytes before exposing it as a CAS hash. A historical file without a server hash may still be authoritative by identity and revision, but the client must keep its server hash empty and must never promote a locally calculated digest to a server CAS precondition.

Creating a canonical path must be create-only after an authoritative absent result. Replacing it requires the file ID and every available revision/hash precondition from the authoritative read. Dashboard upload initialization and extension intermediate-folder creation share a transactional workspace path fence, re-read their parent and target inside the transaction, and use bounded reconciliation after an uncertain commit. This closes the first-create race as well as the ordinary replacement race.

Each canonical project document carries a monotone generation. Every successful content or lifecycle transition advances it from the highest authoritatively discovered generation for that project. Candidate selection compares document generation first, then the coupled server revision, and uses timestamps only as a compatibility fallback for legacy documents. A listing timestamp or derived manifest timestamp must never make an older document win. Project-create reconciliation likewise requires a dashboard-required found or absent result; cached bytes cannot confirm whether a create committed.

20.6 Incoming reconciliation ​

A successful outbound write is only half of project sync. Every project extension must also discover dashboard changes while it remains open.

The extension must:

  • inventory canonical project documents at startup;
  • refresh at a bounded interval; 30 seconds is the default when the host does not advertise an authoritative indexed-cache event capability;
  • when that capability is available, consume its scoped change batches immediately and run an authoritative recovery audit at least every five minutes;
  • refresh when the iframe regains focus or becomes visible on hosts without the indexed-cache event capability;
  • refresh the Shared Project Registry too, when it is used;
  • validate document schema, identity, and canonical path before accepting it;
  • merge remote additions, updates, archives, and removals;
  • retain the last usable generation when an inventory request is unavailable;
  • isolate a malformed project instead of disabling the complete repository; and
  • never overwrite an unsaved, dirty, pending, or conflicted local draft.

Canonical inventory code must preserve found, absent, invalid, and unavailable as distinct outcomes. Convenience helpers that collapse errors to null or [] must not drive deletion or migration decisions.

When active, archived, tombstone, and purge candidates exist, inventory reads every candidate returned by the authoritative listing before selecting the winner. The lifecycle inside the selected canonical document is authoritative; its physical path may remain stable across archive and restore to preserve file identity and CAS continuity. Tombstones and purge markers are physical canonical JSON files with a higher project generation, not manifest-only flags. When a stale active write races a marker and both expose the same explicit generation, the deletion marker wins before timestamps are considered; only a deliberate recreation based on that marker may advance to the next generation and become active again. The manifest is repaired from the selected canonical file and is never allowed to select or republish an older canonical document. Canonical inventory therefore remains usable when the derived manifest is unavailable.

Generic filesystem state events may trigger an immediate refresh, but they are not an authoritative reconciliation mechanism. A host may advertise a dedicated post-reconciliation indexed-cache event whose changed/deleted paths are safe to use as an accelerator. The extension must still inventory at startup, preserve unavailable-versus-absent semantics, and run the bounded authoritative recovery audit because dashboard-first writes do not depend on the offline sync worker.

20.7 Conflicts and deletion ​

When replacement preconditions report a conflict, automatic saving stops. Keep the local draft and its last known base, then offer an explicit choice such as loading the shared version or saving the local work as a copy. Never silently overwrite or rebase a newer shared document.

Deletes use validated canonical paths and the latest revision/hash preconditions when available. An unavailable inventory is not proof that a project was deleted. Archiving a Shared Project Registry record must preserve its ID and must not move or delete extension-owned files.

For an extension-owned project delete or purge, commit and verify the physical tombstone or purge marker before deleting project files or local derived state. Cleanup must inventory both the active and archive paths directly from the dashboard and proceed only when both listings are authoritative. Cleanup must preserve the marker. If inventory or cleanup is interrupted, the higher-generation marker and local project state remain available for retry; the marker still wins over leftover project bytes and prevents resurrection.

20.8 Validation ​

At minimum, test:

  • device A saves and a separate device B repository discovers the project;
  • a mounted device discovers a remote create, update, archive, and removal;
  • focus and interval refresh retain dirty local work;
  • a transient unavailable listing retains the last usable generation;
  • one malformed canonical document does not hide valid projects;
  • root discovery runs on each mounted online launch;
  • immutable assets commit before the mutable project document;
  • replacement conflicts do not silently overwrite remote content;
  • stale cached project bytes are never combined with metadata from a newer listing;
  • equal or misleading timestamps cannot beat a higher canonical project generation;
  • archive, restore, delete, and deterministic-ID recreation preserve monotone generations without reusing a tombstone as a live write base;
  • canonical reads still select the correct project or purge marker while the derived manifest is unavailable;
  • interrupted delete cleanup leaves a physical higher-generation marker that prevents old project bytes from being resurrected;
  • an unavailable active or archive inventory stops shared and local delete cleanup after the canonical marker commit;
  • switching between a group and a group-less context clears the old extension root and manifest before another project read or write;
  • an authoritative retry bypasses a stale host cache;
  • a committed award remains successful when export-state or manifest repair fails;
  • a stale second client cannot overwrite a newer canonical award;
  • an old repair journal cannot replay an older award or export over a newer award;
  • a repair journal captured in workspace A cannot replay in workspace B, even for the same project ID;
  • Shared Project Registry IDs remain stable across archive and restore; and
  • the extension's full package build succeeds.

If offline editing is enabled, also test restart-offline reads, offline saves, reconnect confirmation, and simultaneous-edit conflict handling against a dashboard that advertises the exact file-sync protocol.

ISA Warden extension specification