Status
Update this table as the protocol describes. Keep it in task order.
| Task | Wave | Title | Status | Commit | Date | Notes |
|---|---|---|---|---|---|---|
| T0.1 | 1 | Mock GitLab server | done | see Plan-Task: T0.1 | 2026-10-10 | Unforeseen: 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.2 | 1 | Fixture Astro site | done | see Plan-Task: T0.2 | 2026-10-10 | Astro 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.4 | 1 | Identity mode gitlab-oauth | done | see Plan-Task: T0.4 | 2026-10-10 | OAuth 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.5 | 1 | Health probe and gitlab-unreachable | done | see Plan-Task: T0.5 | 2026-10-10 | probeGitLab 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.6 | 1 | Verify rulesets: prettier, eslint, tsc | blocked | 2026-10-10 | Provided 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.1 | 1 | Benchmark harness | done | see Plan-Task: T1.1 | 2026-10-10 | Baseline --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.1 | 1 | Export the semantic module | done | see Plan-Task: T2.1 | 2026-10-10 | Container 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.3 | 2 | Playwright harness and CI job | done | see Plan-Task: T0.3 | 2026-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.2 | 2 | Build-time history index | done | see Plan-Task: T1.2 | 2026-10-10 | Bench: 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.2 | 2 | Remark plugin for block ids | done | see Plan-Task: T2.2 | 2026-10-10 | Unforeseen: 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.3 | 2 | Block history in the service | done | see Plan-Task: T2.3 | 2026-10-10 | Each 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.4 | 2 | Failure classification | done | see Plan-Task: T2.4 | 2026-10-10 | Acceptance 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.6 | 2 | Word-level diff | done | see Plan-Task: T2.6 | 2026-10-10 | Unforeseen: 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.3 | 3 | Warm index cache | done | see Plan-Task: T1.3 | 2026-10-10 | Bench 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.4 | 3 | GitLab enrichment from the index | done | see Plan-Task: T1.4 | 2026-10-10 | Unforeseen: 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.5 | 3 | Contract tests | done | see Plan-Task: T2.5 | 2026-10-10 | Contract 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.0 | 3 | Shared client store | done | see Plan-Task: T3.0 | 2026-10-10 | The 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.2 | 3 | Remove browser dialogs | done | see Plan-Task: T3.2 | 2026-10-10 | The 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.1 | 3 | Experiment deployment config | done | see Plan-Task: T4.1 | 2026-10-10 | The 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.1 | 4 | Reachability states in components | done | see Plan-Task: T3.1 | 2026-10-10 | Unforeseen 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.2 | 4 | Smoke script | done | see Plan-Task: T4.2 | 2026-10-10 | Against 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.5 | 4 | Experiment report | done | see Plan-Task: T4.5 | 2026-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.3 | 5 | OAuth connect journey | done | see Plan-Task: T3.3 | 2026-10-10 | Unforeseen 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.5 | 5 | Inline redline | done | see Plan-Task: T3.5 | 2026-10-10 | applyInlineRedline 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.6 | 5 | Changed since | done | see Plan-Task: T3.6 | 2026-10-10 | The 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.3 | 5 | Operator setup (human) | human | 2026-10-10 | Instructions: 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.4 | 6 | Anchored comments journey | done | see Plan-Task: T3.4 | 2026-10-10 | GitLab 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.4 | 6 | Deploy and evidence (human) | deferred | 2026-10-10 | Amendment 2026-10-10: replaced by T6.7 (QA and production). | |
| T5.1 | 6 | YYZ history on | deferred | 2026-10-10 | Amendment 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.2 | 7 | YYZ review components | deferred | 2026-10-10 | Amendment 2026-10-10: Stage 2, as T5.1. | |
| T6.1 | 7 | Dashboard site | done | see Plan-Task: T6.1 | 2026-10-10 | 25 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.3 | 7 | QA environment configuration | done | see Plan-Task: T6.3 | 2026-10-10 | With 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.6 | 7 | Lifecycle records | done | see Plan-Task: T6.6 | 2026-10-10 | 8 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.2 | 8 | Service serves the dashboard on its own origin | done | see Plan-Task: T6.2 | 2026-10-10 | Local 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.4 | 9 | Dashboard end to end | done | see Plan-Task: T6.4 | 2026-10-10 | 19 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.5 | 9 | Live smoke spec | done | see Plan-Task: T6.5 | 2026-10-10 | Without 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.7 | 10 | QA and production deployment and evidence (human) | human | 2026-10-10 | Instructions: 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.8 | 10 | Timeline scrubber | done | see Plan-Task: T6.8 | 2026-10-11 | 141 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.9 | 10 | Comments in the text, Word-style | done | see Plan-Task: T6.9 | 2026-10-11 | 6 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. |
| G | 11 | Destination gate | todo |
Merge to main
- 2026-10-11:
experiment/redlinemerged tomainas !13 (b7b9933), after !12 (harvester bump). Pipeline #1454 onmainpassed:container-build-pushbuiltglab.fio.sh:5050/fridai/fio-registry/fio-redline:26.10.11-145.b7b99332(sha256:e59297fa…) with the stepCOPY --chown=node:node site/dist ./siteafter downloading thesiteartifact;promote-container-verifiedmoved it toverified;kb-publishrepublished[email protected]with 38 fragments.promote-container-releasedwaits (manual). GitLab deleted the source branch on merge; it was pushed again ata7d3134because the gate runs from it. Verified by pullingfio-redline:verifiedon 2026-10-11:/app/siteholdsindex.html,404.html,_astro/,kb/and the image setsSITE_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-redlinechangekb/index.mdas forward-looking examples for the timeline; they must not be merged. - 2026-10-11: the QA hostname is
qa-yqa.fio.sh, notqa.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:
.gitleaksignoreallowlists the two fixed e2e secrets inbatches/B0-foundations.md(commit 47c037f); thegitleaksjob failed every pipeline, includingmain, on them. T0.4’stest/identity-mode.test.js(commit 95e32bc) uses the same values; its two fingerprints are allowlisted and the lines carrygitleaks:allow. T0.3 must mark the same values intest/e2e/run-service.jswithgitleaks:allow.