content: tree-aware validation (descends into sections) + 6 tests (416/416)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 30s

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-07 02:03:25 +00:00
parent 6e0edc347b
commit e5a159f350
5 changed files with 108 additions and 23 deletions

View File

@@ -8,7 +8,7 @@
"markdown": {"pass": 20, "fail": 0},
"text": {"pass": 20, "fail": 0},
"section": {"pass": 25, "fail": 0},
"validate": {"pass": 17, "fail": 0},
"validate": {"pass": 23, "fail": 0},
"store": {"pass": 29, "fail": 0},
"snapshot": {"pass": 20, "fail": 0},
"crdt": {"pass": 34, "fail": 0},
@@ -17,7 +17,7 @@
"md-import": {"pass": 24, "fail": 0},
"fed": {"pass": 20, "fail": 0}
},
"total_pass": 410,
"total_pass": 416,
"total_fail": 0,
"total": 410
"total": 416
}

View File

@@ -12,7 +12,7 @@ _Generated by `lib/content/conformance.sh`_
| markdown | 20 | 0 | 20 |
| text | 20 | 0 | 20 |
| section | 25 | 0 | 25 |
| validate | 17 | 0 | 17 |
| validate | 23 | 0 | 23 |
| store | 29 | 0 | 29 |
| snapshot | 20 | 0 | 20 |
| crdt | 34 | 0 | 34 |
@@ -20,4 +20,4 @@ _Generated by `lib/content/conformance.sh`_
| sync | 14 | 0 | 14 |
| md-import | 24 | 0 | 24 |
| fed | 20 | 0 | 20 |
| **Total** | **410** | **0** | **410** |
| **Total** | **416** | **0** | **416** |

View File

@@ -1,8 +1,10 @@
;; Extension — document integrity validation.
;; Extension — document integrity validation (tree-aware: descends into sections).
;; (Conformance loads section.sx before this suite.)
(st-bootstrap-classes!)
(content-bootstrap-blocks!)
(content-bootstrap-doc!)
(content-bootstrap-section!)
;; ── a fully valid document ──
(define
@@ -114,3 +116,51 @@
(mk-heading "hh" 2 "H"))
(mk-text "tt" "T")))
(content-test "all well-formed types valid" (content/valid? allgood) true)
;; ── tree-aware: descends into sections ──
(define
nested
(doc-append
(doc-empty "d")
(mk-section
"s"
(list (mk-heading "nh" 1 "H") (mk-text "np" "ok")))))
(content-test "valid nested section" (content/valid? nested) true)
(define
nested-bad
(doc-append
(doc-empty "d")
(mk-section "s" (list (mk-heading "nh" "notnum" "H")))))
(content-test
"nested bad field detected"
(content/issue-kinds nested-bad)
(list "field"))
;; valid section block itself
(content-test
"section valid"
(content/valid? (doc-append (doc-empty "d") (mk-section "s" (list))))
true)
(content-test
"section bad children"
(content/issue-kinds
(doc-append
(doc-empty "d")
(st-iv-set! (mk-section "s" (list)) "children" "nope")))
(list "field"))
;; duplicate id across a section boundary (top-level id == nested id)
(define
dup-tree
(doc-append
(doc-append (doc-empty "d") (mk-text "x" "top"))
(mk-section "s" (list (mk-text "x" "nested")))))
(content-test
"tree-wide duplicate detected"
(len
(filter
(fn (i) (= (get i :kind) "duplicate"))
(content/validate dup-tree)))
1)
(content-test "tree dup not valid" (content/valid? dup-tree) false)

View File

