Commit Graph

1702 Commits

Author SHA1 Message Date
4e968426c1 plan: behaviour as data — lifecycles + ECA over an effect vocabulary (Slice 9)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 28s
Capture the behaviour layer. Principle: behaviour is data-defined orchestration over a
small fixed vocabulary of effects; only the effect primitives + the interpreter stay code,
everything between is editable posts (meta-circular — Lifecycle/Transition/Rule/Effect are
themselves types). Guards are pure type-system (Datalog) queries; runs on flow-on-sx
(durable: wait-for webhook, after timer; saga compensation). 'Place order'/'ship' = attempt
transition T.

Sketches the effect vocabulary in four tiers — pure guards / data (graph mutations) /
domain (reserve-stock, book-seat) / integration (charge-card, create-shipment, notify,
federate; the code edge, kept small per artdag's S-expr effects) / control (wait-for, after,
emit, transition; flow primitives) — worked through store + events. The fork: declarative
core + guarded code escape-hatch (Scheme/Smalltalk on a post). Start by pinning the
vocabulary + a generic interpreter, and lift commerce-on-sx/events-on-sx from guest-code
into lifecycle+effect DATA (they already implement exactly this, just not editably).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 09:43:13 +00:00
82c0978da6 plan: endgame — the whole platform (store/events/orders) as a typed domain
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 23s
The metamodel targets the entire rose-ash domain model, not just the blog — the finish
line of the host-on-sx strangler off Quart: define the domain schema as data instead of
porting each service's bespoke models. Records the three honest additions store/events
surface beyond a/b/c+d: (1) typed scalar ATTRIBUTES (datatype properties: price:Money,
stock:Int) alongside entity relations — a real addition, likely Slice 8; (2) behaviour/
lifecycle composes from the substrate loops (flow/commerce/events), not reinvented;
(3) integrations (payments/federation/media) stay referenced services. Structure+validation
from the metamodel, behaviour from substrates, integrations as services.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 09:31:23 +00:00
b3363a8631 plan: north star — the metamodel as a system-construction kit
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 17s
Name the destination: the host becomes a self-describing metamodel where you define a
domain (types + relations with signatures/algebra) and a working system falls out — the
blog is one seeded configuration. Most instance UI is already generic (relation editors
iterate the relations, pickers come from declares-anchors, validation from :schema), so
'define the types' is mostly a metamodel editor + a generic instance form + a
clear-and-reseed. Frames Slices 6-7 as the schema language this is for.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 09:29:12 +00:00
64106c89fa plan: design parameterised relations — Slice 6 (role signature: a+b+c) & Slice 7 (algebra: d)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 21s
Capture the Relation<…> design from the discussion. The reframe: the parameters split
into two halves — the role SIGNATURE (shape of a tuple: per-role type a, arity b,
cardinality c) and the relation's ALGEBRA (behaviour: transitivity/symmetry/inverse/
sub-relations d). A relation is Relation<signature>; today's binary typed relations are
the degenerate 2-role case.

Slice 6: generalise :rel to a :roles signature; (a) per-role type = the declares-anchor
made explicit, (b) arity needs reification (instance-posts) for n-ary, (c) cardinality by
counting. Nominal variance, JIT caveat for n-ary role iteration.

Slice 7: declared algebraic properties with GENERIC closure (retires the hardcoded
is-a/subtype closure — OWL property characteristics); real inverse relations; sub-relations.
Decidable core stops here; defined-by-rule + cross-role predicates fenced behind the
predicate-language decision.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 09:26:30 +00:00
d8e951ed27 host: relations-as-posts slice 5 — refinement types (schemas on the type-post)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 22s
A type-post carries its schema in a :schema slot (a list of {:block :msg} rules — a
refinement {x : T | x has these blocks}). host/blog-schema-of reads it off the post;
the hardcoded host/blog-type-schemas table is gone. A NEW refinement type is pure
data: give a type-post a :schema and its instances are validated on save — no code
(tested with a 'guide' type requiring a 'pre' block). article's schema is migrated
onto the article post at boot (host/blog--set-schema!, a single read+write).

host/blog-put! now MERGES over the previous record, so editing a post's
title/content doesn't nuke its :schema/:rel metadata (also closes the Slice 2
'edit drops :rel' gap). schema-of reads the post (a durable read) — only the SAVE
path calls it (a write request, never a render that would VmSuspend).

conformance 299/299 (+4: article h1 enforced from the post, a new refinement type
validates its instances, schema read off the post, edit preserves :schema).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 09:13:30 +00:00
d45da81b80 host: relations-as-posts slice 4 — type ALGEBRA (intersection ∧ union)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 25s
An algebraic type is a post with operand edges: conj edges (intersection members),
disj edges (union members). host/blog-instances-of-expr computes its extent from the
operands' extents by set intersection/union, RECURSIVELY — operands can themselves be
algebraic (meta-circular; tested with (tag ∧ article) ∧ tag). host/blog-is-a-expr?
generalises is-a? to type expressions; make-and!/make-or! build them. Binary today
(nth 0/1, no fold over operands — robust on the serving JIT).

