Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
8a487b1
feat(trinity): natural dialogue and safe deploy approval groundwork
uscolective-byte Oct 10, 2026
4671364
feat(trinity): natural dialogue and safe deploy approval groundwork
uscolective-byte Oct 10, 2026
37b242a
feat(trinity): natural dialogue and safe deploy approval groundwork
uscolective-byte Oct 10, 2026
289507d
feat(trinity): natural dialogue and safe deploy approval groundwork
uscolective-byte Oct 10, 2026
827b483
feat(trinity): natural dialogue and safe deploy approval groundwork
uscolective-byte Oct 10, 2026
1915bf1
fix(trinity): replace simulated AI status with grounded dialogue and …
uscolective-byte Oct 10, 2026
edfbe1c
feat(trinity-ui): interactive multi-turn chat and no-ID deployment co…
uscolective-byte Oct 10, 2026
dc39859
fix(trinity): resolve ambiguous approvals by service name, not intern…
uscolective-byte Oct 10, 2026
c867fbb
test(trinity): run dialogue and approval regression suite in npm test
uscolective-byte Oct 10, 2026
302aa0f
test(trinity): natural approval by service name and expired request r…
uscolective-byte Oct 10, 2026
89b5a7e
docs(trinity): dialogue, approvals, D1 migration and pre-deploy safet…
uscolective-byte Oct 10, 2026
a901b46
ci: test and bundle Cloudflare Worker instead of generic Node template
uscolective-byte Oct 10, 2026
815111e
ci: remove obsolete Webpack workflow (project uses Cloudflare Workers)
uscolective-byte Oct 10, 2026
74d0903
ci: pin stable Ubuntu runner image to troubleshoot runner allocation
uscolective-byte Oct 10, 2026
3f904f9
cloudflare: add isolated non-public Trinity staging Worker config
uscolective-byte Oct 12, 2026
9a2c7b1
cloudflare: add manual isolated private staging deploy with D1 migrat…
uscolective-byte Oct 12, 2026
016eea9
cloudflare: fix generated Wrangler TOML newlines
uscolective-byte Oct 12, 2026
2889ea7
cloudflare: provision isolated D1 staging DB without manually copying…
uscolective-byte Oct 12, 2026
a1b5a1d
cloudflare: auto-discover or provision staging D1 without manual ID i…
uscolective-byte Oct 12, 2026
5f9b057
cloudflare: separate PR validation from approved private staging depl…
uscolective-byte Oct 12, 2026
b01a745
cloudflare: ignore generated staging configuration
uscolective-byte Oct 12, 2026
03589e0
docs: Cloudflare connection and non-public staging deployment checklist
uscolective-byte Oct 12, 2026
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
100 changes: 100 additions & 0 deletions .github/workflows/cloudflare-staging.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
name: Cloudflare Trinity Staging

on:
pull_request:
branches: [main]
workflow_dispatch:
inputs:
operation:
description: 'Validate only, or deploy inaccessible private staging Worker'
type: choice
required: true
default: validate
options:
- validate
- deploy-private

permissions:
contents: read

concurrency:
group: cloudflare-trinity-staging-${{ github.ref }}
cancel-in-progress: false

jobs:
validate:
name: Test Trinity and bundle Worker
runs-on: ubuntu-22.04
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: npm
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Validate private staging Worker bundle
run: npx --yes wrangler@4 deploy --dry-run --config wrangler.staging.toml --outdir .wrangler/staging-ci

deploy-private:
name: Provision and deploy non-public staging
needs: validate
if: ${{ github.event_name == 'workflow_dispatch' && inputs.operation == 'deploy-private' }}
environment: trinity-staging
runs-on: ubuntu-22.04
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: npm
- name: Install dependencies
run: npm ci

- name: Check Cloudflare credentials
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: |
test -n "$CLOUDFLARE_API_TOKEN" || { echo "::error::Missing CLOUDFLARE_API_TOKEN GitHub secret"; exit 1; }
test -n "$CLOUDFLARE_ACCOUNT_ID" || { echo "::error::Missing CLOUDFLARE_ACCOUNT_ID GitHub secret"; exit 1; }

- name: Create/find isolated EU staging D1 and generate binding
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: python3 scripts/prepare_cloudflare_staging.py

