forth's conformance.sh reads a foreign Forth test corpus (Hayes Core core.fr),
preprocesses it with awk + an external python3 chunk-splitter that generates a
chunks.sx of raw source strings, then runs them through the interpreter via
(hayes-run-all). The shared driver only epoch-loads SX preloads and evals SX
test suites emitting a counter/dict scoreboard — it cannot reproduce the
external preprocessing pipeline over a foreign .fr corpus (same category as
lua/smalltalk). No SX tests/*.sx suites exist to migrate. Left conformance.sh
untouched; recorded the exclusion.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
payment.sx — payment-request materialises {:order :amount :currency :return-url}
at the IO edge (amount from the ledger, currency/return-url host-supplied), so
lib/commerce stays vendor-agnostic; SumUp/Stripe adapters live in the orders
service and order-settle!(ref, amount) is the resume seam. pending-payments
enumerates suspended orders + envelopes (host poller seam). Gotcha handled: a
Scheme string flow-payload round-trips back wrapped as {:scm-string ...} —
unwrapped via scm->string. Total 209/209 across 13 suites.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Feed is the canonical MODE=counters shape: each suite runs in a fresh session
with shared preloads and a single feed-test-pass/feed-test-fail pair. Lifted the
old script's inline epoch-2 counter + feed-test helper defs into
lib/feed/test-harness.sx (preloaded last) so the driver can load them before
each suite. conformance.conf + 3-line shim; historical scoreboard schema
preserved. No driver change needed.
Parity verified 189/189 (0 fail), every suite matching baseline.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Closes the BIF half of Step 8. Native http-request primitive landed
in architecture via the fed-prims merge (the m2 plan's Blocker #2),
so the briefing-allowed-exception wrapper in lib/erlang/runtime.sx
can finally be wired.
Marshalling at the BIF boundary:
Url : Erlang binary -> SX string (byte-list -> integer->char).
Method : Erlang atom upcased ('get -> "GET") for HTTP-wire
convention, or Erlang binary passes through verbatim.
Headers : Erlang proplist -> SX dict via er-proplist-to-dict.
Body : Erlang binary -> SX string.
Result {:status :headers :body} marshalled back to Erlang
{ok, Status::integer,
Headers::proplist (binary-keyed via er-of-sx-deep),
Body::binary (char->integer over the SX string)}.
Bad arg shapes (non-binary URL or body) raise error:badarg; native
DNS / connect / bad-URL failures surface as Erlang error markers
that the caller can catch.
Test: next/tests/httpc_request.sh 10/10
- registration under httpc/request/4
- BIF marked non-pure
- wrong-arity (/1) absent from registry
- badarg on non-binary URL
- badarg on non-binary body
- live GET against `python3 -m http.server` -> Status 200
- body bytes match "hello from python\n"
- headers come back as proplist (is_list/1 = true)
- 404 path -> {ok, 404, ...} (not an error tuple)
- method passed as binary works
URLs spelled out as byte-list <<104,116,116,p,...>> binaries since
the parser truncates <<"..."> string-literal binaries (same
workaround backfill_drain.sh uses for inbox paths).
Plan: 8e ticked; Blocker #2 marked RESOLVED with the merge that
unblocked it referenced. Step 8f (live HTTP dispatch through
delivery_worker) and Step 10c (peer-actor doc fetch) are now
unblocked.
No-regression gates green: Erlang conformance 761/761,
http_multi_actor 44/44, follower_graph 18/18, follow_lifecycle 9/9,
backfill 20/20, backfill_drain 6/6, http_listen_bif 5/5.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
attribution.sx — the briefing's marquee "which line item triggered this
discount?" backward query. promo-lines gives each promo's pure scope
(percent/member -> class lines, bundle -> sku lines, fixed -> order-level);
promo-toucheso relates (code, line) for applying promos, run forward
(lines-for-code) and backward (codes-for-line). Additive; promo amounts
unchanged. Total 201/201 across 12 suites.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Erlang's suites load into one session and each exposes a pass counter plus a
*count* (total) counter rather than a fail counter, so MODE=dict fits directly:
each suite's runner is a dict literal {:passed P :failed (- count P) :total count}.
No driver change needed (dict mode already supports arbitrary runner expressions).
conformance.conf + 3-line shim; historical scoreboard schema preserved.
Parity verified 761/761 (0 fail), every suite matching baseline.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Carries {:order :amount :currency :return-url} on the 'payment suspension so any
provider's host adapter can initiate payment without the engine knowing the
vendor; order-settle!(ref, amount) stays the vendor-neutral resume seam.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extend the shared driver's MODE=counters with a backward-compatible SUITES
format: name:file[:pass-var:fail-var[:extra-preload ...]]. Optional per-suite
counter symbols (override the global COUNTERS_PASS/COUNTERS_FAIL) and per-suite
preload chains (loaded after the global PRELOADS). Plain name:file entries are
unchanged — verified against haskell (fib/sieve/quicksort 2/2/5, matches
committed scoreboard).
common-lisp has 8 distinct per-suite counter pairs and a different preload
chain per suite, so it could not fit the single-counter/fixed-preload model;
the extended format expresses it directly. conformance.conf keeps the historical
scoreboard schema; conformance.sh becomes the 3-line shim.
Result 487/487 (0 fail) vs the old 305/0 baseline — higher and explained: the
old per-suite 'timeout 30' was too tight for the slow eval suite (~15-25s under
contention), silently recording it as 0; the driver's 180s budget recovers its
true 182. geometry/mop-trace stay 0/0 (pre-existing refl-class-chain-depth-with
load error; counter vars defined as 0 -> clean gc-result, no fail-fallback).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
recon.sx — reconciliation as relational queries over the ledger: per-order
summary tuples + recon-statuso/neto/mismatcho miniKanren relations, so
overpaid/underpaid/settled and "settled to net N" are backward run* queries.
Tests cover double-charge guard, partial refund, webhook replay.
federation.sx (out-of-scope stub) — a federated catalog is the union of each
instance's product facts, so the same relations query cross-instance
(instances-with-sku, sku-offers, cheapest-offer). In-process mock, no network.
Completes the commerce-on-sx roadmap (Phases 1-4). Total 185/185 across 11 suites.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
timezone.sx: wall-clock LOCAL <-> absolute UTC. :fixed + :dst zones (std/dst
offsets + UTC transition rules, EU-style, no IANA DB) computed via calendar
helpers. ev-event-tz authors in local time; ev-expand expands tz events in
LOCAL time then converts each occurrence to UTC, so a 09:00 weekly meeting
stays 09:00 across a DST change (UTC instant shifts). Predefined utc/london/
paris. Plain events unaffected. 295/295 green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Classified migratable-in-kind (SX suites over epoch, not a foreign runner)
but blocked on driver feature gaps: 8 distinct per-suite counter variable
name pairs and per-suite preload chains, neither supported by MODE=counters
(single global counter + fixed preloads) nor MODE=dict (load-time counter
collisions across suites). Baseline 305/0 across 12 suites. Did not migrate;
conformance.sh left untouched. Driver unchanged (out of per-iteration scope).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
order.sx — reserve -> await-payment -> fulfil as a flow-on-sx flow carrying
only the order-id; the SX driver services each request by appending to the
persist ledger. order-begin! creates+reserves and suspends at payment;
order-settle! (webhook) resumes -> fulfils, idempotent on replay
(:already-settled). order-flow-restart! simulates a process restart Scheme-side
and the suspended order resumes with the ledger intact. Composes all three
substrates: minikanren pricing -> flow lifecycle -> persist ledger.
Total 153/153 across 9 suites.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reading lib/mod (Prolog) and lib/acl (Datalog) side by side shows the convergence
is in module names only. Federation: opposite trust models (SX registry + decision
sharing vs in-engine Datalog trust facts + fact replication), zero shared code.
Audit: only a ~5-fn core overlaps and it diverges (entry shapes, seq base 0 vs 1,
op sets, mutation idiom) — not worth a shared module under two restricted envs.
Outcome: keep them parallel; revisit only on a third same-model consumer.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Settled design for order flow (checkboxes 1-2): Scheme flow carries only the
order-id, SX driver does all ledger IO. Key gotcha captured: never return
flow-make-env from eval (serializer hangs on the cyclic env); run the flow
suite single-process like flow's own conformance with a long timeout.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fetch abstracts how a peer's agenda arrives: (fetch peer-id ws we) ->
{:status :ok :occurrences} | {:status :error}. ev/federated-agenda-via merges
local + trusted peers fetched via the transport; unreachable peers degrade
gracefully. ev/peer-fetch = in-process adapter; ev/federation-status reports
reachability. A real fed-sx transport drops in unchanged. 278/278 green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
identity_tokens:revoke_app(Subject, Client) revokes every grant a subject
holds for one client at once (audited one revoke per grant), exposed at the
facade as identity:revoke_app. The action counterpart to the grants view —
completing the account-security view+action pairs (sessions/logout_all,
grants/revoke_app, history). Other subjects' same-client grants are
untouched. account 11/11, 233/233.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ev/reschedule-notifications: when an event carries per-occurrence overrides,
reads the roster at each overridden occurrence's original occ-key and emits a
reschedule message per booked attendee (old-start/new-start/new-duration).
Idempotency key = original-key/reschedule/new-start. 272/272 green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
booking-notify.sx walks the booking stream into ordered notifications by kind
(booked/promoted/held/confirmed/released/cancelled/waitlisted). Promotion
detected by folding the waitlist (a booking for a waitlisted actor is a
promotion). id=occ-key/seq -> idempotent re-derivation, no double-ping.
Connects ticketing to the delivery layer. 265/265 green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ev-with-override re-times/re-sizes a single instance of a series (keyed by
original start). ev-expand applies overrides after EXDATE/RDATE: agenda
re-sorts, instance moved out of window is dropped (slot vacated), no-op for a
non-occurring start. assoc for immutable event update. 254/254 green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
actor_outbox_response_for/3 in http_server.erl now reads ?since=
from the query string before paging:
Q = field(request_query, Cfg),
Filtered = case parse_since(Q) of
nil -> Entries;
SinceCid -> backfill:since_cid_entries(SinceCid, Entries)
end,
Slice = page_slice(Filtered, Page),
...
New helpers:
parse_since/1 — scan query for since=<Cid>, value is the
binary up to next & or end-of-binary. nil
when absent.
scan_param/2,3 — generic 'find Name=Value anywhere in &-sep
query'. Used for since= today; could be
factored over parse_page=.
skip_to_amp/1 — walk past the next & for the iteration step.
Order-independent: ?since=X&page=2 and ?page=2&since=X both
work. Unknown cid -> backfill:since_cid_entries returns []
-> empty page -> body degrades to tip-only shape (Step 4d
back-compat).
Three new cases in http_multi_actor.sh (44/44 total):
- ?since=<first cid> filters out the first publish, leaving
2 of 3 items in the paged response
- ?since=<unknown cid> -> empty page; body has tip but no
item: lines (tip-only degrade)
- ?since=<cid> + ?page=1 combined — pagination still applies
to the filtered list
Latent issue surfaced + fixed in passing: http_multi_actor.sh
was missing follower_graph + delivery + backfill module loads
(outbox has depended on follower_graph + delivery since Step 7c
and now backfill from 9a). Added all three with epoch 100/101/
102 to match the c6b49200 fix-up pattern. 41 existing tests now
also exercise the live path through outbox:publish without
crashing on missing module deps.
When full, ev/waitlist! queues actors FIFO (:waitlist/:unwaitlist on the
booking stream; waiting fold independent of the seat fold). ev/waitlist,
ev/waitlist-position, ev/leave-waitlist!. ev/cancel-promote! frees a seat and
auto-promotes the head of the queue to a confirmed booking. Idempotent.
240/240 green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
identity_tokens:grants_for(Subject) lists a subject's active grants as
[{Client, Scope}] (revoked excluded), exposed through the facade as
identity:grants(Subject). Completes the per-subject account-security trio:
sessions (where logged in), grants (which apps have access), history (what
happened). New tests/account.sx. Conformance internal timeout raised to
1200s (22 suites, ~10min — run in background). 229/229.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New next/kernel/backfill.erl owns the §13.3 backfill mode
slicing. Given an outbox log + a mode, returns the activity
list to send to a new follower as backfill.
Public API:
slice/2(Mode, LogState) default Wrap=false
slice/3(Mode, LogState, Wrap) Wrap=true wraps entries
wrap_backfill/1 add {backfilled, true}
parse_mode/1 lift Follow :backfill field
Modes:
none new follower: forward-only content
full entire outbox
{last_n, N} last N activities (FIFO)
{last_t, T, NowFn} entries with :published in
(NowFn()-T .. NowFn()]
{since_cid, Cid} entries after the one with :id = Cid
(consumes the matched entry; returns
every entry after it)
wrap_backfill/1 marks each entry {backfilled, true}. Per §13.3
wrapped bodies preserve :id so the receiver's replay defence
still catches duplicates from the live stream.
parse_mode/1 accepts:
nil / none / full / {last_n, _} / {last_t, _, _} /
{since_cid, _} — pass through or normalize
Proplist with :mode + :limit -> {last_n, N}
Proplist with :mode + :duration -> {last_t, T, fun() -> 0 end}
Proplist with :mode = full -> full
Anything else -> none (open-world default)
Substrate gotchas re-confirmed and worked around:
- lists:nthtail/2 not registered — rolled drop_n/2
- Pattern-alias 'Pat = Var' not supported by this port's
parser — parse_mode/1 clauses use explicit deconstruction
20/20 in next/tests/backfill.sh covering all five modes plus
edge cases (N=0, N>length, T=0 -> empty window, since_cid
hit/miss/unknown), wrap_backfill semantics, parse_mode for
atoms / tuple shapes / proplists / unknown / nil.
Step 9b (outbox listing ?since=Cid&limit=N pagination) and
Step 9c (Follow-Accept-backfill wiring) layer on top.
Conformance preserved at 761/761.
Two new projection modules for the rich verbs landed in Step 11a:
next/kernel/announce_state.erl
Per-target-Cid announcer set.
State: [{TargetCid, [AnnouncerActorId, ...]}, ...]
Set semantics — duplicate Announce by the same actor on the
same target is a no-op.
Public API:
new/0, fold/2, fold_fn/0
announcers_for/2, announce_count/2, announced_cids/1
has_announced/3
next/kernel/endorsement_state.erl
Per-target-Cid + per-kind + per-actor endorsement counter.
State: [{TargetCid, [{Kind, [{ActorId, Count}, ...]}, ...]}, ...]
Additive semantics — re-endorse by the same actor under the
same kind bumps the counter. Undo{Endorse} retraction defers
to a follow-up.
Public API:
new/0, fold/2, fold_fn/0
counters_for/2, total_for/2, kinds_for/2
endorsers_for/3, has_endorsed/4
Both fold_fn/0 returns a 2-arity Erlang fun for
projection:start_link/3 (same plug shape as actor_state /
follower_graph / delivery_state). Non-matching activity types
pass through unchanged.
Read-side accessors cover both enumeration (announcers_for,
endorsers_for) and predicates (has_announced, has_endorsed) so
the feed/timeline projection layer doesn't have to re-implement
that logic on every consumer.
19/19 in next/tests/rich_verbs.sh:
announce_state:
- new/0 -> []
- Announce -> announcer added
- Two announces same target -> both in set
- Duplicate announce by same actor -> no-op
- announce_count + announced_cids
- has_announced predicate
- fold_fn/0 is fun/2
- Non-Announce activity passes through
endorsement_state:
- new/0 -> []
- Endorse -> counter 1
- Two likes by different actors -> total 2
- like + share -> two kinds tracked
- endorsers_for(Cid, Kind)
- has_endorsed predicate
- fold_fn/0 is fun/2
- Non-Endorse activity passes through
- Same actor endorsing twice -> total = 2 (additive)
Conformance preserved at 761/761.
federation.sx: a peer publishes a schedule; ev/federated-agenda merges local
(origin :local) with trusted peers' agendas, sorted by start, tagged with
:origin provenance. Trust is a peer-id set re-checked per merge; untrusted
peers contribute nothing. Real transport slots behind ev/peer-agenda.
209/209 green — all four plan phases implemented.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>