Operand edges are KV-only (host/blog--add-edge-kv!, read via host/blog-out), NOT in
lib/relations — feeding extra kinds into the Datalog graph blows up its per-query
re-saturation; load-edges! skips conj/disj on replay too.

conformance 295/295 (+4: intersection/union membership, extent = set op, nested expr).
(NB: host conformance can EXIT 124 purely from a sibling loop's CPU contention — ran
with timeout 1200.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 08:41:41 +00:00
f94b9d0b93 host: relations-as-posts slice 2.5 — picker title reads are O(page), not O(pool)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 45s
relate-candidates computes the available candidate SLUGS (slug-sorted, no per-candidate
read), then reads titles only for the page it returns. On the unfiltered path (q="" —
the initial picker load AND every editor server-fill, the common case) that's ~limit
durable reads instead of one-per-post, cutting the http-listen suspend/resume churn. A
filter (q≠"") still resolves titles across the pool since it matches on the title.

(A boot slug→title cache would make the filter O(1)-perform too, but it's blocked: no
bulk KV read, and a per-post host/blog-get loop at boot hits the JIT 'durable read in a
boot loop drops all-but-first' bug — see plans/relations-as-posts.md.)

conformance 291/291, run-picker-check 3/3 (incl. the title filter + paging).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 07:50:31 +00:00
90190346aa host: relations-as-posts slice 3 — typed relations (target-type constraint enforced)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 46s
A relation's declares-anchor IS its target-type constraint: is-a/subtype-of (anchored
by type) require a type object; tagged (anchored by tag) a tag; related (no anchor) any
post. host/blog--valid-object?(kind, other) = other ∈ the relation's candidate pool — the
SAME set the picker offers — and relate-submit now enforces it (invalid target = silent
no-op). The picker never offers an invalid target, so this guards crafted/API requests:
the jump from candidate set to an enforced relation schema. A new typed relation needs
only a relation-post + a '<TargetType> declares <rel>' edge.

host/blog-relate! (direct/seed) stays unvalidated — validation is a handler boundary
(the seed writes 'X is-a relation', and relation isn't under type).

conformance 291/291 (+4: valid-object? accepts types/tags/any, relate-submit creates the
edge for a type object and no-ops for a non-type).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 07:25:49 +00:00
9c148e58dc plan: note the live serving-JIT iteration gotcha (Slice 2)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 30s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 23:17:04 +00:00
c6627f4954 host: relations-as-posts slice 2 — relation metadata lives on relation-posts
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 48s
is-a/subtype-of/tagged/related are now POSTS (each is-a a new `relation` root),
owning their metadata in a :rel slot {:symmetric :label :inverse-label}. The static
host/blog-rel-kinds registry is gone: kind-spec/rel-kinds/kind-symmetric? read the
relation-posts (via an in-memory cache), and the relation list derives from
host/blog-in "relation" "is-a".

Perform-budget fixes (a durable read inside the http-listen render VM raises
VmSuspended; too many per request 500s the page):
 - relation metadata is loaded into a cache at boot (host/blog-load-rel-kinds!,
   like load-edges!), so kind-spec is pure on render paths;
 - the initial edit page renders its pickers EMPTY (the load trigger fills each) —
   only the relate/unrelate FRAGMENT server-renders candidates (with-cands flag).
   Previously every edit page render did candidate-get × 4 pickers and 500'd.

host conformance 287/287 (+4 slice-2: kind-spec reads :rel, kind-symmetric? off the
post, unknown kind has no spec, rel-kinds derived from the graph). run-picker-check
3/3 (edit page boots, relate/unrelate flow works, no client errors).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 22:49:59 +00:00
b3804ce712 host: relations-as-posts slice 1 — declaration-driven candidate pools
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 42s
Types declare which relation they anchor (type declares is-a/subtype-of, tag
declares tagged) via a 'declares' edge; the picker's candidate set is the
down-closure of a relation's anchors through is-a ∪ subtype-of. So is-a/subtype-of
now offer the WHOLE type closure — the roots (type/tag/article) AND instances —
fixing the wrinkle where only instances showed and you could never pick 'tag' or
'article' as a type. 'related' has no anchor → every post.

Replaces the hardcoded :candidates "types"/"tags"/"all" with graph queries
(host/blog--reach-down + the declares edges). Design + roadmap (relations as
first-class posts, typed relations, type algebra, constraints) in
plans/relations-as-posts.md.

host conformance 283/283 (+5: is-a pool includes type roots, excludes plain posts,
tagged anchored by tag, related = all, is-a relate-options offers Article).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 21:40:27 +00:00
53de29158b plan: Phase 3 render-to-console done; live TTY input loop noted as future
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 18:09:15 +00:00
2b2073cf56 plan: record Phases 0-2 done (SX engine tests + Playwright trim); Phase 3 (console) remains
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 17:56:35 +00:00
b0c0fdd4b1 plan: Phase 3 target is the CONSOLE — engine renders the same picker to a terminal
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 30s
The non-browser platform is a console/TUI renderer: the engine's platform ops map
to a text-node tree (harness-web's mock DOM is ~90% there), render-to-console
prints it, a raw-stdin input loop drives simulate-click/input. The same
~relate-picker runs unchanged in a terminal — browser is one platform binding,
console another, test harness a third.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 17:11:51 +00:00
0b13701ea4 plan: SX-native engine tests (browser-independent) — port picker behaviors to the SX harness, trim Playwright to a boot smoke
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 29s
Durable plan for the next step: drive the engine against the mock platform
(spec/harness.sx :fetch + web/harness-web.sx simulate-click/DOM asserts), so
fetch->swap->DOM behavior is tested without a browser — the same engine could
drive a non-browser target. Phases: PoC (relate-delete), port the rest, trim
Playwright to WASM-boot + content-addressed-load, stretch = non-browser renderer.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 17:09:11 +00:00
059897970e host: doc — blog SPA complete + live on the WASM OCaml kernel
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 27s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 11:11:18 +00:00
05c0a0b01a host: doc — complete boost diagnosis (nil .sxbc bytecode + manifest-mapped lib resolution)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 22s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 09:01:12 +00:00
0ca70eb4b5 host: doc — wasm kernel boot crash fixed (crypto), boost still blocked on web-stack load
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 41s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 07:57:18 +00:00
4df4de7f79 host: doc — SPA WASM bundle rebuild attempt failed (Char.chr crash), reverted
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 21:00:25 +00:00
dbcbc39ebe host: blog SPA scaffolding (WASM kernel) — server side complete, boost blocked on bundle rebuild
Turn the blog into a SPA using the SX-htmx engine (web/engine.sx) booting the
WASM OCaml kernel (same evaluator as the server) in-browser, with sx-boost
fragment-swapping every link into #content.

Server side DONE + verified:
- lib/host/static.sx: GET /static/** serves shared/static via the file-read
  primitive (ctype by ext, traversal-guarded, 404 on missing). Wired into
  serve.sh (module + route group). Tested: kernel JS + .wasm binary-exact.
- host/blog--page is now the SPA shell: full page = WASM boot scripts +
  sx-boost=#content wrapper + #content; on SX-Request:true returns ONLY the
  inner content fragment for the engine to swap. All 13 handlers thread req.
- docker-compose mounts ./shared/static.
- lib/host/playwright/spa-check.{spec.js,run-spa-check.sh}: boot/boost/swap/back.

Client side: the WASM kernel BOOTS (SxKernel object, data-sx-ready=true, web
stack loads). BLOCKER: the bundled .sxbc throw 'VM: unknown opcode 0' vs this
worktree's kernel -> .sx source fallback -> boot.sx source fails 'Expected
list, got string' -> process-boosted never binds links (boosted 0/N). Fix =
rebuild a consistent WASM bundle (recompile .sxbc against the kernel via
scripts/sx-build-all.sh); the browser wasm target isn't built here yet. See
plans/host-spa.md. Live NOT redeployed (stays on pre-SPA process).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 20:53:06 +00:00
d8d7663565 host: fix serving-JIT host miscompile — install IO resolver for http-listen
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 33s
The serving-JIT perform-in-HO-callback miscompile (map/rest/drop wrong
CALL_PRIM args → blank pages, empty picker) is now fully fixed, so the host
runs 100% serving JIT with NO jit-exclude.

sx-vm-extensions 81177d0e resolves a suspended HO-callback's IO inline
(instead of unwinding the native map/filter loop and corrupting the stack),
but ONLY when a synchronous resolver is installed (!_cek_io_resolver = Some).
The host serves via the http-listen primitive, whose handler drove durable IO
through cek_run_with_io with the resolver = None — so it hit the unwinding
path the fix doesn't cover. (The vm-ext repro installed a resolver, so it
never exercised the host's real no-resolver path.)

Fix: extract cek_run_with_io's IO resolution into resolve_io_request, and have
http-listen install _cek_io_resolver := Some (fun req _ -> resolve_io_request
req) — byte-identical resolution, so the inline path resolves durable reads
exactly as the CEK loop would.

Verified: host conformance 271/271; ephemeral durable server at 100% JIT (no
exclude) zero fallbacks + real content + related shown + picker 12 candidates;
live blog.rose-ash.com home/post/tags 200 with related posts, zero error-log
lines; relate-picker Playwright 4/4 (infinite-scroll + filter + relate).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 20:13:24 +00:00
a697904c7c docs: refined serving-JIT miscompile data (3 fns, list-prim-after-perform)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 26s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 19:16:32 +00:00
1c487ebe0e docs: hand off serving-JIT host miscompile to sx-vm-extensions
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 19:09:26 +00:00
17c7b90834 Merge branch 'loops/sx-vm-extensions' into scratch/host-jit
# Conflicts:
#	hosts/ocaml/bin/sx_server.ml
#	lib/erlang/runtime.sx
2026-06-28 18:57:17 +00:00
e6a1180d50 docs: serving-JIT handoff (from sx-vm-extensions) + host-loop correction
Carry the sx-vm-extensions loop's serving-JIT handoff notes, and add a
correction: the post-page slowness was the durable read count (fixed in
0a2f1a61), not the (long-gone) Smalltalk render path — so SX_SERVING_JIT is an
optional general speedup, not the perf blocker.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 18:53:25 +00:00
3049ff92e4 vm-ext: document CL call/cc-caller exclusion in plan
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 16:32:17 +00:00
dc0cf0b4cc host: typed relations — Phase 1, generalize edges to carry a kind
Plan: plans/typed-posts-and-relations.md. "Typing is just relating to a type",
types are posts. Phase 1 lifts the hard-coded kind:"related" into a parameter,
driven by one registry — the spine the later phases (type resolution, tags,
picker) build on. Zero user-visible change.

- host/blog-rel-kinds registry: {kind,label,symmetric,candidates[,inverse-label]}
  for related (symmetric) / is-a / tagged (directed). One place knows each kind's
  direction, label, and candidate set.
- host/blog-relate!/unrelate! take a kind; symmetric kinds write both directions,
  directed kinds write one. host/blog-out/in read children/parents per kind;
  host/blog-related = out(slug,"related") (back-compat).
- relate/unrelate routes carry a `kind` form field (default "related"), validated
  against the registry. delete drops edges across ALL kinds + both directions.

6 tests: symmetric reads both sides, directed writes one (inverse via host/blog-in),
unrelate is kind-scoped, unknown kind rejected, default kind = related. 244/244;
Playwright picker 4/4 (related path unchanged).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 16:21:14 +00:00
b825c36559 vm-ext: document guard/PUSH_HANDLER fix + double-exec residual in plan
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 04:07:51 +00:00
bf298684fd vm-ext: gate serving-JIT behind SX_SERVING_JIT + fix continuation-guest regressions
Enabling the epoch serving-mode JIT globally regressed continuation-based guest
interpreters (the epoch mode is the shared command channel every loop's
conformance runner uses). Two-part fix:

1. SAFE DEFAULT GATE. register_jit_hook in the persistent server branch is now
   opt-in via SX_SERVING_JIT=1 (default OFF). Default behaviour is unchanged
   (no JIT in epoch serving) → zero regression for sibling loops. The
   content/Smalltalk page server opts in.

2. GENERAL FIXES + per-guest interpret-only declarations:
   - callable? (sx_server/run_tests/integration_tests/mcp_tree) now accepts
     VmClosure. A JIT-compiled higher-order function returns its inner closure
     as a VmClosure; callable? previously rejected it, so scheme-apply's
     (callable? proc) guard failed with "not a procedure: <vm:anon>".
   - jit-exclude! gains a trailing-"*" namespace-prefix form
     (Sx_types.jit_excluded_prefixes), the robust way to mark a whole guest
     interpreter interpret-only (a name-list misses functions in extra files —
     it left erlang's vm/dispatcher JIT'd and 13 tests short).
   - Per-guest exclusions in each guest's runtime.sx:
       scheme  "scheme-*" "scm-*"   erlang "er-*" "erlang-*"
       prolog  "pl-*"               common-lisp "cl-*" "clos-*"
       js      "js-*"               haskell "hk-*"

Verified under opt-in JIT (== CEK, no hang): smalltalk 847/847, scheme/flow
166/166, erlang 530/530, prolog 590/590, apl 152/152, js 147/148. Residual
(documented, protected by the default gate): common-lisp 6 fails in advanced
suites (parser-recovery/debugger/CLOS/MOP). lua (0/16) and tcl (3/4) fail
identically on CEK — pre-existing, not JIT. run_tests --jit/no-jit unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 22:22:40 +00:00
2713636e36 host: hand off the native SX-island editor (browser-capable session)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 17s
The editor is the interactivity layer — it belongs on the --http island pipeline
(SSRs + hydrates islands), not the http-listen host, and needs browser/Playwright
iteration which this worktree lacks. plans/blog-editor-island.md is the handoff:
goal, architecture (docs-side island -> host /new), the live host contract
(form-urlencoded title/sx_content/status -> 303), the sx_content markup to emit
(standard tags, NOT legacy ~kg-* cards), island authoring gotchas, and pointers.
Host side is ready (ingest proven; CORS on request). Phase 5.5 marked handed off.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 21:04:21 +00:00
962cb1b43e host: revert legacy-editor shims — clean over fancy, 181/181
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 20s
Pull out the debt that revived the legacy editor: removed kg-compat.sx (uncommitted
bare->namespaced kg-card aliases), the ./blog container mount, the legacy
sx-editor.js + hardcoded asset URLs + ~editor/sx-editor-styles reuse at /new, and
the blog/sx preloads. /new is now a clean minimal form.

Finding that reshapes Phase 5: render-page (5.1) renders STATIC component trees
but is NOT the full evaluator — a component with a data loop ((map fn items) over
(unquote data)) errors 'Not callable: nil'. So clean dynamic component pages + a
native island editor need the aser SSR pipeline (5.2), not just render-page.

Posts still render via per-block guarded render-page; unsupported editor cards
(~kg-md) show placeholders by design (no alias shim). All endpoints 200, boot clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 20:47:17 +00:00
952ff2289c vm-ext: enable JIT in epoch serving mode (Smalltalk 847/847, Datalog 356/356)
register_jit_hook is now installed in the persistent (epoch) serving-mode
branch of sx_server.ml, not just --http/cli/site. Smalltalk-on-SX conformance
under JIT is 847/847 — identical to the no-JIT baseline; Datalog 356/356.
run_tests --jit/no-jit are byte-identical before/after (no regression).

Five distinct root causes fixed (not one "miscompile"):

1. Serving mode never loaded lib/compiler.sx, so JIT used the native
   Sx_compiler.compile stub (arity-0 bytecode, params as GLOBAL_GET →
   "VM undefined: <param>"). Server-mode branch now loads compiler.sx
   before registering the hook, matching http/cli/site.

2. compile-cond / compile-case-clauses / compile-guard-clauses only treated
   keyword :else and true as the catch-all, not the bare symbol `else` that
   the CEK's is-else-clause? accepts → GLOBAL_GET "else". (lib/compiler.sx)

3. OP_DIV produced a float for non-divisible Integer/Integer (1/2 → 0.5)
   instead of the exact Rational the "/" primitive returns. Now delegates to
   the primitive, matching CEK. (sx_vm.ml)

4. OP_EQ / _fast_eq lacked Rational/ListRef cases that the "=" primitive's
   safe_eq has → (= 1/2 1/2) false under JIT. OP_EQ now delegates non-scalars
   to the "=" primitive; _fast_eq gained rational + ListRef. (sx_vm.ml,
   sx_runtime.ml)

5. Continuation-based control flow (Smalltalk ^expr non-local return, block
   escape, exceptions via call/cc) can't run in the stack VM. New data-driven
   exclusion set Sx_types.jit_excluded + `jit-exclude!` primitive, consulted in
   jit_compile_lambda (covers both the CEK hook and vm_call's tiered path).
   lib/smalltalk/eval.sx self-declares its continuation dispatch core
   interpret-only; pure helpers still JIT. The SUnit suite-runner test helper
   pharo-test-class miscompiles mid-loop and is excluded in tests/tokenize.sx.

Also adds SX_JIT_DENY / SX_JIT_ONLY env-var bisection filters to the serving
hook. Known residual documented in plans/jit-bytecode-correctness.md: the hook
re-runs a failed VM execution via CEK (correct result, possible duplicate side
effects); adopting run_tests' propagate-don't-rerun semantics is deferred to
avoid changing shared VM/CEK behavior under this loop.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 20:36:30 +00:00
70759d6ab1 host: Phase 5.1 — interactive SX-page render from a handler, 181/181
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 21s
KERNEL: add a render-page primitive (sx_server.ml, persistent mode) that renders
an UNEVALUATED SX expression with the server env via sx_render_to_html.
render-to-html expands defcomp components and collects keyword attrs itself; SX
handlers can't reach the server env, so the prim supplies it. Fixes the attr
mangling — bare render-to-html on an EVALUATED component tree turns (form :id ..)
into <form>idpost-new-form..; rendering the unevaluated expr keeps :id an attr.

HOST: lib/host/page.sx — host/page (expr -> HTML response) + host/page-route
(mount on a GET path). New page suite (8 tests) proves a generic attributed +
nested component renders correctly through a host route; verified ~editor/form
renders right too. This is the component-render step of the generic
interactive-SX-page capability; shell + static assets + hydration (5.2-5.4) next.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 20:11:49 +00:00
8e817e974f host: scope Phase 5 — generic interactive SX-page serving (host SSR)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 31s
Frame the editor as one instance of a general gap: the host serves JSON + static
content but cannot serve interactive SX component/island pages. Scope the generic
capability — reuse the kernel's existing shell pipeline (~shared:shell/
sx-page-shell + http_inject_shell_statics + http_render_page) rather than
reinvent — in 5 gated sub-steps: page-render from a handler, shell statics,
static-asset serving, island hydration, editor POC. Documents why render-to-html
alone fails (mangles evaluated-component attributes) and that component SSR is
slow until the JIT loop lands. Modern editor = SX reactive island (defisland +
signals) over a content-on-sx model; replace the legacy Lexical/Koenig editor,
don't resurrect it (the POST /new ingest already speaks sx_content).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 20:04:24 +00:00
e201eef686 host: experimental unguarded create-only POST /new — editor publishes live, 173/173
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 19s
host/blog-open-create-routes mounts POST /new with error-trapping but NO auth
(create-only; no PUT/DELETE), so the SX editor can publish to the host
end-to-end on the experimental subdomain. VALIDATED LIVE: editor-style
form-urlencoded POST -> 303 -> post renders at /<slug>/ and lists on /.

Deliberate short-lived public write hole (create-only, obscure subdomain).
MUST be gated before real use: Caddy basicauth on /new, or session auth.
Swap host/blog-open-create-routes -> host/blog-write-routes <resolver> to gate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 19:57:14 +00:00
6ed9e7dbe6 host: blog on the editor's sx_content model + render-to-html, 171/171
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 17s
Pivot blog to the SX editor's content model. The editor (blog/sx/editor.sx)
emits sx_content = SX element markup, NOT content-on-sx CtDoc blocks. So a post
is now a {slug,title,sx_content,status} record in the durable persist KV, and a
post page is render-to-html(parse sx_content) — server-side, static, no client
runtime needed to view.

Endpoints: GET / (HTML index), /<slug>/ (rendered post), /posts (JSON list),
/new (create form); POST /new (form-urlencoded editor ingest, slug from title,
303 redirect), POST /posts (JSON create), PUT/DELETE /posts/<slug>. Writes
behind auth+ACL (edit/blog). Dropped the content-on-sx/Smalltalk preload chain;
added spec/render + web/adapter-html (render-to-html) + lib/dream/form.

BONUS: render-to-html is ~0ms (vs the 2s content-on-sx Smalltalk asHTML) — it
doesn't hit the JIT-miscompiled path, so blog rendering is no longer slow.

Live: blog.rose-ash.com/ lists posts, /welcome/ renders instantly. Reads live;
the form-ingest write path needs an auth decision before going live (browser
forms can't send bearer; needs session or a Caddy basicauth gate).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 19:52:05 +00:00
85e0af83f6 host: blog post CRUD (list/create/update/delete) + fail-loud test runner, 175/175
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 37s
CRUD on the durable content store, per-request IO:
  GET  /posts        list (public)            -> [{slug,title}]
  GET  /<slug>/      read (public)            -> HTML / 404
  POST /posts        create (auth+ACL edit/blog) -> 201/400/409
  PUT  /posts/<slug> update title+body        -> 200/400/404
  DELETE /posts/<slug> delete (truncate)      -> 200/404
Writes behind the auth+ACL pipeline; create=insert ops, update=op-updates,
delete=stream truncate. 16 new CRUD tests (full lifecycle + 401/403/409/404).

GOTCHA fixed:  is a reserved CEK special form — a (let ((guard ...)))
helper was shadowed by it ((guard h) ran the guard special form -> 'first:
expected list'). Renamed to host/blog--protect; namespace-prefix all helpers.

HARDENING: conformance.sh now FAILS LOUD on load/eval errors. A test file that
errors mid-load silently truncates its suite and reports a false green (this hid
the CRUD failure as 'blog 13 passed, 0 failed'). The runner greps for error
markers and aborts. Documented the SX gotcha set + prevention ladder in the plan.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 19:24:59 +00:00
7c11d4edaa host: per-request IO kernel fix + fully-dynamic blog (no cache), 159/159
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 16s
KERNEL (sx_server.ml): route http-listen handlers through cek_run_with_io
instead of bare Sx_runtime.sx_call, so handlers resolve per-request IO
(durable persist reads/writes) via the same IO-driving runner the REPL uses.
Verified: per-request read+write, 10 concurrent writes (15 on disk, no
corruption), handler errors don't crash the server, http contract 6/6.

BLOG: fully dynamic — host/blog-post reads the post from the durable store
(content/head) AND renders (content/html) per request, no in-memory view, no
cached output. Possible because of the IO fix. Honest ~2s due to interpreted
Smalltalk render.

Render speed is NOT solved here: the JIT (precompiler) isn't installed in the
serving mode and currently miscompiles the Smalltalk evaluator's nested ASTs
(enabling it breaks ~60% of tests). Fixing the JIT is a separate, high-payoff
effort. Documented in the plan.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 18:59:36 +00:00
4e79b010b2 host: blog persisted in durable SX store + materialised view, 158/158
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 16s
Blog posts now live in the durable SX store (persist/durable-backend, on-disk
under $SX_PERSIST_DIR — already built: sx_persist_store.ml + lib/persist/
durable.sx). Publishing appends insert ops to the slug's content stream; posts
survive restarts (verified: seq/log stable across container restart, re-seed
idempotent).

Read path: http-listen handlers can't drive per-request perform/IO (sx_call
doesn't resolve the CEK IO suspension the way the main loop does), so posts are
materialised from the store into an in-memory view at boot (host/blog-load-all!
+ host/blog-seed!) and request handlers read the view — perform-free. Store is
source of truth; view is a boot-rebuilt cache.

Deploy: docker-compose.dev-sx-host.yml mounts /root/sx-host-persist (chowned to
appuser 10001) at /data/persist; SX_PERSIST_DIR set. blog.rose-ash.com/welcome/
live. Per-request-IO kernel fix tracked in the plan as the next task.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 18:33:00 +00:00
e2a90e3bbd host: blog published-post read endpoint GET /<slug>/ -> HTML, 156/156
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 15s
lib/host/blog.sx serves blog posts as HTML at GET /<slug>/ (the original
strangler target, Quart blog post_detail). A post is a content-on-sx CtDoc
rendered via content/html; anonymous + world-visible. In-memory slug->doc
registry now (host/blog-lookup swappable for a persist-backed content stream
later, handler/route unchanged). :slug catch-all mounted LAST so /feed,
/health, /internal/* take precedence. Needs the Smalltalk+persist+content
preload chain + (st-bootstrap-classes!)+(content/bootstrap!) — blog.sx
self-bootstraps at load. serve.sh loads the chain + seeds a welcome post.
Ledger gains the migrated blog post-detail (off-Quart 50% -> 53%).

LIVE: blog.rose-ash.com/welcome/ renders real HTML through Cloudflare->Caddy;
/feed still JSON (precedence verified), unknown slug 404.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 18:08:12 +00:00
2217a704a6 host: reconcile Caddy bind (restart) — blog.rose-ash.com durable
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 13s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 18:01:17 +00:00
014dd06d2b host: go live — blog.rose-ash.com served by the SX host in the stack
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 15s
Promote lib/host into the docker stack behind blog.rose-ash.com (reusing a
down Quart subdomain). New compose service sx_host runs lib/host/serve.sh on
externalnet; Caddy reverse-proxies blog.rose-ash.com -> sx-dev-sx_host-1:8000.

hosts/ fix: http-listen bound inet_addr_loopback only, unreachable from other
containers. Add SX_HTTP_HOST env (default loopback for tests/local; stack sets
0.0.0.0) in sx_server.ml. serve.sh made container-friendly (SX_PROJECT_DIR).

Verified live through Cloudflare->Caddy: /health, /feed, relations reads serve
real JSON; / 404 (no root route yet). rose-ash.com untouched. Conformance
145/145 green with the rebuilt binary.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 17:57:38 +00:00
d917a5f92f host: live wiring — native http-listen <-> Dream bridge + serve.sh, 145/145
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 19s
lib/host/server.sx adapts the native http-listen contract (string-keyed
{method,path,query,headers,body} -> {:status :headers :body}) to the Dream
host app: native->dream reassembles path+query into a target dream-request
parses; dream->native is near-identity (dream-response is already
{:body :headers :status}). host/serve = http-listen over host/native-handler
. host/make-app. lib/host/serve.sh boots the full module set and serves in the
foreground (container-entry shaped). Verified live on a host port: health/feed/
feed?actor=/relations reads serve real JSON, unknown->404. server suite (13)
covers the bridge as pure functions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 17:41:58 +00:00
bac80f6c0b host: Phase 3 — relations WRITE cut-over (attach/detach-child), 132/132
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 17s
Migrate the container relations write actions onto lib/relations: POST
/internal/actions/attach-child + /detach-child dispatch to relations/relate
and relations/unrelate over the same "type:id" node model, behind the
auth+ACL pipeline (wrap-errors . require-auth . require-permission), mirroring
POST /feed. Closed-loop test: attach -> visible via get-children -> detach ->
gone; 401/403/400 guards. Ledger now models the full relations surface (7
endpoints): container reads+writes migrated, typed relate/unrelate/can-relate
proxied (registry+cardinality validation not in lib/relations). Off-Quart
coverage 45% -> 50%.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 17:30:45 +00:00
11aba081f4 host: Phase 3 — relations READ cut-over (get-children/get-parents), 121/121
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 20s
Migrate the two internal relations read queries onto lib/relations: GET
/internal/data/get-children + /get-parents dispatch to relations/children
and relations/parents. Bridge the Quart (type,id) node key to a graph atom
symbol "type:id" with relation-type as the edge kind; optional child/parent
-type params filter by "type:" prefix. Golden tests pin each endpoint to
subsystem-call + envelope. Ledger entries flipped to :migrated (off-Quart
coverage 27% -> 45%).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 17:24:37 +00:00
ef7de817bb host: Phase 3 — strangler migration ledger + coverage, 107/107
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 3m32s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 17:11:22 +00:00
065fd248da host: Phase 2 complete — SXTP wire format + Dream bridge, 82/82
lib/host/sxtp.sx implements the host<->subsystem wire format per
applications/sxtp/spec.sx:

- message algebra (request/response/condition/event + status helpers
  ok/created/not-found/forbidden/invalid/fail) as string-keyed dicts;
  verb/status/type stored as symbols (ride the wire bare)
- codec: sxtp/serialize (dict -> text/sx list form, deterministic top-level
  field order, nested messages emitted in their own list form, no :msg leak)
  and sxtp/parse (text/sx -> dict via a deep keyword-token->string normaliser)
- Dream bridge: sxtp/from-dream (HTTP req -> SXTP req, method->verb,
  query->params) and sxtp/to-dream (SXTP resp -> HTTP resp, status->code,
  body serialised to text/sx)
- 39-test suite covering algebra, serialise/parse round-trip, mappings, bridge

Runtime notes: serialize renders string-keyed dicts as {:k v} and symbols
bare; parsed keyword tokens are a distinct type (not = to string literals) so
parse normalises; unquote-splicing is unreliable so the emitter is str-based.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 20:01:25 +00:00
2ffdd6f078 host: Phase 2 — middleware (auth+ACL+error) + guarded POST /feed, 43/43
Composable handler->handler layers over Dream's primitives, with auth and
permission POLICY injected so the layer is policy-free and testable:

- middleware.sx: host/wrap-errors (JSON 500 via dream-catch-with),
  host/require-auth (bearer->principal via dream-bearer-token, JSON 401,
  injected token resolver), host/require-permission (lib/acl acl/permit? gate,
  JSON 403, injected resource extractor), host/pipeline (first = outermost)
- feed.sx: POST /feed via host/feed-write-routes — auth ∘ ACL(post,feed) ∘
  wrap-errors over host/feed-create (parse JSON body -> feed/post -> 201;
  non-object -> 400). Created activity reads back via GET /feed.
- middleware suite (9) + feed write tests (6 new); conformance preloads now
  include the Datalog engine + ACL subsystem + Dream auth/error.

ACL works with string atoms (no symbol coercion). Mute/prefs layer and sxtp.sx
deferred to the next tick.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 19:48:18 +00:00
d5a1c8370c host: Phase 1 — router + handler + GET /feed endpoint on Dream, 28/28
First migrated endpoint onto the SX host. lib/host is a thin wiring layer:
a host handler is a Dream handler (request->response) that calls a subsystem
public API and serialises via a shared JSON envelope.

- handler.sx: host/ok, host/ok-status, host/error, host/json-status (Dream's
  dream-json is 200-only), host/query-int
- router.sx: host/make-app assembles per-domain route groups + /health probe
  into one dream-router (reuses dr/flatten-routes)
- feed.sx: GET /feed reads feed/all + stream combinators, recent-first, with
  ?actor= filter and ?limit= cap
- 3 test suites incl. a golden test (body == subsystem recent stream + envelope)
- conformance.sh mirrors lib/dream's runner

Builds on dream-on-sx (merged, gate green 480/480) rather than a throwaway
native request model; collapses most of plan Phase 4 into Phase 1.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 19:36:55 +00:00
fe958bda69 Merge loops/dream into architecture: dream-on-sx — OCaml Dream web framework reimplemented in plain SX
Full roadmap + 10 extensions, 413/413 tests across 17 suites (lib/dream/).
Five types (request/response/route + handler/middleware fns); router (params,
scopes, 405/HEAD), middleware, sessions (signed), flash, forms+CSRF+multipart,
websockets, static files, error handling, CORS, JSON, auth (base64/basic/bearer),
HTML escaping, security headers, dream-run + api facade, 4 demos.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 19:19:36 +00:00