# Learnitive edu staging → www production audit — 2026-09-15

## Scope

Source-only audit of the Learnitive release for a one-time staging acceptance at `https://edu.learnitive.com`, followed by promotion to canonical production at `https://www.learnitive.com`.

No live DNS, certificate, Apache/Nginx, systemd, Docker, database, firewall, Interactive Shell production, or Learnitive production resource was changed during this audit.

## Result

The release is now staged around two explicit deployment profiles:

| Profile | Public origin | Filesystem root | Environment |
| --- | --- | --- | --- |
| staging | `https://edu.learnitive.com` | `/var/www/edu.learnitive.com/public_html` | `staging` |
| production | `https://www.learnitive.com` | `/var/www/learnitive.com/public_html` | `production` |

The existing single-environment service model is intentionally a **promotion model**: staging is accepted first, then the same Learnitive service identities/config stack are promoted to www. It is not a permanent side-by-side staging/production design.

## High-priority findings fixed

1. **Staging hostname/root was stale.** `os.learnitive.com` and `/var/www/os.learnitive.com` were replaced by `edu.learnitive.com` and `/var/www/edu.learnitive.com` for active staging. Legacy `os` recognition remains read-only/backward-compatible only where needed to adopt an older staging marker/tree.
2. **Staging vhost did not match production SSR behavior.** The edu HTTPS vhost now mirrors the www API/WebSocket/SSR proxy contract instead of falling back to the SPA `index.html` for unknown GET/HEAD routes.
3. **Staging could be indexed.** Staging is now protected at three layers: `X-Robots-Tag: noindex, nofollow, noarchive`, `robots.txt` with `Disallow: /`, and server/client metadata forced to noindex outside production.
4. **Staging client metadata could drift to www.** Browser metadata now uses the active origin while keeping `www.learnitive.com` as the production default. Staging canonical/OG metadata stays on edu and remains noindex.
5. **Fresh staging config could reject its own public URL.** Config repair now safely normalizes only approved Learnitive hosts to the selected deployment target.
6. **GitHub OAuth callback could remain on www during staging.** The active deployment profile now writes the callback for its own hostname.
7. **Passwordless email was on by default in staging.** Fresh edu staging defaults it off; it can be enabled deliberately with staging-safe SMTP/CAPTCHA credentials.
8. **Cutover documentation implied the application installer managed shared Apache.** Documentation/comments now make ingress an independent operator-managed boundary; the installer does not enable/disable/reload Apache/Nginx or run Certbot against the protected listener.
9. **Production SEO smoke tests still probed retired developer comparison routes as public.** Production/staging verifiers now assert the writer-first surface: `/compare/overleaf` is public while developer comparison/migration surfaces such as `/compare/replit` and `/migrate/replit` remain hidden.
10. **The staging configuration was not part of the canonical release gate.** Edu vhost/profile, staging runtime and staging safety tests are now included in `learnitive-current-release-audit.mjs`.

## Vhost changes

Added/updated source templates:

- `deploy/apache/edu.learnitive.com-http.conf`
- `deploy/apache/edu.learnitive.com-https.conf`
- `deploy/apache/edu.learnitive.com-cutover-redirect-http.conf`
- `deploy/apache/edu.learnitive.com-cutover-redirect-https.conf`
- `deploy/apache/www.learnitive.com-http.conf`
- `deploy/apache/www.learnitive.com-https.conf`
- `deploy/apache/learnitive.com-redirect-https.conf`
- `deploy/apache/preview.learnitive.com-https.conf`

The active edu vhost uses private Learnitive endpoints only:

- app/API/SSR: `127.0.0.1:4210`
- collaboration: `127.0.0.1:4211`
- console gateway WebSocket: `127.0.0.1:9887`

The edu cutover redirect templates are **manual post-acceptance templates** only. The Learnitive installer does not activate them.

## New/updated verification

- `scripts/learnitive-edu-staging-vhost.node.test.mjs` — static staging/profile/vhost/noindex/parity regression coverage.
- `scripts/verify-edu-local-vhost.sh` — host-local `curl --resolve` staging ingress acceptance.
- `scripts/verify-edu-staging.mjs` — public staging HTTP/SEO/security/health acceptance.
- `scripts/verify-www-local-vhost.sh` — production local ingress acceptance, including no staging noindex leakage.
- `scripts/verify-www-seo-cutover.mjs` — canonical www SEO/cutover verification and optional retired-staging redirect probe.
- package scripts: `release:staging-edu` and `release:staging-edu-local`.

