Skip to content

Hello-GregKulp/openlos

Repository files navigation

OpenLOS

OpenLOS means Open Letter of Support.

OpenLOS is an open-source, local-first tool for creating a trustworthy, frozen, human-endorsed professional artifact. A candidate writes a reusable support statement, freezes an immutable version, asks supporters to endorse that exact version, and exports the result with visible provenance.

OpenLOS is not a social network, recruiter product, analytics platform, blockchain proof system, or legal e-signature tool.

What OpenLOS Is

OpenLOS helps a person create a professional letter of support that can be reviewed and endorsed by people who know their work.

The core artifact contains:

  • candidate name
  • letter title
  • frozen content snapshot
  • frozen version number and timestamp
  • SHA-256 content hash
  • supporter endorsements tied to that version
  • active or expired endorsement states
  • provenance metadata

Why It Exists

Professional recommendations are valuable but repetitive. Supporters often believe in someone but cannot write a custom letter every time. OpenLOS keeps the burden low without hiding authorship: the candidate writes the statement, freezes it, and supporters decide whether they stand behind that exact frozen version.

Local-First Architecture

OpenLOS is designed to run locally or on infrastructure you control.

  • Next.js and React provide the app UI.
  • Prisma writes to SQLite.
  • Drafts, frozen versions, requests, endorsements, exports, and audit events persist locally.
  • SMTP is optional.
  • External sharing is controlled by APP_BASE_URL.
  • Docker Compose can persist SQLite in a named volume.

No external service is required for first boot. Email and tunnels are user-configured options.

File and Version Model

OpenLOS supports multiple local letter files. The hierarchy is:

Letter
 -> active editable Draft
 -> Frozen Version v1
 -> Frozen Version v2
 -> Frozen Version v3

A letter has one active editable draft and can have many immutable frozen versions. Creating a new letter from Create New creates a separate Letter record. Saving a draft updates only that letter's LetterDraft. Freezing creates a new LetterVersion for the same letter and never overwrites prior frozen versions.

Use Open File to browse all saved letters in SQLite. Selecting a letter opens that specific workspace with its draft, version history, endorsement counts, and version-specific export links.

Creating a revision from a frozen version copies that frozen snapshot into a new editable draft for the same letter. The source frozen version remains immutable, endorsements stay attached to that source version, and prior exports remain tied to their original frozen version.

Local Asset Deletion

OpenLOS deletion is local-only. It removes records from this local SQLite-backed OpenLOS instance; it does not revoke, delete, edit, or retrieve files that were already exported, emailed, downloaded, printed, or shared elsewhere. In short, local-first deletion is not global revocation.

Local asset controls use explicit confirmation language:

  • Delete local copy for a letter removes the local letter record, its active draft, frozen versions, signature requests, endorsements/signatures, and export records.
  • Delete local draft removes only the active editable draft. Prior frozen versions and source frozen versions remain.
  • Delete frozen version locally removes that frozen version plus requests, endorsements/signatures, and export records attached to that version. It does not delete sibling frozen versions or the parent letter.
  • Remove endorsement from this local instance removes the local endorsement/signature record for that frozen version.
  • Delete local request removes the request link record. Existing endorsements are kept unless the endorsement, frozen version, or letter is explicitly deleted.
  • Delete export record removes only local export history. Previously exported or shared PDF/DOCX files are not affected.

Hosted and multi-user deletion semantics are future work. OpenLOS currently treats deletion as local archive management, not as legal revocation, signer-side self-service, or deletion from someone else's inbox, downloads folder, hosted app, tunnel session, or exported document.

Endorsement Loop

  1. Write draft.
  2. Save draft to local SQLite.
  3. Freeze an immutable version.
  4. Create a supporter request.
  5. Supporter opens the request URL.
  6. Supporter reviews the frozen version.
  7. Supporter submits an endorsement back to the running OpenLOS app.
  8. Endorsement persists in local SQLite.
  9. Candidate exports the frozen artifact.

The supporter must be able to reach your running OpenLOS instance. If they cannot open the request URL, they cannot submit an endorsement.

