Record
Objects — Case · Party · Document · Issue · Task · Session · Work product · Event
Every layer of the platform reads and writes one thing: the Record — the system of record for a case. The case, its documents, its issues, the work on it, and every action ever taken all live here, partitioned by case and sealed from every other case. It’s the foundation the rest of the platform stands on, and the reason the other capabilities compose cleanly: they share one record rather than syncing several.
The object model
The Record defines the objects every other capability speaks. They come in two tiers.
Case-scoped — the data of a matter, isolated per case, identical for every customer:
| Object | What it is |
|---|---|
| Case | the matter under review — the unit everything is partitioned and isolated by |
| Party | a participant on the case — the claimant, a representative, a power of attorney |
| Document | a structured slice of the case file — see Reader |
| Issue | an adjudicated matter — a scope, a type, and a disposition that updates as the case moves |
| Task | a unit of work — typed, assigned to a person or a team, tree-structured, with a status |
| Session | a hearing or proceeding — see Sessions |
| Work product | an authored output — a brief, a determination, a decision — see Authoring |
| Event | an append-only record of something that happened, stamped with the actor and the time |
They relate in one sentence: a Case is the root; its file is Documents, the matters it decides are Issues (Documents are their evidence), its work is Tasks (whose tree is the Case’s state), its participants are Parties, its hearings are Sessions, its output is Work product, and every action is an Event. Annotations and tags hang off Documents, and a Document’s read state is held per reviewer — each person keeps their own place in the file while everyone shares one record. Every record, without exception, is keyed to the one Case that owns it.
That Documents are their evidence link is the load-bearing one. It is produced in the Reader — where a reviewer reads the file, tags it, and decides each issue against the pages that prove it — and it is read, unchanged, by everything around it: Authoring composes the outcome from the decided issues and their cited evidence, and Connect assembles the decision package and reports off the same link. The Reader sits at the center of that loop; the rest of the platform consumes what it produces. See The Issue below.
A deployment names the Case for its own work — an appeal before a board, a claim in a benefits operation, a matter or a grievance elsewhere — while the object and everything attached to it stay the same. The developer sandbox holds appeals, so its case resource appears as appeals; the model underneath is the one above. That naming is the first thing a deployment configures, and the clearest sign the engine is generic.
Tenant-scoped — the shape of the operation, configured per deployment, not baked into the core: Member · Team · Role · Case type · Task type · Queue · Boilerplate · Rule · Issue set. The case-scoped objects are the generic engine; the tenant-scoped objects are what each customer makes its own — and that line is the two isolation boundaries below.
The categorization vocabularies the case-scoped objects draw on — an issue’s disposition set, the session dispositions, the review-error reasons, the document categories — are themselves tenant-scoped data: a privileged member adds or deactivates the elements in a set in Admin, and the change takes effect without a release. So the values a Case’s records can hold are configuration the operation owns, not an enumeration fixed in the core.
The Issue
The Issue is the deepest object and the one that carries the most configuration — the unit of adjudication. An issue has a scope (the matter claimed), a type (its category, which makes analysis by issue possible), and a disposition (the matter decided, set when the issue is resolved). Documents are the evidence for an issue, linked many-to-many: one document can support several issues, and one issue can draw on documents from across the file.
This issue-to-evidence link is the hinge of the product. It is where evidence becomes judgment — and it is exactly what every downstream capability reads. A reviewer decides each issue in the Reader against the document pages that prove it; from there the decided issues are the outline of the outcome, and every disposition the outcome states cites the page its evidence sits on. The decision package and reporting read the same link. Nothing downstream re-derives it; it is captured once, at the center, and consumed around the loop.
Two things keep the Issue configurable rather than fixed:
- The disposition vocabulary is a configured set. Granted, denied, remanded, dismissed, withdrawn — and the finer reasons beneath them (the kinds of remand, say) — are an issue set a deployment defines, not values baked into the model. Each operation runs its own dispositions, categories, and special-issue flags.
- Issues carry relationships, driven by rules. A disposition can trigger follow-on — a remand opening new work on the same case — and an issue can contest a prior decision, chaining one review to another. Both are routing rules over the disposition, not hardcoded transitions.
Together, a case’s issues are its unified issue list — what the outcome is organized around, proposed by the AI layer and curated by a reviewer.
When an issue resolves to more than one decision — a split where one part is granted and another remanded — the outcomes are first-class records of their own. The Issue still carries what was claimed; each decision issue carries one decided outcome, with its own disposition, optional structured remand reasons, and notes. For the simple 1:1 case the Issue’s own disposition is sufficient; the decision-issue channel exists for the split.
Streams and substitution
Two events reshape a Case after it’s underway, and the Record captures both as first-class records.
Appellant substitution happens when the appellant changes mid-case — death, incapacity, an executor or heir stepping in. The record names the original party, the substitute, when the substitution took effect, and why; downstream work continues on the same case unless the deployment’s policy says otherwise. The substitution itself is the compliance trail.
Post-decision motions open a new stream of the same case once a decision is signed. A motion is recorded against the originating case with its type (vacate, de_novo, court_remand) and disposition (granted, denied, withdrawn, dismissed, pending); a granted motion produces a new Case carrying a streamType and a parentCaseId back to the originating one. Each stream is its own case with its own task tree, its own issues, its own outcome — and the parent pointer makes the lineage navigable.
State-first
The platform is state-based. Work is expressed as tasks, and a case’s state is materialized from those tasks and indexed — so where is this case, who holds it, and how long has it been there is a direct, real-time read, for one case or for the whole operation at once. State is a first-class, queryable property of every case — a value you read directly.
That single property carries a lot of weight downstream: routing rules read state to decide what happens next, oversight reads it to see the operation live, and reporting reads it without a nightly export to a separate warehouse. The work moves as tasks; the answer is always a state.
Two isolation boundaries
The Record isolates data at two levels, each enforced by a different mechanism.
The tenant boundary is the outer one. Each customer runs in its own isolated deployment — a separate cloud account with its own datastore, object storage, and identity realm. Two customers cannot reach each other’s data because there is no network or trust path between the deployments, not because a query filter holds the line.
The case boundary is the inner one. Within a single tenant, the case is the unit of privacy: work on case A returns nothing from case B. The platform enforces this at the architectural layer — the data path is shaped so that a cross-case read can’t be expressed — rather than as a policy overlay the application has to remember to apply. Three things make that true:
- Every record belongs to a case. A query that doesn’t name a case can’t return case-scoped records — there’s nothing outside a case to return.
- Queries are scoped at the query, not filtered at the result. The query layer takes a case as a required parameter; it never accepts “give me everything matching X” and then narrows down — the pattern that leaves a cross-case path open.
- The access policy lives at the storage layer. Object storage is partitioned per case and the policy is attached to the storage credential, so a bug that confused one case’s id for another’s produces an access-denied error from storage before any application code runs.
This is the storage boundary. Who may open which cases is Access; what they did is the event history below.
The event history
Because every record carries a case, an actor, and a timestamp, the Record is also where audit lives. Every view, edit, assignment, status change, and queue movement is an append-only event — the full lifetime of a case: what happened, by whom, when, and how long it sat at each step. There is one store for it: audit, retention, and analytics all read the same case-keyed events, so there’s nothing to reconcile between a “real” store and an audit copy, and the trail reconstructs exactly what a reviewer saw on the day they relied on it.
Reporting
Because state and the full event history are one queryable store, Reporting reads the operation live, off the same record the work runs on — standard dashboards and ad-hoc queries over current state, with nothing to reconcile between a “real” store and a reporting copy. Every queue movement, status, actor, and dwell time is already on the record across a case’s lifetime, so the figures a governing statute or reporting regime asks for are captured as the work happens. The Record is the substrate; Reporting is the capability that reads it.
Retention
Retention is policy over those same records: how long a deployment keeps closed-case data, what’s placed on legal hold, and what’s produced on a records request. Because every artifact is keyed by case with an actor and a timestamp, retention and hold operate over one consistent store — the same substrate Security & compliance accounts for.
Migrating in
A deployment imports its history from a prior system into the same record. Cases, documents, parties, issues, and the events that preceded the cutover load through the Integration API into the one case-keyed store the live platform reads — so a migrated case and a case opened yesterday are the same shape, with the same audit trail, the same isolation, and the same state read.
What the import normalizes
A prior system holds the same matters in its own shapes — its case object, its document store, its party rolls, its task or workflow records, its decision artifacts. The import maps each one onto the object model above:
| Source-side concept | Resolves to |
|---|---|
| Case file, matter, claim, request | Case (typed by the deployment’s case type) |
| Stored documents, scanned exhibits, attached evidence | Document (with category, receipt date, position, page boundaries) |
| Inbound correspondence (mail, fax, electronic submission); outbound letters and notices | Document on the case + an Event stamped with the dispatch or receipt time |
| Annotations, margin notes, highlights, cross-references | Annotation (page + normalized coordinates + comment + relevant date) |
| Claimant rolls, representative records, POA filings | Party (with role drawn from the configured set) |
| Appellant substitution records (death, incapacity, executor takeover) | AppellantSubstitution (original party, substitute, effective date, reason) |
| Issue lists, claimed matters, granted/denied items | Issue + (for split outcomes) DecisionIssue |
| Workflow steps, task records, assignment entries | Task (typed, parented into the case’s tree) |
| Task escalations, deadline reminders, timer fires | TaskTimer records + an Event for each escalation that fired |
| Hearing schedules, hearing logs | Session (with disposition, participants) |
| Hearing transcripts and audio recordings | Document on the case (the same data model the Reader indexes) |
| Signed decisions, briefs, determinations | Work product (status signed, with signedAt and signedBy preserved) |
| Post-decision motion filings (vacate, de novo, court remand) | PostDecisionMotion + (on grant) a new Case carrying streamType + parentCaseId |
| Distribution and case-assignment audits | Distribution records (assignee, batch size, lever set, selected case ids) |
| Audit logs, action histories | Event (with the original actor and timestamp preserved) |
Heterogeneous and legacy records resolve to the same Case · Party · Document · Issue at the Connect boundary, not as a parallel format the rest of the platform has to special-case. A case that came in from a legacy system reads the same as a case opened in the Workspace yesterday.
Pre-cutover audit trail
The audit trail before the cutover loads as Events on the case, with the original actor and timestamp preserved. So an action a person took in the prior system five years ago appears as an Event stamped with that person’s identifier and the date they took it, in the same event history the live work writes to. Reconstructing what a reviewer saw on a given day reaches across the cutover seamlessly — the platform doesn’t lose visibility into the years before it ran the operation.
For events whose actor or time the prior system did not record, the Event is stamped with a migrated:source-system sentinel actor and the closest known time, with a note on the Event recording the gap. The gap is visible rather than papered over.
How the load runs
The import is a bulk load through the same Integration API the live platform reads. Each object class loads in a phase — Cases first, then Parties and Documents on each case, then Issues, then Tasks, then Sessions and Work products, then the historical Events — with the references between them resolved as each phase lands. The load is idempotent: a re-run does not duplicate a case that already imported, and a partial run can resume from where it stopped.
A typical migration runs a dry-run cycle first — load against a clean throwaway tenant, validate the counts and the references, spot-check a sample of cases against the source — then runs the real load against the production tenant with the live platform paused, then unpauses. The duration of the cutover window is the load’s runtime plus a verification pass, both proportional to the data volume.
What a deployment runs at go-live
Go-live is a sequence on the deployment’s own schedule:
- Provision the tenant — the isolated environment that will hold the record, configured with the default profile and the deployment’s overrides.
- Dry-run the migration — load into a throwaway tenant, validate, discard.
- Cut over — pause writes on the prior system, run the production load, verify counts and references, unpause Adjudicate.
- Run live — new arrivals enter through Connect the same way migrated cases did; the boundary between the two disappears at the record layer.
The platform that runs the work post-cutover is the same platform that read the migration. There is no second system to keep in step with, no nightly sync, no parallel write path. The record is one store, populated through one API, and the work that runs on it does not know — or need to know — which case came from where.
One store, read many ways
The Reader renders documents from it and writes the reviewed, tagged evidence and decided issues back into it, the work engine drives tasks against it, the AI layer proposes records into it, Authoring composes the outcome from the decided issues and their cited evidence, and reporting queries it in real time. They all read and write the same case-partitioned record — which is what lets the platform stay one product rather than a set of systems kept in step. The Reader is the center where the issue-to-evidence link is made; the rest of the platform is the loop that reads it.