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
56 changes: 56 additions & 0 deletions .github/workflows/release-candidate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: Release candidate evidence

on:
workflow_dispatch:
inputs:
candidate_id:
description: Non-secret release candidate identifier
required: true
default: rc-unassigned

permissions:
contents: read

jobs:
verify-candidate:
runs-on: ubuntu-latest
timeout-minutes: 25
steps:
- name: Check out candidate
uses: actions/checkout@v4

- name: Use Node.js 22
uses: actions/setup-node@v4
with:
node-version: 22.13.0
cache: npm
cache-dependency-path: |
backend/package-lock.json
frontend/package-lock.json
website/package-lock.json

- name: Install locked dependencies
run: npm run install:all

- name: Run the complete engineering gate
run: npm run check

- name: Confirm governed artifacts are current
run: npm run test:release-manifest

- name: Archive non-secret candidate evidence
uses: actions/upload-artifact@v4
with:
name: ross-${{ inputs.candidate_id }}-evidence
retention-days: 30
if-no-files-found: error
path: |
reports/ontario-evaluation-v1.json
reports/legal-source-health-v1.json
reports/release-manifest-v1.json
config/release-approvals.v1.json
config/operations-readiness.v1.json
config/launch-readiness.v1.json

# Deliberately no deploy job. Production promotion is a separate, human-approved
# action after every fail-closed record and external review is complete.
10 changes: 9 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Contributing

Thanks for helping improve Mike. Please keep contributions small, focused, and easy to review.
Thanks for helping improve ROSS, a modified fork of Mike. Please keep
contributions small, focused, and easy to review while preserving the inherited
Mike functionality recorded in the baseline contract.

## Guidelines

Expand All @@ -9,12 +11,18 @@ Thanks for helping improve Mike. Please keep contributions small, focused, and e
- Update docs or env examples when changing setup, config, or user-facing behavior.
- Please do not propose local-hosting refactors for the main app, such as local LLMs, local databases, or local filesystem storage. Those ideas are better suited to a future fully local version of the project.
- Do not commit secrets, API keys, private documents, or local `.env` files.
- Do not use confidential, privileged, regulated, proprietary, or real client
material in fixtures, previews, demonstrations, issues, or pull requests.
- Do not scrape CanLII. Licensed connectors require an approved agreement,
transport, entitlement, and activation record.

## Before Opening a PR

- Install all dependencies from the repository root with `npm run install:all`.
- Run the baseline contract tests with `npm test`.
- Run the full local verification suite with `npm run check`.
- For a release-candidate change, regenerate and verify the governed manifest
with `npm run build:release-manifest` and `npm run test:release-manifest`.
- If deployed test URLs are available, run `ROSS_E2E_API_URL=... ROSS_E2E_APP_URL=... npm run test:e2e`.
- Check `git diff` and remove unrelated changes.
- Write a concise Markdown PR description with:
Expand Down
30 changes: 28 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

ROSS (Ranade OSS, fork of Mike OSS) is a legal document assistant with a Next.js frontend, an Express backend, Supabase Auth/Postgres, and Cloudflare R2-compatible object storage.

ROSS public website: planned; placeholder domains are recorded in
`config/ross-brand.json` until approved.
The ROSS public website source is in `website/`. Its current hosted checkpoint
is owner-only and no-index; placeholder production domains and owners remain in
`config/ross-brand.json` until independently approved.

