# TuneVault Architecture Rules

This file is the canonical lock for TuneVault's architecture invariants.
A CI check (`scripts/check-architecture-rules.sh`) enforces these rules on every PR.
Violations fail the build. The check is in `.github/workflows/architecture.yml`.

---

## Rule 1: Cloudflare Tunnels are NOT part of TuneVault's product architecture.

### The Rule

No customer-facing code, documentation, runbooks, or installer output may reference
Cloudflare tunnel services (`cloudflared`, `cloudflare-tunnel`, `tunevault-tunnel.service`,
Cloudflare Zero Trust tunnels, `*.cfargotunnel.com`, `trycloudflare.com`).

### Rationale

TuneVault's agent communication model is **outbound-only long-poll**:

- `oracle-proxy.py` runs on the Oracle server as a systemd service (`tunevault-proxy`).
- The proxy opens an outbound HTTPS connection to `tunevault.app/api/agent/poll` every 10 seconds.
- TuneVault sends health check jobs through this channel; the proxy executes them locally.
- **No inbound ports. No DNS records. No third-party tunnel services.**

Cloudflare tunnel support was removed from the product in May 2026 (install.sh v4.0).
Any reference to it in customer-facing surfaces:
1. Confuses users who follow outdated steps and get stuck on non-existent infrastructure.
2. Creates support load for a feature that no longer exists.
3. Contradicts the "400 KB Python script, zero inbound ports" marketing differentiator.

### Patterns that are FORBIDDEN in customer-facing code

```
cloudflared
cloudflare-tunnel
tunevault-tunnel.service
cf_enabled
cf_tunnel
trycloudflare
*.cfargotunnel.com
tunevault-tunnel
```

### Where these patterns ARE allowed (never customer-facing)

- `scripts/personal-lab/**` — Kiran's personal dev/test scripts only
- `docs/internal/**` — Internal notes marked NOT CUSTOMER-FACING
- `docs/installer/wave1-matrix.md` — records the `grep` that PROVES install.sh is clean
- Comment lines anywhere — naming a pattern to forbid it ships nothing

### What to do instead

If a customer asks about connectivity, refer them to the outbound-only architecture:
- **Runbook:** `/resources/runbooks/agent-connection-failure`
- **Architecture page:** `/architecture`
- **Setup guide:** `/setup/fresh`

The agent requires only outbound HTTPS (port 443) to `tunevault.app`. No tunnel setup.
No third-party accounts. No DNS records. If that's blocked, Direct TCP is the fallback.

---

## Rule 2: Thick-mode Oracle client setup lives only in `agent/bootstrap.py`.

### The Rule

`agent/bootstrap.py` is the sole owner of Instant Client download, `ORACLE_HOME`
discovery and the `oracledb.init_oracle_client()` call. No other module under
`agent/` may reference `ORACLE_HOME`, `libclntsh` or `init_oracle_client` in
executable code. `cx_Oracle` and `LD_LIBRARY_PATH` are forbidden everywhere in
`agent/`, bootstrap included.

### Rationale

The agent runs python-oracledb in **thin mode** by default — pure Python, no Oracle
client install. Thick mode is a documented fallback, activated only when a first
connect raises DPY-3015, DPY-3001, ORA-28040, or ORA-01017 against an 11g verifier.
`agent/oracle_worker.py` detects that and calls `bootstrap.ensure_thick_client()`.

Centralising it is what keeps thin mode the default. If any module could reach for
Instant Client on its own, thick mode would spread by accident and the "400 KB Python
script, no Oracle client" guarantee would erode silently. `agent/db.py`'s own module
docstring states the split: *"Does NOT own: SSH, poll loop, CLI parsing, probe
orchestration, thick-mode bootstrap."*

### Superseded wording

Until 2026-08-06 this rule read "v6 uses **zero** Oracle client" and scanned `agent/`
plus `install-v6.sh` — a file that no longer exists. That premise died when thick-mode
fallback shipped, so the check flagged `bootstrap.py` for doing exactly its job, and
`docs/ci-required-checks.md` recorded the rule as stale and ungateable. Centralisation,
not absence, is the invariant that actually holds.

### What to do instead

Call `bootstrap.ensure_thick_client()`. Never call `oracledb.init_oracle_client()`
directly. See `agent/db.py` for the thin-mode connect path.

---

## Rule 3: No lab artifacts, and no example SID as a runtime default.

### The Rule

**3a.** Lab-only hostnames (`ebs12210-db-dev`) must never appear in shipping code
(`.js`, `.html`, `.sh`, `.py`, `.ts`). They are legitimate in `docs/**` validation
records, and `ebs12210` is a legitimate enum value of `installer_validation_runs.os`
alongside `ol7`/`ol8`, so `.sql` is out of scope.

**3b.** An example SID (`EBSDB`, `EBSCDB`) must never be used as a runtime default or
fallback — `x || 'EBSDB'`, `x ?? 'EBSDB'`, `x or 'EBSDB'`, `default: 'EBSDB'`,
`os.environ.get('SID', 'EBSDB')`.

### Rationale

Bug #1670982 was a stale SID **poisoning probe 5** — a lab value silently standing in
for a real one. The defect is the silent substitution, not the string. Naming a SID is
ordinary and necessary: `placeholder="e.g. EBSDB or ORCL"`, the `is_demo` fixture, the
`/sample-report` page and unit-test fixtures all mention one legitimately.

So mentions are allowed and defaults are not. Where a value is genuinely required,
demand it explicitly and fail loudly when it is absent — a 400 beats a silent
connection to somebody else's database.

### Superseded wording

Until 2026-08-06 Rule 3 banned the bare strings `ebsdb`/`ebscdb` anywhere in code. Those
are also the generic names for an EBS database, so the rule fired on 15 lines of
placeholders, demo fixtures and test comments and caught zero real leaks — while the
two genuine `|| 'ebs12210-db-dev'` defaults in `routes/ready-to-test.js` and
`public/status-ready-to-test.html` sat undetected, because the old rule never looked
for hostnames at all.

---

## Adding New Architecture Rules

Append a new numbered `## Rule N` section. Update `scripts/check-architecture-rules.sh`
to add the new forbidden patterns and the file glob scope.

Reference this file from every PR that touches connectivity, installer, or agent code:
> "Checked against docs/ARCHITECTURE_RULES.md — no violations."
