RedlineKnowledge base

Status

Update this table as the protocol describes. Keep it in task order.

TaskWaveTitleStatusCommitDateNotes
T0.11Mock GitLab serverdonesee Plan-Task: T0.12026-10-10Unforeseen: package.json test and test:coverage now run node --test 'test/*.test.js'; the default glob ran test/fixtures/**/*.js as test files and the server CLI hung the run (later test/e2e/*.spec.js would hit the same). Mock also serves the commit signature endpoint (c3 PGP verified, others 404); MR discussion notes are resolvable: true. data.js adds BRANCHES, REF_SHAS, SIGNATURES, token constants. startMockGitLab also returns mock.
T0.21Fixture Astro sitedonesee Plan-Task: T0.22026-10-10Astro 7.3.8. Unforeseen: fixture site builds with the working-tree package runs npm --prefix test/fixtures/site ci when the site has no node_modules, because the CI test job installs only the root package. test/fixtures/site/.astro/ is ignored (generated types; removed in a second T0.2 commit after being committed by mistake). Footer, track changes and comments all receive serviceUrl, siteId and documentPath.
T0.41Identity mode gitlab-oauthdonesee Plan-Task: T0.42026-10-10OAuth path taken, no Tailscale fallback. Unforeseen: production gitlab-oauth mode drops the whole REQUIRE_ACCESS=true and ALLOW_ANONYMOUS_READ=false rule, not only the Access half, because T4.1 expects production validation with ALLOW_ANONYMOUS_READ=true. SITES_JSON.<site>.gitlabPublicUrl accepts http: outside production, like GITLAB_PUBLIC_URL, since it defaults to it.
T0.51Health probe and gitlab-unreachabledonesee Plan-Task: T0.52026-10-10probeGitLab reports status: null when the fetch throws (no HTTP status exists). Upstream 502/503/504 are still retried before becoming gitlab-unreachable, so that response takes about 450 ms.
T0.61Verify rulesets: prettier, eslint, tscblocked2026-10-10Provided token has scope read_registry only; npm install of @fridai/quality-rulesets answers 403 insufficient_scope. Needs a read_package_registry token. See BLOCKERS.md.
T1.11Benchmark harnessdonesee Plan-Task: T1.12026-10-10Baseline --pages 50 --commits 100: 203 git calls cold, 153 with --warm. _resetGitState() flushes the cache before resetting so --warm keeps the first run’s writes. Commit 1 adds every page, so the repo has exactly C commits. --keep prints the repo path on stderr to keep stdout one JSON line.
T2.11Export the semantic moduledonesee Plan-Task: T2.12026-10-10Container built and /v1/health answered locally (Docker 29.6.2). Unforeseen: test/release.test.js export-surface list gains the three new root exports (deliberate contract change from step 5); .dockerignore whitelists semantic.js; test/mock-gitlab.test.js imports ../semantic.js. SemanticBlock, SemanticOperation, LayerConflict are aliases of the existing RedlineSemanticBlock, RedlineSemanticOperation, RedlineConflict, which already matched the contract.
T0.32Playwright harness and CI jobdonesee Plan-Task: T0.32026-10-10@playwright/test 1.64.0, image mcr.microsoft.com/playwright:v1.64.0-noble (Node v24.21.0). Unforeseen: fixture:preview gains --ignore-lock, because Astro 7.3 auto-backgrounds astro preview when it detects an AI agent (AI_AGENT, CLAUDECODE), which made the webServer exit early; the flag forces a foreground one-off server. run-service.js marks the two fixed secrets gitleaks:allow.
T1.22Build-time history indexdonesee Plan-Task: T1.22026-10-10Bench: 6 git calls at 300 pages × 1200 commits and at 3 × 1200 (was 203 at 50 × 100); index equals the walk on all 120 pages of a 1000-commit bench repo. Unforeseen, so existing tests that change the repository mid-process keep passing: the index stores a filesystem-only signature (.git/index, HEAD, branch ref, packed-refs mtimes, read after git status because status refreshes .git/index) and rebuilds when it changes; a path whose file changed after the snapshot, or a path the index does not know, uses the per-path walk; absolute candidates are also tried by real path (macOS /var → /private/var). git status uses -z --untracked-files=all, ls-files uses -z; a gitRaw helper returns untrimmed output with a 512 MB buffer. Untracked paths in the status set are answered from the index (the task only named tracked and rows).
T2.22Remark plugin for block idsdonesee Plan-Task: T2.22026-10-10Unforeseen: Astro 7.3 renders Markdown with the Sätteri processor by default, which ignores markdown.remarkPlugins. With blocks: true the integration appends the plugin to a unified() processor’s options.remarkPlugins, uses markdown.remarkPlugins only when no processor exists (Astro 6 before 6.4), and otherwise logs a warning. The fixture site uses markdown: { processor: unified() } and depends on @astrojs/markdown-remark 7.3.2. YYZ uses its own processor (yyz-markdown-provider), so T5.2 must apply the plugin inside YYZ’s engine. semantic.js exports fingerprintBlock. test/release.test.js export map gains ./remark-redline-blocks.js (deliberate, step 3).
T2.32Block history in the servicedonesee Plan-Task: T2.32026-10-10Each mapped commit also carries parentIds. File fetch errors other than 404 (empty document) and document-too-large (blockChanges: null) propagate, so an outage still answers gitlab-unreachable. A commit whose parent is oversized also gets null.
T2.42Failure classificationdonesee Plan-Task: T2.42026-10-10Acceptance grep prints 8, not 7: the pre-existing test classifies verified, unsigned, unverified, and unavailable signatures (test/hardening.test.js) also matches ^✔ classifies; all seven named tests pass and neither name was changed. The gate should grep the seven names exactly. A rejected fetch keeps its own error (a TypeError in browsers) with kind added, rather than being wrapped. REDLINE_MESSAGES.static is ''. test/release.test.js root export list gains the four names (deliberate, step 3).
T2.62Word-level diffdonesee Plan-Task: T2.62026-10-10Unforeseen: the plain token LCS pairs "a b c" → "a x y c" as insert "x", equal " ", insert "y ", so diffWords adds a cleanup pass: a whitespace-only equal run between two changes joins them, and whitespace both sides of a change share returns to the neighbouring equal text. Segments always rebuild both texts (tested). lcsMatches takes a key function (default fingerprint); block diffs are unchanged. test/release.test.js root export list gains diffWords (deliberate, step 2).
T1.33Warm index cachedonesee Plan-Task: T1.32026-10-10Bench 300 × 1200: cold 7 git calls (walk), warm 6 (cache). The summary line prints only for a walk. The HEAD check also runs when a mid-process repository change rebuilds the index.
T1.43GitLab enrichment from the indexdonesee Plan-Task: T1.42026-10-10Unforeseen: local commits replace the per-page repository/commits request only when the GitLab ref is the local history, decided without a request (ref equals HEAD sha, or CI_COMMIT_SHA is HEAD and the ref is the CI job’s ref). Otherwise seven existing tests failed: their fake GitLab history differs from the temp repo, and two count every request. git.js _getGitContext exposes head from the index. Signature 404s are cached too (30 days), so unsigned commits are also looked up once. complete is localCommits.length <= maxCommits rather than always true, so a truncated list keeps local counts.
T2.53Contract testsdonesee Plan-Task: T2.52026-10-10Contract 1.1.0. Unforeseen: the old health schema said service: redline-service but the service has always answered fio-redline; Health uses the real value. Error moved from an inline response schema to components.schemas.Error (the response references it). Commit also documents parentIds (added in T2.3). Health.gitlab.status is [integer, null]. Added ajv 8.20.0, ajv-formats 3.0.1, yaml 2.9.1 (exact).
T3.03Shared client storedonesee Plan-Task: T3.02026-10-10The store reuses createRedlineClient for the request (only Accept and credentials: include). load() never rejects; it resolves to the snapshot. A now option drives the five-second throttle in tests. message() for error uses the error code, else the HTTP status, else unknown. Re-indented requestUrl lines that T2.4 left misaligned. test/release.test.js root export list gains createRedlineStore and getRedlineStore (deliberate).
T3.23Remove browser dialogsdonesee Plan-Task: T3.22026-10-10The inline reply form and two-step delete are not yet exercised in a browser; T3.4’s Playwright journey covers them. The reply form sits after the action buttons and is styled so the component’s .agc form padding and border do not apply to it.
T4.13Experiment deployment configdonesee Plan-Task: T4.12026-10-10The compose acceptance commands fail as written: FIO_REDLINE_IMAGE is required (:?) and env_file: ./service.env must exist. With FIO_REDLINE_IMAGE set and service.env copied from the example (in a scratch copy, not the repo), they print service, and service + cloudflared with --profile tunnel. The operator repeats them in T4.3 with real values. Correction (2026-10-10, T6.3): ops/.env.example already existed (a plain ls ops hid the dotfile); it was not changed in T4.1.
T3.14Reachability states in componentsdonesee Plan-Task: T3.12026-10-10Unforeseen fixes: (1) service error responses had no CORS headers, so the browser saw every 502/401 as a network failure; index.js now adds them for an allowed origin (unit test error responses carry cors headers for an allowed origin). (2) The footer never put its id prop on the root, so #history matched nothing; the root now carries id. (3) history.js renders build-time commit rows from localCommits when GitLab enrichment is absent, which the static-fallback spec needs. (4) The e2e helpers post the GitLab webhook after every mock change so the service response cache cannot mask it. recovers to live when gitlab comes back asserts live or connect-required: the anonymous harness reader is in connect-required, the design’s live substate. Components use store.message() rather than importing REDLINE_MESSAGES; comments writes go through createRedlineClient; the comments status shows the store message instead of the thread count.
T4.24Smoke scriptdonesee Plan-Task: T4.22026-10-10Against the healthy harness the smoke state is connect-required (anonymous reader, comments enabled), the design’s live substate; it exits 0 as specified. A failed state request prints the JSON line with the classified state and zero counts and exits 4. Requests time out after 15 s. Missing arguments exit 64 with usage.
T4.54Experiment reportdonesee Plan-Task: T4.52026-10-10--since is inclusive and --until exclusive; since/until print null when not given. documents counts the audit path field, which comment mutations carry. An actor without gitlabUsername counts by actor. Invalid dates exit 64.
T3.35OAuth connect journeydonesee Plan-Task: T3.32026-10-10Unforeseen fixes: (1) the service sends anonymous state with Cache-Control: public, max-age=15, so after connecting the browser reused the pre-login payload; the store now requests with cache: 'no-store' (client.js passes init.cache through; store unit test asserts it). (2) connectGitLab waited for the site URL the page was already on; it now waits for the service callback 302, then the return to the site. (3) playwright.config.js sets workers: 1: specs share one mock and one service, and a spec that switched GitLab down broke another running in parallel.
T3.55Inline redlinedonesee Plan-Task: T3.52026-10-10applyInlineRedline defaults diffWords to the package’s own (client.js imports semantic.js) and clears any previous layer first; an insert whose afterBlockId is not on the page is skipped. scripts/check-components.js also compiles GitRedline.astro. test/release.test.js gains ./GitRedline.astro in the exports map and applyInlineRedline, clearInlineRedline in the root exports (deliberate, the task adds them).
T3.65Changed sincedonesee Plan-Task: T3.62026-10-10The status strings live in REDLINE_MESSAGES['changed-since'](count, shortSha) (a non-state entry) so tests import them; wording is exactly the plan’s, including “1 blocks”. The select starts with a “Choose a revision” option and stays disabled until history arrives. scripts/check-components.js also compiles GitChangedSince.astro. test/release.test.js gains ./GitChangedSince.astro, changedBlocksSince and markChangedSince (deliberate, the task adds them).
T4.35Operator setup (human)human2026-10-10Instructions: kb/setup/experiment-operator-setup.md (sections 1 to 9). Operator to provide: (1) DNS yqa.fio.sh A record → GitLab host Tailscale IP, same zone and method as glab.fio.sh; (2) host nginx TLS vhost yqa.fio.sh → fio-redline:8787 (or a loopback port), same certificate method as glab.fio.sh; (3) GitLab OAuth app, confidential, scope api, redirect exactly https://yqa.fio.sh/v1/auth/gitlab/callback; (4) GitLab read token with read_api; (5) ops/service.env from ops/service.env.example with the experiment values (IDENTITY_MODE=gitlab-oauth, REQUIRE_ACCESS=false, ALLOW_ANONYMOUS_READ=true, WRITE_ENABLED=true, features.editing=false), ops/secrets/* per the compose file except the tunnel token, and ops/.env from ops/.env.example (FIO_REDLINE_IMAGE; with QA also FIO_REDLINE_QA_IMAGE); (6) run the two docker compose config checks from T4.1. Evidence to paste: dig +short yqa.fio.sh from a tailnet device (a 100. address), nginx -t, the OAuth redirect URI. No secrets.
T3.46Anchored comments journeydonesee Plan-Task: T3.42026-10-10GitLab discussions held anchors reliably, so no interim store and no T3.7. The comments spec also exercises T3.2’s inline reply and two-step delete in a browser and fails on any dialog.
T4.46Deploy and evidence (human)deferred2026-10-10Amendment 2026-10-10: replaced by T6.7 (QA and production).
T5.16YYZ history ondeferred2026-10-10Amendment 2026-10-10: Stage 2. YYZ content now comes from kb-packages without build-time history or block ids; re-plan after TD-002 to TD-004.
T5.27YYZ review componentsdeferred2026-10-10Amendment 2026-10-10: Stage 2, as T5.1.
T6.17Dashboard sitedonesee Plan-Task: T6.12026-10-1025 kb pages + overview; build log [fio-redline] indexed 26 paths in 377 ms (7 git calls). Pages come from an Astro content collection (glob loader on REDLINE_DOCS_DIR; ids keep the file path and case) because import.meta.glob cannot take a path from the environment; collection render runs the same unified() processor, so block ids are present. The overview imports ../../../client.js (inside this repo) because the package exports no client subpath; stores are shared through globalThis. .dockerignore re-includes only site/dist. test/release.test.js root exports gain resolveServiceUrl (deliberate, step 1).
T6.37QA environment configurationdonesee Plan-Task: T6.32026-10-10With ops/.env and service*.env copied from the examples (scratch copy): default profile prints service; --profile qa prints service, service-qa; --profile tunnel still prints service, cloudflared. fio-redline is GitLab project 16. The production example keeps the yyz site and adds fio-redline. ops/.env.example already existed: its first T6.3 commit overwrote the file’s comments by mistake; a second T6.3 commit restores them and adds FIO_REDLINE_QA_IMAGE. That commit also changes the QA image to an empty default (${FIO_REDLINE_QA_IMAGE:-}): compose interpolates every service whatever the profile, so :? broke production-only hosts; verified that a production-only .env still lists service and that --profile qa without the variable refuses to start.
T6.67Lifecycle recordsdonesee Plan-Task: T6.62026-10-108 ADRs, 7 TD entries, roadmap and index; the harvester (local fio-kb-harvester) renders 37 fragments with document governance OK (output written to the scratchpad instead of /tmp). ADR-0008 states that building the dashboard into the image is T6.2 and not yet implemented.
T6.28Service serves the dashboard on its own origindonesee Plan-Task: T6.22026-10-10Local container acceptance passed (/ contains data-dashboard-status, /v1/health JSON, /kb/ and a hashed _astro asset 200, /app/site holds the build). Unforeseen: the site job runs in stage test, not build with a needs on the container job: container-build-push declares no needs, so it already receives every earlier-stage artifact (including CALVER_VERSION), and a job-level needs would have had to re-list version-calver. The container job runs only on main, so the in-pipeline image check (/app/site/index.html) happens on the first main pipeline after merge (gate G5). Added site/src/pages/404.astro (served with status 404). Paths with .. (plain or encoded) or NUL answer 404; a directory without its slash redirects 308.
T6.49Dashboard end to enddonesee Plan-Task: T6.42026-10-1019 browser specs pass (twice). Unforeseen: site/astro.config.mjs reads REDLINE_OUT_DIR so the fixture build goes to site/dist-e2e and never replaces the real site/dist; the e2e helpers invalidate both services’ caches and connectGitLab takes the service and site origins. The one-origin spec asserts every browser request went to localhost:8788.
T6.59Live smoke specdonesee Plan-Task: T6.52026-10-10Without REDLINE_LIVE_URL the config throws REDLINE_LIVE_URL is required: … (exit 1); against a hand-started mock and dashboard service on :8788 it prints 4 passed. The spec reaches a kb page through the overview’s first navigation link rather than assuming /kb/ exists (the fixture build has only /kb/doc/).
T6.710QA and production deployment and evidence (human)human2026-10-10Instructions: kb/setup/experiment-operator-setup.md (sections 10 to 14); host nginx reaches the containers through ops/docker-compose.tailscale.yml (loopback ports 18787 and 18788). Operator, beyond T4.3: (1) DNS and a host nginx TLS vhost for qa-yqa.fio.sh → fio-redline-qa:8787; production yqa.fio.sh now proxies the whole origin (dashboard and /v1) to fio-redline:8787; (2) second OAuth redirect URI https://qa-yqa.fio.sh/v1/auth/gitlab/callback; (3) merge experiment/redline (or its successor) to main so a :verified image containing the dashboard exists, and confirm the container job log shows /app/site/index.html; (4) ops/.env, ops/service-qa.env, ops/secrets/session-secret-qa from the examples, then cd ops && docker compose --profile qa pull && docker compose --profile qa up -d; (5) QA evidence from a tailnet device: npm run smoke -- https://qa-yqa.fio.sh fio-redline kb/index.md (exit 0), the same off the tailnet (exit 2, offline-network), REDLINE_LIVE_URL=https://qa-yqa.fio.sh npm run test:live (4 passed); (6) Stage 1 demonstration on QA: a merge request on fio-redline changing a kb/ paragraph shown inline, and an anchored comment visible in the [Document Review] kb/… issue under your own account (URLs and a screenshot path); (7) promote to released, then the same smoke and live checks against https://yqa.fio.sh. Record outputs, image tags, URLs and dates. No secrets.
T6.810Timeline scrubberdonesee Plan-Task: T6.82026-10-11141 unit tests, 24 browser specs. Unforeseen: (1) Astro scopes component CSS to template elements only, so the script-created markers were unstyled default buttons under the rail and clicks never reached them; GitTimeline’s styles are global, every selector carrying its agtl__ prefix (TD-008 records the same pattern in older components). (2) The local QA kit’s loopback port moved from 8787 to 18787: the e2e harness reused the running QA container on 8787 and its webhook rejected the test secret. Contract 1.2.0 (DocumentVersion, anchorBlockId); the gate’s G1 contract count is now 5 and G4 is 24 specs in 9 files. test/release.test.js gains ./GitTimeline.astro, renderVersion, restoreVersion; test/dashboard.test.js and test/e2e/dashboard.spec.js now name the timeline instead of the removed components (deliberate, step 6).
T6.910Comments in the text, Word-styledonesee Plan-Task: T6.92026-10-116 inline-comment specs; 30 browser specs and 141 unit tests pass. Marks wrap the commented text in spans across inline elements; a mark whose offsets no longer fit falls back to the quoted text, else the comment is listed as a page comment. Styles are global with the agic__ prefix (TD-008). Also fixed in client.js: a deleted non-list block anchored to a list item now goes after the whole list (found on the real proposal !16). The dashboard spec now connects from the selection menu (deliberate, step 5). Gate G4 is now 30 specs in 10 files.
G11Destination gatetodo

Merge to main

  • 2026-10-11: experiment/redline merged to main as !13 (b7b9933), after !12 (harvester bump). Pipeline #1454 on main passed: container-build-push built glab.fio.sh:5050/fridai/fio-registry/fio-redline:26.10.11-145.b7b99332 (sha256:e59297fa…) with the step COPY --chown=node:node site/dist ./site after downloading the site artifact; promote-container-verified moved it to verified; kb-publish republished [email protected] with 38 fragments. promote-container-released waits (manual). GitLab deleted the source branch on merge; it was pushed again at a7d3134 because the gate runs from it. Verified by pulling fio-redline:verified on 2026-10-11: /app/site holds index.html, 404.html, _astro/, kb/ and the image sets SITE_DIR=/app/site (gate G5).

Operator decisions

  • 2026-10-11: QA runs on any tailnet computer through a Cloudflare Tunnel (ops/local-qa/, kb/setup/local-qa.md), behind Cloudflare Access, as well as or instead of the GitLab-host QA. No database is involved.
  • 2026-10-11: the dashboard shows each capability as an empty box waiting for a click. Added T6.8, a timeline scrubber: the playhead shows the page as it was at a commit (or as a merge request would make it) with that point’s change redlined.
  • 2026-10-11: comments work as in a word processor (T6.9): select text, comment from a menu beside it, unresolved comments underlined and shown on hover; no comment box below the article. Two draft merge requests (!15, !16) on fio-redline change kb/index.md as forward-looking examples for the timeline; they must not be merged.
  • 2026-10-11: the QA hostname is qa-yqa.fio.sh, not qa.yqa.fio.sh: on the Cloudflare free plan Universal SSL covers only first-level names (*.fio.sh). Renamed in the plan, guides, env examples, compose comments and tests.

Fixes outside any task

  • 2026-10-10: .gitleaksignore allowlists the two fixed e2e secrets in batches/B0-foundations.md (commit 47c037f); the gitleaks job failed every pipeline, including main, on them. T0.4’s test/identity-mode.test.js (commit 95e32bc) uses the same values; its two fingerprints are allowlisted and the lines carry gitleaks:allow. T0.3 must mark the same values in test/e2e/run-service.js with gitleaks:allow.
Git history

Loading the page's history…