AtlatestRepositoryfolio
1
;;; F4a graph probe -- a FACT EMITTER, deliberately not a judge.2
;;;3
;;; Every subcommand prints machine-readable lines and exits 0 unless the4
;;; product raised. It never decides that an arm passed. That split is the5
;;; point: if this file computed verdicts, a sabotage planted in `(folio graph)`6
;;; could make the probe report PASS using the very code under test, and the7
;;; gate would be asserting the product's opinion of itself. `test/gate-f4a.sh`8
;;; owns every comparison.9
;;;10
;;; A raise is NOT swallowed. A probe that guards its own body turns a broken11
;;; product into an empty result set, and an empty result set is what a clean12
;;; corpus also looks like.14
(import (sigil core) (sigil string) (sigil list) (sigil fs) (sigil path)15
(sigil sqlite)16
(folio store) (folio graph) (folio doc model) (folio doc inspect))18
;; `(sigil sqlite)` is imported for the DAMAGE commands only. Arm 7 needs a19
;; file that is a perfectly valid SQLite database and simply is not OUR schema20
;; -- the case a "can we open it" check passes and a "does it answer correctly"21
;; check does not. There is no sqlite3 CLI on this host, so the damage is done22
;; here, through the same library, but deliberately OUTSIDE the product's API:23
;; the recovery path must meet the damage as an accomplished fact, not as24
;; something the product did to itself.26
(define (args-after-script rest)27
(cond28
((null? rest) '())29
((string-ends-with? (car rest) ".sgl") (cdr rest))30
(else (args-after-script (cdr rest)))))32
(define (emit . parts)33
(display (string-join (map (lambda (p) (format "~a" p)) parts) "\t"))34
(newline))36
(define (open-graph root ws provider)37
(make-folio-graph (make-folio-store root) ws provider))39
(define (rows->lines tag rows keys)40
(for-each41
(lambda (r)42
(apply emit tag43
(map (lambda (k)44
(let ((hit (assq k r)))45
(if hit (format "~a" (cdr hit)) "")))46
keys)))47
rows))49
(define (main-probe args)50
(if (null? args)51
(begin (display "usage: f4a_graph_probe <cmd> <root> <ws> <provider> [...]\n") 2)52
(let* ((cmd (car args))53
(rest (cdr args)))54
(cond55
;; `vocabulary` needs no workspace: it reports what the MODEL says the56
;; built-in edge kinds are, plus the frozen vocabulary names, so the57
;; gate can check this module's one name-level restatement against the58
;; artifact rather than against a list in a comment.59
((equal? cmd "vocabulary")60
(for-each (lambda (k) (emit "EDGE_KIND" k)) (graph-built-in-edge-kinds))61
(for-each (lambda (n) (emit "VOCAB_NAME" n)) (folio-vocabulary-names))62
0)63
;; The damage commands do NOT go through `make-folio-graph`; opening64
;; through the product would recover the database before it could be65
;; broken.66
((equal? cmd "damage-foreign")67
(let* ((p (path-join (car rest) ".folio-graph.db"))68
(db (begin (when (file-exists? p) (delete-file p)) (sqlite-open p))))69
(unless db (error "damage-foreign: could not create the decoy database"))70
(sqlite-exec db "CREATE TABLE somebody_elses (id INTEGER PRIMARY KEY, v TEXT)")71
(sqlite-exec db "INSERT INTO somebody_elses (v) VALUES ('not folio')")72
(sqlite-close db)73
(emit "DAMAGED" "foreign-schema" p)74
0))75
((equal? cmd "damage-version")76
(let* ((p (path-join (car rest) ".folio-graph.db"))77
(db (sqlite-open p)))78
(unless db (error "damage-version: could not open the database"))79
(sqlite-exec db "UPDATE graph_meta SET value = '999' WHERE key = 'schema_version'")80
(sqlite-close db)81
(emit "DAMAGED" "schema-version-999" p)82
0))83
((equal? cmd "damage-drop-table")84
(let* ((p (path-join (car rest) ".folio-graph.db"))85
(db (sqlite-open p)))86
(unless db (error "damage-drop-table: could not open the database"))87
(sqlite-exec db "DROP TABLE edges")88
(sqlite-close db)89
(emit "DAMAGED" "edges-table-dropped" p)90
0))91
((null? rest)92
(display "usage: f4a_graph_probe <cmd> <root> <ws> <provider> [...]\n") 2)93
(else94
(let* ((root (car rest))95
(ws (cadr rest))96
(provider (caddr rest))97
(extra (cdddr rest))98
(g (open-graph root ws provider)))99
(emit "OPEN" root ws provider (graph-open-state g))100
(cond101
((equal? cmd "rebuild")102
(emit "DOCS" (graph-rebuild! g)) 0)103
((equal? cmd "index")104
(emit "DOC_UID" (graph-index-document! g (car extra))) 0)105
((equal? cmd "remove")106
(graph-remove-document! g (car extra))107
(emit "REMOVED" (car extra)) 0)108
((equal? cmd "remount")109
(graph-remount! g)110
(emit "REMOUNTED" root provider) 0)111
((equal? cmd "dump")112
(for-each (lambda (l) (emit "R" l)) (graph-relation-dump g))113
(emit "DUMP_LINES" (length (graph-relation-dump g)))114
0)115
((equal? cmd "sources")116
(let ((ss (graph-source-files g)))117
(for-each (lambda (s)118
(emit "SOURCE" (car s) (cadr s) (caddr s)119
(if (cadddr s) "contested" "sole")))120
ss)121
(emit "SOURCE_COUNT" (length ss)))122
0)123
((equal? cmd "documents")124
(rows->lines "DOCUMENT" (graph-documents g)125
'(doc_uid slug format path revision parsed contested))126
(emit "DOCUMENT_COUNT" (length (graph-documents g)))127
0)128
((equal? cmd "nodes")129
(rows->lines "NODE" (graph-nodes g)130
'(node_uid doc_uid parent_uid kind name vocabulary node_id))131
(emit "NODE_COUNT" (length (graph-nodes g)))132
0)133
((equal? cmd "edges")134
(rows->lines "EDGE" (graph-edges g)135
'(src_uid kind kind_known declared ordinal136
dst_workspace dst_slug dst_node dst_uid resolution))137
(emit "EDGE_COUNT" (length (graph-edges g)))138
0)139
((equal? cmd "spans")140
(rows->lines "SPAN" (graph-spans g)141
'(owner_kind owner_uid part byte_start byte_length142
scalar_start scalar_end line column_no))143
(emit "SPAN_COUNT" (length (graph-spans g)))144
0)145
((equal? cmd "revisions")146
(rows->lines "REVISION" (graph-revisions g) '(doc_uid revision))147
(emit "REVISION_COUNT" (length (graph-revisions g)))148
0)149
((equal? cmd "diagnostics")150
(rows->lines "DIAGNOSTIC" (graph-diagnostics g)151
'(doc_uid ordinal severity code message))152
(emit "DIAGNOSTIC_COUNT" (length (graph-diagnostics g)))153
0)154
((equal? cmd "backlinks")155
(let ((groups (graph-backlinks-by-kind g (car extra))))156
(for-each (lambda (grp)157
(for-each (lambda (s) (emit "BACKLINK" (car grp) s)) (cdr grp)))158
groups)159
(emit "BACKLINK_KINDS" (length groups)))160
0)161
((equal? cmd "cycles")162
(let ((cs (graph-dependency-cycles g)))163
(for-each (lambda (c)164
;; the ring, then its entry point again, so the165
;; printed form closes visibly166
(emit "CYCLE" (string-join (append c (list (car c))) " -> ")))167
cs)168
(emit "CYCLE_COUNT" (length cs)))169
0)170
((equal? cmd "supersessions")171
(let ((ss (graph-supersession-chains g)))172
(for-each (lambda (p) (emit "SUPERSEDES" (cadr p) (car p))) ss)173
(emit "SUPERSESSION_COUNT" (length ss)))174
0)175
((equal? cmd "unresolved")176
(rows->lines "UNRESOLVED" (graph-unresolved-targets g)177
'(src_uid kind dst_workspace dst_slug dst_node resolution))178
(emit "UNRESOLVED_COUNT" (length (graph-unresolved-targets g)))179
0)180
((equal? cmd "orphans")181
(rows->lines "ORPHAN" (graph-orphans g) '(doc_uid))182
(emit "ORPHAN_COUNT" (length (graph-orphans g)))183
0)184
((equal? cmd "search")185
(rows->lines "HIT" (graph-search g (car extra)) '(doc_uid slug title))186
(emit "HIT_COUNT" (length (graph-search g (car extra))))187
0)188
((equal? cmd "meta")189
(emit "SCHEMA_VERSION" (graph-schema-version))190
(emit "OPEN_STATE" (graph-open-state g))191
0)192
(else193
(display (format "f4a_graph_probe: unknown command: ~a~%" cmd))194
2))))))))196
;; The terminal marker prints only on a path that RETURNED. A raise inside the197
;; product skips it, so the gate can tell "the probe ran and found nothing" from198
;; "the probe died before it could find anything" -- two states that otherwise199
;; both render as an empty result set, which is the failure this whole module's200
;; checked-SQLite layer exists to prevent one level down.201
(let* ((argv (args-after-script (command-line)))202
(rc (main-probe argv)))203
(emit "PROBE_DONE" (if (null? argv) "-" (car argv)) rc)204
(exit rc))