AtlatestRepositoryfolio
1
;;; Differential and timing check for a disposable Markdown corpus.2
;;; Run with FOLIO_CORPUS pointing to a private copy, never a live store.3
(import (sigil fs)4
(sigil string)5
(sigil list)6
(sigil time)7
(sigil process)8
(sigil sqlite)9
(sigil mcp server)10
(sigil mcp protocol)11
(folio store)12
(folio note)13
(folio project)14
(folio links)15
(folio index)16
(folio query)17
(folio tools))19
(define root (getenv "FOLIO_CORPUS"))20
(unless root (error "FOLIO_CORPUS must name a disposable corpus copy"))21
(define store (make-folio-store root))23
(define (cpu-ms)24
(/ (string->number25
(car (string-split (read-file-string "/proc/self/schedstat") " ")))26
1000000.0))28
(define (wall-ms)29
(* 1000.0 (/ (current-jiffy) (jiffies-per-second))))31
(define (measure label thunk)32
(let* ((cpu (cpu-ms))33
(wall (wall-ms))34
(value (thunk))35
(elapsed (- (wall-ms) wall))36
(used (- (cpu-ms) cpu)))37
(display (format "~a wall_ms=~a cpu_ms=~a\n" label elapsed used))38
(flush-output-port (current-output-port))39
value))41
(define notes (measure "oracle_load_notes" (lambda () (all-notes store))))42
(define names (map folio-note-name notes))43
(define incoming (dict))45
;; This is the old source traversal, executed once for all targets: a source46
;; contributes at most one backlink for each literal target, in traversal order.47
(define (record-source! name kind targets)48
(let ((seen (dict)))49
(for-each50
(lambda (target)51
(unless (dict-contains? seen target)52
(set! seen (dict-set seen target #t))53
(set! incoming54
(dict-set incoming target55
(cons (cons name kind) (dict-ref incoming target '()))))))56
targets)))58
(for-each59
(lambda (note)60
(record-source! (folio-note-name note) 'note (note-outgoing-links note)))61
notes)62
(for-each63
(lambda (project)64
(record-source! (folio-project-name project) 'project65
(extract-crosslinks (read-file-string (folio-project-file-path project)))))66
(all-projects store))68
(define (oracle-backlinks name)69
(reverse (dict-ref incoming name '())))71
;; Validate the amortized oracle against the actual old implementation too.72
(define sample-names73
(let* ((count (min 25 (length names)))74
(stride (max 1 (quotient (length names) (max 1 count)))))75
(let loop ((i 0) (out '()))76
(if (= i count)77
(reverse out)78
(loop (+ i 1) (cons (list-ref names (* i stride)) out))))))80
(define (percentile samples p)81
(list-ref (list-sort < samples)82
(max 0 (- (quotient (+ (* p (length samples)) 99) 100) 1))))84
(define (benchmark label lookup expected)85
(let loop ((rest sample-names) (walls '()) (cpus '()))86
(if (null? rest)87
(begin88
(display89
(format "~a n=~a wall_p50_ms=~a wall_p95_ms=~a cpu_p50_ms=~a cpu_p95_ms=~a\n"90
label (length walls) (percentile walls 50) (percentile walls 95)91
(percentile cpus 50) (percentile cpus 95)))92
(flush-output-port (current-output-port)))93
(let* ((cpu (cpu-ms))94
(wall (wall-ms))95
(actual (lookup (car rest)))96
(elapsed (- (wall-ms) wall))97
(used (- (cpu-ms) cpu)))98
(unless (equal? actual (expected (car rest)))99
(error "Benchmark backlink mismatch" (car rest)))100
(loop (cdr rest) (cons elapsed walls) (cons used cpus))))))102
(define (old-note-links-output note backlinks)103
(let* ((name (folio-note-name note))104
(tasks (note-tasks note))105
(outgoing (all-links store (folio-note-body note))))106
(string-append107
(format "Links for ~a:\n\n" name)108
(if (null? tasks)109
""110
(string-append " Tasks: "111
(string-join (map (lambda (task) (format "~a" task)) tasks) ", ") "\n"))112
(if (null? outgoing)113
" Outgoing: none\n"114
(string-append " Outgoing:\n"115
(string-join116
(map (lambda (link) (format " → ~a (~a)" (car link) (cadr link))) outgoing)117
"\n") "\n"))118
"\n"119
(if (null? backlinks)120
" Backlinks: none\n"121
(string-append " Backlinks:\n"122
(string-join123
(map (lambda (link) (format " ← ~a (~a)" (car link) (cdr link))) backlinks)124
"\n") "\n")))))126
(define (expected-output name)127
(old-note-links-output128
(read-note (find-note-file store name) (store-notes-dir store))129
(oracle-backlinks name)))131
(unless (getenv "FOLIO_SKIP_OLD_BENCHMARK")132
(benchmark "before_backlinks" (lambda (name) (find-all-backlinks store name)) oracle-backlinks)133
(benchmark "before_note_links"134
(lambda (name)135
(old-note-links-output136
(read-note (find-note-file store name) (store-notes-dir store))137
(find-all-backlinks store name)))138
expected-output))139
(define idx (measure "open_index" (lambda () (make-folio-index store))))140
(measure "first_reconcile" (lambda () (index-reconcile! idx)))141
(benchmark "after_backlinks" (lambda (name) (index-backlinks idx name)) oracle-backlinks)143
(define server (mcp-server name: "corpus-check"))144
(register-folio-tools! server store idx)145
(define (tool-text name args)146
(let ((response147
(mcp-server-handle-message server148
(jsonrpc-request id: 1 method: "tools/call"149
params: (dict name: name arguments: args)))))150
(unless (jsonrpc-response? response) (error "Tool returned an error" name))151
;; Include the same JSON serialization the stdio transport performs.152
(response->json response)153
(cdr (assoc 'text154
(car (cdr (assoc 'content (jsonrpc-response-result response))))))))156
(benchmark "after_note_links"157
(lambda (name) (tool-text "folio/note-links" (dict name: name)))158
expected-output)160
(measure "all_note_backlinks_differential"161
(lambda ()162
(for-each163
(lambda (name)164
(unless (equal? (oracle-backlinks name) (index-backlinks idx name))165
(error "Differential backlink mismatch" name)))166
names)))167
(display (format "DIFFERENTIAL_OK notes=~a\n" (length names)))169
(measure "all_note_links_output_differential"170
(lambda ()171
(for-each172
(lambda (note)173
(let ((name (folio-note-name note)))174
(unless (equal? (old-note-links-output note (oracle-backlinks name))175
(tool-text "folio/note-links" (dict name: name)))176
(error "Differential note-links output mismatch" name))))177
notes)))178
(display (format "OUTPUT_DIFFERENTIAL_OK notes=~a\n" (length names)))180
(measure "status_tool"181
(lambda ()182
(let ((text (tool-text "folio/status" (dict))))183
(unless (string-starts-with? text "# Folio Status")184
(error "Status did not complete successfully"))185
(display (format "status_response_chars=~a\n" (string-length text))))))187
(define previous-orphans188
(map folio-note-name (find-orphaned-notes store notes)))189
(define indexed-orphans (index-orphan-names idx))190
(unless (equal? (list-sort string<? previous-orphans)191
(list-sort string<? indexed-orphans))192
(error "Differential orphan mismatch"))193
(display "ORPHAN_DIFFERENTIAL_OK\n")194
(sqlite-close (folio-index-db idx))195
(define reopened (measure "reopen_index" (lambda () (make-folio-index store))))196
(measure "persisted_reconcile" (lambda () (index-reconcile! reopened)))197
(benchmark "reopened_backlinks" (lambda (name) (index-backlinks reopened name)) oracle-backlinks)198
(sqlite-close (folio-index-db reopened))199
(display "CORPUS_CHECK_OK\n")