- name: Apply D1 dialogue schema
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: npx --yes wrangler@4 d1 execute trinity-dialogue-staging-db --remote --file migrations/0001_trinity_dialogue.sql --config wrangler.staging.generated.toml --yes

- name: Deploy separate Worker without routes or workers.dev
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: npx --yes wrangler@4 deploy --config wrangler.staging.generated.toml

- name: Configure Gemini key if provided
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
run: |
if [ -n "$GEMINI_API_KEY" ]; then
printf '%s' "$GEMINI_API_KEY" | npx --yes wrangler@4 secret put GEMINI_API_KEY --config wrangler.staging.generated.toml
fi

- name: Deployment summary
if: always()
run: |
echo "### Trinity Cloudflare staging" >> "$GITHUB_STEP_SUMMARY"
echo "- Worker: trinity-dialogue-staging (no public routes)" >> "$GITHUB_STEP_SUMMARY"
echo "- Dedicated EU database: trinity-dialogue-staging-db" >> "$GITHUB_STEP_SUMMARY"
echo "- Production and existing Workers: untouched" >> "$GITHUB_STEP_SUMMARY"
68 changes: 46 additions & 22 deletions .github/workflows/node.js.yml
Original file line number Diff line number Diff line change
@@ -1,31 +1,55 @@
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs

name: Node.js CI
name: Trinity Worker CI

on:
push:
branches: [ "main" ]
branches: [main]
pull_request:
branches: [ "main" ]
branches: [main]
workflow_dispatch:

permissions:
contents: read

concurrency:
group: trinity-worker-ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
build:
check:
name: Test and bundle Cloudflare Worker
runs-on: ubuntu-22.04
timeout-minutes: 15
steps:
- name: Checkout repository
uses: actions/checkout@v4

runs-on: ubuntu-latest
- name: Set up Node.js 22
uses: actions/setup-node@v4
with:
node-version: '22'
cache: npm

