The sx-forge native-loop blocker: clone! of the live giles/rose-ash
never returned over gitea/http-app. Root cause was NOT the transport —
pack-line-parse ran every pack line through the interpreted spec parser
(~6.6KB/s on the CEK machine; a full-repo pack = hours), and a non-hex
byte in a pkt length header parsed negative (index-of -1), walking the
scan index backwards forever.
- gitea/parse-obj: use the host reader (open-input-string + read,
~3700x faster, value-identical) when the host provides it; hosts
without string ports keep sx-parse. Feature-detected at load.
- pkt-sections-loop: (< n 4) guard — malformed lengths error instead
of hanging.
- push-cmd!: haves = every advertised remote ref held locally, so a
NEW branch pushes only its delta, not the whole repo closure.
- tests/wire.sx: malformed-len errors, truncated-pkt clamps, parse-obj
= sx-parse equivalence (blob/commit + cid). 83/83.
- tests/wire-http.sh + wire-http-client.sx: end-to-end over REAL
http-listen/http-request on :8943 — ls-remote/clone/push-new-branch/
fresh-clone-verify/delete. The coverage gap that hid all this.
Proven vs the live forge (in sx-gitea-1): full 4468-file clone in 77s
(was: hang), commit, push heads/sx-smoke-test ok, branch advertised on
sx.sx-web.org. Conformance 620/620.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Import from an IMMUTABLE snapshot (git archive), not the live tree — a
replay diverges the moment a source file changes (the forge's own
non-fast-forward check caught exactly that). import-stage-msg! carries
the source SHA in the commit message; import-delete-remote! + push
replaces a partial import's history in two requests.
rose-ash mirrored to sx.sx-web.org/giles/rose-ash: 4468 files @
4a7c05a2, one commit, zero skipped, single push under the linear wire.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The closure walk rebuilt its seen-set with assoc — which on this kernel
copies the entire hashtable per call — and stacked pending cids with
concat; pack-cids then insertion-sorted the result. All three are
quadratic, which surfaced the moment a real repo (4.5k files) went over
the wire: a single push spent an hour in the walk. The seen-set is now a
private dict mutated in place (dict-set!, the acl engine's own pattern),
pending cids are cons-stacked, and packs are unsorted (order is
irrelevant to the receiver). Wire suite stays 78/78; every clone/fetch/
push on repo-scale histories now walks each object once.
lib/gitea/import.sx: working-tree importer — file-read + http-request
adapt the Phase 3 wire client to a live server (gitea/http-app);
staging (deterministic commits, so an interrupted import replays to
identical CIDs and resumes without re-pushing) is separate from the
single delta push; pack lines that exceed the pkt limit are skipped and
reported.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
lib/gitea/serve.sx: durable live forge on the kernel persist store
(SX_PERSIST_DIR) with idempotent seeding (instance id, admin user +
rotating token, welcome repo), blocking in the native http-listen loop
via host/native-handler — the same wiring that serves blog.rose-ash.com.
lib/gitea/serve.sh: full-stack launcher (every substrate the eight
phases compose, in dependency order, + dream/session for the cookie
bridge) — container entrypoint and local launcher in one.
docker-compose.dev-sx-gitea.yml: sx_docs image, bind-mounted worktree +
binary, /root/sx-gitea-persist for durable state, externalnet so Caddy
can proxy sx.sx-web.org. Serving JIT off until validated for this path.
Smoke-tested locally: pages, authed API, markdown-rendered issues,
pkt-line ref advertisement, 401 gating, and full state survival across
a restart against the same persist dir.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
lib/gitea/fed.sx: forges federate as peers. Each forge carries an
instance id; users and repos project as AP actor documents (Person/
Group/Repository with inbox/outbox + clone endpoint); the outbox is the
activity log in an AP-shaped envelope.
Trust follows the events-federation pattern — a kv set of peer ids
RE-CHECKED on every operation (inbox, mirror sync, delivery), so
revoking a peer takes effect immediately; peer transports (dream app
fns) live only in the runtime cache.
Inbox (POST /api/ap/inbox, trust-gated): every accepted activity lands
in a federated log with :origin provenance; open-issue/comment/open-pr
MATERIALIZE — the foreign author becomes an auto-created proxy user
'<name>@<peer>' and the issue/comment/PR is created locally under that
identity. fed-deliver! pushes public-repo activities (cursor-based,
never private) to every trusted peer's inbox. Cross-instance repo
follow = mirror!/mirror-sync! over the Phase 3 wire client.
fed-timeline merges local + foreign activities with provenance tags.
Suite: two in-memory forges federating end to end — actor docs, trust
lifecycle, materialization, proxy-user reuse, wire inbox 400/403/200,
mirrors (clone/sync/trust-revocation), cursor delivery, timelines.
Adds lib/gitea/README.md (composition map, architectural rules, known
limits). Final scoreboard: 615/615 across repo/access/wire/issues/pr/
activity/search/fed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
lib/gitea/search.sx: the forge builds document corpora SX-side — code
files from the default branch head (path + blob text), issues (title +
body + comments), PRs (title + body + reviews) — embeds them as one
haskell-on-sx program and asks searchRankTfIdf for ranked doc ids
(terms, AND/OR/NOT, phrases).
Cost model honored: one evaluation parses the Haskell layers
(~20s CPU), extra queries are nearly free — so the core primitive is
gitea/search-multi (any number of corpora and queries in a single
evaluation; each corpus an idxN binding) and only the six layers
searchRankTfIdf needs are compiled, not the full search/src. The test
suite runs its thirteen SX-level queries over five corpora as ONE
evaluation.
Global search spans exactly the repos the caller can read. Web:
/:owner/:name/search page (kind filter), repo + global JSON search.
Suite timeout raised to 900s for the haskell-backed suites.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
lib/gitea/activity.sx: every forge action lands as a feed activity in an
append-only persist log stream. Instrumentation is done IN the runtime —
repo-create!/issue-create!/issue-comment!/pr-create!/pr-review!/pr-merge!
are redefined around their originals, so SX callers and web handlers emit
activity with zero call-site edits (failed mutations emit nothing).
Timelines are lib/feed (APL) queries: global/repo/user, newest-first,
visibility follows repo access (private-repo activity invisible to
non-readers). Follows (user: or repo: targets) drive a dashboard of
followed actors/repos minus one's own actions.
Notifications ride lib/events durable delivery: activities after a
cursor expand to (id recipient body) messages (comment -> author+
participants, review/merge -> PR author, open-issue -> assignees, never
the actor), ev/deliver-messages runs the at-least-once digest flow, and
delivered messages file into per-user kv inboxes; the cursor advance
makes reruns no-ops.
Web: /activity + /:owner/:name/activity pages, user-activity/dashboard/
follow/notifications/notify-run JSON API. gitea/all-routes now hoists
every /api/* route ahead of the wildcard /:owner/:name patterns so later
packs can add API endpoints without being shadowed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
lib/gitea/pr.sx: PRs as kv records sharing the per-repo number counter
with issues. Diffs are LIVE, computed from the merge base of the current
branch heads to the source head via sx-git (no spurious deletions when
the target moves on). Reviews: latest verdict per reviewer wins; authors
cannot review their own PR; approved? = some approve and no outstanding
request-changes.
Lifecycle is a lib/flow durable workflow (deterministic-replay suspend):
open -(approval)-> approved -(merge)-> merged; review! resumes the
approval suspend when the verdict set first approves, merge! resumes the
rest, close! cancels, reopen! starts a fresh flow. The flow env lives in
the forge handle; the record's :state stays the source of truth.
Merge via git/merge-commits over the merge base: up-to-date, fast-
forward (ref move only), true two-parent merge commit, or conflicts with
the conflicting paths. Every ref move is branch-cas! — concurrent pushes
surface as 'stale'. Merge queue: approved PRs merge in order,
failures stay queued.
Web: pulls list + PR page (body html, reviews, lifecycle, unified diff),
JSON API for create/review/merge (409 on conflicts/stale)/close (author
or write)/enqueue/queue-process.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
lib/gitea/issues.sx: issues as kv records (zero-padded per-repo
numbering, title/author/state, sorted label+assignee sets, Markdown
body, comment thread). Bodies and comments are content-on-sx documents:
content/from-markdown -> block doc -> content/html for pages, with the
round-trip law asserted in the suite. The issue graph (issue->repo
parent, author origin, assignee member, label link, commenter reply) is
DERIVED into lib/relations facts and rebuilt on fact change — same
pattern as the acl db, so deleting a repo can never dangle edges.
Views: open/closed/by-label/by-assignee; graph queries: repo-issue-nodes,
user-authored, user-assigned, label-issues, issue-participants.
Web: issues list + issue page (rendered HTML body + comments), JSON API:
create (any authenticated reader), comment, close/reopen (author or
write), label/assignee management (write). All read-gated like the rest.
Infra: gitea/route-packs registry — wire/issues append their routes at
load; gitea/app serves all packs. repo-delete! now purges collab/issue/
issue-seq rows too (ghost-state regression tested). Conformance runner
gains per-suite extra modules; the issues suite loads relations +
smalltalk + content (~5s).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
lib/gitea/wire.sx: git-style pkt-line framing (byte-compatible hex4
lengths + flush sections); object closure walker (commits/trees/blobs/
tags) with missing-object detection; wants/haves pack negotiation.
Objects travel as '<cid> <serialized-sx>' pkt lines — receivers re-derive
the CID from the bytes, so packs are tamper-evident by construction.
Server endpoints: GET info/refs (read-gated advertisement incl. '@ HEAD'
symref line), POST git-upload-pack (read), POST git-receive-pack (write;
401/403/404 like the rest of the API) with per-ref command application:
create/update/delete via ref-CAS, fast-forward enforcement on heads/*,
closure-completeness check, stale detection, heads|tags-only.
Client: gitea/remote over any dream app fn — ls-remote, clone! (sets
HEAD + default-branch, cleans up on unreachable remote), mirror fetch!,
push!/push-delete! with local pack computation. Suite syncs two
in-memory forges end to end: clone, incremental fetch, push, non-ff
rejection + recovery, branch create/delete, tag push, private-repo
credentialed round trip.
sx-parse comes from spec/parser.sx on the OCaml server host — added to
the conformance load order. Also merged loops/git (git-wire export/
import adapters, 267/267) for future stock-git interop.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
lib/gitea/access.sx: repo role groups (admin>write>read) as acl facts
saturated by the datalog engine; user-owner => admin; collaborators
(per-repo role, upsert); org teams (one role, 'all' or scoped repo
list); org-admin?; visible-repos; create-allowed?; bearer tokens in kv.
Facts derived from forge state, acl db cached in the forge handle and
rebuilt only when facts change.
lib/gitea/web.sx: every repo route now requires read (404 hides private
repos); repo create needs owner/org-admin, delete + collaborator API
need admin (401 no credentials / 403 not allowed); index + /api/repos
list only visible repos; PUT/DELETE collab endpoints.
tests/access.sx (103) + repo suite updated for gating (91). Fixed a
web.sx corruption from the known sx_find_all/sx_replace_node path
mismatch by rewriting via sx_write_file; suite timeout 300->600s.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>