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
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:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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** |
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -19,7 +19,7 @@ injected adapter, not core.
|
||||
|
||||
## Status (rolling)
|
||||
|
||||
`bash lib/content/conformance.sh` → **410/410** (Phases 1–4 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 1–4 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
|
||||
|
||||
Reference in New Issue
Block a user