strategy:
matrix:
node-version: [18.x, 20.x, 22.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
- name: Install locked dependencies
run: npm ci

steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- run: npm ci
- run: npm run build --if-present
- run: npm test
- name: Run automated tests
run: npm test

- name: Validate JavaScript entry points
run: |
node --check src/index.js
node --check src/routes/ai.js
node --check src/routes/dialogue.mjs
node --check src/core/conversation.mjs
node --check src/frontend/app.js

- name: Build Cloudflare Worker without deploying
run: npx wrangler deploy --dry-run --outdir .wrangler/ci-output

- name: Summary
if: always()
run: |
echo "### Trinity CI" >> "$GITHUB_STEP_SUMMARY"
echo "- Environment: validation only (no production deploy)" >> "$GITHUB_STEP_SUMMARY"
echo "- Node.js: 22" >> "$GITHUB_STEP_SUMMARY"
echo "- Scope: unit tests, source syntax, Workers dry-run bundle" >> "$GITHUB_STEP_SUMMARY"
28 changes: 0 additions & 28 deletions .github/workflows/webpack.yml

This file was deleted.

4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@
.env.local
node_modules/
.dev.vars

# Generated by Cloudflare staging CI (contains account-scoped D1 binding)
wrangler.staging.generated.toml
.wrangler/staging-ci/
36 changes: 36 additions & 0 deletions docs/cloudflare-staging.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Trinity Dialogue v3 — Cloudflare staging deployment

## Scope

The GitHub workflow `.github/workflows/cloudflare-staging.yml` is **manual for deployment**. It validates pull requests but can only deploy after selecting `deploy-private` on the default branch. It uses a new Cloudflare Worker named `trinity-dialogue-staging`; **does not deploy to `tenebris-core` or `aura-trinity`**.

The staging config `wrangler.staging.toml` explicitly disables `workers_dev` and `preview_urls`. No public routes are configured, so a successful private deployment does not provide a public website. This is deliberate while access control is incomplete.

## One-time connection (do not paste secrets into issues, PRs or chat)

1. Create a **scoped Cloudflare API token** for the intended account with Cloudflare Workers Scripts **Edit** and D1 **Edit** permissions. Prefer the narrowest account/resources possible. Store it in the GitHub repository under **Settings → Secrets and variables → Actions → New repository secret** as `CLOUDFLARE_API_TOKEN`.
2. Store the Cloudflare account identifier there as `CLOUDFLARE_ACCOUNT_ID`.
3. Optionally store Gemini's secret under `GEMINI_API_KEY`; it will be set as a Worker secret after the staging deployment. Without it, conversational AI will report that the model is unavailable.
4. Configure a GitHub **trinity-staging** environment with restricted access and required reviewers if available. Workflow deployment requires that environment.
5. Ensure GitHub-hosted Actions runners can start. A run failing before assigning a runner cannot deploy anything.

## How to use

Merge the reviewed development branch after code security checks and successful CI. Under **Actions → Cloudflare Trinity Staging → Run workflow** select:
- `validate`: runs tests and compiles the Worker; does not access Cloudflare credentials.
- `deploy-private`: after approval checks, automatically finds or creates an EU-jurisdiction **trinity-dialogue-staging-db** D1 database, applies the dialogue schema, and deploys the isolated non-public Worker. No database ID needs to be manually copied.

Every required Cloudflare credential must be configured before running `deploy-private`. Never reuse a production database for this staging process.

## Requirements before enabling a public endpoint or production deployment

- Replace the sample admin password/static 2FA and in-memory sessions from the legacy project with **verified Cloudflare Access identity**. Only trusted allowed administrators should have control.
- Configure Cloudflare Access application and policy for the approved hostname; validate signed JWTs at the Worker or origin. Setting a header without verifying its signature is not authentication.
- Do not serve the admin UI on an unprotected `workers.dev` endpoint or expose a direct route that bypasses Access.
- Review all AI write/permissions endpoints and replace demo state with real authorized policy checks and audit logs.
- Verify deployment version, target, scope and rollback before opening any route; chat approvals only record a decision and cannot deploy by themselves.
- Prove live API/model connectivity and authenticated D1 persistence, then perform a staging smoke test before production.

## Notes

The workflow intentionally does **not** create a Cloudflare Access application or domain route automatically; doing so would require known account routing and security policy. Do not claim production is deployed just because GitHub tests passed.
25 changes: 25 additions & 0 deletions docs/trinity-dialogue-v3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Trinity Dialogue v3 — vývojová integrácia

Konverzačná vrstva pre existujúci Cloudflare Worker, aktivovaná cez `POST /api/ai/command` (spätná kompatibilita) alebo `POST /api/ai/dialogue`. Obe cesty vyžadujú serverové prihlásenie. API vracia `response`, `engine`, `mode`, `conversation_id` a `persistence`.

## Nové správanie

- Prirodzená slovenčina, krátka história rozhovoru, explicitná identifikácia neúspešného volania modelu namiesto vymyslených úspechov.
- Pripravovanie nasadenia musí obsahovať názov služby, prostredie a konkrétnu verziu / commit.
- Schvaľovanie bez ručného ID: „Schvaľujem“, „Potvrdzujem“, „Áno“, „Zamietam“. Pri viacerých čakajúcich návrhoch „Schvaľujem trinity-gateway“.
- Žiadosť je platná 15 minút a viazaná na konkrétneho prihláseného používateľa aj konverzáciu.
- **Schválenie iba zmení stav návrhu. Neexistuje tu vykonávač deploymentu.** Žiadne nasadenie sa nespustí len na základe správy v chate.

## D1 je potrebná pre trvalé schvaľovanie

Na produkčnej Cloudflare službe vytvorte alebo určte existujúcu D1 databázu, pridajte binding s názvom `TRINITY_DB` a aplikujte SQL zo súboru `migrations/0001_trinity_dialogue.sql`. Identifikátor databázy sa nastavuje výhradne podľa skutočnej konfigurácie, nie podľa náhodného príkladu. Bez migrácie alebo bindingu beží iba prechodný konverzačný režim; systém **neprijíma schválenia**.

Konfigurácia GEMINI kľúča používa Cloudflare secret `GEMINI_API_KEY` (tiež toleruje staršie názvy). Nikdy nevkladajte kľúče do klientského kódu.

## Bezpečnosť pred produkciou

Základný repozitár má ukážkové prihlasovacie údaje v zdrojovom kóde a nestále sessions. **Nezapájajte reálny deploy executor, kým nebude autentifikácia prepracovaná**, prednostne cez Cloudflare Access s presne obmedzenými oprávneniami. Treba ďalej zabezpečiť audit a vykonávanie iba schválených nezmeniteľných commitov.

## Overenie

`npm test` spustí testy spracovania slovenčiny, kontextu, zlyhania AI, izolácie session, schvaľovania, zamietnutia, viacnásobných návrhov a expirácie.
24 changes: 24 additions & 0 deletions migrations/0001_trinity_dialogue.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
-- Deploy review and conversation state. No real deployment is triggered here.
CREATE TABLE IF NOT EXISTS trinity_chat_turns (
id TEXT PRIMARY KEY,
owner TEXT NOT NULL,
conversation_id TEXT NOT NULL,
message TEXT NOT NULL,
response TEXT NOT NULL,
created_at TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_trinity_chat_owner_conv ON trinity_chat_turns(owner,conversation_id,created_at);
CREATE TABLE IF NOT EXISTS trinity_deployment_approvals (
id TEXT PRIMARY KEY,
owner TEXT NOT NULL,
conversation_id TEXT NOT NULL,
label TEXT NOT NULL,
target TEXT NOT NULL,
environment TEXT NOT NULL CHECK (environment IN ('production','staging')),
version TEXT NOT NULL,
status TEXT NOT NULL CHECK (status IN ('pending','approved','rejected','executed','expired')),
created_at TEXT NOT NULL,
expires_at TEXT NOT NULL,
decided_at TEXT
);
CREATE INDEX IF NOT EXISTS idx_trinity_approval_pending ON trinity_deployment_approvals(owner,conversation_id,status,expires_at);
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"description": "Underground Street Collective - Tenebris API Gateway",
"main": "src/index.js",
"scripts": {
"deploy": "wrangler deploy"
"deploy": "wrangler deploy",
"test": "node --test tests/*.test.mjs"
},
"devDependencies": {
"wrangler": "^3.0.0"
Expand Down
57 changes: 57 additions & 0 deletions scripts/prepare_cloudflare_staging.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#!/usr/bin/env python3
"""Prepare an isolated staging D1 binding using Wrangler credentials from CI.

Never prints credentials, changes production DBs, or exposes public Worker routes.
Only run in an explicitly approved staging deployment job.
"""
import json
import pathlib
import re
import subprocess

DB_NAME = "trinity-dialogue-staging-db"
CONFIG_PATH = pathlib.Path("wrangler.staging.generated.toml")
BASE_PATH = pathlib.Path("wrangler.staging.toml")
UUID = re.compile(r"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$")


def wrangler(*arguments):
return subprocess.run(["npx", "--yes", "wrangler@4", *arguments], check=True, text=True, capture_output=True).stdout


def find_db():
entries = json.loads(wrangler("d1", "list", "--json"))
if not isinstance(entries, list):
raise RuntimeError("Wrangler returned an invalid D1 database list")
exact = [entry for entry in entries if isinstance(entry, dict) and entry.get("name") == DB_NAME]
if len(exact) > 1:
raise RuntimeError("Multiple staging D1 databases have the same name")
return exact[0] if exact else None


def main():
db = find_db()
if db is None:
# Creates only a dedicated new staging database, with EU jurisdiction.
wrangler("d1", "create", DB_NAME, "--jurisdiction", "eu")
db = find_db()
if db is None:
raise RuntimeError("Staging D1 database was not found after creation")
database_id = db.get("uuid") or db.get("id")
if not isinstance(database_id, str) or not UUID.fullmatch(database_id):
raise RuntimeError("Invalid database identifier returned by Wrangler")
if CONFIG_PATH.exists():
raise RuntimeError("Generated configuration already exists; refusing overwrite")
base = BASE_PATH.read_text(encoding="utf-8")
binding = (
"\n[[d1_databases]]\n"
'binding = "TRINITY_DB"\n'
f'database_name = "{DB_NAME}"\n'
f'database_id = "{database_id}"\n'
)
CONFIG_PATH.write_text(base + binding, encoding="utf-8")
print("Dedicated private staging D1 database ready; generated local Wrangler config.")


if __name__ == "__main__":
main()
Loading
Loading