host: P0 review — fix edit-submit ordering bug + record carried-forward debt
REVIEW at the P0-complete milestone found one live bug and several forward prerequisites. FIX (was live): edit-submit ran maybe-publish! BEFORE set-field-values!, so an edit that set a category and published in one submit fired the publish activity on the STALE category (wrong branch). Reordered — fields land before the transition fires. Regression test added (fields-first → newsletter→digest, not stale→notify). blog 210/210. Recorded carried-forward debt in the plan: activity identity (DEBT #1, blocks P2 — :id=CID false- dedups relation events), capability bind not wired into the live engine (DEBT #2, P1), synchronous- in-request dispatch (DEBT #3, RA needs the async boundary + background pump), the 'urgent' default smell (DEBT #4). Sequencing note: P1's runner-derivation is vacuous until RA adds a 2nd runner, and RA is the load-bearing risk — recommend a narrow RA spike next to de-risk the durable/federated half. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -2659,14 +2659,16 @@
|
||||
(if (= (len issues) 0)
|
||||
(begin
|
||||
(host/blog-put! slug title sx-content status)
|
||||
;; P0.3: a draft→published transition fires the publish flow through the seam.
|
||||
(host/blog--maybe-publish! slug (get r :status) status)
|
||||
;; store the typed field values from the generic, type-driven form (Slice 8b)
|
||||
;; store the typed field values FIRST — the publish activity reads :category from
|
||||
;; them, so field-writes must land before the transition fires (else it branches on
|
||||
;; the stale category on an edit that both sets a category and publishes).
|
||||
(host/blog--set-field-values! slug
|
||||
(reduce (fn (acc f)
|
||||
(assoc acc (get f :name)
|
||||
(or (host/field req (str "field-" (get f :name))) "")))
|
||||
{} post-fields))
|
||||
;; P0.3: a draft→published transition fires the publish flow through the seam.
|
||||
(host/blog--maybe-publish! slug (get r :status) status)
|
||||
(dream-redirect (str "/" slug "/")))
|
||||
(let ((issue-items (map (fn (i) (quasiquote (li (unquote i)))) issues)))
|
||||
(host/blog--resp req 400
|
||||
|
||||
@@ -1191,6 +1191,17 @@
|
||||
(list (get e :type) (get (get e :object) :type)
|
||||
(get (get e :object) :slug) (get (get e :object) :category))))
|
||||
(list "create" "article" "pub4" "newsletter"))
|
||||
;; P0 review regression: field-writes must PRECEDE the transition, else the publish activity
|
||||
;; branches on the stale category. (edit-submit now sets fields before maybe-publish!.)
|
||||
(host-bl-test "publish reads the FRESH category (fields set before the transition fires)"
|
||||
(begin
|
||||
(set! host/blog--flow-log (list))
|
||||
(persist/backend-kv-put host/blog-store host/blog--flowlog-key (list))
|
||||
(host/blog-put! "p04r" "R" "(article (h1 \"r\"))" "draft") ;; a draft, no category yet
|
||||
(host/blog--set-field-values! "p04r" {"category" "newsletter"}) ;; fields FIRST (the fix order)
|
||||
(host/blog--maybe-publish! "p04r" "draft" "published") ;; then the transition
|
||||
(map (fn (e) (get e "verb")) host/blog--flow-log))
|
||||
(list "validate" "digest")) ;; newsletter→digest, not stale→notify
|
||||
;; P0.2: the publish WORKFLOW as an execute-fold DAG — branches on category, needs {effect,branch},
|
||||
;; binds to the synchronous execute-fold runner (derived, not chosen).
|
||||
(host-bl-test "publish-DAG: category branch (newsletter→digest) via the execute-fold"
|
||||
|
||||
@@ -170,7 +170,35 @@ substrate-agnostic seam, durably, in the canonical activity shape, with the Erla
|
||||
staged. Every piece is a swappable adapter: RA (Erlang runner) + TA (fed-sx transport) plug in next
|
||||
without touching the DAG or the wiring.
|
||||
|
||||
### P0 REVIEW (2026-07-02) — findings + carried-forward debt
|
||||
- **FIXED (was a live bug):** edit-submit ran maybe-publish! BEFORE set-field-values!, so an edit
|
||||
that set a category AND published branched on the STALE category. Reordered (fields first);
|
||||
regression test added. blog 210/210.
|
||||
- **DEBT #1 (blocks P2): activity identity.** Dedup uses :id = the object CID. Relation Add/Remove
|
||||
events don't change the CID → multiple relation activities would share :id → false dedup. P2 needs
|
||||
a real activity id (verb+object+seq/edge), NOT the bare CID.
|
||||
- **DEBT #2 (P1): the capability bind isn't wired into the live engine.** host/blog--publish-engine
|
||||
calls exec-runner directly; host/flow--bind is tested but decorative in the live path. P1's engine
|
||||
must DERIVE the runner via bind (required ⊆ advertised), not hardcode it.
|
||||
- **DEBT #3 (RA): flow execution is synchronous in the request path.** Fine for 2 cheap effects, but
|
||||
a durable/suspend runner CANNOT block a request — RA needs dispatch moved OFF the request path
|
||||
(emit in-request → a background loop calls behavior/pump). The seam supports it (pump); the wiring
|
||||
doesn't exist. This is structural, not just the marshaller.
|
||||
- **DEBT #4 (minor): the "urgent" category default** notifies everyone for an uncategorised post —
|
||||
demo-convenient, semantically wrong. Default to skip/none once the demo value is past.
|
||||
- Acknowledged/known: unbounded flow log (cap/rotate), registry :register! is a stub (P1 makes it
|
||||
real), :actor "site" placeholder (TA needs the actor model), marshaller unverified-until-RA.
|
||||
|
||||
### Sequencing note (hidden prerequisites)
|
||||
- **P1's "runner derived via caps" is near-vacuous until a SECOND runner exists** — with only
|
||||
exec-runner, everything derives to it. The derivation only MEANS something once RA lands.
|
||||
- **RA is where the real risk lives** (SX→erlang-on-sx dispatch + suspend→resume→pump + the
|
||||
er-scheduler context bug) AND P1/P2 quietly depend on what RA forces (a 2nd runner, the async
|
||||
boundary). RECOMMENDATION: a narrow **RA SPIKE** next — prove one dispatch + one suspend/resume/
|
||||
pump cycle in isolation — de-risks the whole durable/federated half before building P1/P2 on it.
|
||||
|
||||
## P1 — types DECLARE behavior (generalize)
|
||||
<!-- PREREQ (review): wire host/flow--bind into the engine (DEBT #2). Note: derivation is trivial until RA gives a 2nd runner. -->
|
||||
- [ ] The type carries :behavior = [{:on {:verb :object-type :guard} :dag <ref>}] — edited in the
|
||||
type-def editor beside grammar + relations. NO runner hint (the runner is DERIVED from the DAG's
|
||||
required capabilities).
|
||||
@@ -181,10 +209,12 @@ without touching the DAG or the wiring.
|
||||
to (the derived sync/durable/distributed classification, visible not hand-set).
|
||||
|
||||
## P2 — state-change → activity emission (ALL events, not just publish)
|
||||
<!-- PREREQ (review): fix activity identity (DEBT #1) — :id must not be the bare CID, or relation events false-dedup. -->
|
||||
- [ ] Wire the host write path: put!/set-comp!/edit-submit → Create/Update; relate!/unrelate!/tag →
|
||||
Add/Remove. Emit canonical activities into the transport log. Define the delta summary.
|
||||
|
||||
## RA — the ERLANG (durable) RUNNER adapter ← the old "fed-sx spike", now an adapter
|
||||
<!-- PREREQ (review): move dispatch OFF the request path (DEBT #3) — background loop calls behavior/pump; suspend can't block a request. Do a NARROW SPIKE first. -->
|
||||
- [ ] A seam runner wrapping next/'s flow_dispatch + flow_store: marshal the canonical activity →
|
||||
next/'s proplist, dispatch to a named flow (blog_publish_digest), map the result back to
|
||||
{:status done|suspended :effects :resume}. On suspend, wire the resumed completion → the transport
|
||||
|
||||
Reference in New Issue
Block a user