// Pinned acceptance for P6.1 (subplan 06) — the read-only admin console // served by distd at /admin. // // Publishes two releases into a fresh store, mints a token, sets a // retention policy, cross-promotes onto a second channel, then starts // the BUILT `build/dist server run` and asserts every admin screen over // curl: // // * /admin apps overview: app link, iOS mode chip, // platform coverage, latest release // * /admin/apps/ channels with policy + retention // ("keep 2" / "keep all"), install links, // releases table with both releases // * /admin/releases/ artifact row (file, size, sha prefix, // validation chip), serving channels, // audit timeline (artifact.upload + // release.publish + the cli promote) // * /admin/tokens token name + lifecycle status; the // page NEVER contains the secret or any // hash material // * /admin/audit newest-first log naming every actor // and action recorded so far // * unknown slug / release / route → 404 JSON errors with stable codes // * / (public index) links the admin console #import "modules/std.sx"; #import "modules/std/json.sx"; process :: #import "modules/std/process.sx"; STORE :: ".sx-tmp/server_admin"; MDIR :: ".sx-tmp/server_admin_m"; PORT :: "18799"; BASE :: "http://127.0.0.1:18799"; MANIFEST_A :: "{\"app\":\"acme-app\",\"version\":\"1.2.3\",\"channel\":\"stable\",\"artifacts\":[{\"platform\":\"android_apk\",\"path\":\"../../examples/fixtures/acme-1.2.3-android.apk\"}]}"; MANIFEST_B :: "{\"app\":\"acme-app\",\"version\":\"1.2.4\",\"channel\":\"stable\",\"artifacts\":[{\"platform\":\"android_apk\",\"path\":\"../../examples/fixtures/acme-1.2.3-android.apk\"}]}"; REL_A :: "rel-acme-app-1.2.3"; REL_B :: "rel-acme-app-1.2.4"; get :: (o: Object, key: string) -> Value { i := 0; while i < o.len { if o.items[i].key == key { return o.items[i].val; } i += 1; } process.assert(false, concat("missing json key: ", key)); dummy : Value = .null_; return dummy; } get_str :: (o: Object, key: string) -> string { return get(o, key).str; } get_obj :: (o: Object, key: string) -> Object { return get(o, key).object; } write_file :: (path: string, body: string) { cmd := concat(concat(concat("printf '%s' '", body), "' > "), path); process.run(cmd); } // True iff `needle` occurs in `hay` (plain scan; bodies are small). contains :: (hay: string, needle: string) -> bool { if needle.len == 0 { return true; } if needle.len > hay.len { return false; } i := 0; while i + needle.len <= hay.len { j := 0; ok := true; while j < needle.len { if hay[i + j] != needle[j] { ok = false; break; } j += 1; } if ok { return true; } i += 1; } return false; } fetch :: (path: string) -> string { r := process.run(concat(concat("curl -s -m 2 ", BASE), path)); process.assert(r != null, concat("curl spawn failed: ", path)); return r!.stdout; } fetch_code :: (path: string) -> string { r := process.run(concat(concat("curl -s -m 2 -o /dev/null -w '%{http_code}' ", BASE), path)); process.assert(r != null, concat("curl spawn failed: ", path)); return r!.stdout; } parse_body :: (body: string, what: string, scratch: Allocator) -> Object { v, e := parse(body, scratch); if e { process.assert(false, concat("response must be valid JSON: ", what)); dummy : Object = .{}; return dummy; } return v.object; } publish_cmd :: (mpath: string) -> string { c := concat("build/dist ci publish --manifest ", mpath); c = concat(c, concat(" --local-store ", STORE)); return concat(c, " --json 2>/dev/null >/dev/null"); } main :: () -> i32 { gpa := GPA.init(); arena := Arena.init(xx gpa, 1 << 20); defer arena.deinit(); process.run("pkill -f 'dist server run --local-store .sx-tmp/server_admin' 2>/dev/null"); process.run(concat("rm -rf ", STORE)); process.run(concat("rm -rf ", MDIR)); process.run(concat("mkdir -p ", MDIR)); write_file(path_join(MDIR, "a.json"), MANIFEST_A); write_file(path_join(MDIR, "b.json"), MANIFEST_B); // ── seed: two releases, a token, a retention policy, a cross-promote ─ ra := process.run(publish_cmd(path_join(MDIR, "a.json"))); process.assert(ra != null and ra!.exit_code == 0, "publish A must exit 0"); rb := process.run(publish_cmd(path_join(MDIR, "b.json"))); process.assert(rb != null and rb!.exit_code == 0, "publish B must exit 0"); tc := process.run(concat(concat("build/dist token create --name ci-main --local-store ", STORE), " --json 2>/dev/null")); process.assert(tc != null and tc!.exit_code == 0, "token create must exit 0"); tco := parse_body(tc!.stdout, "token create", xx arena); secret := get_str(get_obj(tco, "token"), "secret"); process.assert(secret.len > 0, "token create returned the secret"); cs := process.run(concat(concat("build/dist channel set --app acme-app --channel stable --retention-keep 2 --local-store ", STORE), " --json 2>/dev/null")); process.assert(cs != null and cs!.exit_code == 0, "channel set must exit 0"); pr := process.run(concat(concat(concat("build/dist release promote --app acme-app --channel beta --release ", REL_A), concat(" --local-store ", STORE)), " --json 2>/dev/null")); process.assert(pr != null and pr!.exit_code == 0, "promote A onto beta must exit 0"); // ── start the server, poll /healthz ────────────────────────────── sp := process.run(concat(concat(concat("sh -c 'build/dist server run --local-store ", STORE), concat(concat(" --port ", PORT), " >/dev/null 2>&1 & echo $!'")), "")); process.assert(sp != null, "server spawn failed"); pid := sp!.stdout; ready := false; tries := 0; while tries < 50 { c := fetch_code("/healthz"); if c == "200" { ready = true; break; } process.run("sleep 0.2"); tries += 1; } process.assert(ready, "server must answer /healthz within 10s"); print(" server up\n"); // ── /admin — apps overview ──────────────────────────────────────── r := process.run(concat(concat("curl -s -m 2 -o /dev/null -w '%{http_code} %{content_type}' ", BASE), "/admin")); process.assert(r != null and contains(r!.stdout, "200 text/html"), "/admin is a 200 HTML page"); ov := fetch("/admin"); process.assert(contains(ov, "href=\"/admin/apps/acme-app\""), "overview links the app detail"); process.assert(contains(ov, "ios: artifact-only"), "overview shows the iOS mode chip"); process.assert(contains(ov, "android_apk"), "overview shows platform coverage"); process.assert(contains(ov, "1.2.4"), "overview shows the latest release version"); print(" /admin overview ok\n"); // ── /admin/apps/ — channels + retention + releases ───────── ad := fetch("/admin/apps/acme-app"); process.assert(contains(ad, "keep 2"), "stable shows its retention policy"); process.assert(contains(ad, "keep all"), "beta shows the keep-everything default"); process.assert(contains(ad, "href=\"/install/acme-app/stable\""), "channels link their install pages"); process.assert(contains(ad, REL_A) and contains(ad, REL_B), "releases table lists both releases"); process.assert(contains(ad, "manual"), "channels show their rollout policy"); print(" /admin/apps/acme-app ok\n"); // ── /admin/releases/ — artifacts, serving, timeline ────────── rd := fetch(concat("/admin/releases/", REL_A)); process.assert(contains(rd, "acme-1.2.3-android.apk"), "release detail names the artifact file"); process.assert(contains(rd, ">valid") or contains(rd, ">pending"), "artifact carries a validation chip"); process.assert(contains(rd, "href=\"/download/"), "artifact links its download"); process.assert(contains(rd, "servingbeta"), "release detail names the channel serving it"); process.assert(contains(rd, "artifact.upload"), "timeline shows the upload event"); process.assert(contains(rd, "release.publish"), "timeline shows the publish event"); process.assert(contains(rd, "channel.promote"), "timeline shows the cli promote (metadata names this release)"); print(" /admin/releases/ ok\n"); // ── /admin/tokens — status without secret material ──────────────── tk := fetch("/admin/tokens"); process.assert(contains(tk, "ci-main"), "tokens screen names the token"); process.assert(contains(tk, ">active"), "tokens screen shows lifecycle status"); process.assert(!contains(tk, secret), "tokens screen NEVER contains the secret"); process.assert(!contains(tk, "token_hash"), "tokens screen never mentions hash material"); print(" /admin/tokens ok\n"); // ── /admin/audit — every recorded actor/action ──────────────────── au := fetch("/admin/audit"); process.assert(contains(au, "token.create"), "audit shows token.create"); process.assert(contains(au, "channel.update"), "audit shows the retention change"); process.assert(contains(au, "channel.promote"), "audit shows the promote"); process.assert(contains(au, "release.publish"), "audit shows the publishes"); process.assert(contains(au, ">cli") and contains(au, ">ci"), "audit names both actors"); print(" /admin/audit ok\n"); // ── 404s with stable codes ──────────────────────────────────────── process.assert(fetch_code("/admin/apps/nope") == "404", "unknown slug is 404"); na := parse_body(fetch("/admin/apps/nope"), "unknown slug body", xx arena); process.assert(get_str(get_obj(na, "error"), "code") == "admin.unknown_app", "unknown slug names admin.unknown_app"); process.assert(fetch_code("/admin/releases/nope") == "404", "unknown release is 404"); nr := parse_body(fetch("/admin/releases/nope"), "unknown release body", xx arena); process.assert(get_str(get_obj(nr, "error"), "code") == "admin.unknown_release", "unknown release names admin.unknown_release"); process.assert(fetch_code("/admin/bogus") == "404", "unknown admin route is 404"); print(" admin 404s ok\n"); // ── the public index links the console ──────────────────────────── idx := fetch("/"); process.assert(contains(idx, "href=\"/admin\""), "public index links /admin"); // ── teardown ────────────────────────────────────────────────────── process.run(concat("kill ", pid)); process.run(concat("rm -rf ", STORE)); process.run(concat("rm -rf ", MDIR)); print("server_admin: ALL CASES PASS\n"); return 0; }