Independent adversarial review (prompt 06B) of branch phase/06a-build, starting at edaa5b3.
Windows 10, Node 24, Python 3.14.7, Playwright 1.64 with installed Microsoft Edge. Every result
below is from commands actually run; evidence logs were kept in the reviewer's scratchpad.
| Run | Command | Exit | Result |
|---|---|---|---|
| Before fixes, in place | powershell -File scripts\verify.ps1 *>&1 (redirected to a log) |
0 | 54 unit, 29 pytest, 10 e2e passed; ALL CHECKS PASSED |
Fresh git clone in a scratch folder, new .venv from backend/requirements.lock.txt (pip exit 0) |
scripts\verify.ps1 *>&1 |
0 | same counts; clone deleted afterwards |
| Clone, deliberately unformatted test added | scripts\verify.ps1 |
1 | FAILED: format check (exit 1) |
| Clone, deliberately failing unit test | scripts\verify.ps1 |
1 | FAILED: frontend unit tests (exit 1) |
| Clone, deliberately failing pytest | sh scripts/verify.sh |
1 | stopped at backend tests |
| Clone, deliberately broken e2e assertion | scripts\verify.ps1 |
1 | FAILED: browser e2e (exit 1) |
After fixes (0147e37), in place |
scripts\verify.ps1 *>&1 |
0 | 64 unit (4 files), 31 pytest, 13 e2e passed; ALL CHECKS PASSED |
| After fixes, in place | sh scripts/verify.sh |
0 | same counts; ALL CHECKS PASSED |
| README dev command | npm run dev, GET http://localhost:5177/script-audit/ |
- | 200, then stopped |
The e2e runs regenerate docs/screenshots; after the fixes only audits-desktop.png and
audits-mobile.png changed (the double-space finding now shows its neighbouring words). They were
inspected and committed; later runs reproduced them byte for byte.
- Import, four audits, reject, apply, compare, export (
examples/draft.md): imported as revision 1 with the#title; ran grounding (2 open, coverage 0 of 2), narrative (1, N6), spoken (4: S5, S1, S3, S3) and packaging (1, P2) separately; every tab badge equalled the listed open findings. Rejected P2 (still "1 rejected" after the next revision), previewed and applied the S1 split (preview created no revision; revision 1 stayed byte-identical to the file body); Compare showed exactly,/andremoved and./Andadded, and both sides equalled revisions 1 and 2. The Markdown exportthe-shocking-story-of-the-paper-bridge-r2.mdequalled# titleplus revision 2; the report states the audits do not predict views; the narration has no tags; the workspace JSON re-imported with 2 revisions. - Claim IDs (sample script, coverage 1 of 10): a tag at the end of a paragraph changed only the
last sentence (G1 to G4 "first", coverage unchanged);
[c:worst-storm]on its own sentence cleared exactly that G1 (2 of 10, nothing else changed); an unrelated sourced claim gave G4; a two-id tag with one matching claim covered; an unsourced claim gave G3;[c: Worst-Storm ]resolved.
| Area | Result |
|---|---|
| False positives | dialogue, lists, Markdown links (numbers in URLs), inline code, numbers in headings: no grounding finding. "First, ..." / "at first" was flagged (fixed). always, never, best, reported and bare numbers ("Call me at 5") remain assertions by design (rejectable). |
| Undo / revert | append-only and exact (unit + e2e); an approved script refuses apply, undo and revert, even with a forced DOM click |
| Diff | both sides reconstruct exactly incl. emoji; 199 k-character worst case 0.2 s |
| Long script (199 k chars, one section) | spoken audit 20.5 s and narrative 3.1 s in Node before the fix (5 s and 8 s in the browser); 0.09 s and 0.17 s after |
| Unicode | emoji, ZWJ sequences, mathematical letters: no finding range splits a surrogate pair; applied fixes keep pairs intact (browser check) |
| CRLF / BOM | \r\n and lone \r normalised, BOM dropped, title taken |
| Persistence | decisions and history survive reload; a corrupt store is kept under .corrupt and is still there after starting over; a storage refusal is shown |
| NUL / lone surrogate in an edit | was saved, then the whole workspace was refused on reload (fixed) |
| Stale suggestions | refused by the engine; AI results are cleared when leaving the Audits page |
| Rejected-key stability | rejecting one double space moved to another gap after a fix (fixed) |
| AI fake markers (live backend) | NUMBER, CITE, QUOTE rewrites dropped; OTHER dropped; PROSE and EXTRA 502 ai_invalid_output; ERROR 502 ai_provider_error; grounding rewrites dropped; a repeat is served from the cache |
| Hostile backend answers (route interception) | extra keys, non-JSON, HTTP 500, wrong original: refused or dropped; HTML in explanations rendered as text. Spelled-out numbers, superlatives, attributions, ‘’/«» quotes, Markdown links and duplicate answers were accepted (fixed in browser and backend) |
| Backend guards (live) | Host evil.example and a loopback host with an @evil.example user-info part 400; Origin evil and null 403; preflight from evil 400; 250 kB body and chunked 300 kB 413; 9 findings, duplicate keys, extra keys, bad rule 422; no or wrong opt-in 403; disabled provider 403; the 21st provider call in an hour 429 with Retry-After: 600; database at user_version 2 with both tables; ledger rows hold only a hash and counts |
| Layout and access | no horizontal page scroll on any view at 320/375/768/1440, light and dark; the skip link is first and moves focus to main; every tabbed control shows a focus outline; every input, select, textarea and button has an accessible name; unknown hash routes fall back to the Editor |
| Static demo separation | served from a non-loopback host (https://demo.example/script-audit/, simulated) the page contacted 127.0.0.1:8799 on its own (fixed: "Not checked" until the user clicks); after the click a backend without that origin in CORS is reported unreachable |
| Ethics | no screen, export or doc claims a score predicts views or retention; the app never creates claims or sources |
| Secrets / licence | npm run secrets clean; history grep for key patterns empty; single author; MIT licence; sample and example invented and labelled; .env.example has no values |
Commit 0147e37; regression tests in tests/acceptance.test.ts, e2e/acceptance.spec.ts and
backend/tests/test_ai.py. Each defect was reproduced on edaa5b3 before the fix.
- AI rewrites could add spelled-out numbers, superlatives, attributions, single or angle quotation marks, Markdown or bare-domain links, and two answers for one finding (browser and backend).
- An edit with a NUL or a lone surrogate was saved and made the stored workspace unreadable on the next load; a title-only paste imported an empty script.
- Settings number fields clamped on every keystroke: 180 words per minute or a 40-word limit could not be typed (220 and 60 were stored).
- A rejected double-space finding moved to a different gap after another one was fixed.
- The packaging title fix replaced inside a longer word ("Insanely" became "Remarkablely").
- Spoken and narrative audits were quadratic (20 s on a 199 k script).
- "First," and "at first" counted as superlatives.
- The public static demo contacted
127.0.0.1without being asked. - Cosmetic: a double period in the apply message; "NaN calls per hour" for a malformed capability.
- All run audits re-run synchronously after each change (about 0.5 s in total on a 199 k script); there is no worker.
- On a 320 px screen the History table scrolls sideways inside its frame.
- Grounding treats
always,never,best,reportedand any digit as assertion triggers; this is the documented rule set and each finding can be rejected. /api/docsloads Swagger UI from a CDN (local backend only).- The CI and Pages workflows have never run (no remote); action versions were not verified online.
Windows 10, Node 24, Python 3.14 (virtualenv from backend/requirements.lock.txt), Playwright 1.64
with the installed Microsoft Edge (msedge channel). CI is configured for Chromium and Python 3.12
on Ubuntu but has never run (no remote).
powershell -File scripts\verify.ps1 (output redirected), exit code 0 (ALL CHECKS PASSED):
| Step | Command | Result |
|---|---|---|
| install | npm ci |
ok, 0 vulnerabilities |
| format | npm run format:check |
ok |
| lint | npm run lint |
ok |
| types | npm run typecheck |
ok |
| unit tests | npm test |
54 passed (3 files) |
| backend lint | npm run backend:lint |
ruff: all checks passed |
| backend format | npm run backend:format |
16 files already formatted |
| backend types | npm run backend:types |
mypy --strict: no issues in 11 files |
| backend tests | npm run backend:test |
29 passed |
| production build | npm run build |
ok, 381 kB JS (117 kB gzip) |
| secret scan | npm run secrets |
no findings |
| e2e | npm run e2e |
10 passed: production build under /script-audit/ on 4178, real backends on 8799 (fake AI) and 8800 (no AI) |
- Text model: CRLF/BOM normalisation, title extraction, same-length masking of tags, markup and link
targets, sentence splitting (abbreviations, initials, decimals, closing quotes,
…, beat ends), claim tags after the full stop, sections and beats, empty script, word counting, hand-computed duration with speed clamping. - Grounding: triggers shown; journey 2 in the engine (a tag covers only its own sentence and the untouched sentence keeps the same key); unknown id, claim without a complete source, numbers and superlatives missing from the claim; multiple ids; tags in non-assertions; numbers inside markup ignored; no automatic fixes.
- Narrative, spoken and packaging: each rule when it fires and when it must not, with the exact fix text for S1, S3, S5 and P2 (title and hook).
- Determinism (same output twice), key stability across edits elsewhere, and that the sample script triggers every audit.
- Revisions: apply, stale refusal, approval lock on apply/edit/revert, exact undo and revert, original kept, title suggestions, the 300-revision and 200,000-character caps.
- Diff: 300 seeded random edits reconstruct both sides exactly; word-level change; a 4,000-line script diffs in well under 3 s.
- Workspace schema: sample round trip; unknown keys, wrong version, broken numbering, duplicates, lone
surrogates, CR in text,
javascript:URLs,__proto__, bad JSON. - Exports: Markdown round trip through import, narration without tags and with the glossary, report disclaimer.
- AI answers in the browser: request contents, valid rewrite becomes an unapplied suggestion; rewrites adding numbers, claim tags, links or quotes, or targeting other text, are dropped; unknown keys reject the whole answer; unknown finding keys are dropped.
Health and capabilities without AI; disabled provider (403); opt-in header required; validated recommendations; 8 invalid request shapes (422); Origin and Host guards; streamed body cap (413); 404 envelope; usage counts and a ledger without script text; rate limit counting provider calls only and surviving a restart, recovering after an hour; cache hits; migrations in order and refusal of a newer schema; configuration parsing; the API key sent to the provider but never returned; adversarial provider output (new numbers, claim tags, quotations, grounding rewrites, unsent finding keys, extra keys, prose, provider failure) dropped or refused; failed calls not cached; rewrite rules; fenced JSON.
- Everyday absolutes (
every,all,last, a baremost) made ordinary sentences "assertions"; the trigger list was narrowed and the spec says why. - A claim tag pointing at any sourced claim cleared a warning even when the claim did not support the
sentence (
She was the first keeper [c:keeper-log]). Coverage now requires the claim to contain the sentence's numbers, superlatives and number words (G4). - N1 reported overlapping four-word fragments of one repeated phrase separately; they are merged.
- Stemming turned "bridges" into "bridg", so a title word did not match the hook.
- Prettier rewrote the example draft and removed the double space the demo relies on;
examples/is excluded from formatting. - The active audit tab's count badge was invisible (white on white).
- Journey 1: import
examples/draft.md, run all four audits, reject the title suggestion, preview and apply the double-space fix, rejection kept, compare revision 1 and 2 (exactly one changed space), download Markdown...-r2.mdand the audit report. - Journey 2: sample script, grounding coverage 1 of 10; tagging one sentence clears only it (2 of 10, the other sentence still flagged, one finding fewer); a non-covering tag gives G4; an unknown id gives G2; a changed number gives G4; adding a source in Claims covers the G3 sentence.
- Approval lock (read-only editor, disabled Apply), exact undo (compare shows no differences), unsaved edits block applying.
- Rejections and history persist across a reload; a corrupted store is kept aside and reported.
- Import validation: empty, binary and wrong-version files refused; pasted CRLF and Unicode punctuation normalised and kept.
- AI with the fake provider: opt-in required, explanations shown as unverified, preview does not change anything, applying creates an "AI rewrite" revision, a rewrite adding a number is dropped.
- AI states: provider not configured (8800) and backend offline (port 9); audits still work.
- Glossary from a pronunciation finding; narration export; empty states on all pages.
- No horizontal page scroll at 375 px on all seven pages.
- Screenshots in
docs/screenshots(editor, audits with a diff, grounding, mobile audits, dark editor), which were inspected.
- A live AI provider (only the fake provider and a mocked HTTP transport).
- Firefox, Safari, screen readers.
- CI and Pages workflows (no remote).
The notes above that CI and Pages "have never run (no remote)" describe the state before publication and are superseded:
- 2026-10-19: public repository https://github.com/PatrickNDevelopment/script-audit created from the verified commit on
main; the first CI run (Ubuntu, Chromium) and the first Pages deployment were green. A live check of https://patrickndevelopment.github.io/script-audit/ loaded the app, reloaded it, and found no console errors and no requests to localhost. - 2026-10-20: the final cross-portfolio audit (a separate Claude Code session) ran the main journey on the live demo, found no P0 or P1 issue, and saw the last three CI runs green.