## Verification performed in this audit

- Canonical dependency-independent release audit: **481/481 tests passed**.
- Release-quality orchestration: **PASS** (current audit, public assets, frozen-lock sync, workspace dependency versions, public barrels).
- Targeted edu staging/server tests: **79/79 passed** before inclusion in the canonical suite; subsequent canonical suite includes the staging gates.
- Shell syntax: **23 shell files passed `bash -n`**.
- Changed JS/MJS files passed `node --check`.
- `package.json` parses successfully.
- Apache source templates: **7 staging/production/cutover templates parsed successfully with Apache 2.4 `Syntax OK`** in a non-mutating fixture using temporary certificate/root substitutions and the required proxy/rewrite/header/SSL modules.

## Deployment model and remaining recommendations

### 1. Decide whether edu is temporary or permanent

Current code is correct for the requested flow: **edu staging → test → promote to www**. Staging and production deliberately reuse `/etc/learnitive`, the `learnitive-*` systemd unit names, private ports, Redis identity and normally the same relational database configuration during promotion.

If `edu.learnitive.com` must stay live as an ongoing staging environment after production launch, do **not** run it in parallel with this same stack. Give permanent staging its own:

- config root (for example `/etc/learnitive-staging`);
- systemd service names;
- host ports;
- Redis container/volume/queue namespace;
- Docker/network identities as needed;
- staging database and database user;
- preview origin if preview must also be environment-isolated.

### 2. Protect public staging access

`noindex` prevents search indexing but does not make staging private. For pre-release confidentiality, put `edu.learnitive.com` behind an independent access layer such as Cloudflare Access or another staging-only authentication boundary. Keep automated health probes able to authenticate.

### 3. Keep staging outbound integrations non-production

Fresh staging has SaaS disabled and passwordless email off. When enabling acceptance features, use staging-safe OAuth apps/callbacks, Turnstile host configuration, SMTP/test mail routing, analytics settings and any licence-provider credentials. Avoid using production secrets simply to make staging pass.

### 4. Be deliberate about staging data

If SaaS is enabled against `learnitive_os` during this one-time promotion model, staging accounts, licences, course edits and other relational data become production-bound unless cleaned explicitly. For destructive/realistic QA or long-lived staging, use an independent staging database.

### 5. Run dependency-backed/browser gates on the target candidate

This container does not currently provide the installed PNPM dependency tree required for the complete browser build/lint/Playwright certification. Before production cutover run on the staged release:

```bash
pnpm install --frozen-lockfile
pnpm lint:ci
pnpm test:ci
pnpm build
pnpm local-ci-check
```

Then run the relevant Playwright/mobile/auth/collaboration/terminal/manual smoke tests against edu.

## Recommended staging acceptance order

1. Publish the release at `/var/www/edu.learnitive.com/public_html`.
2. Configure staging-safe secrets only as required.
3. Run the staging installer with `LEARNITIVE_DEPLOYMENT_PROFILE=staging`.
4. Provision `edu.learnitive.com` DNS and TLS on the independent ingress.
5. Run the ingress configuration syntax test before activation.
6. Run `bash scripts/verify-edu-local-vhost.sh` on the target host.
7. Run `node scripts/verify-edu-staging.mjs` against public edu.
8. Run dependency-backed CI/build/E2E plus manual product acceptance.
9. Freeze the accepted candidate; do not make unrelated changes between staging acceptance and production promotion.
10. Install the same accepted release at `/var/www/learnitive.com/public_html` using the production profile/root launcher.
11. Activate independent www/apex ingress and DNS.
12. Run `bash scripts/verify-www-local-vhost.sh` and `node scripts/verify-www-seo-cutover.mjs`.
13. Only after www acceptance, optionally retire edu or enable the supplied one-hop edu → www redirect templates.

## Release decision

Source-level staging/cutover work is **ready for host staging acceptance**. Production cutover remains conditional on target-host DNS/TLS/ingress setup, dependency-backed build/browser tests, manual product acceptance, and a final read-only Interactive Shell non-interference verification.
