J3vbandClaude Opus 5 5a70f7ae0f B1-8: platform contract map, HP-1 structural review, and the B1 exit gate (RL-02 / L-02) (#1420)
* docs: record the desktop/browser platform contract map (B1-8, RL-02/L-02)

Client/src/platform/ does not exist — no commits, no files, zero importers.
RL-02 asked for the boundary to be *recorded* in B1 so that B7 executes a
decided plan rather than rediscovering the surface. This is that record, and
nothing more: no directory, no interface, no code.

Measured against dev @ eb873fe7, not estimated: 20 files under Client/src/
import @tauri-apps, using 26 distinct invoke command names against 30
#[tauri::command] handlers, with zero dangling calls and zero uses of the
window.__TAURI__ global. Every native dependency is an import, so a static
check can find all of them — which is what BPR-025 will eventually enforce.

The count is 26 and not 22 because Client/src/lib/ws.ts binds core.invoke to a
local tauriInvoke before calling it; a regex matching only invoke("…") misses
ws_connect, ws_send, ws_disconnect and accept_cert_fingerprint. Any future
lint rule enforcing the seam has to match the binding, not the call site.

The 20 files collapse into 13 capability clusters, three of which have no
browser equivalent and are flagged as product decisions rather than shims:
certificate TOFU in ws.ts, the OS keychain behind credentials.ts/identity.ts,
and out-of-focus push-to-talk in ptt.ts.

Ownership is recorded by phase (B7/B8/B2). No human owners exist for these
folders anywhere in the repository; the document says so rather than leaving
the absence to read as an oversight.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs: perform the HP-1 structural review and measure the B1 exit gate

HP-1 asks whether B1's migrations were mechanical. It had never been run, and
it cannot be run against dev: dev is squash-merge only, so #1411 landed as one
commit and the pure-move/path-rewrite separation the hold point exists to
review survives only on refs/pull/1411/head. The scorecard records the
pre-squash SHAs so the review is reproducible.

Four proofs, all passing:

- Pure move (4befe699): 473 renames, all R100, zero non-rename entries, zero
  line changes, and every renamed blob byte-identical. The blob-OID comparison
  is what actually covers the six binaries — --numstat prints "-" for them, so
  the obvious line-count filter reports false positives.
- Path rewrite (38ddca73): 983 added / 983 removed, and after normalising the
  substitution, six unpaired pairs remain — all relative-path depth arithmetic
  from losing one directory level. Each was resolved against HEAD. The release
  signer is among them and runs only on a tag, so no CI run on any branch
  executes it; it is correct (working-directory: Client, artifacts at the root)
  and guarded by a downstream verify step that fails closed.
- Go module rename (7a4e5dc3): 350 files, 728/728, zero unpaired lines. The
  largest change in B1 is provably a pure substitution.
- Active path inventory: 11 files still name tauri-client, all historical —
  ledger lens labels, dated audits, and plans that describe the move. Zero in
  code, workflows, scripts, hooks or the Dockerfile.

The seed move (93ee14d5) does change behaviour — init() deleted, os.MkdirAll
moved into main(). That was authorised by the plan and is isolated in its own
commit, which is what HP-1 asks for.

Exit gate: seven of eight conditions evidenced. Condition 6 is recorded as
PARTIALLY MET and is a real gap — dev has 11 required checks pinned but
strict:false, so when dev advances after a PR goes green that PR can still
merge without re-testing, and the squash commit that lands was never itself
tested. Deliberately not changed here: flipping strict forces a rebase on every
open PR whenever another lands, and enforce_admins is on. Owner's call.

ENV-01 is closed. Every B0 number was measured on Node 26 while CI pins 24. The
client suite now re-runs on Node 24 from a fresh clone in a node:24 container:
192 files, 5257 tests — identical to B0, and the clone doubles as the exit
gate's Linux setup smoke. ENV-02 also reproduces at 50.1 MB booting on :8443.

Corrects the plan's stale Docker command along the way: the script moved to
Server/scripts/ and now takes the image as an argument, and the build context
is Server/ rather than the repository root — building from the root streams the
whole working tree and then fails on the missing go.mod.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs: record the applied repository settings in the HP-1 scorecard

Both checked-in settings scripts were run on 2026-08-27 — they had landed in
#1418 and #1419 but were deliberately never executed, because repo-settings
writes need a person.

b0-dev-branch-protection.sh pinned the twelfth required check on dev,
"Docs & Ledger Consistency". Until that run the FINDINGS.md drift gate reported
but could not block a merge. Condition 6 now reads 12 pinned checks; it stays
PARTIALLY MET because strict is still false, which the script itself encodes as
a deliberate choice.

b1-release-tag-protection.sh created the "Release tags" ruleset (active, target
tag, refs/tags/v*, blocks update and deletion, zero bypass actors) and the
release environment with one required reviewer. Checked for a pre-existing
ruleset of that name first — the POST half is not idempotent and a second run
would have created a duplicate. Three rulesets existed, all targeting branches,
none named "Release tags".

Condition 7 closes: B1-7 merged, and the Discussions slugs its issue-template
config hardcodes — q-a and ideas — both exist, so the contact links resolve
rather than silently dropping the user on the category picker.

Two things the read-back surfaced, both recorded as open, neither blocking:

- The release environment has can_admins_bypass: true, GitHub's default. The
  ruleset has zero bypass actors, but the reviewer gate does not. Moot while
  the sole admin is also the sole reviewer.
- claude.yml passes secrets.CLAUDE_CODE_OAUTH_TOKEN and the repository has no
  such secret. Nothing is failing, because all five issue_comment runs are
  skipped at the B1-7 guard before the missing secret would matter — but the
  paid-automation surface RL-22 hardens is inert today.

environment: release is still absent from release.yml, deliberately. The
environment now exists, so that is a separate two-line change.

Gate re-run after rebasing onto c0c87366 so condition 8 is measured over the
final tree, B1-7 included: green, 5257 client tests, exit 0. B1-7's
check-workflow-guards.mjs runs locally; its sibling verify-gate-evidence.mjs
does not — CI runs the selftest, and the assert form needs a token and a real
SHA.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 18:11:47 +00:00

CI Release Status Go Tauri Platforms License: AGPL-3.0

OwnCord

A self-hosted chat app I build for me and my friends — text channels, voice and video, and a server you actually own.

Alpha, and a hobby project. This is something I build for fun and run for a small group of friends. It isn't a product, it comes with no support commitment, and it isn't production-ready. Expect rough edges, rapid changes, and the occasional breaking change.

Don't use it for anything sensitive.

It's a Go server plus a Tauri desktop client: real-time messaging, voice/video via LiveKit, file sharing, and a web admin panel. Run the server on a spare box or a VPS, hand your friends an invite code, and that's the whole thing.

OwnCord Client

Login Page Admin Panel

How it's built

Most of the implementation is generated with AI tooling, with quality held up by automated checks — CI, tests, linting — and by me and my friends actually using it. That keeps iteration fast, and it also means behaviour can change quickly between releases.

What works right now

Area Status
Core chat flow Working in alpha
Voice/video Working in alpha
Admin panel Working in alpha
Security hardening Ongoing review passes; findings and their statuses are tracked in the dated audits in docs/ (see the Docs Index below)

Platform Support (Current Releases)

Component Windows x64 Linux x64 Linux ARM64
Server binary Yes Yes Not yet
Desktop client Yes (NSIS installer) Yes (AppImage, .deb) Yes (AppImage, .deb)
Docker server N/A Build from source (compose) Not yet

Start Here

Quick Start

Option A: Prebuilt binaries

  1. Download assets from Releases (binaries, checksums, signatures, and a full source snapshot per release).
  2. Run the server binary:
    • Windows: chatserver.exe
    • Linux: ./chatserver
  3. Open https://localhost:8443/admin and complete the setup wizard — it creates your Owner account and configures the server for you (settings are saved to config.yaml automatically).
  4. Generate invite codes in the admin panel and share them with friends.

Option B: Docker (Linux server)

cd Server
cp .env.example .env
cp livekit.yaml.example livekit.yaml
# Edit both files before starting
docker compose up -d

See the full setup guide in docs/deployment.md.

The client uses TOFU (Trust On First Use) for self-signed certificates: it prompts once, then pins the certificate for future connections.

What OwnCord Already Has

  • Real-time channels and direct messages over WebSocket
  • Voice/video channels via LiveKit — the LiveKit server binary is downloaded and managed for you
  • Invite-only registration and role-based permissions
  • Web admin panel with logs, backups, and update tooling
  • File uploads and inline media rendering
  • TOTP 2FA support and API rate limiting
  • Desktop client auto-update with signature verification
  • WASM plugin system (slash commands; sandboxed, default-disabled — enable via plugins.enabled and build with -tags wazero)
  • GIF picker — off by default; each server supplies its own Klipy key via gif.api_key (setup)

See deeper feature and architecture docs in docs/architecture/ and docs/protocol.md.

Architecture

Two main components:

  • Go server (REST API, WebSocket hub, SQLite, admin panel)
  • Tauri v2 desktop client (Rust backend + TypeScript frontend)
+---------------------+         +---------------------+
|   OwnCord Client    |         |   OwnCord Server    |
|   (Tauri v2)        |         |       (Go)          |
|                     |         |                     |
|  +---------------+  |  WSS    |  +---------------+  |
|  |  Chat UI      |--+------->|  |  WebSocket Hub|  |
|  +---------------+  |         |  +---------------+  |
|  +---------------+  |  HTTPS  |  +---------------+  |
|  |  REST Client  |--+------->|  |  REST API     |  |
|  +---------------+  |         |  +---------------+  |
|  +---------------+  | LiveKit |  +---------------+  |
|  |  Voice/Video  |--+------->|  |  LiveKit SFU  |  |
|  +---------------+  |         |  +---------------+  |
+---------------------+         |  +---------------+  |
                                |  |  SQLite DB    |  |
                                |  +---------------+  |
                                +---------------------+

Build and Test

Prerequisites

  • Go 1.26+
  • Node.js 24+ (see Client/.nvmrc)
  • Rust stable (client builds)

Build from source

# Server (Windows)
cd Server
go build -o chatserver.exe -ldflags "-s -w -X main.version=1.2.0-alpha.3" .

# Server (Linux)
cd Server
CGO_ENABLED=0 go build -o chatserver -ldflags "-s -w -X main.version=1.2.0-alpha.3" .

# Client
cd Client
npm install
npm run tauri build

Core verification commands

Everything CI gates on, from the repository root:

npm run check                  # server + client + Rust
npm run check:server           # or one stack at a time
node scripts/run.mjs --list    # exactly what each task runs, and where

Or run the stacks directly — the facade is a convenience, not the only path, and server work needs no Node at all:

# Server
cd Server
go test ./...

# Client
cd Client
npm run typecheck
npm run lint
npm test

For the full command set, use docs/contributing.md.

Configuration

On first run, the server generates config.yaml and a local data/ directory:

data/
├── chatserver.db
├── certs/
├── uploads/
└── backups/

Key options include TLS mode, upload limits, LiveKit settings, and admin CIDR restrictions. See docs/server-configuration.md.

Security and Vulnerability Reporting

  • For vulnerabilities, use GitHub Security Advisories (private disclosure flow).
  • Do not open public issues for security bugs.
  • Read full policy and hardening notes in docs/security.md.

Update Signing Notes (Maintainers)

Client and server update signing keys are intentionally separate.

Required Actions secrets for release signing:

  • TAURI_SIGNING_PRIVATE_KEY
  • TAURI_SIGNING_PRIVATE_KEY_PASSWORD
  • SERVER_UPDATE_SIGNING_PRIVATE_KEY
  • SERVER_UPDATE_SIGNING_PRIVATE_KEY_PASSWORD

When rotating the server updater key, update Server/updater/server_update_public_key.txt and use staged rollover for live fleets.

Docs Index

docs/README.md is the complete index — every document in docs/, grouped by whether it is guidance, a reference contract, a dated audit, or a plan. The most-used entries:

Audits are dated snapshots and are not maintained after the fact — read them as history. docs/README.md lists all nine, newest first.

Contributing

  1. Create a branch from dev (the active development branch).
  2. Keep changes focused and tested.
  3. Open a PR targeting devdev is merged to main for releases.

See docs/contributing.md for the full process.

Getting Help and Reporting Problems

Nothing here is a support promise — see the note at the top — but there is a right place for each kind of message:

Kind Where
A reproducible bug Issues
A question about setup or usage Discussions → Q&A
An idea or feature suggestion Discussions → Ideas
A security vulnerability Private advisory — never an issue

License

AGPL-3.0

S
Description
OwnCord is a self-hosted, open-source chat platform with text channels, voice/video chat, direct messages, and a desktop client — built for communities that want full control over their data.
Readme AGPL-3.0
111 MiB
Languages
TypeScript 48.7%
Go 44%
JavaScript 2.2%
Rust 2.1%
CSS 1.4%
Other 1.5%