AtlatestRepositoryfolio
1
;;; RED ARM for the extent-syntactic redesign (t-a6ba).2
;;;3
;;; A multi-line capture must round-trip: added, listed, moved, completed, and4
;;; read back identically. This is the case that has never worked: on the old5
;;; writer the {id:} metadata landed on the capture's LAST line, the6
;;; line-oriented parser saw only the headless first line, and `backfill-ids!`7
;;; then minted a phantom id onto it. One capture became two tasks, one of them8
;;; a phantom, and the real body went invisible while `folio_status` reported9
;;; the inbox empty. Seven items were buried this way between April and August10
;;; 2026; the seventh (t-a979a6) hid a live CI blocker.11
;;;12
;;; The fixture below is built from the ACTUAL seventh burial: the same13
;;; paragraph structure as the t-a979a6 capture recovered from ~/Ops git14
;;; history (3d96327f~1:folio/inbox.md), abridged but shape-identical —15
;;; a first line, blank-separated numbered paragraphs, a closing cross-ref16
;;; line. These tests were committed and demonstrated FAILING against the17
;;; pre-redesign writer/parser before any implementation code was written.19
(import (sigil test)20
(sigil struct)21
(sigil string)22
(sigil list)23
(sigil dict)24
(sigil fs)25
(sigil path)26
(folio store)27
(folio task)28
(folio id)29
(folio inbox)30
(folio project)31
(folio tools))33
;; The t-a979a6 burial shape, abridged. Multi-line, paragraphs separated by34
;; blank lines — exactly what folio_inbox-add receives for a real capture.35
(define burial-text36
(string-join37
(list38
"TWO OBLIGATIONS ON WHOEVER CLOSES THIS, both in jobs/enclave.run:"39
""40
"(1) RE-MEASURE AND TIGHTEN THE JOB TIMEOUT. The 2400s margin covers a"41
"step that has never completed, and that reason is spent once this lands."42
""43
"(2) REMOVE THE XFAIL EXEMPTION. It will XPASS loudly when this starts"44
"passing — that XPASS is the signal to delete the exemption."45
""46
"See tasks/kiln-enclave-job-with-pwa-gates and t-c3d0b1 for the family.")47
"\n"))49
;; Count raw occurrences of a needle in a string.50
(define (count-occurrences hay needle)51
(let loop ((from 0) (n 0))52
(let ((hit (string-find hay needle from)))53
(if hit54
(loop (+ hit (string-length needle)) (+ n 1))55
n))))57
(test-group "extent-syntactic red arm: multi-line capture round-trips"59
(test "added then read back: one task, the SUPPLIED id, the FULL text"60
(call-with-temp-directory61
(lambda (dir)62
(let* ((store (make-folio-store dir))63
(added (inbox-add! store burial-text '((id . "t-a979a6"))))64
(tasks (read-inbox store)))65
(assert-equal 1 (length tasks))66
(assert-equal "t-a979a6" (folio-task-id (car tasks)))67
(assert-equal burial-text (folio-task-text (car tasks)))))))69
(test "the read path's backfill does not mint a phantom id onto the capture"70
(call-with-temp-directory71
(lambda (dir)72
(let* ((store (make-folio-store dir)))73
(inbox-add! store burial-text '((id . "t-a979a6")))74
;; the exact mechanism of all ten burials: backfill runs on read75
(backfill-ids! (store-inbox-path store))76
(let ((content (read-file-string (store-inbox-path store))))77
;; exactly ONE id on disk — a second one is the phantom78
(assert-equal 1 (count-occurrences content "id: t-")))))))80
(test "moved to a project: the whole item travels, nothing stays behind"81
(call-with-temp-directory82
(lambda (dir)83
(let* ((store (make-folio-store dir))84
(file (project-create! store "Kiln" "CI" #f)))85
(inbox-add! store burial-text '((id . "t-a979a6")))86
(let ((result (move-task-in-store! store "t-a979a6" "kiln")))87
(assert-true (string-contains? result "Moved"))88
;; the inbox no longer contains ANY fragment of the capture89
(let ((inbox-content (read-file-string (store-inbox-path store))))90
(assert-false (string-contains? inbox-content "OBLIGATIONS"))91
(assert-false (string-contains? inbox-content "XFAIL"))92
(assert-false (string-contains? inbox-content "t-a979a6")))93
;; and the project holds the identical task94
(let ((landed (folio-project-tasks (read-project file))))95
(assert-equal 1 (length landed))96
(assert-equal "t-a979a6" (folio-task-id (car landed)))97
(assert-equal burial-text (folio-task-text (car landed)))))))))99
(test "completed in place: done flips, id and full text intact"100
(call-with-temp-directory101
(lambda (dir)102
(let* ((store (make-folio-store dir)))103
(inbox-add! store burial-text '((id . "t-a979a6")))104
(complete-task-in-store! store "t-a979a6")105
(let ((tasks (read-inbox store)))106
(assert-equal 1 (length tasks))107
(assert-true (folio-task-done? (car tasks)))108
(assert-equal "t-a979a6" (folio-task-id (car tasks)))109
(assert-equal burial-text (folio-task-text (car tasks))))))))111
(test "a body containing checkbox-looking lines stays ONE item"112
(call-with-temp-directory113
(lambda (dir)114
(let* ((store (make-folio-store dir))115
(text (string-join116
(list "Close out the enclave job"117
"Acceptance criteria:"118
"- [ ] timeout re-measured"119
"- [ ] xfail exemption removed")120
"\n")))121
(inbox-add! store text '((id . "t-c3d0b1")))122
(let ((tasks (read-inbox store)))123
(assert-equal 1 (length tasks))124
(assert-equal "t-c3d0b1" (folio-task-id (car tasks)))125
(assert-equal text (folio-task-text (car tasks))))))))127
(test "visible count agrees with raw id count on a mixed corpus"128
(call-with-temp-directory129
(lambda (dir)130
(let* ((store (make-folio-store dir)))131
(inbox-add! store "plain one-liner" '((id . "t-000001")))132
(inbox-add! store burial-text '((id . "t-a979a6")))133
(inbox-add! store "another one-liner" '((id . "t-000002")))134
(let* ((tasks (read-inbox store))135
(content (read-file-string (store-inbox-path store)))136
(raw (count-occurrences content "id: t-")))137
;; the acceptance criterion: folio's count and grep's count agree138
(assert-equal raw (length tasks))139
(assert-equal 3 (length tasks))140
;; and agree about WHICH ids, not merely how many: a count can141
;; match while a headless burial is counted under a phantom id142
;; (this arm was GREEN on the pre-redesign code when it compared143
;; counts alone — the ambiguous-green trap, made discriminating)144
(assert-equal '("t-000001" "t-a979a6" "t-000002")145
(map folio-task-id tasks))))))))