Upstream project: [Mike source](https://github.com/Open-Legal-Products/mike) and
[Mike website](https://mikeoss.com). ROSS is independently developed and is not
Expand All @@ -20,6 +21,10 @@ materials only. Architecture decisions and their approval status are indexed in
- `backend/` - Express API, Supabase access, document processing, and database schema
- `backend/schema.sql` - Supabase schema for fresh databases
- `backend/migrations/` - dated, incremental schema migrations; on an existing database, apply the files dated after the Mike version you deployed
- `website/` - separate public website with governed Ontario, trust, coverage,
status, and launch-readiness content
- `config/` and `reports/` - versioned policy, approval, evaluation,
source-health, and immutable release-manifest records

## Prerequisites

Expand Down Expand Up @@ -116,6 +121,7 @@ Install each app package:
```bash
npm install --prefix backend
npm install --prefix frontend
npm install --prefix website
```

## Run Locally
Expand Down Expand Up @@ -162,3 +168,23 @@ npm run check

Use `npm run lint:strict` to inspect the inherited frontend lint debt separately
from the non-regression gate.

## Production and controlled-beta release boundary

Self-hosted local development remains available. An operator-hosted beta is
limited to synthetic or affirmatively non-confidential material. Production is
not approved and fails closed while the operator, domains, vendors, effective
legal notices, independent reviews, live source health, and operational
exercises are pending.

Start with these records and runbooks:

- `config/release-approvals.v1.json`
- `config/operations-readiness.v1.json`
- `config/launch-readiness.v1.json`
- `docs/operations/release-runbook.md`
- `docs/operations/launch-checklist.md`

`npm run release:check` is the production promotion gate. Do not replace a
blocked result with an environment flag or deploy a different artifact from the
one reviewed in `reports/release-manifest-v1.json`.
24 changes: 24 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# ROSS security policy

ROSS is under active development and is not approved for confidential,
privileged, regulated, proprietary, or real client material in an
operator-hosted environment.

## Reporting a vulnerability

Use GitHub private vulnerability reporting for this repository. Do not include
client information, production credentials, unnecessary personal information,
or exploit details in a public issue. A public issue may be used for a
non-sensitive software defect only.

The project does not yet publish a response-time commitment because the legal
operator and security owner are unassigned. The incident process in
`docs/security/incident-response.md` is the engineering response model, not an
operational service-level agreement.

## Supported versions

No production release is supported. The default branch is the only engineering
line receiving security changes. Fork operators are responsible for their own
deployment, access control, secrets, dependencies, logs, backups, retention,
vendors, monitoring, and incident response.
5 changes: 5 additions & 0 deletions backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ ROSS_DATA_BOUNDARY_VERSION=2026-07-16
HOSTED_MODEL_PROVIDERS=
# Production remains fail-closed until recorded legal/privacy/security approvals.
ROSS_PRODUCTION_CONTROLS_APPROVED=false
# Required in production and must identify the exact approved candidate and the
# SHA-256 digest of its governed release manifest. These values are evidence
# references, not substitutes for npm run release:check.
ROSS_RELEASE_ID=
ROSS_RELEASE_MANIFEST_SHA256=
# Comma-separated exact browser origins. FRONTEND_URL remains a compatibility fallback.
CORS_ALLOWED_ORIGINS=http://localhost:3000

Expand Down
31 changes: 31 additions & 0 deletions backend/src/config/runtime.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ const KEYS = [
"ROSS_HOSTED_MODE",
"HOSTED_MODEL_PROVIDERS",
"ROSS_PRODUCTION_CONTROLS_APPROVED",
"ROSS_RELEASE_ID",
"ROSS_RELEASE_MANIFEST_SHA256",
"LOG_RAW_LLM_STREAM",
"RAW_LLM_STREAM_LOG_DIR",
"CORS_ALLOWED_ORIGINS",
Expand Down Expand Up @@ -101,6 +103,9 @@ test("non-local raw model logging and unapproved production fail closed", () =>
R2_ACCESS_KEY_ID: "production-access-value",
R2_SECRET_ACCESS_KEY: "production-storage-secret",
R2_BUCKET_NAME: "ross-production",
ROSS_RELEASE_ID: "ross-2026-07-16-rc1",
ROSS_RELEASE_MANIFEST_SHA256:
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
},
() =>
assert.throws(
Expand All @@ -109,3 +114,29 @@ test("non-local raw model logging and unapproved production fail closed", () =>
),
);
});

test("production requires a valid immutable release manifest identity", () => {
withEnvironment(
{
ROSS_ENV: "production",
ROSS_HOSTED_MODE: "production",
HOSTED_MODEL_PROVIDERS: "openai",
CORS_ALLOWED_ORIGINS: "https://app.ross.test",
SUPABASE_URL: "https://ross.supabase.co",
SUPABASE_SECRET_KEY: "production-secret-value",
DOWNLOAD_SIGNING_SECRET: "production-signing-value",
R2_ENDPOINT_URL: "https://objects.ross.test",
R2_ACCESS_KEY_ID: "production-access-value",
R2_SECRET_ACCESS_KEY: "production-storage-secret",
R2_BUCKET_NAME: "ross-production",
ROSS_RELEASE_ID: "ross-2026-07-16-rc1",
ROSS_RELEASE_MANIFEST_SHA256: "not-a-digest",
ROSS_PRODUCTION_CONTROLS_APPROVED: "true",
},
() =>
assert.throws(
() => loadRuntimeConfig(),
/ROSS_RELEASE_MANIFEST_SHA256 must be a lowercase SHA-256/,
),
);
});
11 changes: 11 additions & 0 deletions backend/src/config/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ export type RuntimeConfig = {
hostedMode: RossHostedMode;
dataBoundaryVersion: string;
hostedModelProviders: Array<"claude" | "gemini" | "openai">;
releaseId: string | null;
releaseManifestSha256: string | null;
};

const PLACEHOLDER =
Expand Down Expand Up @@ -129,8 +131,14 @@ export function loadRuntimeConfig(): RuntimeConfig {
"R2_ACCESS_KEY_ID",
"R2_SECRET_ACCESS_KEY",
"R2_BUCKET_NAME",
"ROSS_RELEASE_ID",
"ROSS_RELEASE_MANIFEST_SHA256",
])
requiredProductionValue(name);
if (!/^[a-f0-9]{64}$/.test(process.env.ROSS_RELEASE_MANIFEST_SHA256!))
throw new Error(
"ROSS_RELEASE_MANIFEST_SHA256 must be a lowercase SHA-256 digest.",
);
if (allowedOrigins.some((origin) => PLACEHOLDER.test(origin))) {
throw new Error(
"Production CORS origins cannot use localhost or placeholder domains.",
Expand Down Expand Up @@ -167,5 +175,8 @@ export function loadRuntimeConfig(): RuntimeConfig {
dataBoundaryVersion:
process.env.ROSS_DATA_BOUNDARY_VERSION?.trim() || "2026-07-16",
hostedModelProviders: hostedModelProviders(currentHostedMode),
releaseId: process.env.ROSS_RELEASE_ID?.trim() || null,
releaseManifestSha256:
process.env.ROSS_RELEASE_MANIFEST_SHA256?.trim() || null,
};
}
2 changes: 2 additions & 0 deletions backend/src/middleware/dataBoundary.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ const config: RuntimeConfig = {
hostedMode: "controlled-beta",
dataBoundaryVersion: "test-v1",
hostedModelProviders: ["openai"],
releaseId: null,
releaseManifestSha256: null,
};

test("content-bearing write routes are identified without blocking reads or deletion", () => {
Expand Down
15 changes: 15 additions & 0 deletions config/launch-readiness.v1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"version": "1.0.0-draft",
"releaseId": "unassigned",
"status": "blocked-awaiting-launch-decisions",
"decisions": {
"legalOperator": { "status": "pending", "approver": null, "date": null, "evidence": null },
"accountableOwners": { "status": "pending", "approver": null, "date": null, "evidence": null },
"productionDomains": { "status": "pending", "approver": null, "date": null, "evidence": null },
"vendorsAndResidency": { "status": "pending", "approver": null, "date": null, "evidence": null },
"effectiveLegalNotices": { "status": "pending", "approver": null, "date": null, "evidence": null },
"supportAndPrivacyChannels": { "status": "pending", "approver": null, "date": null, "evidence": null },
"betaCohortAndTerms": { "status": "pending", "approver": null, "date": null, "evidence": null },
"goLiveDecision": { "status": "pending", "approver": null, "date": null, "evidence": null }
}
}
38 changes: 38 additions & 0 deletions config/legal-source-operations.v1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"version": "1.0.0",
"policyId": "ross-legal-source-operations-v1",
"status": "engineering-policy-approved",
"defaultTimeoutMs": 10000,
"providers": {
"a2aj-canada": {
"requiredForProduction": true,
"maximumAgeHours": 24,
"quarantineAfterFailures": 3,
"recoverAfterSuccesses": 2
},
"ontario-elaws": {
"requiredForProduction": true,
"maximumAgeHours": 24,
"quarantineAfterFailures": 3,
"recoverAfterSuccesses": 2
},
"justice-laws-canada": {
"requiredForProduction": true,
"maximumAgeHours": 24,
"quarantineAfterFailures": 3,
"recoverAfterSuccesses": 2
},
"courtlistener-us": {
"requiredForProduction": false,
"maximumAgeHours": 72,
"quarantineAfterFailures": 3,
"recoverAfterSuccesses": 2
},
"canlii-licensed": {
"requiredForProduction": false,
"maximumAgeHours": 24,
"quarantineAfterFailures": 1,
"recoverAfterSuccesses": 2
}
}
}
16 changes: 16 additions & 0 deletions config/operations-readiness.v1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"version": "1.0.0-draft",
"releaseId": "unassigned",
"status": "blocked-awaiting-operational-evidence",
"evidence": {
"ci": { "status": "pending", "approver": null, "date": null, "evidence": null },
"stagingJourney": { "status": "pending", "approver": null, "date": null, "evidence": null },
"migrationDryRun": { "status": "pending", "approver": null, "date": null, "evidence": null },
"backupRestore": { "status": "pending", "approver": null, "date": null, "evidence": null },
"rollbackExercise": { "status": "pending", "approver": null, "date": null, "evidence": null },
"observability": { "status": "pending", "approver": null, "date": null, "evidence": null },
"sourceHealth": { "status": "pending", "approver": null, "date": null, "evidence": null },
"dependencyReview": { "status": "pending", "approver": null, "date": null, "evidence": null },
"incidentExercise": { "status": "pending", "approver": null, "date": null, "evidence": null }
}
}
20 changes: 20 additions & 0 deletions config/release-manifest.v1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"version": "1.0.0",
"releaseId": "unassigned",
"generatedAt": "2026-07-16T00:00:00.000Z",
"artifacts": [
"backend/schema.sql",
"backend/src/config/runtime.ts",
"config/legal-source-operations.v1.json",
"config/launch-readiness.v1.json",
"config/operations-readiness.v1.json",
"config/public-source-coverage.json",
"config/release-approvals.v1.json",
"config/ross-brand.json",
"reports/legal-source-health-v1.json",
"reports/ontario-evaluation-v1.json",
"scripts/lib/release-readiness.mjs",
"website/app/site-config.ts",
"workflows/ontario/catalogue.json"
]
}
7 changes: 5 additions & 2 deletions docs/architecture/ADR-012-release-and-source-governance.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,8 @@ Ontario evaluation gates are implemented and assigned owners.