Self-Hosting Connectivity

OpenLOS includes a local settings page at /settings/connectivity for SMTP, APP_BASE_URL, and optional tunnel workflow guidance. Environment variables override matching settings saved through the UI. UI-saved connectivity settings are stored in local SQLite; SMTP passwords are plaintext in this MVP, so use environment variables or protect the local database when stronger secret handling is required.

APP_BASE_URL

APP_BASE_URL controls the links sent or copied for supporters.

Examples:

APP_BASE_URL="http://localhost:3000"
APP_BASE_URL="http://192.168.1.25:3000"
APP_BASE_URL="https://your-subdomain.trycloudflare.com"

Use the right value for the sharing context:

  • http://localhost:3000 works only on the same machine.
  • http://192.168.x.x:3000 may work on the same LAN.
  • A tunnel or domain URL lets external supporters reach your local OpenLOS instance.

Supporter submissions post back into the running OpenLOS app and persist in local SQLite.

SMTP Setup

Email sending requires SMTP configuration. OpenLOS will not create accounts, generate credentials, or configure external email providers for you.

Recommended SMTP providers:

  • Gmail app passwords
  • Fastmail
  • Postmark
  • Resend
  • SMTP2GO

Without SMTP configured, OpenLOS still works: generate the request link and send it manually.

Example .env:

DATABASE_URL="file:./dev.db"
APP_BASE_URL="http://localhost:3000"
SMTP_HOST="smtp.example.com"
SMTP_PORT="587"
SMTP_USER="your-user"
SMTP_PASS="your-password-or-app-password"
SMTP_FROM_ADDRESS="you@example.com"
SMTP_FROM_NAME="OpenLOS"
SMTP_SECURITY="starttls"

The legacy SMTP_FROM="OpenLOS <you@example.com>" format is still accepted and overrides SMTP_FROM_ADDRESS and SMTP_FROM_NAME when set.

If SMTP_HOST is blank, the app shows SMTP as unavailable and switches request creation to manual link generation. Use /settings/connectivity to save SMTP settings locally and send a test email. Test messages go only through the configured SMTP server; OpenLOS does not send secrets anywhere else.

Cloudflare Tunnel Workflow

Cloudflare Tunnel is a practical optional workflow for local-first external sharing. It is not required, and OpenLOS does not create or run tunnels automatically.

Run OpenLOS locally, then in a separate terminal run:

cloudflared tunnel --url http://localhost:3000

Cloudflare prints a temporary public URL. Paste it into /settings/connectivity, or set it as APP_BASE_URL:

APP_BASE_URL="https://your-subdomain.trycloudflare.com"

If using environment variables, restart OpenLOS so new request links use the tunnel URL. If using /settings/connectivity, save the value before creating new supporter requests.

What this means:

  • OpenLOS still runs locally.
  • SQLite remains local.
  • The tunnel temporarily exposes supporter request links.
  • Supporter submissions persist back into your local SQLite database.
  • You can stop the tunnel when the request window is done.

Request and Endorsement Expiration

OpenLOS separates request expiration from endorsement validity.

Request link expiration:

  • request links automatically expire after 3 months
  • candidates do not choose arbitrary request expiration dates
  • expiration controls how long the request URL remains valid

Endorsement validity:

  • defaults to 3 months from signing
  • supporter may choose a shorter or longer duration
  • minimum is 14 days
  • maximum is 1 year
  • invalid dates are rejected inline

Expired endorsements remain visible as expired instead of disappearing.

Export Behavior

Exports are tied to a frozen version, not the editable draft.

The export target is a formal Letter of Support artifact, not a web-app screenshot. It includes:

  • page 1 formal Letter of Support with candidate name, date, frozen version, and letter body
  • page 2 and later endorsement table pages
  • supporter full name, role/title, relationship, signed date, valid-through date, active or expired status, and signature
  • typed, drawn, and uploaded signature images when available
  • frozen timestamp
  • content hash
  • source URL provenance
  • OpenLOS, Apache-2.0, copyright, generated timestamp, and page number footer metadata

