File size: 5,475 Bytes
3d968d9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# Tests

This deployment repo would normally carry no tests β€” upstream has them, and a
copy-and-push pipeline has little to verify. These exist because of one thing:
`overlay/sdr-api-core.js` is a hand-written JavaScript port of upstream's
1,200-line Python server, and a port that drifts from the server it mirrors makes
the Space lie about what the tool does.

Nothing else in the build would notice that drift. These two suites do.

```powershell
node tests\parity\run-parity.mjs         # the port vs. the real app.py
node tests\browser\run-browser-check.mjs # the built site in a real browser
```

Both run automatically in `scripts\deploy.ps1` before anything is pushed, since a
Static Space goes live the instant the push lands.

## parity/ β€” is the port the same program?

`run-parity.mjs` replays one 67-request workflow twice and diffs the results:

- **The reference.** `reference.py` imports the vendored `app.py` from `app/`,
  points its storage at a temporary directory, replaces `utc_now` and `uuid4`
  with counters, and serves it over real HTTP on a single-threaded server.
- **The port.** `run-parity.mjs` loads `overlay/sdr-api-core.js` in Node with an
  in-memory store and the same counters.

Then it compares every response status, every response body, the SHA-256 of the
downloaded bytes, the final record store, and the list of stored uploads.

Two details do most of the work:

- **Sequential timestamps and ids.** Because the clock advances one second per
  call and ids count up from 1, the port has to *call* them the same number of
  times in the same order. A missing `utc_now()` or an extra `uuid4()` β€” easy to
  introduce, since Python builds its migration `defaults` dict eagerly and
  consumes an id whether it uses one or not β€” becomes a visible mismatch instead
  of a value nobody compares.
- **A JSON round-trip on every load and save.** `app.py` re-reads its file per
  request, so a handler that mutates a record and then fails without saving
  throws the change away. The in-memory store copies on load and save for the
  same reason; handing out a live object would let those changes stick and hide
  the difference.

### The corpus

`corpus/` is committed and covers the rubric and the text pipeline:

| File | What it is for |
| --- | --- |
| `clean-policy.md` | All six required sections, long enough, no findings |
| `messy-draft.md` | Every placeholder form, two vague phrases, an overlong sentence, missing sections |
| `tiny.txt` | Short-document and every missing-section finding at once |
| `unicode.md` | Word counting across scripts, apostrophes, hyphens, and combining marks β€” where Python's Unicode-aware `\w` and JavaScript's ASCII `\w` would otherwise disagree |
| `whitespace.txt` | Rejected: no readable text |
| `notes.rtf` | Rejected: unsupported type |

`.corpus/` is generated by `reference.py` on each run and gitignored, because the
fixtures are binary or encoding-specific: a deflated DOCX, a stored DOCX, a ZIP
with no `word/document.xml`, a file that is not a ZIP at all, UTF-16 with a BOM,
cp1252 with bytes that are not valid UTF-8, and an empty file.

`corpus/**` is marked `-text` in `.gitattributes`. One rubric rule counts
characters between sentence terminators, so a CRLF checkout would change a
finding.

### Accepted divergences

`ACCEPTED_DIVERGENCES` in `run-parity.mjs` lists differences that are correct,
each with its reason, and prints them on every run rather than dropping them.
There is currently one: `mimetypes.guess_type` returns
`application/octet-stream` for `.docx` on a Windows machine with no Office
mapping, where the port always sends the real OOXML type.

Add to that list only for a difference you can justify in a sentence. Anything
not on it fails the run.

## browser/ β€” does it work as a page?

The parity suite runs the port in Node, so it says nothing about the parts
between the port and the browser. `run-browser-check.mjs` serves the repository
root over HTTP β€” plainly, with no SPA fallback, the way a Static Space does β€”
opens it in headless Chromium through the DevTools protocol, and drives the real
UI: upload, review, add a reviewer, assign, approve, download, reload.

It checks the things only a browser can prove:

- both overlay scripts loaded, in order, before `app.js` ran
- upstream's `XMLHttpRequest` calls are answered locally
- IndexedDB keeps the workspace across a reload
- the Download button produces a file instead of navigating to a path that does
  not exist on a static host
- no console errors anywhere in the run

It talks the DevTools protocol directly instead of adding a browser-automation
dependency this repo otherwise has no use for. It finds Edge or Chrome from the
usual Windows install paths; set `BROWSER` to override, and pass `--headed` to
watch it happen.

One quirk worth knowing: approving a document that still has findings goes
through `window.confirm`, and an unanswered dialog blocks the renderer β€” including
the evaluation waiting on it. The client accepts the dialog over CDP rather than
stubbing `confirm` in the page, so the real code path runs.

## When a test fails after an upstream change

That is the intended outcome, not a broken test. `build-static.ps1` will already
have refused to build for the same reason. Read the upstream diff, carry the
behavior into `overlay/sdr-api-core.js`, re-run both suites, then acknowledge the
change with `.\scripts\build-static.ps1 -AcceptUpstreamChanges`.