Commit 879a805
committed
Type the values nobody had typed, and run a runbook
The fifth pass left a list of what had never been audited. This works it: load
at the size the lab actually is, the loop in front of a slow board, and an
adversarial read of the one script that runs once against a database nobody has
seen. Five defects, each proved by running it first.
1. A page cursor too big to be an id answered 503.
Number('9223372036854775807') is 9223372036854776000, so a cursor past
Number.MAX_SAFE_INTEGER stops being the number that was typed and Postgres
refuses it for a bigint. The bigint maximum itself failed, and so did 1e30,
which arrives as "1e+30" and is not even integer syntax. All three paginated
routes: /api/audit, /api/door/events and /api/me/door-events. A cursor that
big means the same thing as no cursor, which is to start from the newest, so
that is what page() does with one now.
2. A null byte in any text field answered 503.
A Postgres text column cannot hold U+0000 and postgres.js hands the value
straight through, so signup, a profile edit and a credential label all read as
the database being down. text() is the one place every free text field goes
through and it now answers null for a string the database cannot store, which
is what its contract already said it does for a string that is not one.
readPatch checks it before the per field branches, because that is where the
contact fields bypass text().
3. A command result and the event recording it were two writes.
POST /door/commands/:id/result updated door_commands and then inserted into
door_events as separate statements. A detail jsonb refused left the command
resolved with nothing recording it, and answered 503 saying "Nothing was
changed" when the command row had been. Proved against the running stack: the
command read done, the event count read zero, and the caller was told nothing
had moved.
Rule Four of the README is that a change and its record go in the same
transaction, not beside it. change() makes that impossible to forget for
audit_log and door_events was outside it. Both this and the expiry path in the
same file are one transaction now.
A transaction on its own would turn an unstorable detail into a command
re-claimed and re-run forever, which is the poison message the second pass
already fixed once for events. So the detail is dropped and logged where jsonb
cannot hold it, the same way `when` replaces a time that is not a time. In
POST /door/events that is a straight improvement: a card read whose detail was
unstorable used to be skipped, losing the card id that enrolment depends on,
and now the event lands without its decoration.
4. The first step of a 2am runbook cannot be run.
the-door-service-will-not-talk-to-the-controller.md opens with
`curl -s localhost:9000`. The health server binds to localhost inside the
container and compose.lab.yml publishes no port, because this service accepts
nothing inbound, so on the lab host that connects to nothing. The line above
it reaches into the container for the logs, so the shape was known.
Running the corrected command turned up a second layer: localhost inside the
image resolves to IPv6 first and the health server is on IPv4, so it needs the
address rather than the name. Both docs now carry the command that was run
here and answered.
The health answer also gains `running`, whether a pass is in flight. A slow
board takes as long as it takes, and without that the answer during one is ok
with a lastTickAt from before it started, which is the reading a volunteer
takes from step one.
5. The import report counted intentions rather than rows.
scripts/import.ts has always skipped a row it could not place and the report
counted the legacy table. Against a fixture carrying a payment with no date
and a grant naming a certification that is not there, it printed payments: 5
and wrote three, and said nothing about two of the four rows it dropped. Money
and tool access, in the script whose whole job is to report rather than guess.
The counts are what will be written now, with a leftBehind beside them, every
dropped row is named with its reason, and the write walks the same lists the
preflight counted so the two cannot drift. A preflight refusal is added for
two certifications that normalise to one slug, which would merge two tool
certifications and misplace every grant of the second. The ten production
slugs are distinct, so that one is a guard rather than a fix.
Measured, from the same list.
The directory at a thousand members with every free text field full is a
4.1 MB answer in 0.15 s. Twenty at once cost the API container about 90 MB on
top of a resting 125 MB, against the 512 MB it is limited to, and serialise
from 0.49 s to 2.67 s. The choice to answer with everybody stands, and the
number says not to poll it. A ?q= pattern cannot be made expensive: name and
email are short, so the worst measured 115 ms.
A first pass writing 200 cards is 203 requests with nothing bounding a pass as
a whole. 10.4 s at 50 ms a request, 41 s at 200 ms, during which no command is
claimed. Commands expire at 120 s, so at 200 cards a pass slower than about
590 ms a request expires them before claiming them. The lab holds 64 cards, so
the real figure is about 1.8 s a request.
Proved sound, and recorded so nobody spends the time again: the import does not
need null guards on email or the timestamps. Those columns are null: false in
db/schema.rb, which the fixture mirrors, and the tolerance rule in CONTRIBUTING
is about *_id columns dangling, every one of which the import already handles.
That was about to be changed.
Image scanning is still not done. docker scout wants a login this session did not
have, and it is written down in section 5 with the command.
API suite 85 tests to 89. Typecheck, the copy gate and both suites green.1 parent 1c1aa71 commit 879a805
9 files changed
Lines changed: 431 additions & 63 deletions
File tree
- api/src
- routes
- docs
- runbooks
- door/src
- scripts
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3 | 3 | | |
4 | 4 | | |
5 | 5 | | |
6 | | - | |
| 6 | + | |
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
| |||
19 | 19 | | |
20 | 20 | | |
21 | 21 | | |
22 | | - | |
| 22 | + | |
23 | 23 | | |
24 | 24 | | |
25 | 25 | | |
| |||
68 | 68 | | |
69 | 69 | | |
70 | 70 | | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
71 | 83 | | |
72 | 84 | | |
73 | 85 | | |
| |||
250 | 262 | | |
251 | 263 | | |
252 | 264 | | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
253 | 279 | | |
254 | 280 | | |
255 | 281 | | |
| |||
309 | 335 | | |
310 | 336 | | |
311 | 337 | | |
312 | | - | |
313 | | - | |
| 338 | + | |
| 339 | + | |
314 | 340 | | |
315 | 341 | | |
316 | 342 | | |
| |||
327 | 353 | | |
328 | 354 | | |
329 | 355 | | |
| 356 | + | |
330 | 357 | | |
331 | 358 | | |
332 | 359 | | |
| |||
388 | 415 | | |
389 | 416 | | |
390 | 417 | | |
| 418 | + | |
| 419 | + | |
| 420 | + | |
| 421 | + | |
| 422 | + | |
| 423 | + | |
| 424 | + | |
| 425 | + | |
| 426 | + | |
| 427 | + | |
| 428 | + | |
| 429 | + | |
| 430 | + | |
| 431 | + | |
| 432 | + | |
| 433 | + | |
| 434 | + | |
| 435 | + | |
| 436 | + | |
| 437 | + | |
| 438 | + | |
| 439 | + | |
| 440 | + | |
| 441 | + | |
| 442 | + | |
| 443 | + | |
| 444 | + | |
| 445 | + | |
| 446 | + | |
| 447 | + | |
| 448 | + | |
| 449 | + | |
| 450 | + | |
| 451 | + | |
| 452 | + | |
| 453 | + | |
| 454 | + | |
| 455 | + | |
| 456 | + | |
| 457 | + | |
| 458 | + | |
| 459 | + | |
391 | 460 | | |
392 | 461 | | |
393 | 462 | | |
| |||
453 | 522 | | |
454 | 523 | | |
455 | 524 | | |
| 525 | + | |
| 526 | + | |
| 527 | + | |
| 528 | + | |
| 529 | + | |
| 530 | + | |
| 531 | + | |
| 532 | + | |
| 533 | + | |
| 534 | + | |
| 535 | + | |
| 536 | + | |
| 537 | + | |
| 538 | + | |
| 539 | + | |
| 540 | + | |
| 541 | + | |
| 542 | + | |
| 543 | + | |
| 544 | + | |
| 545 | + | |
| 546 | + | |
| 547 | + | |
| 548 | + | |
| 549 | + | |
| 550 | + | |
| 551 | + | |
| 552 | + | |
| 553 | + | |
| 554 | + | |
| 555 | + | |
| 556 | + | |
| 557 | + | |
456 | 558 | | |
457 | 559 | | |
458 | 560 | | |
| |||
485 | 587 | | |
486 | 588 | | |
487 | 589 | | |
| 590 | + | |
| 591 | + | |
| 592 | + | |
| 593 | + | |
| 594 | + | |
| 595 | + | |
| 596 | + | |
| 597 | + | |
| 598 | + | |
488 | 599 | | |
489 | 600 | | |
490 | 601 | | |
| |||
498 | 609 | | |
499 | 610 | | |
500 | 611 | | |
501 | | - | |
502 | | - | |
503 | | - | |
504 | | - | |
505 | | - | |
506 | | - | |
507 | | - | |
508 | | - | |
509 | | - | |
510 | | - | |
511 | | - | |
512 | | - | |
513 | | - | |
514 | | - | |
| 612 | + | |
| 613 | + | |
| 614 | + | |
| 615 | + | |
| 616 | + | |
| 617 | + | |
| 618 | + | |
| 619 | + | |
| 620 | + | |
| 621 | + | |
| 622 | + | |
| 623 | + | |
| 624 | + | |
| 625 | + | |
| 626 | + | |
| 627 | + | |
| 628 | + | |
| 629 | + | |
515 | 630 | | |
516 | 631 | | |
517 | 632 | | |
| |||
547 | 662 | | |
548 | 663 | | |
549 | 664 | | |
550 | | - | |
| 665 | + | |
551 | 666 | | |
552 | | - | |
| 667 | + | |
553 | 668 | | |
554 | 669 | | |
555 | 670 | | |
556 | 671 | | |
| 672 | + | |
| 673 | + | |
| 674 | + | |
557 | 675 | | |
558 | 676 | | |
559 | 677 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
9 | 9 | | |
10 | 10 | | |
11 | 11 | | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
12 | 24 | | |
13 | 25 | | |
14 | | - | |
| 26 | + | |
15 | 27 | | |
16 | 28 | | |
17 | 29 | | |
| |||
49 | 61 | | |
50 | 62 | | |
51 | 63 | | |
52 | | - | |
| 64 | + | |
53 | 65 | | |
54 | 66 | | |
55 | | - | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
56 | 74 | | |
57 | 75 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
8 | | - | |
| 8 | + | |
9 | 9 | | |
10 | 10 | | |
11 | 11 | | |
| |||
362 | 362 | | |
363 | 363 | | |
364 | 364 | | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
365 | 370 | | |
366 | 371 | | |
367 | 372 | | |
| |||
0 commit comments