Commit Graph

11 Commits

Author SHA1 Message Date
agra
48a13c43ee distd serves through std.http — the readiness loop lands (PLAN-HTTPZ A1)
The hand-rolled sequential accept loop, its SO_RCVTIMEO band-aid, and
the whole src/server/http.sx module are retired: distd is now a
std.http handler. Server.init gets the store directory through the ctx
word; route() fills a Response instead of writing to a socket; every
handler ports mechanically (respond_error/load_or_503/respond_render
take *Response; bodies allocate from the per-request arena, never the
stack, since serialization happens after the handler returns).
Downloads keep X-Checksum-SHA256 via extra_headers; auth takes the
extracted Authorization value; the 411 contract (POST/PUT must declare
Content-Length) moves into the handler, pinned as before.

Config: 512 MiB read cap (whole-body artifact uploads), 120s request
deadline, 5s keepalive, 200 requests per connection. Idle connections
now cost nothing — timeouts evict, never block.

http_client gains its own 10s read timeout (the old shared helper's
secs->ms change had silently shrunk it to 10ms).

tests/server_http.sx pins the architecture: a request answers within
1s while SIX idle preconnects are held open (the retired loop paid
250ms-2s per idle socket serially), and two requests ride one
keep-alive connection. make test 24/24 green.
2026-06-12 22:00:31 +03:00
agra
a6052bbb4c distd: read timeout 2s -> 250ms (interim, PLAN-HTTPZ)
Each idle browser preconnect blocks the sequential accept loop for the
full SO_RCVTIMEO serially (measured: ~1.9s behind one, ~3.9s behind
two), so the timeout must stay well under human patience until the
readiness loop lands (PLAN-HTTPZ A1 retires the mechanism).
set_read_timeout takes milliseconds now.
2026-06-12 20:24:19 +03:00
agra
eef3d5c437 admin console: read-only /admin screens served by distd (P6.1)
Subplan 06, first slice. src/server/admin.sx server-renders the
operator console the same way the index and install pages are built —
sx string rendering, no client framework, no build step:

  /admin                 apps overview (platform coverage, channel and
                         release counts, latest release)
  /admin/apps/<slug>     metadata + iOS install-mode chip, channels with
                         policy/rollout/retention, releases newest-first
  /admin/releases/<id>   artifacts with validation chips and download
                         links, channels serving the release, audit
                         timeline (by target, by metadata, by artifact
                         id prefix)
  /admin/tokens          lifecycle status via token_status; never a
                         secret or hash
  /admin/audit           the full log, newest first

Reads are public like every distd GET in v0; mutations stay on the
token-gated POST endpoints (UI actions are a later slice). Unknown
slug/release/route answer 404 JSON errors with stable codes
(admin.unknown_app / admin.unknown_release / http.not_found). The
module is self-contained (adm_-prefixed helpers) so distd can import it
without a cycle; timestamps render through a local civil-from-days
formatter. The public index footer links the console.

tests/server_admin.sx drives the built binary over curl and pins every
screen, the no-secret-material guarantee, and the 404 codes.
make test 24/24 green.
2026-06-12 19:50:24 +03:00
agra
a1f13c4356 sqlite persistence: the store moves from db.json to dist.db (P5.2)
src/repo/db.sx persists the whole Repo to <store>/dist.db through the
vendored SQLite bindings, keeping the load-whole/save-whole call shape.
One table per entity; enums as lowercase variant names; list order
round-trips via rowid. Enforced uniqueness: apps.slug,
channels(app_id, name), tokens.token_hash; lookup indexes on
releases(app_id) and artifacts(sha256) (non-unique - identical bytes
may ship in several releases). save is DELETE-all + INSERT-all inside
BEGIN IMMEDIATE...COMMIT with rollback on failure; every connection
sets busy_timeout so the CLI and a running distd interleave safely.

A store holding only a pre-SQLite db.json imports once on first load,
then the file is renamed db.json.imported; a store with neither starts
empty. Consumers gate on db.store_exists instead of probing db.json.
The JSON read-back stays for the import path; the entity->json writers
stay for distd's /api responses.

