For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
1Context
The alpha release is live at https://expertadoe.app (main 96c8d9f, 2026-10-19). Its execution ledger (2026-10-17-alpha-execution-ledger.md) parked 51 ledger lines of minor review findings. On 2026-10-19 Fadel M. Megahed asked that all of them be done. A read-only audit of main (33fa98e) found 63 findings in those lines and in the later sections: 47 are open, 11 are already fixed and 5 cannot be acted on. Two open findings share one cause, so there are 46 fixes. None is large. This plan does all 46, grouped by area into eight tasks.
The review of Alan R. Vazquez runs while this plan runs. No task changes a text that the review asks about (the guarantee table and its statements, the glossary, R23 and the references).
Goal: Every open deferred minor of the alpha ledger is fixed, with a test where the finding is about behaviour, and the ledger records each finding id with its commit or its reason.
Architecture: No new module except one shared helper for the store choice (lib/ops/stores.ts). The fixes stay in the files that the findings name. Production behaviour changes in four places only, each with a test: the Redis keys get their time-to-live once (not on each request), the in-memory spend tracker keeps one total per UTC date, the translate handler computes the reserve amounts before its store try, and the review route keeps a fetched issue list only if no answer was filed meanwhile.
Tech Stack: Next.js 16.3.8, React 19.3.0, TypeScript 5.9.3, zod 4.6.5, vitest 4.1.11, Playwright 1.63.0 (Chromium), Lua scripts on Upstash Redis (tests on ioredis-mock 8.13.1). No new package.
Spec: replication_materials/plans/2026-10-14-ff-design-translator-design.md (version 6), Sections 13.1, 13.2, 14.4 and 17.1. The finding ids (T1-a to T9-f, FR-1, B2-a, B2-b) are those of the audit table replication_materials/plans/2026-10-19-alpha-minors-audit.md (read-only audit of main 33fa98e), which Task 8 copies into the ledger.
2Global Constraints
- No package changes. Implementers do not run
npm install. The repository is in Dropbox; only the controller installs packages, after the user pauses Dropbox. - Secrets. Never read
.env. Tests use made-up values. Never print a secret. - Outside contact. Subagents make no network call to Vercel, Upstash, GitHub or an LLM provider. Only the controller pushes, deploys or runs the deployed checks (Task 3), after the author's approval.
- Texts under review stay. Do not change
content/glossary.json,data/guarantee-table.json, the R23 text or the reference list. - Fixed texts. Every new user-visible text is a fixed string in one module with a unit test of its exact text. No advice words (
ADVICE_WORDSinlib/translate/messages.ts). - Wording. Comments and documents about 80% ASD-STE100, plain academic tone; at most 25 words in a sentence of a spec cell.
- Tests. All tests in
replication_materials/tests/. Test first: each behaviour fix starts with a failing test, and the report names the failure.npm run check -- --fastat the end of each task;npm run check(with Chromium) at the end of Tasks 5, 7 and 8. - Commits. One commit per task. Each message ends with the attribution lines of the session (
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>and theClaude-Session:line).
3Review Focus
- A Redis key that already has no time-to-live (written by the old script, or by a manual edit): the new scripts must give it one, or it lives for ever. Test in Task 4.
- The in-memory tracker sees an older time after a newer one (two requests near UTC midnight): the reserve of one day must not erase the total of the other. Test in Task 4.
- A review answer filed while a list request is in flight: the next read must show the new answer, not the list from before. Test in Task 6.
- A review comment of 4,000 characters that JSON escapes to 6 bytes each: a valid answer must never get "The answer does not have the expected form." Test in Task 6.
- The review key is rejected while the reviewer files an answer: the page must go to the state without a key, as it does on load. Test in Task 7.
4Task 1: Secret scan (T1-a to T1-g)
Files:
- Modify:
scripts/lib/secret-scan.mjs,scripts/secret-scan.mjs - Test:
replication_materials/tests/unit/secret-scan.test.ts
Interfaces:
- Produces:
scanLog(log, values)andsecretValues(text)keep their signatures.failureLine(error)keeps its output formThe secret scan failed (<code>).
-
"a deleted file does not take the next path"(T1-a): a log with+++ /dev/nullafter a file header, then an added secret line in the next file; the finding has the next file's path."the path resets at each commit"(T1-a): two commits, the second without a+++line before its first added line; the finding of the second commit has no path from the first commit (path"(unknown)", the value the code uses today for no path, or the code's existing equivalent)."an added line that starts with ++ is content"(T1-a, T1-b): an added line+++ token=<value>inside a hunk is scanned, not read as a header."quoted and tab-suffixed paths"(T1-b):+++ "b/dir/a b.txt"givesdir/a b.txt;+++ b/c.txt\tgivesc.txt."CRLF in the env file"(T1-a):secretValues("A_KEY=abcdefghijklmnop\r\n")gives the value without\r."export prefix and inline comment"(T1-c):export A_KEY=abcdefghijklmnop # notegivesabcdefghijklmnop; a quoted value"abc#def123456789"keeps the#; an unquoted value keeps a#that has no white space before it."failureLine prints only a safe code"(T1-f): code"ENOENT"prints it; code"bad code; rm -rf"and a 40-character code printerror; numeric status2prints2."exit status 2 with the fixed line"(T1-g): spawnnode scripts/secret-scan.mjs --env <tmp>/missing.envwithcwdin a new empty temp directory (not a git repository); expect status 2 and stdoutThe secret scan failed (ENV_NOT_FOUND).
-
- Header state (T1-b): a file header starts at a
diff --gitline and ends at the first@@line. Only inside a header are---and+++lines headers.+++ /dev/nullsets the path to none. Acommit:line resets the path. Unquote a C-quoted path ("b/...", with\",\\and\tescapes) and drop a trailing tab and what follows it. secretValues(T1-a, T1-c): strip\r; strip a leadingexport; for an unquoted value, drop#(white space then#) and the rest.- Entry point (T1-d, T1-e): when
--envis given without an argument, throw an error with codeENV_ARGUMENT; when the given file does not exist, codeENV_NOT_FOUND. Without--env, a missing.envstays allowed. The log command becomesgit -c core.quotepath=off log -p -m --no-ext-diff --no-textconv --all --format=commit:%H(-mshows the diff of a merge against each parent; findings are already unique by key). failureLine(T1-f): a string code must match/^[A-Z0-9_]{1,32}$/; a number must be an integer from 0 to 255; elseerror.
- Header state (T1-b): a file header starts at a
5Task 2: Measurement helpers and budget scripts (T2-a, T2-b, T2-c, B2-a, B2-b, T9-b)
Files:
- Modify:
replication_materials/tests/deployed/helpers.ts,replication_materials/tests/deployed/site.spec.ts,replication_materials/tests/deployed/slow-start.spec.ts,scripts/derive-budgets.mjs,README.md - Test:
replication_materials/tests/unit/deployed-helpers.test.ts(new),replication_materials/tests/unit/budgets.test.ts
Interfaces:
- Produces:
maxLoadingGap(timeline)returnsnullwhen the timeline has no"loading ("text (was 0).slowRuns(env: string | undefined): number(new export ofslow-start.spec.tsor ofhelpers.ts): 20 when unset, the integer when it is a positive integer, else it throwsFFD_SLOW_RUNS must be a positive integer.
-
deployed-helpers.test.ts:maxLoadingGapof a timeline without"loading ("isnull; with"loading ("but no"preparing"text, the window ends at the last"loading ("entry, not at the end of the timeline;slowRuns(undefined)is 20,slowRuns("5")is 5,slowRuns("0"),slowRuns("x")andslowRuns("2.5")throw.budgets.test.ts:deriveBudgetswith a used cell""or"abc"throwsNot a finite number in <file>, row <n>, column <name>.
6Task 3: CPU throttling of the engine worker (T2-d)
The slow-profile runs set a 4x CPU slowdown on the page through CDP. The R engine runs in a dedicated worker, which is another CDP target. Nothing shows that the slowdown reaches it.
Files:
- Create:
replication_materials/tests/deployed/throttle-probe.spec.ts - Modify:
replication_materials/tests/deployed/helpers.ts - Data:
replication_materials/tests/perf/throttle-probe.csv(new)
Interfaces:
- Produces:
applySlowProfile(page)inhelpers.tssets the network and CPU throttling on the page and on each worker of the page (CDPTarget.setAutoAttachwithflatten: trueandwaitForDebuggerOnStart: true; on each attached worker session,Emulation.setCPUThrottlingRateand thenRuntime.runIfWaitingForDebugger).
7Task 4: Store and spend (T3-a to T3-d, T4-a, T4-b with FR-1, T4-d to T4-g)
Files:
- Modify:
lib/ops/scripts.ts,lib/ops/spend.ts,lib/ops/stores.ts,lib/translate/service.ts,lib/translate/handler.ts,lib/translate/reserve.ts,lib/security-headers.ts,app/api/review/route.ts,content/messages.json, spec 17.1 (self-hosting row) - Test:
replication_materials/tests/unit/ops/scripts.test.ts,replication_materials/tests/unit/ops/spend.test.ts,replication_materials/tests/unit/ops/stores.test.ts,replication_materials/tests/unit/translate/handler.test.ts,replication_materials/tests/unit/privacy.test.ts
Interfaces:
- Produces:
chooseLimiter(store: Store, limits: RateLimits): RateLimiterexported fromlib/ops/stores.ts(moved fromservice.tsandapp/api/review/route.ts; the type names are the ones the two copies use today).productionDepscallschooseStoreonce and passes the result tochooseLimiterand tochooseSpend.
-
scripts.test.ts(T3-d): after two allowed requests 10 s apart (TIMEadvanced in the mock, orTTLread after each call), the day key's TTL is the TTL of the first request minus 10 s, not 172,800 again. A key set beforehand withSETand no expiry gets a TTL after one allowed request. The same two checks for the spend key ofRESERVE_SCRIPTand the non-block path ofSETTLE_SCRIPT.spend.test.ts(T4-a): reserve on day D at 23:59:59 UTC, reserve on day D+1 at 00:00:01, then a settle stamped D; the total of D+1 is unchanged. (T4-b): with a cap of 1e7 USD, a voter blocked by a settle cannot pass a reserve of 0;settleStepwith a reserved amount above the block value returns a finite safe integer.stores.test.ts(T3-b):chooseLimitergives the Redis limiter for a Redis store, the memory limiter for a memory store and the "unavailable" limiter for no store (the three branches of today's code).handler.test.ts(T4-d): a reserve-amount function that throws gives status 500, not 503store_unavailable. (T4-f): a valid run with non-zerounreadsettlesusage + unread; a Jev call that fails without a deadline settlesusageonly.privacy.test.ts(T3-c): assert that the sentence about the counters is the exact sentence of today's page, instead ofnot.toContain("in memory")on the whole page.
-
- Lua (T3-d): after each
INCRorINCRBY,if redis.call('TTL', KEYS[n]) < 0 then redis.call('EXPIRE', KEYS[n], ARGV[ttl]) end. The key gets its life once, at its first write, and a key without a TTL gets one. Update the script comments. - Memory tracker (T4-a): totals keyed by
voter + ":" + utcDate(now); drop dates older than the day before the newest date seen. Remove the midnight sentence from the spec 17.1 self-hosting row. - Clamps (T4-b, FR-1): clamp the cap to
BLOCKED_MICRO_USD - 1; cap the reserved micro-dollars atBLOCKED_MICRO_USDinsettleStep. - Handler (T4-d): compute
costUsd,llmReserveUsage(once) and the Jev amount before thetry; only thespend.reserveawaits stay inside it. - Wiring (T3-b, T4-e): move the limiter choice to
lib/ops/stores.ts; onechooseStorecall inproductionDepsand one in the review route. - Comments (T4-g): replace the plan labels "(Task 4)" and "Task 5" in
spend.ts,reserve.tsandsecurity-headers.tswith the decision ids (A10, AP2, AP3; AP12 for the headers, or the id that the alpha plan gives). - JSON (T3-a):
"rate_limited": "..."with a space after the colon (the text does not change).
- Lua (T3-d): after each
8Task 5: Security policy and import guards in the tests (T5-a to T5-f)
Files:
- Modify:
replication_materials/tests/e2e/helpers/test.ts,replication_materials/tests/e2e/perf.spec.ts,replication_materials/tests/e2e/csp.spec.ts,replication_materials/tests/unit/zod-import.test.ts,replication_materials/tests/unit/isolation.test.ts
Interfaces:
- Produces:
guardedContext(browser: Browser, options?: BrowserContextOptions): Promise<BrowserContext>inhelpers/test.ts: a new context with the same init script and the same CSP listeners as the default fixture. A violation in it fails the test as in the default context.
-
csp.spec.ts(T5-c): the worker path also has COOPsame-originand X-Robots-Tagnoindex.csp.spec.ts(T5-d): insideallowCspViolations(), a control test starts a worker from a blob URL whose script logs a message in the form of a CSP violation to the console;consoleHitscatches it.csp.spec.ts(T5-a): a page in aguardedContextthat loads a blocked image fails the guard (run insideallowCspViolations()and assert the hit).zod-import.test.ts(T5-e): the pattern also catchesimport("zod")andimport( 'zod' ); scanscripts/andnext.config.tstoo.
9Task 6: Review route (T6-b to T6-f)
Files:
- Modify:
lib/review/handler.ts,lib/review/github.ts, spec 14.4 - Test:
replication_materials/tests/unit/review/handler.test.ts,replication_materials/tests/unit/review/github.test.ts,replication_materials/tests/unit/review/questions.test.ts
Interfaces:
- Produces:
MAX_BODY_BYTESexported fromlib/review/handler.tsand derived from the answer limits:Math.ceil((6 * (MAX_COMMENT_CHARS + MAX_NAME_CHARS) + 512) / 1024) * 1024(= 25,600). JSON escapes a control character or a lone surrogate to 6 bytes; 512 bytes cover the keys, the longest question id and the choice.MAX_ISSUE_PAGES = 10ingithub.ts(1,000 issues, the same bound asscripts/export-review.mjs).
-
handler.test.ts(T6-b): a GET whose GitHub list is held, then a POST that completes, then the held list returns; the next GET reads GitHub again (the old list was not cached).handler.test.ts(T6-c): a good GET (warm cache), then a GET with a wrong key, gives 401.handler.test.ts(T6-d): an answer with a 4,000-character comment of"\u0001"and a 100-character name of"\u0001"gets 201 (the stub accepts), and a body ofMAX_BODY_BYTES + 1bytes gets 400.github.test.ts(T6-f): a stub that always returns 100 issues stops afterMAX_ISSUE_PAGESpages and throws; the route answers 503 withREVIEW_MESSAGES.unavailable.questions.test.ts(T6-e): the advice-word test also reads the glossary entry texts and the reference citations. If an existing text has an advice word, list it in an explicit allow list in the test with the entry key, and report it; do not change the text (it is under review).
10Task 7: Review page (T7-a to T7-e, T7-g, T7-h)
Files:
- Modify:
components/review/ReviewPage.tsx,components/review/QuestionCard.tsx,lib/review/answer.ts - Test:
replication_materials/tests/unit/review/answer.test.ts,replication_materials/tests/unit/review/review-messages.test.ts,replication_materials/tests/e2e/review.spec.ts
Interfaces:
- Produces: the failure result of
checkAnswergetsfield: "name" | "comment" | "choice" | null(T7-g);REVIEW_MESSAGES.filing = "Filing the answer."(T7-d).FileResultgetsstatus?: numberso the page can tell a rejected key (401) from other failures.
-
review-messages.test.ts: the exact-text table getsfiling: "Filing the answer.".answer.test.ts(T7-g): a missing name givesfield: "name"; a missing comment for "change" givesfield: "comment"; an unknown question givesfield: null.review.spec.ts(T7-a): POST answers 429, 502 and 503 each show their message under the card and announce it; after a POST error the focus is on the card's first control or its error text (not the body); the arrow keys move between the radios of one question; while the POST is held, the button hasaria-disabled="true", a second click files nothing, and "Filing the answer." is shown and announced politely (T7-d).review.spec.ts(T7-b): a POST that answers 401 removes the key: the controls go and the page shows "This page files answers only from the link with the review key." and the bad-key message.review.spec.ts(T7-c): an error under a card goes when the user changes the choice or the comment.review.spec.ts(T7-e): with the GET held, the progress line is not shown; it shows after the GET returns.review.spec.ts(T7-h): when card B reports a missing name, the old error under card A goes; the 401-on-load test also asserts the announcement in the live region.
11Task 8: Test and document cleanups, and the record (T8-a, T9-d, T9-e, T9-f)
Files:
- Modify:
replication_materials/tests/e2e/describe.spec.ts,components/shell/Workspace.tsx, spec Sections 13.1, 13.2 and 14.4,replication_materials/process/process.json,scripts/build-spec-page.mjs,replication_materials/plans/2026-10-17-alpha-execution-ledger.md, the spec page files
12Execution notes
- The controller merges by pull request and fast-forward after the author's approval of the merge, as for pull requests 1, 3 and 4. A push to main redeploys production.
- After the deploy:
site.specwithFFD_EXPECT_CSP=1againsthttps://expertadoe.app, andGET /api/reviewwithout a key (401). Task 4 also changed RESERVE_SCRIPT and SETTLE_SCRIPT, which run on every translation, and the free check runs only RATE_SCRIPT. So one smoke translation (about USD 0.05) after the deploy checks them; it needs the author's approval.