AtlatestRepositoryfolio
1
;;; F2a span manifest -- the descriptor's spans, emitted as flat text so that a2
;;; verifier written in ANOTHER LANGUAGE can check them against the manuscript's3
;;; raw bytes.4
;;;5
;;; WHY A MANIFEST AND NOT AN ASSERTION.6
;;;7
;;; The acceptance bar for this phase is that span exactness is MEASURED AGAINST8
;;; SOURCE BYTES, not against the parse tree the spans came from. Any check9
;;; written here, in this language, against this descriptor, shares the10
;;; descriptor's reader, the descriptor's coordinate arithmetic and the11
;;; descriptor's idea of where things are -- so it agrees with itself and proves12
;;; nothing. That is taxonomy shape 13 in its real form: not "we did not test13
;;; enough" but "every instrument we pointed at it had the same blind spot".14
;;;15
;;; So this file only REPORTS. `test/f2a-span-verify.py` opens the manuscript in16
;;; BINARY mode, slices `bytes[start:start+length]`, recomputes every offset from17
;;; the file itself, and decides. It shares no code, no reader and no arithmetic18
;;; with anything here.19
;;;20
;;; This file also does not use `(sigil test)`: a direct `sigil <file>` run21
;;; REGISTERS `test` forms and never executes them -- measured, `1 = 2` printed22
;;; nothing and exited 0. Everything below either prints or raises.23
(import (sigil core) (sigil fs) (sigil language) (folio doc inspect))25
(define source-path26
(let ((v (getenv "F2A_MANIFEST_SOURCE")))27
(if (and v (> (string-length v) 0)) v "test/fixtures/f2a-corpus.sgd")))29
(define source (read-file-string source-path))31
;; The revision is an INPUT to the inspector (ruled 2026-08-11: sigil-crypto is32
;; native-only and a WASM folio client must be able to compute revisions33
;; itself). The gate supplies the real one, computed by `sha256sum` -- a34
;; DIFFERENT implementation from anything in this repository, which is the35
;; point. The placeholder below only keeps this file runnable by hand.36
(define revision37
(let ((v (getenv "F2A_MANIFEST_REVISION")))38
(if (and v (valid-revision? v)) v (string-append "sha256:" (make-string 64 #\0)))))40
(define header-end41
(let ((header (inspect-language-source source source-path)))42
(and (language-header? header) (language-header-end header))))44
(define descriptor (inspect-folio-source source source-path revision header-end))46
;; ---------------------------------------------------------------------------47
;; Escaping. A field value or a node name can contain a tab or a newline, and a48
;; line-oriented manifest that let one through would hand the verifier a49
;; truncated record it would parse as a short line rather than as an error.50
;; ---------------------------------------------------------------------------51
(define (escape s)52
(let loop ((i 0) (out ""))53
(if (>= i (string-length s))54
out55
(let ((c (string-ref s i)))56
(loop (+ i 1)57
(string-append out58
(cond ((char=? c #\\) "\\\\")59
((char=? c #\tab) "\\t")60
((char=? c #\newline) "\\n")61
((char=? c #\return) "\\r")62
(else (string c)))))))))64
(define (num v) (if v (number->string v) "-"))66
(define (value-type v)67
(cond ((string? v) "string")68
((symbol? v) "symbol")69
((number? v) "number")70
((boolean? v) "boolean")71
((pair? v) "list")72
((null? v) "list")73
(else "other")))75
(define (value-text v)76
(cond ((string? v) v)77
((symbol? v) (symbol->string v))78
((number? v) (number->string v))79
((boolean? v) (if v "#t" "#f"))80
(else "")))82
(define nodes-emitted 0)83
(define fields-emitted 0)85
(define (emit . parts)86
(let loop ((rest parts))87
(cond ((null? rest) (newline))88
(else (display (car rest))89
(if (pair? (cdr rest)) (display "\t"))90
(loop (cdr rest))))))92
(define (span-cols s)93
(list (num (span-byte-start s)) (num (span-byte-length s))94
(num (span-scalar-start s)) (num (span-scalar-end s))95
(num (span-line s)) (num (span-column s))96
(num (span-end-line s)) (num (span-end-column s))))98
(define (emit-span-row tag path extra span)99
(apply emit (append (list tag path) extra (span-cols span))))101
(define (walk node path)102
(set! nodes-emitted (+ nodes-emitted 1))103
(emit-span-row "NODE" path104
;; EVERY column is escaped, not just the ones that looked105
;; risky. An unescaped tab anywhere shifts the metadata columns106
;; while the span columns -- always the last eight -- stay in107
;; place, so the row parses as a VALID row carrying the wrong108
;; name, kind or id.109
(list (escape (symbol->string (inspected-node-kind node)))110
(if (inspected-node-name node)111
(escape (symbol->string (inspected-node-name node)))112
"-")113
(escape (symbol->string (inspected-node-vocabulary node)))114
(if (inspected-node-id node) (escape (inspected-node-id node)) "-"))115
(inspected-node-span node))116
(for-each117
(lambda (f)118
(set! fields-emitted (+ fields-emitted 1))119
(if (field-span f)120
(emit-span-row "FIELD" path121
(list (if (field-name f) (escape (symbol->string (field-name f))) "-")122
(number->string (field-index f))123
(escape (value-type (field-value f)))124
(escape (value-text (field-value f))))125
(field-span f))126
;; A field with no span is a REAL condition -- an argument whose syntax127
;; carried no srcloc -- and it is emitted as such rather than skipped.128
;; A verifier that never sees the row cannot notice the hole.129
(emit "FIELD-NO-SPAN" path130
(if (field-name f) (escape (symbol->string (field-name f))) "-")131
(number->string (field-index f)))))132
(inspected-node-fields node))133
(let loop ((children (inspected-node-children node)) (i 0))134
(if (pair? children)135
(begin (walk (car children) (string-append path "." (number->string i)))136
(loop (cdr children) (+ i 1))))))138
(emit "SOURCE" (escape source-path))139
(emit "REVISION" (descriptor-revision descriptor))140
(emit "LENGTHS"141
(number->string (descriptor-byte-length descriptor))142
(number->string (descriptor-scalar-length descriptor)))143
(emit "HEADER-END" (num (descriptor-header-end descriptor)))144
(emit "PARSED" (if (descriptor-parsed? descriptor) "yes" "no"))146
(for-each (lambda (d)147
(emit "DIAGNOSTIC"148
(symbol->string (diagnostic-severity d))149
(symbol->string (diagnostic-code d))150
(escape (diagnostic->string d))))151
(descriptor-diagnostics descriptor))153
(let loop ((roots (descriptor-nodes descriptor)) (i 0))154
(if (pair? roots)155
(begin (walk (car roots) (number->string i))156
(loop (cdr roots) (+ i 1)))))158
;; A descriptor holding a callable is the one thing that would make "this data159
;; cannot be evaluated" false. Reported as data, checked by the verifier.160
(for-each (lambda (path) (emit "CALLABLE" (escape path)))161
(descriptor-callable-values descriptor))163
;; THE TRAILER IS LOAD-BEARING. A manifest truncated by a crash, a full disk or164
;; a redirect that returned before the writer finished looks exactly like a165
;; manifest of a small document. The verifier refuses to grade a manifest whose166
;; trailer is missing or whose counts disagree with the rows it actually parsed.167
(display "F2A_MANIFEST_DONE nodes=")168
(display nodes-emitted)169
(display " fields=")170
(display fields-emitted)171
(newline)