DOCX download generates a real .docx using the existing docx dependency.

Direct PDF download is not enabled in this build. The app provides Open printable PDF view, which can be saved as PDF from the browser and is the Docker-safe supported path.

If direct PDF generation is added with Playwright later, local development requires installing a browser:

npx playwright install chromium

The current Docker image does not bundle Chromium or Playwright system dependencies, so Docker keeps the printable view as the supported PDF path. DOCX export is independent and remains available.

Trust Model

OpenLOS focuses on provenance and human endorsement.

  • Frozen versions are immutable snapshots.
  • Endorsements attach to one frozen version.
  • Content hashes make changes visible.
  • Supporters control endorsement validity within the allowed bounds.
  • Provenance metadata is visible in exports.
  • OpenLOS does not claim legal e-signature compliance.

Local Development

Copy the environment template:

cp .env.example .env

Install dependencies:

npm install

Generate Prisma client and create the local SQLite schema. The touch command creates the SQLite file that file:./dev.db resolves to from the Prisma schema directory:

npm run prisma:generate
touch prisma/dev.db
npx prisma migrate dev

Start the development server:

npm run dev

Open:

http://localhost:3000

Useful validation commands:

npm run typecheck
npm run lint
npm run test:lineage
npx prisma validate
npm run build

Docker Usage

Build the image:

docker build -t openlos:local .

Run with Docker Compose:

cp .env.example .env
docker compose up --build

The app is exposed on port 3000 by default:

http://localhost:3000

If port 3000 is occupied:

OPENLOS_HOST_PORT=3001 docker compose up --build

Then open:

http://localhost:3001

Docker Compose persists SQLite data in the named volume openlos-sqlite, mounted at /data in the container. Compose intentionally sets the container database URL to the Docker path even if your local .env uses file:./dev.db:

DATABASE_URL="file:/data/openlos.db"

The container creates /data/openlos.db if needed, then runs Prisma migrations before startup so a new volume gets the required tables.

Prisma resolves relative SQLite paths from the Prisma schema directory. For local development, DATABASE_URL="file:./dev.db" resolves to prisma/dev.db. Avoid DATABASE_URL="file:./prisma/dev.db"; from the schema directory that can resolve to prisma/prisma/dev.db.

Environment Variables

  • DATABASE_URL: SQLite database path. Local npm development should use file:./dev.db; Docker uses file:/data/openlos.db.
  • APP_BASE_URL: base URL used in supporter request links.
  • OPENLOS_HOST_PORT: host port used by Docker Compose.
  • SMTP_HOST: SMTP server host. Blank means manual links only.
  • SMTP_PORT: SMTP server port, usually 587 or 465.
  • SMTP_USER: optional SMTP username.
  • SMTP_PASS: optional SMTP password or app password.
  • SMTP_FROM_ADDRESS: email sender address.
  • SMTP_FROM_NAME: optional email sender display name.
  • SMTP_SECURITY: starttls, ssl, or none.
  • SMTP_FROM: backward-compatible full sender header; overrides split from fields when set.

Contributor Setup

Development prerequisites:

  • Node.js 20.11 or newer
  • npm
  • Docker, if validating the container path

Common workflow:

cp .env.example .env
npm install
npm run prisma:generate
touch prisma/dev.db
npx prisma migrate dev
npm run dev

Prisma commands:

npm run prisma:generate
npx prisma migrate dev
npm run prisma:deploy
npx prisma validate

Docker commands:

docker build -t openlos:local .
docker compose config
docker compose up --build

Mockup references live in:

docs/mockups/

Non-Goals

OpenLOS intentionally avoids:

  • LinkedIn-style profiles
  • social feeds
  • followers
  • endorsement scores
  • engagement metrics
  • recruiter analytics
  • enterprise HR workflows
  • legal e-signature compliance claims
  • AI-generated endorsements
  • blockchain, tokens, or web3 proofs
  • Go code

License

Apache-2.0.

Copyright 2026 Gregory Kulp

About

Allows peers and mentors to provide structured endorsements in a single portable document that candidates maintain and recruiters can quickly review.

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Contributors