## Follow-up

- [ ] Define release artefacts and approval matrix in ROSS-150 and ROSS-170.
- [ ] Add source health, freshness, quarantine, and rollback procedures.
- [x] Define release artifacts and approval matrices in ROSS-150 and ROSS-170.
- [x] Add source health, freshness, quarantine, and rollback procedures.
- [ ] Assign owners and complete staging, restore, rollback, source, security,
accessibility, legal-content, privacy, and launch evidence for one immutable
candidate.
25 changes: 25 additions & 0 deletions docs/operations/backup-restore.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Backup and restore exercise

Production remains blocked until the selected database, object storage, auth,
configuration, and audit systems have documented backup scope and a successful
isolated restore exercise.

## Required evidence

1. Inventory every stateful system, encryption key dependency, backup region,
retention period, access role, and deletion limitation.
2. Create a backup from synthetic staging data containing a known record set and
file hashes. Confirm encryption and least-privilege access.
3. Restore into a new isolated environment—not over staging or production.
4. Apply migrations in documented order and verify counts, hashes, ownership,
sharing boundaries, soft-deletion state, download controls, and audit access.
5. Exercise authentication recovery without copying production secrets.
6. Run the complete engineering gate and the staging user journey against the
restored environment.
7. Record recovery point and recovery time observations. The product owner must
approve targets before they are described as commitments.
8. Destroy the exercise environment and confirm its backup/secret cleanup.

A database-only restore is insufficient when object storage, auth identities,
keys, or source versions are required to make records usable and correctly
isolated. Backup expiry and deletion propagation remain privacy-review items.
Loading
Loading