Tests that parsed db.json directly now assert by querying dist.db
through the SQLite bindings; tests/db_import.sx pins the import path;
tests/repo_roundtrip.sx pins the SQLite round-trip. make test 22/22.
2026-06-12 16:16:13 +03:00
agra
29e09fa924 ua_platform: direct enum-literal returns (sx 0098 fixed)
The typed-local workaround is unnecessary since sx 1bc60d3: enum
literals returned into an optional target now resolve against the
unwrapped child and wrap. tests/server_install.sx pins the UA
detection behavior; 20/20 green.
2026-06-12 12:36:46 +03:00
agra
aea3d62b60 P4.6: install pages with accurate iOS install modes
The human install surface (subplan 04 Slice 5), honest per PLAN.md's
iOS Install Policy. App gains ios_mode (artifact_only default /
testflight / enterprise) and testflight_url; absent db.json members
load as defaults. dist app set is the admin mutator (apps are created
by publish), enforcing the policy preconditions as machine-readable
errors: testflight requires --testflight-url, enterprise requires
--ios-bundle-id. Every set appends an app.update audit event.

GET /install/<slug>/<channel> renders the channel's current release as
platform sections — the User-Agent's platform ordered first and marked
— with per-mode iOS actions: TestFlight link, itms-services OTA deep
link, or an IPA download explicitly labeled as not installable from
the page. Size + sha256 are visible on every artifact row.
GET /install/<slug>/<channel>/manifest.plist serves the enterprise OTA
manifest (bundle id, version, https package URL off the Host header)
and 404s in any other mode. The index links channels to their pages.

KNOWN sx BOUNDARY (issue 0098): an enum literal returned directly into
an optional target silently lowers to variant 0 — ua_platform routes
every variant through a typed local.

make test 19/19 (new: server_install.sx pinned acceptance).
2026-06-12 11:49:14 +03:00
agra
e2a5150542 P4.4: bearer auth + write endpoints on distd
distd stops being read-only. http.sx learns the write-side request
surface: header capture with case-insensitive lookup, a Content-Length-
bounded body read loop (8K header cap, 512 MiB body cap -> 413, 411 for
length-less POST/PUT), and the matching status texts.

Auth (server/auth.sx): Authorization: Bearer is re-hashed and resolved
via find_token_by_hash, then gated through check_token — 401 for
missing/malformed/unknown credentials, 403 with a refusal-specific code
(auth.revoked/expired/missing_scope/app_forbidden/channel_forbidden);
successful auth stamps last_used_at. check_token's app gate now treats
an empty REQUEST app like an empty request channel (uploads are
app-agnostic until a release references them).

Write routes (POST, publish scope): /api/upload content-addresses the
body; /api/apps/<slug>/releases publishes over already-uploaded objects
through commit_publish — the back half extracted from run_publish so
CLI and HTTP publishes share one find/create-app -> transaction ->
audit -> persist pipeline; channels/<name>/promote|rollback delegate to
the P3.5 CLI pipelines. Reads stay public.

make test 17/17 (new: server_write.sx pinned acceptance over curl).
2026-06-12 11:18:31 +03:00
agra
6c19f1073f lang migration: rename signed integer types sN -> iN
Mechanical sweep of all .sx sources and plan docs (PLAN.md, current/,
.agents/) for the sx language rename (s8/s16/s32/s64 -> i8/i16/i32/i64).
Verified: make build + make test, 14/14.
2026-06-12 09:39:49 +03:00
agra
cf39589798 P4.2: HTML index at / — the install-page seed
GET / now serves a dense server-rendered console: each app with its
channels' current releases and every release's artifacts as direct
/download/<sha256> links. Read-only off the per-request db.json reload;
text escaped for HTML. A browser hitting the server root sees the store
instead of a 404.
2026-06-12 07:26:01 +03:00
agra
886b48630b P4.1-001: 2s read timeout on accepted sockets (idle preconnect wedged the loop)
A browser speculative preconnection sends no bytes; the sequential
accept loop blocked in read() on it forever while real requests sat in
the backlog — LAN clients saw a dead server while curl (connect+send in
one shot) worked. SO_RCVTIMEO frees the loop. Regression case pinned in
tests/server_http.sx (fails 000 pre-fix, 200 post-fix).
2026-06-12 01:47:30 +03:00
agra
415ddeaac6 P4.1: distd — read-only HTTP API + downloads over the local store
dist server run binds 0.0.0.0:<port> (default 8787) and serves /healthz,
/api/apps, /api/apps/<slug>, and /download/<sha256> (X-Checksum-SHA256,
bytes verified content-identical). db.json reloads per request so CLI
publishes/promotes are visible immediately. Errors reuse the CLI's JSON
error shape with matching HTTP statuses. HTTP/1.1 is an in-repo shim over
std.socket (src/server/http.sx), liftable to the sx stdlib later.

Response buffers are heap slices: a 64K+ stack array in one frame
segfaults the sx LLVM backend (DAGCombiner); 4-16K stack buffers are
fine. Pinned in tests/server_http.sx including a freshness case.
2026-06-12 01:32:46 +03:00