Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,11 @@ jobs:
- name: Build production bundle
run: npm run build

- name: Install Chromium for browser smoke test
run: npx playwright install --with-deps chromium

- name: Run browser smoke test
run: npm run test:e2e

- name: Audit production dependencies
run: npm audit --omit=dev --audit-level=high
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ lerna-debug.log*
node_modules
dist
dist-ssr
playwright-report
test-results
*.local

# Local env files (hold the private deck path; never commit)
Expand Down
270 changes: 154 additions & 116 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,159 +1,197 @@
# SourceDeck

[![CI](https://github.com/Xyloth/SourceDeck/actions/workflows/ci.yml/badge.svg)](https://github.com/Xyloth/SourceDeck/actions/workflows/ci.yml)
[![Live demo](https://img.shields.io/badge/live-sourcedeck.vercel.app-f0b45d)](https://sourcedeck.vercel.app)
[![Trust model](https://img.shields.io/badge/trust-explicit-73c7cb)](TRUST_MODEL.md)

**A local-first evidence command center for high-stakes meetings.** SourceDeck turns
PDFs, Word documents, notes, and record folders into source-linked evidence cards,
issue maps, live retrieval, and exportable packets—while deterministic gates keep
unsupported claims out of the strongest path.

[![SourceDeck evidence workspace](docs/screenshots/live-evidence.png)](https://sourcedeck.vercel.app)

**[Open the live worked example](https://sourcedeck.vercel.app)** ·
**[Read the explicit trust model](TRUST_MODEL.md)** ·
**[Inspect the 63-case adversarial gauntlet](reports/source-gauntlet-report.md)**

## What This Repository Demonstrates

- **Document engineering:** real browser-side PDF.js and Mammoth extraction,
page-aware source records, rendered PDF page geometry, and encrypted source-byte
custody.
- **Deterministic trust boundaries:** exact-quote resolution, content addressing,
append-only hash chains, signed packet manifests, redaction hard walls, and
fail-closed packet assembly.
- **Bounded AI architecture:** source text is treated as hostile data, model output
can propose candidates but cannot create verified facts, and privacy mode is a
hard routing ceiling.
- **Adversarial validation:** 90 unit/security tests, a 63-case evidence gauntlet,
and a production-browser critical-path test.
- **Secure local integration:** optional speech and CLI-intelligence sidecars bind
to loopback, reject arbitrary browser origins, and require per-process bearer
capabilities.
- **Shipping discipline:** locked dependencies, zero current npm advisories,
automated lint/test/build/browser/audit CI, a live Vercel deployment, and an
explicit list of what the system does **not** yet guarantee.

## Product Walkthrough

SourceDeck is designed for the moment when a meeting moves faster than a folder
tree. The worked example opens with a meeting battle card, lets the user retrieve
the precise supporting record, and blocks packet export when source proof is
insufficient.

![SourceDeck battle card and meeting posture](docs/screenshots/live-battle-card.png)

The main workflow supports:

1. Import records locally and index text, pages, metadata, and custody hashes.
2. Search exact and fuzzy retrieval lanes without merging their confidence levels.
3. Turn findings into evidence cards with quote, source, page, meaning, question,
likely defense, and counter-response.
4. Review source-chain diagnostics and human verification state.
5. Assemble Markdown, HTML, CSV, encrypted workspace, or signed forensic outputs;
packet hard walls fail the complete export when selected claims are unresolved.
6. Enter live meeting mode for rapid quote, question, refusal, commitment, and
action-item capture.

## System Design

```mermaid
flowchart LR
A[Local PDF / DOCX / records] --> B[Browser extraction + page geometry]
B --> C[Content-addressed artifact + encrypted source vault]
C --> D[Source graph: document → page → span → evidence]
D --> E{Deterministic verification gate}
F[Bounded model / OCR candidate lanes] --> E
E -->|proof resolves| G[Human signoff + packet factory]
E -->|proof fails| H[Blocked with exact diagnostic]
G --> I[Signed manifest / encrypted workspace / forensic bundle]
```

SourceDeck is a local-first evidence command center for high-stakes meetings.
It turns PDFs, Word documents, notes, and record folders into searchable
evidence cards with exact quotes, source references, issue maps, meeting
questions, missing-record trackers, and exportable packets.

Live demo: [https://sourcedeck.vercel.app](https://sourcedeck.vercel.app)

> The right quote, the right page, the right moment.

## Why This Exists
The kernel owns factual truth. Intelligence lanes may rank, extract, or suggest,
but no model result becomes verified evidence until its quote and source chain
resolve deterministically.

Important meetings often turn on records: vendor and SLA disputes, legal prep, HR
disputes, medical appeals, insurance reviews, compliance audits, and
investigations. People may know the evidence exists, but still lose leverage
because they cannot find the exact quote, page, or document fast enough.
## Verifiable Engineering Evidence

SourceDeck is built around one principle:
| Control | Current proof |
| --- | --- |
| Unit and contract behavior | `npm test`: 87 Vitest cases + 3 Node sidecar-security cases |
| Adversarial evidence behavior | [`reports/source-gauntlet-report.md`](reports/source-gauntlet-report.md): 63/63 cases pass |
| Browser critical path | `npm run test:e2e`: Chromium navigation, record search, and textless document persistence |
| Production bundle | `npm run build`: TypeScript project build + Vite production bundle |
| Dependency posture | `npm audit`: 0 current vulnerabilities |
| Continuous verification | [GitHub Actions](https://github.com/Xyloth/SourceDeck/actions/workflows/ci.yml) runs lint, tests, build, Chromium smoke, and production audit |
| Deployment | [sourcedeck.vercel.app](https://sourcedeck.vercel.app) |

> Never say "I will find it later."
Run the same evidence locally:

The user can preload records, organize the issues, enter live meeting mode, and
pull up the source-backed quote or question while the conversation is happening.
```powershell
npm ci
npm run lint
npm test
npm run build
npx playwright install chromium
npm run test:e2e
npm run gauntlet:report
npm audit
```

## Current Capabilities

- Local document vault with DOCX, PDF, text, CSV, JSON, image, and legacy DOC
handling.
- Browser-side DOCX extraction through Mammoth.
- Browser-side PDF text extraction through PDF.js.
- DOCX, PDF, text, CSV, JSON, image, and legacy DOC handling.
- Browser PDF extraction through PDF.js and DOCX extraction through Mammoth.
- Local Node case-folder preloader for private folders and legacy `.doc` files.
- Evidence cards with quote, source, exhibit, page, meaning, strategic use,
likely defense, counter-response, tags, priority, and confidence.
- Search across evidence cards, source document text, detected dates/entities,
and missing-record rows.
- Issue maps, contradiction map, timeline, document completeness score, and
source-integrity audit.
- Live meeting mode with critical issue buttons, quote copy, question copy,
refusal logging, commitment logging, action items, transcript companion, and
source-grounded response composer.
- Case templates for vendor/SLA, HR, medical/insurance, legal,
compliance, and audit workflows.
- Agreement guard that flags vague or risky terms and generates cleaner
replacement language.
- Export tools for Markdown packets, printable HTML, CSV quote indexes, exhibit
indexes, missing-record requests, remedy plans, meeting briefs, redacted
packets, and encrypted workspace JSON.
- Local-first privacy posture: sensitive records are processed locally and are
not committed to this repository.
- Verbatim extracted text is session-only and removed before browser
`localStorage` serialization; encrypted workspace export is the durable path.

## Case Folder Importer

For private record folders, SourceDeck includes a local importer that builds a
workspace JSON without uploading files to a server.
- Search across evidence, source text, detected dates/entities, and missing records.
- Issue maps, contradiction views, timeline, completeness score, verification queue,
and source-integrity audit.
- Case templates for vendor/SLA, HR, medical/insurance, legal, compliance, and
audit workflows.
- Agreement guard for vague or risky language and cleaner replacement terms.
- Markdown, printable HTML, CSV, exhibit, missing-record, remedy, briefing,
redacted, encrypted-workspace, and forensic-bundle outputs.
- ECDSA P-256 signing-key custody, independently verifiable packet manifests, and
signer-fingerprint pinning.
- Local-first privacy controls: original bytes and rendered page images can be
encrypted in IndexedDB; verbatim extracted text is session-only and removed
before browser `localStorage` serialization.

## Run Locally

Requirements: Node.js 24 and npm.

```powershell
npm run case:import -- "C:\Example Case Folder"
npm ci
npm run dev
```

The importer writes these files into the selected folder:
The public app starts with fictional records and a complete worked example. No
private case material is committed to this repository.

- `sourcedeck-workspace.json`
- `sourcedeck-pressure-test-report.md`
## Private Case Folder Import

The generated workspace can be imported from SourceDeck's export screen. Private
case exports are ignored by git and should not be committed.
The local importer builds a workspace JSON beside the selected folder without
uploading files to a server:

The importer currently extracts:
```powershell
npm run case:import -- "C:\Example Case Folder"
```

- `.docx` files through Mammoth
- legacy `.doc` files through `word-extractor`
- text-based PDFs through PDF.js
- text-like files such as `.txt`, `.md`, and `.csv`
It extracts `.docx`, legacy `.doc`, text-based `.pdf`, `.txt`, `.md`, and `.csv`.
Image-only PDFs and chart-only documents are marked `Needs OCR`; the importer does
not pretend those records are quote-searchable.

Image-only PDFs, chart-only DOCX files, and scanned records are marked as
`Needs OCR` so the user knows they are not quote-searchable yet.
The generated files are:

## Architecture
- `sourcedeck-workspace.json`
- `sourcedeck-pressure-test-report.md`

- React 19
- TypeScript
- Vite
- PDF.js for PDF text extraction
- Mammoth for DOCX extraction
- `word-extractor` for local legacy DOC preloading
- Browser localStorage for the current workspace prototype
- Session-only extracted source text plus encrypted source-byte custody in IndexedDB
- Web Crypto PBKDF2/AES-GCM for encrypted workspace export/import
- Vercel deployment
Private case exports are gitignored and should never be committed.

## Local Sidecar Security

The optional speech and CLI-intelligence sidecars bind to `127.0.0.1`. Browser
operations accept only the shipped SourceDeck origin or loopback development and
Electron origins, then require a per-process bearer capability. There is no
wildcard CORS access. Additional exact origins can be configured through
`SOURCEDECK_ALLOWED_ORIGINS`; see `.env.example`.
Electron origins, then require a per-process capability. Wildcard CORS is not
used. Additional exact origins can be configured through
`SOURCEDECK_ALLOWED_ORIGINS`; see [`.env.example`](.env.example).

These controls prevent an unrelated web page from invoking a local model command
These controls prevent an unrelated webpage from invoking a local model command
or reading its output. They are a browser boundary, not an operating-system
sandbox: processes running as the same local user remain in the local trust
sandbox: processes running as the same local user remain inside the local trust
boundary.

## Run Locally

```powershell
npm install
npm run dev
```

Build:
## Honest Current Boundaries

```powershell
npm run build
```
SourceDeck intentionally documents its unfinished edges:

Lint:
- No live frontier-model runtime or OCR worker is wired in yet; those lanes are
typed and gated scaffolding.
- Re-anchoring is primarily lexical, not full semantic/geometric relocation.
- Signer trust is local/manual; there is no third-party key directory.
- There is no collaboration, sync, account system, or multi-tenant backend.
- Derived fields such as evidence-card quotes and meeting notes still use
plaintext browser `localStorage`; use a trusted browser profile and encrypted
export for durable private custody.
- SourceDeck organizes a user's records and does not provide legal advice.

```powershell
npm run lint
```
See [`TRUST_MODEL.md`](TRUST_MODEL.md) for the guarantee-by-guarantee boundary and
the conditions under which a packet must be treated as unverified.

## Roadmap

- OCR worker for scanned PDFs and image-only DOCX/chart files.
- True highlighted PDF/page export with source-page overlays.
- Human-confirmed page anchors for Word imports, because raw DOCX extraction
does not preserve original page layout.
- Durable encrypted local database instead of browser localStorage.
- Guided case-prep workflow for first-time users.
- AI provider layer for stronger evidence extraction, contradiction detection,
likely defenses, and source-grounded meeting prep.
- Collaboration/export workflow for attorney review, mediation packets, and
post-meeting follow-up packets.

## Privacy Note

The sample data in the demo is fictional. Do not commit real contract,
medical, HR, legal, financial, or other private records to a public
repository. SourceDeck's product direction is local-first because the target
documents are often sensitive.

The current browser prototype still persists derived workspace fields such as
evidence-card quotes and meeting notes in plaintext `localStorage`. Use a trusted
browser profile, reset the workspace after sensitive sessions, and use encrypted
workspace export when durable custody is required. The full boundary is stated
in [TRUST_MODEL.md](TRUST_MODEL.md).
- Real OCR worker with page-image review and human-confirmed anchors.
- Full-workspace encrypted persistence rather than derived-field `localStorage`.
- Semantic and structural re-anchoring with relocation diagnostics.
- Controlled collaboration and independently reviewable trust exchange.
- Production model connectors that remain subordinate to the deterministic
source and privacy gates.

## Product Rule

AI can prepare the deck, organize issues, suggest evidence cards, draft clean
questions, detect contradictions, and retrieve quotes live. The human remains in
control of what gets used in the meeting, but the AI is not artificially blocked
from doing useful work.
control of what gets used in the meeting, and the deterministic kernel remains in
control of what can be called verified.
Binary file added docs/screenshots/live-battle-card.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/screenshots/live-evidence.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
61 changes: 61 additions & 0 deletions e2e/critical-path.e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { expect, test } from "@playwright/test";

test("worked example supports source search and textless browser persistence", async ({ page }) => {
const consoleErrors: string[] = [];
page.on("console", (message) => {
if (message.type() === "error") consoleErrors.push(message.text());
});

await page.goto("/");

await expect(
page.getByRole("heading", {
name: "The right quote, page, and question at the right moment.",
}),
).toBeVisible();
await expect(page.getByRole("navigation", { name: "Primary" })).toBeVisible();

await page.getByRole("button", { name: "Evidence", exact: true }).click();
await expect(page.getByRole("heading", { name: "Evidence cards" })).toBeVisible();
await expect(
page.getByRole("heading", {
name: "Written admission: contracted work not delivered",
}).first(),
).toBeVisible();

await page
.getByPlaceholder(/Search records or commands/i)
.fill("contracted reporting work");
await page.getByRole("button", { name: "Search records" }).click();
await expect(page.getByRole("heading", { name: "Record search" })).toBeVisible();
await expect(page.getByText("Exact lane", { exact: true }).first()).toBeVisible();

const privateMarker = "E2E_PRIVATE_SOURCE_TEXT_MUST_NOT_PERSIST";
await page.evaluate((marker) => {
window.localStorage.setItem(
"sourcedeck.documents",
JSON.stringify([
{
id: "privacy-smoke-document",
title: "Privacy smoke document",
type: "TXT",
date: "2026-08-31",
author: "Test fixture",
pages: 1,
exhibit: "Test",
tags: [],
status: "Indexed",
extractedText: marker,
pageTexts: [{ page: 1, text: marker }],
},
]),
);
}, privateMarker);
await page.reload();
await expect(page.getByText("SourceDeck", { exact: true })).toBeVisible();
const persistedDocuments = await page.evaluate(() =>
window.localStorage.getItem("sourcedeck.documents"),
);
expect(persistedDocuments).not.toContain(privateMarker);
expect(consoleErrors).toEqual([]);
});
Loading
Loading