@@ -1,9 +1,10 @@
;; content-on-sx — document integrity validation.
;;
;; Guards imports, edits and federated input: checks each block's id and the
;; required fields/types for its kind, plus document-level duplicate ids. Returns
;; a list of issue dicts {:id :kind :detail}; an empty list means valid. Dispatch
;; on block type is a validation-boundary concern, not core behaviour.
;; Guards imports, edits and federated input: walks the whole block TREE (into
;; nested sections) checking each block's id and required fields/types, plus
;; tree-wide duplicate ids. Returns issue dicts {:id :kind :detail}; empty = ok.
;; Tree detection is inline (class + st-iv-get) so this file needs no section.sx.
;; Dispatch on block type is a validation-boundary concern, not core behaviour.
;;
;; Requires (loaded by harness): block.sx, doc.sx.
@@ -35,6 +36,30 @@
(define ct-uniq (fn (xs) (ct-uniq-loop xs (list))))
;; ── tree flatten (descends into CtSection children; guards malformed children) ──
(define
ct-section-block?
(fn (b) (and (st-instance? b) (= (get b :class) "CtSection"))))
(define
ct-tree-blocks
(fn
(blocks)
(if
(= (len blocks) 0)
(list)
(let
((b (first blocks)))
(append
(cons
b
(if
(ct-section-block? b)
(let
((ch (st-iv-get b "children")))
(if (list? ch) (ct-tree-blocks ch) (list)))
(list)))
(ct-tree-blocks (rest blocks)))))))
;; ── id checks ──
(define
content/-id-issues
@@ -120,32 +145,36 @@
id
(list? (blk-get b "items"))
"list items must be a list")))
((= t "section")
(ct-field-issue
id
(list? (blk-get b "children"))
"section children must be a list"))
(else (list (ct-issue id "type" (str "unknown block type: " t))))))))
(define
content/-block-issues
(fn (b) (append (content/-id-issues b) (content/-field-issues b))))
;; ── document-level: duplicate ids ──
;; ── duplicate ids across the whole tree ──
(define
content/-dup-issues
(fn
(doc)
(let
((ids (doc-ids doc)))
(map
(fn (id) (ct-issue id "duplicate" (str "duplicate block id: " id)))
(ct-uniq
(filter (fn (id) (> (ct-count-in id ids) 1)) ids))))))
(ids)
(map
(fn (id) (ct-issue id "duplicate" (str "duplicate block id: " id)))
(ct-uniq (filter (fn (id) (> (ct-count-in id ids) 1)) ids)))))
;; ── public ──
(define
content/validate
(fn
(doc)
(append
(content/-dup-issues doc)
(ct-flatmap content/-block-issues (doc-blocks doc)))))
(let
((all (ct-tree-blocks (doc-blocks doc))))
(append
(content/-dup-issues (map (fn (b) (blk-id b)) all))
(ct-flatmap content/-block-issues all)))))
(define
content/valid?

View File

@@ -19,7 +19,7 @@ injected adapter, not core.
## Status (rolling)
`bash lib/content/conformance.sh`**410/410** (Phases 14 COMPLETE + 10 extensions: HTML/SX escaping, Markdown render+import, CRDT replication, validation, snapshot cache, doc metadata, plain-text render, nested block trees)
`bash lib/content/conformance.sh`**416/416** (Phases 14 COMPLETE + 10 extensions: HTML/SX escaping, Markdown render+import, CRDT replication, tree-aware validation, snapshot cache, doc metadata, plain-text render, nested block trees)
## Ground rules
@@ -80,7 +80,7 @@ lib/content/api.sx ── (content/edit) (content/render) (content/history) ─
- [x] asSx wire string-escaping (`String>>sxEscaped`: \ and " in SX literals)
- [x] Markdown render mode (`asMarkdown:` / `content/render doc "md"`)
- [x] durable CRDT replication (`crdt-store.sx`: ops on persist, replay + converge)
- [x] document validation (`validate.sx`: ids, per-type fields, duplicate ids)
- [x] document validation (`validate.sx`: ids, per-type fields, duplicate ids; tree-aware — descends into sections, tree-wide dup ids, section field check)
- [x] Markdown import adapter (`md-import.sx`: text → blocks, round-trips export)
- [x] snapshot cache over replay (`snapshot.sx`: cache-not-primary, transparent)
- [x] document metadata (`meta.sx`: title/slug/tags + Ghost title plumbing)
@@ -89,6 +89,12 @@ lib/content/api.sx ── (content/edit) (content/render) (content/history) ─
## Progress log
- 2026-06-07 — Refinement: tree-aware validation. `validate.sx` now flattens the
whole block tree (descending into `CtSection` children, guarding malformed
non-list children) so field checks and duplicate-id detection cover nested
blocks and span section boundaries; added a `section` field-type case. Inline
tree detection (class + st-iv-get) keeps it free of a section.sx dependency.
+6 tests; suite 416/416.
- 2026-06-07 — Extension: nested block trees (`section.sx`). `CtSection` is a
block whose `children` ivar is a list of blocks (incl. nested sections →
arbitrary depth), turning the flat document into the ordered TREE from the