host: typed relations — Phase 6 (schema validation) + post-page perf fix

Phase 6 — gradual schema validation made real:
- host/blog-type-schemas now carries a declarative schema (a list of
  {:block :msg} required-element rules); "article" requires an h1.
- host/blog--all-tags / --schema-issues / host/blog-type-issues walk the parsed
  content and report each missing required block; host/blog-type-valid? = no
  issues. A type with no schema imposes nothing (gradual).
- seed an "article" type-post (article subtype-of type). edit-submit now lists
  the specific schema issues on a 400 ("an article needs a heading"), so a post
  that is-a article must satisfy it on save.

Post-page performance (the unresponsiveness): a post page was ~1s even with no
relations and no load — NOT CPU (render-page ~2ms, in-memory handler ~5ms) but
the DURABLE read path: host/blog--relation-blocks called host/blog-out/in, each
re-scanning the whole KV (host/blog-slugs + an all-edges scan), so a page did ~7
kv-keys performs deep in the call stack. Each durable perform routes through
cek_run_with_io and is costly there. Fixes:
- host/blog-out/in read DIRECT edges from the durable edge store (string scan),
  not lib/relations (whose queries re-saturate the Datalog ruleset, ~seconds).
- host/blog--relation-blocks reads the KV key list ONCE and derives both the post
  set and the edges in memory (host/blog--edges-for / --recs-slugs), one kv-keys
  plus a host/blog-get per linked post. Post pages: ~1s -> ~0.02s (46x); live
  11-135s -> ~0.15s. lib/relations stays for TRANSITIVE queries only.
- conformance timeout 300 -> 600s: the relations-heavy blog suite is CPU-bound
  under shared-box contention and was tripping a false truncation at 300.

271/271 (blog 100). Verified live: post pages fast, Tags/Related/Tagged-with-this
render, schema rejection works.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-28 18:52:56 +00:00
parent 7e50d3d1bb
commit 0a2f1a61d1
3 changed files with 183 additions and 56 deletions

View File

@@ -411,6 +411,37 @@
(host-bl-test "/tags is 200 (not shadowed by /:slug)"
(dream-status (host-bl-app (host-bl-req "/tags"))) 200)
;; -- Phase 6: gradual schema validation --
(host/blog-seed-types!) ;; ensures the "article" type + its schema (requires h1)
(host-bl-test "all-tags finds nested element tags"
(let ((tags (host/blog--all-tags (parse-safe "(article (h1 \"T\") (p \"x\"))"))))
(list (contains? tags "h1") (contains? tags "p") (contains? tags "section")))
(list true true false))
(host-bl-test "schema-issues: missing required block -> 1 issue; present -> 0"
(let ((sch (host/blog-schema-of "article")))
(list (len (host/blog--schema-issues sch "(p \"no heading\")"))
(len (host/blog--schema-issues sch "(article (h1 \"yes\"))"))))
(list 1 0))
(host-bl-test "type-valid? enforces an is-a article's schema"
(begin
(host/blog-put! "art1" "Art 1" "(p \"x\")" "published")
(host/blog-relate! "art1" "article" "is-a")
(list (host/blog-type-valid? "art1" "(p \"no heading\")")
(host/blog-type-valid? "art1" "(article (h1 \"H\") (p \"x\"))")))
(list false true))
(host-bl-test "a post with no schema'd type is vacuously valid"
(host/blog-type-valid? "ppost" "(p \"anything\")") true)
(host-bl-test "edit-submit rejects content violating the type schema (not saved)"
(begin
(host-bl-wapp (host-bl-send "POST" "/art1/edit" "Bearer good"
"application/x-www-form-urlencoded" "sx_content=(p+%22still+no+heading%22)"))
(contains? (dream-resp-body (host-bl-wapp (host-bl-req "/art1/"))) "still no heading"))
false)
(host-bl-test "edit-submit accepts content satisfying the schema -> 303"
(dream-status (host-bl-wapp (host-bl-send "POST" "/art1/edit" "Bearer good"
"application/x-www-form-urlencoded" "sx_content=(article+(h1+%22Heading%22)+(p+%22body%22))")))
303)
;; -- experimental unguarded create-only route (POST /new, no auth) --
(define host-bl-oapp (host/make-app (list host/blog-open-create-routes host/blog-routes)))
(host/blog-use-store! (persist/open))