max / makenotwork
- Co-Authored-By
- Claude Opus 5 (1M context) <noreply@anthropic.com>
22 files changed,
+406 insertions,
-34 deletions
| @@ -178,7 +178,7 @@ | |||
| 178 | 178 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 179 | 179 | checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" | |
| 180 | 180 | dependencies = [ | |
| 181 | - | "windows-sys 0.60.2", | |
| 181 | + | "windows-sys 0.61.2", | |
| 182 | 182 | ] | |
| 183 | 183 | ||
| 184 | 184 | [[package]] | |
| @@ -189,7 +189,7 @@ | |||
| 189 | 189 | dependencies = [ | |
| 190 | 190 | "anstyle", | |
| 191 | 191 | "once_cell_polyfill", | |
| 192 | - | "windows-sys 0.60.2", | |
| 192 | + | "windows-sys 0.61.2", | |
| 193 | 193 | ] | |
| 194 | 194 | ||
| 195 | 195 | [[package]] | |
| @@ -2831,7 +2831,7 @@ | |||
| 2831 | 2831 | checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" | |
| 2832 | 2832 | dependencies = [ | |
| 2833 | 2833 | "libc", | |
| 2834 | - | "windows-sys 0.52.0", | |
| 2834 | + | "windows-sys 0.61.2", | |
| 2835 | 2835 | ] | |
| 2836 | 2836 | ||
| 2837 | 2837 | [[package]] | |
| @@ -3594,7 +3594,7 @@ | |||
| 3594 | 3594 | checksum = "1a791e6620676a875f362f3156ed213e73ca099a09bf992c18812abe65cc37b1" | |
| 3595 | 3595 | dependencies = [ | |
| 3596 | 3596 | "bstr", | |
| 3597 | - | "hashbrown 0.15.5", | |
| 3597 | + | "hashbrown 0.17.1", | |
| 3598 | 3598 | ] | |
| 3599 | 3599 | ||
| 3600 | 3600 | [[package]] | |
| @@ -4745,7 +4745,7 @@ | |||
| 4745 | 4745 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 4746 | 4746 | checksum = "82cb6a9f675da968c63b6208c641b9dca58fc0133ae53375736b1767b0cab8bd" | |
| 4747 | 4747 | dependencies = [ | |
| 4748 | - | "windows-sys 0.59.0", | |
| 4748 | + | "windows-sys 0.61.2", | |
| 4749 | 4749 | ] | |
| 4750 | 4750 | ||
| 4751 | 4751 | [[package]] | |
| @@ -5311,9 +5311,9 @@ | |||
| 5311 | 5311 | ||
| 5312 | 5312 | [[package]] | |
| 5313 | 5313 | name = "makeover-build" | |
| 5314 | - | version = "0.20.1" | |
| 5314 | + | version = "0.20.2" | |
| 5315 | 5315 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 5316 | - | checksum = "b61d78cbf82d8a25505d4150472501be53e2e92cac07540225e65ecefa385d35" | |
| 5316 | + | checksum = "cd400b7498878fb8465a4f01ee6b918fb6d0ae30479b8f8792d2b85722282e73" | |
| 5317 | 5317 | dependencies = [ | |
| 5318 | 5318 | "makeover", | |
| 5319 | 5319 | "makeover-geometry", | |
| @@ -5328,15 +5328,15 @@ | |||
| 5328 | 5328 | ||
| 5329 | 5329 | [[package]] | |
| 5330 | 5330 | name = "makeover-layout" | |
| 5331 | - | version = "0.14.0" | |
| 5331 | + | version = "0.15.0" | |
| 5332 | 5332 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 5333 | - | checksum = "e08cfaa62476d03061dc86a2befc0c24129399ba37a6f7645fbb72b011abf2f0" | |
| 5333 | + | checksum = "9f18920c6ac0fe10a8f7b45c2224d0ad39e4863151b3fbe0fe042c938b9b0a9d" | |
| 5334 | 5334 | ||
| 5335 | 5335 | [[package]] | |
| 5336 | 5336 | name = "makeover-touch" | |
| 5337 | - | version = "0.8.2" | |
| 5337 | + | version = "0.8.3" | |
| 5338 | 5338 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 5339 | - | checksum = "42481d14654fcbe37a3b2fcfca239448e3c6c7cf474e26a9837b02447fbb1e5c" | |
| 5339 | + | checksum = "eb5ea885f63ad2509b4b028a7cff0e027d4201c40e990346ecae01228edcc962" | |
| 5340 | 5340 | dependencies = [ | |
| 5341 | 5341 | "makeover-geometry", | |
| 5342 | 5342 | "makeover-layout", | |
| @@ -5344,9 +5344,9 @@ | |||
| 5344 | 5344 | ||
| 5345 | 5345 | [[package]] | |
| 5346 | 5346 | name = "makeover-webview" | |
| 5347 | - | version = "0.25.1" | |
| 5347 | + | version = "0.26.0" | |
| 5348 | 5348 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 5349 | - | checksum = "41fb147debeae14c46a48286459712f70cd89d1cce49595fd46a947528c302d5" | |
| 5349 | + | checksum = "104d377d660eabeace98de31fab5dfb63f6ccbf679a68c2e761cde29f7f79062" | |
| 5350 | 5350 | dependencies = [ | |
| 5351 | 5351 | "makeover-geometry", | |
| 5352 | 5352 | "makeover-layout", | |
| @@ -5633,7 +5633,7 @@ | |||
| 5633 | 5633 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 5634 | 5634 | checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" | |
| 5635 | 5635 | dependencies = [ | |
| 5636 | - | "windows-sys 0.59.0", | |
| 5636 | + | "windows-sys 0.61.2", | |
| 5637 | 5637 | ] | |
| 5638 | 5638 | ||
| 5639 | 5639 | [[package]] | |
| @@ -7212,7 +7212,7 @@ | |||
| 7212 | 7212 | "errno", | |
| 7213 | 7213 | "libc", | |
| 7214 | 7214 | "linux-raw-sys 0.12.1", | |
| 7215 | - | "windows-sys 0.52.0", | |
| 7215 | + | "windows-sys 0.61.2", | |
| 7216 | 7216 | ] | |
| 7217 | 7217 | ||
| 7218 | 7218 | [[package]] | |
| @@ -7292,7 +7292,7 @@ | |||
| 7292 | 7292 | "security-framework 3.7.0", | |
| 7293 | 7293 | "security-framework-sys", | |
| 7294 | 7294 | "webpki-root-certs", | |
| 7295 | - | "windows-sys 0.52.0", | |
| 7295 | + | "windows-sys 0.61.2", | |
| 7296 | 7296 | ] | |
| 7297 | 7297 | ||
| 7298 | 7298 | [[package]] | |
| @@ -7923,7 +7923,7 @@ | |||
| 7923 | 7923 | checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" | |
| 7924 | 7924 | dependencies = [ | |
| 7925 | 7925 | "libc", | |
| 7926 | - | "windows-sys 0.60.2", | |
| 7926 | + | "windows-sys 0.61.2", | |
| 7927 | 7927 | ] | |
| 7928 | 7928 | ||
| 7929 | 7929 | [[package]] | |
| @@ -8411,7 +8411,7 @@ | |||
| 8411 | 8411 | "getrandom 0.4.3", | |
| 8412 | 8412 | "once_cell", | |
| 8413 | 8413 | "rustix 1.1.4", | |
| 8414 | - | "windows-sys 0.52.0", | |
| 8414 | + | "windows-sys 0.61.2", | |
| 8415 | 8415 | ] | |
| 8416 | 8416 | ||
| 8417 | 8417 | [[package]] | |
| @@ -9850,7 +9850,7 @@ | |||
| 9850 | 9850 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 9851 | 9851 | checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" | |
| 9852 | 9852 | dependencies = [ | |
| 9853 | - | "windows-sys 0.48.0", | |
| 9853 | + | "windows-sys 0.61.2", | |
| 9854 | 9854 | ] | |
| 9855 | 9855 | ||
| 9856 | 9856 | [[package]] | |
| @@ -10686,11 +10686,11 @@ | |||
| 10686 | 10686 | ] | |
| 10687 | 10687 | ||
| 10688 | 10688 | [[patch.unused]] | |
| 10689 | - | name = "kberg" | |
| 10689 | + | name = "quasi-store" | |
| 10690 | 10690 | version = "0.1.0" | |
| 10691 | 10691 | ||
| 10692 | 10692 | [[patch.unused]] | |
| 10693 | - | name = "painhours" | |
| 10693 | + | name = "quasi-tauri" | |
| 10694 | 10694 | version = "0.1.0" | |
| 10695 | 10695 | ||
| 10696 | 10696 | [[patch.unused]] | |
| @@ -10702,9 +10702,9 @@ | |||
| 10702 | 10702 | version = "0.2.0" | |
| 10703 | 10703 | ||
| 10704 | 10704 | [[patch.unused]] | |
| 10705 | - | name = "quasi-store" | |
| 10705 | + | name = "kberg" | |
| 10706 | 10706 | version = "0.1.0" | |
| 10707 | 10707 | ||
| 10708 | 10708 | [[patch.unused]] | |
| 10709 | - | name = "quasi-tauri" | |
| 10709 | + | name = "painhours" | |
| 10710 | 10710 | version = "0.1.0" |
| @@ -159,7 +159,7 @@ | |||
| 159 | 159 | # rather than reached through quasi-router's re-export because a described | |
| 160 | 160 | # screen names FieldKind and Tone directly; it has to track what quasi-router | |
| 161 | 161 | # resolves or the two `layout::` paths are different crates. | |
| 162 | - | makeover-layout = "0.14.0" | |
| 162 | + | makeover-layout = "0.15.0" | |
| 163 | 163 | # For the request head the per-viewer state factory reads. axum re-exports it, | |
| 164 | 164 | # but the factory's signature is quasi-axum's and names `http::request::Parts`. | |
| 165 | 165 | http = "1.3.1" | |
| @@ -236,7 +236,7 @@ | |||
| 236 | 236 | # Emits static/geometry.css (makeover-geometry) and static/layout.css | |
| 237 | 237 | # (makeover-webview). The same generator GO and BB run; only the output paths | |
| 238 | 238 | # differ, since the server serves its stylesheets rather than bundling them. | |
| 239 | - | makeover-build = "0.20.1" | |
| 239 | + | makeover-build = "0.20.2" | |
| 240 | 240 | # Read directly for the embeds' own copy of the spacing layer, which they need | |
| 241 | 241 | # because an iframe cannot link a stylesheet. makeover-build does not re-export | |
| 242 | 242 | # it, so the pin lives here and has to track the one makeover-build resolves. |
| @@ -35,7 +35,10 @@ | |||
| 35 | 35 | //! lists and lost their column headers. **Closed** by quasi@`b4e3e21`: a | |
| 36 | 36 | //! table cell holds controls as well as a string, and both are tables again. | |
| 37 | 37 | //! 2. **No date field.** The token form's expiry was `<input type="date">` and | |
| 38 | - | //! is now `Text`. Still open. See [`add_token_form`]. | |
| 38 | + | //! had to be `Text` with a format hint. **Closed** by makeover-layout 0.15.0: | |
| 39 | + | //! `FieldKind::Date` and `FieldKind::DateTime`, admitted on the argument | |
| 40 | + | //! `Email` was, and the format named once as `layout::DATE_FORMAT`. See | |
| 41 | + | //! [`add_token_form`]. | |
| 39 | 42 | ||
| 40 | 43 | use makeover_layout as layout; | |
| 41 | 44 | use quasi_router::screen::{Act, Cell, Cells, Choice, Column, Field}; | |
| @@ -345,20 +348,21 @@ | |||
| 345 | 348 | ||
| 346 | 349 | /// The mint-a-token form. | |
| 347 | 350 | /// | |
| 348 | - | /// `expires_on` is a `Text` field and the Askama form spelled it | |
| 349 | - | /// `<input type="date">`. That is a real regression in the browser (no native | |
| 350 | - | /// picker, no platform validation) and it is a vocabulary gap rather than a | |
| 351 | - | /// choice: `layout::FieldKind` has `Email`, `Url` and `Tel`, each admitted for | |
| 352 | - | /// exactly the reason a date qualifies, and `Date` is the one common input type | |
| 353 | - | /// missing. Filed on quasicoherent; when it lands this becomes one word. | |
| 351 | + | /// `expires_on` was a `Text` field carrying a "YYYY-MM-DD" hint, against an | |
| 352 | + | /// Askama form that spelled it `<input type="date">`: no native picker, no | |
| 353 | + | /// platform validation, and the hint doing both jobs in prose. That was a | |
| 354 | + | /// vocabulary gap rather than a choice, and `layout::FieldKind::Date` closed it | |
| 355 | + | /// at makeover-layout 0.15.0. It did become one word, and the hint came out with | |
| 356 | + | /// it: the format is the description's now, `layout::DATE_FORMAT`, so saying it | |
| 357 | + | /// again here would be a second place for it to drift. | |
| 354 | 358 | fn add_token_form() -> Node { | |
| 355 | 359 | Node::Form { | |
| 356 | 360 | action: Action::post("/api/users/me/git-tokens"), | |
| 357 | 361 | submit: "Create Token".into(), | |
| 358 | 362 | fields: vec![ | |
| 359 | 363 | Field::new(layout::FieldKind::Text, "name", "Name").required(), | |
| 360 | - | Field::new(layout::FieldKind::Text, "expires_on", "Expires (optional)") | |
| 361 | - | .hint("YYYY-MM-DD. Leave blank for a token that does not expire."), | |
| 364 | + | Field::new(layout::FieldKind::Date, "expires_on", "Expires (optional)") | |
| 365 | + | .hint("Leave blank for a token that does not expire."), | |
| 362 | 366 | Field::new( | |
| 363 | 367 | layout::FieldKind::Checkbox, | |
| 364 | 368 | "can_push", |
| @@ -1,0 +1,4 @@ | |||
| 1 | + | target | |
| 2 | + | corpus | |
| 3 | + | artifacts | |
| 4 | + | coverage |
| @@ -1,0 +1,200 @@ | |||
| 1 | + | # This file is automatically @generated by Cargo. | |
| 2 | + | # It is not intended for manual editing. | |
| 3 | + | version = 4 | |
| 4 | + | ||
| 5 | + | [[package]] | |
| 6 | + | name = "arbitrary" | |
| 7 | + | version = "1.4.2" | |
| 8 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 9 | + | checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" | |
| 10 | + | dependencies = [ | |
| 11 | + | "derive_arbitrary", | |
| 12 | + | ] | |
| 13 | + | ||
| 14 | + | [[package]] | |
| 15 | + | name = "cc" | |
| 16 | + | version = "1.4.2" | |
| 17 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 18 | + | checksum = "5d262e149917187838d5b42777c8253bcb64500067342904e7d429499a6f277e" | |
| 19 | + | dependencies = [ | |
| 20 | + | "find-msvc-tools", | |
| 21 | + | "jobserver", | |
| 22 | + | "libc", | |
| 23 | + | "shlex", | |
| 24 | + | ] | |
| 25 | + | ||
| 26 | + | [[package]] | |
| 27 | + | name = "cfg-if" | |
| 28 | + | version = "1.0.4" | |
| 29 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 30 | + | checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" | |
| 31 | + | ||
| 32 | + | [[package]] | |
| 33 | + | name = "derive_arbitrary" | |
| 34 | + | version = "1.4.2" | |
| 35 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 36 | + | checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" | |
| 37 | + | dependencies = [ | |
| 38 | + | "proc-macro2", | |
| 39 | + | "quote", | |
| 40 | + | "syn", | |
| 41 | + | ] | |
| 42 | + | ||
| 43 | + | [[package]] | |
| 44 | + | name = "find-msvc-tools" | |
| 45 | + | version = "0.1.10" | |
| 46 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 47 | + | checksum = "26b73573e6edcd2af0cdf47bd6cb58f0b3839491263c314eaad1ccf24430e1de" | |
| 48 | + | ||
| 49 | + | [[package]] | |
| 50 | + | name = "getrandom" | |
| 51 | + | version = "0.4.3" | |
| 52 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 53 | + | checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" | |
| 54 | + | dependencies = [ | |
| 55 | + | "cfg-if", | |
| 56 | + | "libc", | |
| 57 | + | "r-efi", | |
| 58 | + | ] | |
| 59 | + | ||
| 60 | + | [[package]] | |
| 61 | + | name = "jobserver" | |
| 62 | + | version = "0.1.35" | |
| 63 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 64 | + | checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" | |
| 65 | + | dependencies = [ | |
| 66 | + | "getrandom", | |
| 67 | + | "libc", | |
| 68 | + | ] | |
| 69 | + | ||
| 70 | + | [[package]] | |
| 71 | + | name = "libc" | |
| 72 | + | version = "0.2.189" | |
| 73 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 74 | + | checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" | |
| 75 | + | ||
| 76 | + | [[package]] | |
| 77 | + | name = "libfuzzer-sys" | |
| 78 | + | version = "0.4.13" | |
| 79 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 80 | + | checksum = "a9fd2f41a1cba099f79a0b6b6c35656cf7c03351a7bae8ff0f28f25270f929d2" | |
| 81 | + | dependencies = [ | |
| 82 | + | "arbitrary", | |
| 83 | + | "cc", | |
| 84 | + | ] | |
| 85 | + | ||
| 86 | + | [[package]] | |
| 87 | + | name = "proc-macro2" | |
| 88 | + | version = "1.0.107" | |
| 89 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 90 | + | checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" | |
| 91 | + | dependencies = [ | |
| 92 | + | "unicode-ident", | |
| 93 | + | ] | |
| 94 | + | ||
| 95 | + | [[package]] | |
| 96 | + | name = "quote" | |
| 97 | + | version = "1.0.47" | |
| 98 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 99 | + | checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" | |
| 100 | + | dependencies = [ | |
| 101 | + | "proc-macro2", | |
| 102 | + | ] | |
| 103 | + | ||
| 104 | + | [[package]] | |
| 105 | + | name = "r-efi" | |
| 106 | + | version = "6.0.0" | |
| 107 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 108 | + | checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" | |
| 109 | + | ||
| 110 | + | [[package]] | |
| 111 | + | name = "regex-lite" | |
| 112 | + | version = "0.1.9" | |
| 113 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 114 | + | checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" | |
| 115 | + | ||
| 116 | + | [[package]] | |
| 117 | + | name = "shlex" | |
| 118 | + | version = "2.0.1" | |
| 119 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 120 | + | checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" | |
| 121 | + | ||
| 122 | + | [[package]] | |
| 123 | + | name = "subst" | |
| 124 | + | version = "0.1.0" | |
| 125 | + | dependencies = [ | |
| 126 | + | "regex-lite", | |
| 127 | + | ] | |
| 128 | + | ||
| 129 | + | [[package]] | |
| 130 | + | name = "subst-fuzz" | |
| 131 | + | version = "0.0.0" | |
| 132 | + | dependencies = [ | |
| 133 | + | "libfuzzer-sys", | |
| 134 | + | "subst", | |
| 135 | + | ] | |
| 136 | + | ||
| 137 | + | [[package]] | |
| 138 | + | name = "syn" | |
| 139 | + | version = "2.0.119" | |
| 140 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 141 | + | checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" | |
| 142 | + | dependencies = [ | |
| 143 | + | "proc-macro2", | |
| 144 | + | "quote", | |
| 145 | + | "unicode-ident", | |
| 146 | + | ] | |
| 147 | + | ||
| 148 | + | [[package]] | |
| 149 | + | name = "unicode-ident" | |
| 150 | + | version = "1.0.24" | |
| 151 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 152 | + | checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" | |
| 153 | + | ||
| 154 | + | [[patch.unused]] | |
| 155 | + | name = "kberg" | |
| 156 | + | version = "0.1.0" | |
| 157 | + | ||
| 158 | + | [[patch.unused]] | |
| 159 | + | name = "painhours" | |
| 160 | + | version = "0.1.0" | |
| 161 | + | ||
| 162 | + | [[patch.unused]] | |
| 163 | + | name = "tagtree" | |
| 164 | + | version = "0.4.0" | |
| 165 | + | ||
| 166 | + | [[patch.unused]] | |
| 167 | + | name = "quasi-axum" | |
| 168 | + | version = "0.1.0" | |
| 169 | + | ||
| 170 | + | [[patch.unused]] | |
| 171 | + | name = "quasi-http" | |
| 172 | + | version = "0.1.0" | |
| 173 | + | ||
| 174 | + | [[patch.unused]] | |
| 175 | + | name = "quasi-router" | |
| 176 | + | version = "0.1.0" | |
| 177 | + | ||
| 178 | + | [[patch.unused]] | |
| 179 | + | name = "quasi-store" | |
| 180 | + | version = "0.1.0" | |
| 181 | + | ||
| 182 | + | [[patch.unused]] | |
| 183 | + | name = "quasi-tauri" | |
| 184 | + | version = "0.1.0" | |
| 185 | + | ||
| 186 | + | [[patch.unused]] | |
| 187 | + | name = "quasi-webview" | |
| 188 | + | version = "0.1.0" | |
| 189 | + | ||
| 190 | + | [[patch.unused]] | |
| 191 | + | name = "synckit-client" | |
| 192 | + | version = "0.8.0" | |
| 193 | + | ||
| 194 | + | [[patch.unused]] | |
| 195 | + | name = "synckit-config" | |
| 196 | + | version = "0.2.0" | |
| 197 | + | ||
| 198 | + | [[patch.unused]] | |
| 199 | + | name = "docengine" | |
| 200 | + | version = "0.5.0" |
| @@ -1,0 +1,24 @@ | |||
| 1 | + | [package] | |
| 2 | + | name = "subst-fuzz" | |
| 3 | + | version = "0.0.0" | |
| 4 | + | publish = false | |
| 5 | + | edition = "2024" | |
| 6 | + | ||
| 7 | + | [package.metadata] | |
| 8 | + | cargo-fuzz = true | |
| 9 | + | ||
| 10 | + | [dependencies] | |
| 11 | + | # `arbitrary` with `derive` is what lets the target take `&str` instead of | |
| 12 | + | # `&[u8]`. Byte input would spend most of the fuzzer's budget rediscovering | |
| 13 | + | # UTF-8 before it ever reached the marker grammar. | |
| 14 | + | libfuzzer-sys = { version = "0.4", features = ["arbitrary-derive"] } | |
| 15 | + | ||
| 16 | + | [dependencies.subst] | |
| 17 | + | path = ".." | |
| 18 | + | ||
| 19 | + | [[bin]] | |
| 20 | + | name = "substitute" | |
| 21 | + | path = "fuzz_targets/substitute.rs" | |
| 22 | + | test = false | |
| 23 | + | doc = false | |
| 24 | + | bench = false |
| @@ -1,0 +1,100 @@ | |||
| 1 | + | //! Byte fuzz over the whole substitution surface. | |
| 2 | + | //! | |
| 3 | + | //! Row 6 of `astra-soak-overview`: creator templates reach this parser, so the | |
| 4 | + | //! boundary is hostile input and the failure cost is injection or a panic DoS. | |
| 5 | + | //! It is named there as the cheapest target in the tree, which is why it is the | |
| 6 | + | //! one that proves the soak loop before eight more harnesses are written. | |
| 7 | + | //! | |
| 8 | + | //! `substitute` is the entry point rather than `parse_expr`, which is | |
| 9 | + | //! `pub(crate)` and only half the surface. One call runs code-span detection, | |
| 10 | + | //! the marker regex, path lookup, the filter chain and the formatter, so | |
| 11 | + | //! fuzzing the public function covers the private parser and everything the | |
| 12 | + | //! parser hands off to. | |
| 13 | + | //! | |
| 14 | + | //! ## Oracles | |
| 15 | + | //! | |
| 16 | + | //! Not-panicking is the weakest thing a fuzz target can assert, and a target | |
| 17 | + | //! that asserts only that will report clean forever while returning wrong | |
| 18 | + | //! answers. Three real properties are checked besides: | |
| 19 | + | //! | |
| 20 | + | //! 1. **Code-span ranges are usable as byte ranges.** `code_span_ranges` is | |
| 21 | + | //! public API and its output is used to index the input. A range past the | |
| 22 | + | //! end, or one that splits a UTF-8 sequence, is a panic in every caller | |
| 23 | + | //! rather than in this crate. | |
| 24 | + | //! 2. **Text with no marker is returned unchanged.** The engine's whole | |
| 25 | + | //! contract is that it touches `{{ ... }}` and nothing else. | |
| 26 | + | //! 3. **Substitution reaches a fixed point.** Output that still contains a | |
| 27 | + | //! resolvable marker would mean a value could inject a further | |
| 28 | + | //! substitution, which is the injection half of the failure cost. | |
| 29 | + | ||
| 30 | + | #![no_main] | |
| 31 | + | ||
| 32 | + | use libfuzzer_sys::fuzz_target; | |
| 33 | + | use subst::{Substituter, Value, code_span_ranges}; | |
| 34 | + | ||
| 35 | + | /// A table with one value per `Value` shape, so the filter chain is reachable. | |
| 36 | + | /// | |
| 37 | + | /// Built per call rather than once in a `static`: a `Substituter` holds boxed | |
| 38 | + | /// trait objects, and a target that shares mutable state between runs stops | |
| 39 | + | /// being reproducible from its input alone, which is what makes a crash | |
| 40 | + | /// artifact worth keeping. | |
| 41 | + | fn substituter() -> Substituter { | |
| 42 | + | Substituter::new() | |
| 43 | + | .with_value("price.basic", Value::Int(16)) | |
| 44 | + | .with_value("stripe.percent", Value::Float(0.029)) | |
| 45 | + | .with_value("brand.name", Value::String("Make Creative".into())) | |
| 46 | + | .with_value("nested.deep.path.here", Value::Int(-1)) | |
| 47 | + | // No value here contains `{{`, and that is load-bearing for oracle 3. | |
| 48 | + | // | |
| 49 | + | // A value whose own text is a marker breaks the fixed point trivially: | |
| 50 | + | // one pass yields the marker, a second resolves it. That says nothing | |
| 51 | + | // about this crate, because the untrusted input on this path is the | |
| 52 | + | // TEMPLATE, not the table -- `astra-soak-overview` row 6 is creator | |
| 53 | + | // templates, and callers populate values from config they control. The | |
| 54 | + | // first version of this target planted such a value and the fuzzer | |
| 55 | + | // reported it in under a minute, which was the oracle being wrong rather | |
| 56 | + | // than the engine. | |
| 57 | + | // | |
| 58 | + | // Whether hostile values should also be inert is a real question and a | |
| 59 | + | // different one. It needs the caller contract stated first, and if it is | |
| 60 | + | // ever answered "yes" it wants its own target rather than a weakened | |
| 61 | + | // oracle here. | |
| 62 | + | } | |
| 63 | + | ||
| 64 | + | fuzz_target!(|text: &str| { | |
| 65 | + | // Oracle 1. Checked before substitute, because substitute consumes these | |
| 66 | + | // ranges and a bad one panics there instead of here, where the message | |
| 67 | + | // would say which invariant broke. | |
| 68 | + | let ranges = code_span_ranges(text); | |
| 69 | + | let mut prev_end = 0usize; | |
| 70 | + | for &(start, end) in &ranges { | |
| 71 | + | assert!(start <= end, "inverted code span {start}..{end}"); | |
| 72 | + | assert!(end <= text.len(), "code span {start}..{end} past len {}", text.len()); | |
| 73 | + | assert!( | |
| 74 | + | text.is_char_boundary(start) && text.is_char_boundary(end), | |
| 75 | + | "code span {start}..{end} splits a UTF-8 sequence" | |
| 76 | + | ); | |
| 77 | + | assert!(start >= prev_end, "code spans overlap or are unsorted at {start}"); | |
| 78 | + | prev_end = end; | |
| 79 | + | // The range is what callers slice with. Prove it. | |
| 80 | + | let _ = &text[start..end]; | |
| 81 | + | } | |
| 82 | + | ||
| 83 | + | let s = substituter(); | |
| 84 | + | let Ok(out) = s.substitute(text) else { | |
| 85 | + | // An unresolved marker is the documented error, not a defect. Nothing | |
| 86 | + | // further is promised about the output in that case. | |
| 87 | + | return; | |
| 88 | + | }; | |
| 89 | + | ||
| 90 | + | // Oracle 2. | |
| 91 | + | if !text.contains("{{") { | |
| 92 | + | assert_eq!(out, text, "text with no marker was modified"); | |
| 93 | + | } | |
| 94 | + | ||
| 95 | + | // Oracle 3. A second pass over the output must be a no-op: `substitute` is | |
| 96 | + | // not recursive by design, so anything it produced must be inert. | |
| 97 | + | if let Ok(twice) = s.substitute(&out) { | |
| 98 | + | assert_eq!(twice, out, "substitution is not a fixed point"); | |
| 99 | + | } | |
| 100 | + | }); |
| @@ -1,0 +1,24 @@ | |||
| 1 | + | # Seed corpora | |
| 2 | + | ||
| 3 | + | Hand-written starting inputs, one directory per fuzz target. These are | |
| 4 | + | committed; `corpus/` is not. | |
| 5 | + | ||
| 6 | + | The split is deliberate and follows `astra-soak-overview`, which calls a | |
| 7 | + | minimized corpus "accumulated compute, not a build artifact". Both halves of | |
| 8 | + | that sentence decide something: | |
| 9 | + | ||
| 10 | + | - **These seeds are human intent.** Each one names a shape of the grammar worth | |
| 11 | + | reaching in the first second rather than the first hour: a filter chain, a | |
| 12 | + | fenced code block, an unbalanced quote. They are reviewable in a diff, they | |
| 13 | + | do not churn, and they are what a fresh machine needs to start somewhere | |
| 14 | + | better than random bytes. | |
| 15 | + | - **`corpus/` is machine output and lives on astra**, under the soak runner's | |
| 16 | + | persistent directory, where it accumulates across restarts. It is worth | |
| 17 | + | committing only once it represents real soak hours; a corpus minimized after | |
| 18 | + | two minutes of local fuzzing is neither accumulated nor compute. Minimize | |
| 19 | + | with `cargo +nightly fuzz cmin <target>` and commit it when that day comes, | |
| 20 | + | not before. | |
| 21 | + | ||
| 22 | + | Run a target against these on a machine with no corpus: | |
| 23 | + | ||
| 24 | + | cargo +nightly fuzz run substitute seeds/substitute |
| @@ -1,0 +1,1 @@ | |||
| 1 | + | Basic is ${{ price.basic }} ({{ stripe.percent | percent }} fee). |
| @@ -1,0 +1,1 @@ | |||
| 1 | + | {{ brand.name | upper }} |
| @@ -1,0 +1,1 @@ | |||
| 1 | + | {{ price.basic | money("$") }} |