AtlatestRepositoryfolio

folio / tree / testtest-extent-regressions.sgl

1;;; Regression fixtures from the WITHDRAWN 2026-08-02 block-parsing attempt.
2;;;
3;;; Two adversarial review rounds found sixteen data-loss defects in it, and
4;;; round two's fixes introduced four more of the same character. The four
5;;; named round-two regressions each get a fixture here, plus the destructive
6;;; shapes from the sixteen-defect table that apply to the redesign. These
7;;; are the known-bad inputs; every one destroyed (or hid) data under the
8;;; withdrawn code and must be inert under the syntactic-extent model.
9;;;
10;;; See archive/tasks/folio-inbox-integrity-and-bulk-moves ("Handoff to the
11;;; redesign worker") for the full classified table.
13(import (sigil test)
14 (sigil struct)
15 (sigil string)
16 (sigil list)
17 (sigil dict)
18 (sigil fs)
19 (sigil path)
20 (folio store)
21 (folio task)
22 (folio id)
23 (folio inbox)
24 (folio project)
25 (folio tools))
27(define (write-inbox! store content)
28 (write-file-string (store-inbox-path store) content))
30(define (inbox-content store)
31 (read-file-string (store-inbox-path store)))
33(test-group "named defect 1: #Notes deletion"
35 ;; Withdrawn round two: requiring a space after `#` fixed `#infra` but made
36 ;; `#Notes` a non-heading -- and removing ONE inbox item then deleted the
37 ;; #Notes section and both lines under it (5 lines -> `# Inbox`).
38 (test "removing an item leaves every other line BYTE-IDENTICAL"
39 (call-with-temp-directory
40 (lambda (dir)
41 (let ((store (make-folio-store dir)))
42 (write-inbox! store
43 (string-join
44 (list "# Inbox"
45 "- [ ] the item {id: t-aaaa}"
46 "#Notes"
47 "remember the milk"
48 "remember the eggs")
49 "\n"))
50 (assert-true (inbox-remove-by-id! store "t-aaaa"))
51 ;; not "the section survives" -- the file must be EXACTLY the
52 ;; original minus the removed item's one line. A weaker assertion
53 ;; would have passed the withdrawn attempt's bug (file reduced to
54 ;; `# Inbox`) had it merely checked the item was gone.
55 (assert-equal
56 (string-join
57 (list "# Inbox"
58 "#Notes"
59 "remember the milk"
60 "remember the eggs")
61 "\n")
62 (inbox-content store))))))
64 ;; The dual: `#infra #urgent` as a hashtag body line. Under the withdrawn
65 ;; heading heuristic it split the block; here an UNINDENTED hashtag line
66 ;; belongs to no task and cannot be removed by a task operation, while an
67 ;; INDENTED one is body and travels with its item.
68 (test "an unindented hashtag line survives an adjacent removal"
69 (call-with-temp-directory
70 (lambda (dir)
71 (let ((store (make-folio-store dir)))
72 (write-inbox! store
73 (string-join
74 (list "# Inbox"
75 "- [ ] the item {id: t-aaab}"
76 "#infra #urgent")
77 "\n"))
78 (assert-true (inbox-remove-by-id! store "t-aaab"))
79 (assert-true (string-contains? (inbox-content store)
80 "#infra #urgent")))))))
82(test-group "named defect 4: foreign ids are not folio's to touch"
84 ;; Withdrawn round two: a loosened backfill header-check DELETED
85 ;; `{id: JIRA-421}` from disk on a plain listing.
86 (test "a task with a foreign id survives a listing byte-identically"
87 (call-with-temp-directory
88 (lambda (dir)
89 (let ((store (make-folio-store dir)))
90 (write-inbox! store "# Inbox\n- [ ] import the tracker {id: JIRA-421}\n")
91 (let ((before (inbox-content store)))
92 ((make-tool-inbox store) (dict))
93 (assert-equal before (inbox-content store)))))))
95 ;; Withdrawn round one, defect 1: `adopt-trailing-metadata` DELETED the
96 ;; body line `upstream {id: 3, port: 80}` from disk when the inbox was
97 ;; merely LISTED. Body lines are never metadata-parsed now.
98 (test "a config fragment in a body survives a listing byte-identically"
99 (call-with-temp-directory
100 (lambda (dir)
101 (let ((store (make-folio-store dir)))
102 (inbox-add! store
103 (string-join
104 (list "proxy setup notes"
105 "upstream {id: 3, port: 80}"
106 "keep this verbatim")
107 "\n")
108 '((id . "t-abab")))
109 (let ((before (inbox-content store))
110 (out ((make-tool-inbox store) (dict))))
111 (assert-equal before (inbox-content store))
112 ;; and the fragment is not reported as a burial
113 (assert-false (string-contains? out "INTEGRITY"))
114 ;; the body round-trips
115 (assert-true (string-contains?
116 (folio-task-text (car (read-inbox store)))
117 "upstream {id: 3, port: 80}")))))))
119 (test "a config fragment survives COMPLETING its own task"
120 (call-with-temp-directory
121 (lambda (dir)
122 (let ((store (make-folio-store dir)))
123 (inbox-add! store "proxy notes\nupstream {id: 3, port: 80}"
124 '((id . "t-abac")))
125 (complete-task-in-store! store "t-abac")
126 (let ((task (car (read-inbox store))))
127 (assert-true (folio-task-done? task))
128 (assert-true (string-contains? (folio-task-text task)
129 "upstream {id: 3, port: 80}"))))))))
131(test-group "buried legacy shapes freeze normalisation instead of minting"
133 ;; The exact mechanism of every burial: a headless first line above a
134 ;; body whose LAST line carries the id. backfill must not mint a phantom
135 ;; onto the head; the listing must report the burial instead.
136 (test "a legacy buried item is reported, and the file is not rewritten"
137 (call-with-temp-directory
138 (lambda (dir)
139 (let ((store (make-folio-store dir)))
140 (write-inbox! store
141 (string-join
142 (list "# Inbox"
143 "- [ ] tighten sigil test discovery"
144 ""
145 "Current behavior: it walks into deps."
146 ""
147 "Cross-link the observation. {id: t-5506, tags: sigil}")
148 "\n"))
149 (let* ((before (inbox-content store))
150 (out ((make-tool-inbox store) (dict))))
151 ;; loud: the burial is named with its id
152 (assert-true (string-contains? out "INTEGRITY"))
153 (assert-true (string-contains? out "t-5506"))
154 ;; frozen: no phantom mint, no rewrite of any kind
155 (assert-equal before (inbox-content store)))))))
157 ;; Named defect 2 (task-id? on both sides): an UPPERCASE-hex id was
158 ;; invisible to the withdrawn check because a predicate filtered both
159 ;; sides of its comparison. Buried uppercase must be reported.
160 (test "a buried UPPERCASE id is reported"
161 (call-with-temp-directory
162 (lambda (dir)
163 (let ((store (make-folio-store dir)))
164 (write-inbox! store
165 "# Inbox\nlost prose capture. {id: t-A8FD, priority: high}\n")
166 (let ((out ((make-tool-inbox store) (dict))))
167 (assert-true (string-contains? out "INTEGRITY"))
168 (assert-true (string-contains? out "t-A8FD")))))))
170 ;; Named defect 3 (hard-coded needle): `{id:t-bbbb}` -- no space after the
171 ;; colon -- is valid to the parser and was invisible to the withdrawn
172 ;; scanner. Buried, it must be reported.
173 (test "a buried no-space id is reported"
174 (call-with-temp-directory
175 (lambda (dir)
176 (let ((store (make-folio-store dir)))
177 (write-inbox! store "# Inbox\nlost prose capture. {id:t-bbbb}\n")
178 (let ((out ((make-tool-inbox store) (dict))))
179 (assert-true (string-contains? out "INTEGRITY"))
180 (assert-true (string-contains? out "t-bbbb"))))))))
182(test-group "tool-call markup is refused at the door"
184 ;; Measured live 2026-08-06: a malformed client tool call leaked
185 ;; `...</text>` and `<parameter name="priority">high` INTO the stored
186 ;; text, filing the capture under a phantom id with the real id buried
187 ;; (t-c52ae8; again minutes later as t-4ec418). folio cannot fix the
188 ;; client; it can refuse to store the wreckage.
189 (test "inbox-add with leaked markup stores NOTHING and says why"
190 (call-with-temp-directory
191 (lambda (dir)
192 (let ((store (make-folio-store dir)))
193 (inbox-ensure! store)
194 (let* ((before (inbox-content store))
195 (out ((make-tool-inbox-add store)
196 (dict text: "web-client-update returns #t for ... </text>\n<parameter name=\"priority\">high"))))
197 (assert-true (string-contains? out "REFUSING"))
198 (assert-true (string-contains? out "tool-call markup"))
199 (assert-equal before (inbox-content store)))))))
201 (test "task-add with leaked markup stores NOTHING"
202 (call-with-temp-directory
203 (lambda (dir)
204 (let* ((store (make-folio-store dir))
205 (file (project-create! store "Target" "Goal" #f))
206 (out ((make-tool-task-add store)
207 (dict text: "broken </parameter> capture"
208 project: "target"))))
209 (assert-true (string-contains? out "REFUSING"))
210 (assert-equal 0 (length (folio-project-tasks (read-project file))))))))
212 (test "task-update with leaked markup leaves the task unchanged"
213 (call-with-temp-directory
214 (lambda (dir)
215 (let ((store (make-folio-store dir)))
216 (inbox-add! store "clean item" '((id . "t-cc01")))
217 (let ((out ((make-tool-task-update store)
218 (dict id: "t-cc01"
219 text: "mangled <parameter name=\"project\">enclave"))))
220 (assert-true (string-contains? out "REFUSING"))
221 (assert-equal "clean item"
222 (folio-task-text (car (read-inbox store)))))))))
224 ;; positive control: the refusal is NARROW -- ordinary angle brackets,
225 ;; XML-ish prose and html tags are all legitimate capture content.
226 (test "ordinary markup-ish text is still accepted"
227 (call-with-temp-directory
228 (lambda (dir)
229 (let* ((store (make-folio-store dir))
230 (out ((make-tool-inbox-add store)
231 (dict text: "fix <div class=\"x\"> rendering and the <text> element handling"))))
232 (assert-true (string-contains? out "Added to inbox"))
233 (assert-equal 1 (length (read-inbox store))))))))
235(test-group "extent table: rewrite and removal shapes"
237 ;; Withdrawn defect 14: backfill DE-INDENTED a nested item on rewrite.
238 (test "an indented header keeps its indentation through complete/reopen"
239 (call-with-temp-directory
240 (lambda (dir)
241 (let ((store (make-folio-store dir)))
242 (write-inbox! store
243 (string-join
244 (list "# Inbox"
245 "grouping prose"
246 " - [ ] nested child {id: t-cccc}")
247 "\n"))
248 (complete-task-in-store! store "t-cccc")
249 (assert-true (string-contains? (inbox-content store)
250 " - [x] nested child"))
251 (uncomplete-task-in-store! store "t-cccc")
252 (assert-true (string-contains? (inbox-content store)
253 " - [ ] nested child"))))))
255 ;; Withdrawn defect 3: task-update on multi-line text truncated it to the
256 ;; first line. New text (of any line count) must replace the whole block.
257 (test "task-update with multi-line text replaces the block, loses nothing"
258 (call-with-temp-directory
259 (lambda (dir)
260 (let ((store (make-folio-store dir)))
261 (inbox-add! store "old head\nold body" '((id . "t-dddd")))
262 (inbox-add! store "neighbour" '((id . "t-eeee")))
263 (update-task-in-store! store "t-dddd"
264 (dict id: "t-dddd"
265 text: "new head\nnew body line 1\n\nnew body line 2"))
266 (let ((tasks (read-inbox store)))
267 (assert-equal 2 (length tasks))
268 (assert-equal "new head\nnew body line 1\n\nnew body line 2"
269 (folio-task-text (car tasks)))
270 (assert-equal "neighbour" (folio-task-text (cadr tasks))))))))
272 ;; Withdrawn defect 7/#neighbour class: removing one item must not disturb
273 ;; the body of the item next to it.
274 (test "removal takes exactly one block; the neighbour's body is intact"
275 (call-with-temp-directory
276 (lambda (dir)
277 (let ((store (make-folio-store dir)))
278 (inbox-add! store "first item\nfirst body" '((id . "t-f001")))
279 (inbox-add! store "second item\nsecond body" '((id . "t-f002")))
280 (assert-true (inbox-remove-by-id! store "t-f001"))
281 (let ((tasks (read-inbox store)))
282 (assert-equal 1 (length tasks))
283 (assert-equal "second item\nsecond body"
284 (folio-task-text (car tasks))))
285 (assert-false (string-contains? (inbox-content store) "first body"))))))
287 ;; Withdrawn defect 5: a move whose id matched only the `## Log` archive
288 ;; copy ate the narrative around it. An archived copy is a small indented
289 ;; block; moving it takes that block and nothing else.
290 (test "moving a log-archived id leaves the surrounding narrative alone"
291 (call-with-temp-directory
292 (lambda (dir)
293 (let* ((store (make-folio-store dir))
294 (src (project-create! store "Source" "Goal" #f))
295 (dst (project-create! store "Target" "Goal" #f)))
296 ;; construct the post-clean shape by hand
297 (write-file-string src
298 (string-join
299 (list "---"
300 "status: active"
301 "---"
302 ""
303 "# Source"
304 ""
305 "## Tasks"
306 ""
307 "## Log"
308 ""
309 "### 2026-08-01"
310 ""
311 "Completed:"
312 " - [x] archived thing {id: t-f003, completed: 2026-08-01}"
313 "Narrative before."
314 "Narrative after.")
315 "\n"))
316 (move-task-in-store! store "t-f003" "target")
317 (let ((content (read-file-string src)))
318 (assert-true (string-contains? content "Narrative before."))
319 (assert-true (string-contains? content "Narrative after."))
320 (assert-false (string-contains? content "t-f003"))))))))