RedlineKnowledge base

B4 Network

Deploy the service on the private network and measure the experiment. Tasks marked (human) are performed by the operator; the executor prepares files and records evidence the operator reports.


T4.1 Experiment deployment config

  • Wave: 3
  • Depends on: T0.4
  • Size: S
  • Design: network.md
  • Touches: ops/docker-compose.yml, ops/service.env.example, ops/.env.example, kb/setup/index.md (one new section), test/hardening.test.js
  • Why: the same compose file must run with or without the tunnel sidecar, and the service must validate an experiment environment.

Steps

  1. ops/docker-compose.yml: add profiles: ["tunnel"] to the cloudflared service only. Nothing else changes. Add a comment: without --profile tunnel the stack is the Tailscale-only experiment and ingress is host nginx.
  2. ops/service.env.example: add IDENTITY_MODE=cloudflare-access with a comment describing gitlab-oauth, and a commented experiment block:
    # Experiment (Tailscale-only): IDENTITY_MODE=gitlab-oauth, REQUIRE_ACCESS=false,
    # ALLOW_ANONYMOUS_READ=true, WRITE_ENABLED=true, features.editing=false.
  3. kb/setup/index.md: add a section “Experiment deployment (Tailscale-only)” that links to kb/plans/redline-experiment/design/network.md and lists the operator inputs table. Do not edit the runbook.
  4. test/hardening.test.js: add
    • production experiment environment validates in gitlab-oauth mode (NODE_ENV=production, IDENTITY_MODE=gitlab-oauth, REQUIRE_ACCESS=false, ALLOW_ANONYMOUS_READ=true, no ACCESS_*, PUBLIC_SERVICE_URL=https://yqa.fio.sh → createNodeServer succeeds)
    • production experiment environment refuses http service url (same with http:// → throws)

Acceptance

npm test 2>&1 | grep -c "^✔ production experiment environment"   # prints 2
docker compose -f ops/docker-compose.yml config --services              # prints only: service
docker compose -f ops/docker-compose.yml --profile tunnel config --services   # prints: service, cloudflared

(If Docker is unavailable locally, record that and rely on the two unit tests; the operator runs the compose checks in T4.3.)


T4.2 Smoke script

  • Wave: 4
  • Depends on: T0.5, T2.4, T0.1
  • Size: S
  • Touches: scripts/smoke.js (new), test/smoke.test.js (new), package.json (script smoke)
  • Why: the operator and the gate need one command that says which edge is broken.

Steps

  1. node scripts/smoke.js <serviceUrl> <siteId> <documentPath>:
    • GET <serviceUrl>/v1/health; on a thrown fetch error print offline-network and exit 2.
    • If gitlab.reachable === false print offline-gitlab and exit 3.
    • GET <serviceUrl>/v1/sites/<siteId>/documents/state?path=<documentPath>; print one JSON line { "state": "live" | "connect-required" | …, "commits": n, "layers": n, "comments": n } using classifyRedlineFailure and deriveRedlineState from client.js; exit 0 for live/connect-required, 4 otherwise.
  2. package.json script "smoke": "node scripts/smoke.js".

Acceptance

test/smoke.test.js (starts the mock and the service in-process on ephemeral ports via createNodeServer):

  • smoke exits 0 and prints live state against a healthy service
  • smoke exits 3 when gitlab is down
  • smoke exits 2 when the service is unreachable (point at a closed port)
npm test 2>&1 | grep -c "^✔ smoke"   # prints 3

T4.5 Experiment report

  • Wave: 4
  • Depends on: none
  • Size: S
  • Touches: scripts/experiment-report.js (new), test/fixtures/audit-sample.jsonl (new), test/experiment-report.test.js (new), package.json (script report:experiment)
  • Why: the kill criterion needs numbers from the audit log, not impressions.

Steps

  1. The service already prints one JSON line per mutation with type: "redline-audit", action, actor, gitlabUsername, at, siteId. The script reads JSON lines from stdin (one per line; ignore lines that are not valid JSON or not redline-audit), optional flags --since <ISO> and --until <ISO>, and prints one JSON object: { "since", "until", "actors": <distinct gitlabUsername or actor>, "writes": <count>, "actions": { "<action>": n }, "documents": <distinct path> }.
  2. test/fixtures/audit-sample.jsonl: 12 lines mixing three actors, five actions, two documents, two non-audit lines, one malformed line.

Acceptance

test/experiment-report.test.js:

  • experiment report counts actors writes actions and documents (exact numbers from the sample)
  • experiment report honours since and until
docker logs fio-redline 2>&1 | node scripts/experiment-report.js   # on the host, later

T4.3 Operator setup (human)

  • Wave: 5
  • Depends on: T4.1
  • Size: human
  • Design: network.md (operator inputs)
  • Touches: nothing in the repository; STATUS.md notes only

The executor sets this task to human and lists in STATUS.md what the operator must provide. The operator performs:

  1. DNS: yqa.fio.sh A record to the GitLab host’s Tailscale IP, in the same zone and by the same method as glab.fio.sh.
  2. Host nginx vhost for yqa.fio.sh with TLS, proxying to fio-redline:8787 on the Docker network (or a published loopback port). Same certificate method as glab.fio.sh.
  3. GitLab OAuth application: confidential, scope api, redirect URI exactly https://yqa.fio.sh/v1/auth/gitlab/callback.
  4. GitLab read token with read_api.
  5. ops/.env, ops/service.env and ops/secrets/* per ops/*.example with the experiment values; no tunnel token.
  6. Run the two docker compose config checks from T4.1.

Evidence to paste into STATUS.md notes: the DNS answer from a tailnet device (dig +short yqa.fio.sh shows a 100. address), the nginx -t result, and the OAuth application’s redirect URI. No secrets.


T4.4 Deploy and evidence (human)

Deferred (amendment 2026-10-10): replaced by T6.7 in B6, which deploys QA and production and records the same evidence for both.

  • Wave: 6
  • Depends on: T4.3, and a :released image that contains every task up to wave 5 (the operator promotes it)
  • Size: human

The operator runs on the host:

cd ops && docker compose pull && docker compose up -d
docker compose ps                                  # service healthy

Then, from a device on the tailnet, in this repository:

npm run smoke -- https://yqa.fio.sh yyz docs/<an existing document>.md   # prints a JSON line with "state":"connect-required" or "live"; exit 0

And from a device off the tailnet (or with Tailscale disconnected):

npm run smoke -- https://yqa.fio.sh yyz docs/<the same document>.md       # prints offline-network; exit 2

Evidence for STATUS.md: the two smoke outputs, the image tag from docker compose ps, and the date. No secrets, no cookies.

Git history

Loading the page's history…