diff --git a/docs/research/phase-0.md b/docs/research/phase-0.md new file mode 100644 index 0000000..119c1e8 --- /dev/null +++ b/docs/research/phase-0.md @@ -0,0 +1,391 @@ +--- +status: draft +last_updated: 2026-11-07 +--- + +# alkforge: Phase 0 Research Findings + +This document captures Phase 0 (Exploration) findings and open design +questions for the `alkforge` assembly. The objective of Phase 0 per +`docs/sdd_process.md` is: *"Capture vision and guiding principles; +research options; validate approaches; converge on a recommended +approach."* It is the input to Phase 1 (Architecture), and its +conclusions feed two family-level open questions: OQ-06 (alkforge 0.1's +exact feature set and Phase 0 shape) and OQ-07 (the alkgit asks list). + +This is a **draft**: the sections below record what is already settled +by family decisions and published crates, then set up the work items +that still need real research. The load-bearing-surface enumeration +(§3) is explicitly incomplete and must be driven by a walk of the live +gitea deployment (behaviors only, never its source, see AGENTS.md +convention 13). + +## 1. What alkforge is + +alkforge is the family's near-term assembly: a minimal private forge +that replaces the gitea deployment, built by composing family crates +rather than re-implementing them (family ADR-002, +`/workspace/@alkdev/alkfamily/docs/architecture/decisions/002-alkforge-gitea-replacement.md`). + +The one-sentence scope from ADR-002/roadmap: a web view for the OSS +projects, public clones for them, and SSH-based git transport for +everything else. Minimal first, because the standing exposure +(unpatchable-by-architecture CVE pressure on gitea, recently realized +as an actual compromise) is unacceptable; built out over time toward +alkgit's longer-term distributed-platform goal. + +The binding constraint is the **no-deformation rule** (ADR-002 decision +3): alkforge is an assembly on alkgit's core and may not pull, patch, +or fork it to fit assembly needs. Gaps surface as upstream asks (OQ-07, +see §6), never local workarounds. A shortcut inside alkforge that would +deform a family crate is a family-level decision, by ADR. + +The standing security posture (ADR-002 decision 5, family layering.md) +is a day-one design requirement, not an afterwards checklist: secrets +via alkvault, no env-var configuration sprawl, minimal wire surface, +fail-closed capability features, bounded resources on every door. The +CVE classes that took gitea down are structural to avoid, and +exploit-finding is getting automated (structure is the defense). + +## 2. What is already settled + +The heavy design weight does not live in this repo; it lives in the +published family crates. What alkforge adds is composition, identity +wiring, configuration, and the web surface. + +- **Git core**: alkgit 0.1.0 (protocol core, `alk/git` ALPN, git + protocol V2, producer/consumer halves) + alkgit-substrate 0.1.0, the + shipped backend composition (refs/cas in alkstore, objects in + alkblobs, LFS per alkgit ADR-022). Objects, refs, pack handling, + receive-pack/upload-pack semantics: done, ledger-tracked. alkforge + never speaks pkt-line and never shells out to `git`. +- **SSH door**: alkssh 0.2.0, published with the `git` feature (git + channel-type mapping onto alkgit), fail-closed feature graph (a + git-only build cannot serve shells, tunnels, or sftp; family + ADR-003 decision 3), alkvault-backed key sources, per-session + resource limits. The auth bridge (verified key fingerprint, then + identity resolution) is shipped. The `alk/ssh` ALPN is pinned + (alkssh ADR-001) but no shipped leg speaks it yet; alkforge is the + planned first consumer. The topology question (edge termination vs + relay leg) is recorded in alkssh's mapping-table research and is a + door-level detail alkforge adopts, not rediscovers. +- **HTTP gate**: alkhttp 0.8.1 (the family's HTTP adapter shape, + fuzzed), with the git door shape recorded in alkgit's + `docs/architecture/doors.md` (alkhttp `git`/`lfs` features bridging + HTTP transport to alkgit's halves). reverse-proxy (produced, + outside the alk\* namespace) stands in front as the HTTP front door, + per ADR-002 decision 4. +- **SSR engine**: alkgen 0.1.0, released 2026-11-07 specifically with + the alkforge SSR scope (alkgen ADR-006): handlebars-rust templates + over serde contexts, strict mode, deterministic render, partials + first-class, in-process library posture (no alk\* deps). alkforge's + SSR path is settled at the engine choice level; the residue + (OQ-GN-09: in-process render-per-request vs pre-rendering, HTML + output unit) is carried in alkgen's phase 0 and resolved when + alkforge's web-view spec lands. Templates are code: versioned + in-repo, strict-mode render errors, golden-output tests (AGENTS.md + convention 9). +- **Persistence**: alkstore 0.1.0 (mem/sqlite/postgres backends behind + one abstraction, the contract suite as the backend-acceptance gate) + holds relational state: accounts, repos, permissions, forge config. + alkblobs 0.2.2 holds large objects incl. git LFS media (alkgit + ADR-022 keying). alkforge writes no bespoke database code. +- **Substrate + identity/ACL**: alkcall 0.10.1 carries the channels + wire, the operation registry, and the identity/ACL model + (`AccessControl::check(peer_identity)`). The visible-surface = + authorized-surface invariant is spec-level (AGENTS.md convention 6). +- **Secrets**: alkvault 0.2.0 (`decrypt` returns `Zeroizing`; + the plaintext-window fix landed via alkssh's consumer review). Host + keys, stored credentials, and any secret material ride the vault; + config stores hold references, never material (AGENTS.md + convention 5). +- **TLS**: alktls 0.1.0 exists; where TLS termination sits (reverse + proxy edge vs alkhttp) is a deployment-shape question (§7, OQ-FG-08). + +## 3. The load-bearing gitea surface (work item #1) + +OQ-06 requires enumerating what the gitea deployment is *actually* +used for, separating load-bearing surface from inertia. This repo was +initialized before that walk happened, so what follows is the +provisional list implied by family ADR-002's own scope sentence, each +item marked with what must be confirmed against the live deployment: + +| Need | Implied serving shape | To verify on the deployment | +|---|---|---| +| Web view for the OSS projects | alkgen SSR pages behind alkhttp | what pages are actually visited/linked (repo landing, file browse, commit log, README render?); how they are linked to from the outside | +| Public clones of OSS projects | git transport with public-read ACL | protocol mix (HTTP smart vs SSH); anonymous vs authenticated share; refspec/branch expectations of downstream users | +| SSH git transport for everything else | alkssh `git` feature over alkgit | who holds keys; write vs read split; whether any non-git SSH usage exists | +| Repo/account model | alkstore rows | org vs user layout; how many repos; who can create/delete | +| LFS or large media | alkblobs + alkhttp `lfs` door | whether LFS is actually in use | +| Issues / PRs / releases / wiki | TBD (not implied by ADR-002 scope) | whether any of it is load-bearing or pure inertia | + +Rules for the walk (recorded so the next session does not re-derive +them): + +1. Behavior only: enumerate endpoints, features, and workflows from + the outside (what is configured, what is used in logs/configs), + never from gitea source (license posture, AGENTS.md convention 13). +2. Every finding lands in one of three buckets: load-bearing (must + appear in the 0.1 spec), inertia (dropped, with a migration note), + deferred (real need, later version). +3. The output becomes the feature-freeze line for alkforge 0.1 and the + migration plan's source of truth. + +## 4. Mapping needs onto the family stack + +Provisional composition (draft; refined by §3's findings): + +``` +reverse-proxy (front door) + │ + ┌──┴────────────────────────────────────────────┐ + │ alkforge (the assembly) │ + │ config (alkstore) · vault refs (alkvault) │ + │ repo/permission model (alkstore) │ + │ identity/ACL wiring (alkcall) │ + │ web UI (alkgen SSR pages) │ + └──┬───────────┬──────────┬─────────────────────┘ + │ │ │ + alkhttp alkssh alkstore/alkblobs + (HTTP gate, (SSH door, (persistence; git objects + git/lfs git + LFS media via + features) feature) alkgit-substrate) + │ │ + └───── alkgit (git core: produce/consume + halves over `alk/git`) +``` + +Per-need mapping: + +- **Clone/push over SSH**: alkssh `git` feature maps the exec/subsystem + channel onto alkgit's protocol halves; ACL decision from the + fingerprint-resolved identity; fail-closed declines for any other + channel type come from the feature graph alkforge's build enables. +- **Public clones over HTTP**: alkhttp's `git` feature bridging to + alkgit; the "public read" property is an explicitly granted + capability per repo (never a global anonymous default). Details of + the anonymous-peer identity story are OQ-FG-03. +- **Web view**: browser → reverse-proxy → alkhttp ops → alkgen render. + Pages are strictly server-rendered HTML (no client-side framework in + the minimal forge); rendering is deterministic, so caching headers + and render tests both work off golden output. +- **Forge state** (accounts, repos, permissions, config): alkstore + rows; the reactive pattern follows alkstore's established usage in + the family rather than bespoke plumbing. +- **Secrets**: alkvault references in alkstore config; SSH host keys + via alkssh's vault-backed key sources; any outbound credential is + injected at the assembly layer (`OperationContext.capabilities`). + +## 5. The repo/permission model (draft posture) + +The model ADR-002 names: OSS projects are public read, everything else +is private. The draft posture this phase 0 seeds: + +- **Repos** are rows in alkstore with an owner identity and a + visibility field; **grant lists** (explicit capability) sit beside + them. The ACL check at every door is `AccessControl::check` against + the resolved peer identity; "public read" is a capability the door + grants to the anonymous peer class for repos carrying the public flag. +- **Accounts**: a minimal account model (identity, keys, membership) + in alkstore. Key management rides the alkvault/alkssh key-source + model already shipped; the forge UI's account model (registration? + invite-only?) is OQ-FG-04 and should be sized to the actual usage + found in §3 (the family forge is probably invite-only territory). +- **Write access**: pushes resolve fingerprints to identities the same + way alkssh's auth bridge works; the write capability is a per-repo + grant checked at the door before alkgit's receive-pack sees the + session. +- Nothing here introduces a new wire format; permissions are + assembly-side alkstore state read by door-side ACL wiring. + +## 6. The upstream asks list (OQ-07 draft) + +The no-deformation rule means gaps between what alkforge needs and +what the doors/core expose are filed upstream. The draft asks list, +from what phase 0 already knows (each gets validated and then filed in +the owning repo; nothing here is implemented in alkforge instead): + +**To alkgit:** + +1. Auth/identity surface for consumers beyond raw protocol operation: + where the consumer (alkforge) hooks per-session authorization + before upload-pack/receive-pack run (the "auth surface for the web + view and the doors" item in OQ-07's note). Possibly already shaped + by alkssh's git mapping; verify and re-scope before filing. +2. Public-read serving posture: what the producer half needs for an + anonymous, read-only peer (rate limits, ref advertisement caps). +3. SSH wiring: confirm the alkssh `git` feature's channel mapping + matches alkforge's needs end to end (it shipped with alkforge as + the named first consumer; gaps found become asks, not workarounds). + +**To alkhttp:** + +4. The git door's anonymous-request shape (public clone without an + identity), including how it composes with reverse-proxy in front. + +**To alkgen:** + +5. OQ-GN-09 residue: alkforge's SSR library posture (in-process + render-per-request vs pre-rendered) and whatever the first real + render turns up (0.1.0 exists precisely to be shaken by this). + +**To alkcall:** + +6. Only if the ACL wiring hits substrate gaps; the visible-surface = + authorized-surface model looks sufficient for the draft posture. + +The asks-list discipline from the family ledger: early consumers file +asks; they never work around substrate issues locally. + +## 7. Seeded open questions + +Numbered OQ-FG-NN for later promotion to `docs/architecture/ +open-questions.md` in Phase 1. Order below is rough priority. + +### OQ-FG-01: What is the exact load-bearing gitea surface? + +Section 3's walk. Blocks the 0.1 feature freeze, the migration plan, +and the web-view spec. Highest priority; everything downstream sizes +from it. + +### OQ-FG-02: What is alkforge 0.1's binary/service shape? + +alkforge is the assembly; unlike the protocol crates there is no +"library until a consumer asks". Draft hunch: one tokio binary +embedding alkhttp (with alkgen rendering wired behind it) and alkssh +(SSH door), alkstore as the persistence layer, reverse-proxy in front +handling TLS (alktls's role then depends on where TLS terminates, see +OQ-FG-08). Confirm in §3 whether any deployment constraint (systemd +unit, container, port layout) shapes this. This decides `src/lib.rs` +vs `src/main.rs` posture and the config file surface. + +### OQ-FG-03: How do anonymous peers resolve to an identity class? + +The web view and public clones both receive unauthenticated requests. +The draft posture: the doors map "no valid identity" to a peer +identity class that ACLs can grant read capabilities to explicitly. +Confirm this composes with alkcall's `AccessControl` model and alkhttp/ +alkssh door auth, and that rate limiting attaches to the class, not +per-peer. + +### OQ-FG-04: Account model for the forge UI? + +Minimal: what §3 finds (family forge suggests invite-only, small +count). Registration vs admin-provisioned, session handling for the +browser surface (alkstore-backed sessions? signed tokens?), and where +session keys live (alkvault). Deliberately minimal in 0.1; the +distributed-platform goal lives in alkgit, not here. + +### OQ-FG-05: What does the web view render in 0.1? + +The page list sizes from OQ-FG-01. Draft hunch: project listing, repo +landing (README + clone lines), file browse/download. Commit browsing +scope (log, tree, diff) is a real 0.1 question: alkgit serves protocol +operations, not web-browse queries; the ask list (§6.2/6.3 territory) +may need an item for cheap ref/object queries for UI purposes, vs +serving them from alkstore metadata. HTML sanitization policy for +README rendering is part of this question. + +### OQ-FG-06: Config file and storage layout? + +No env vars (AGENTS.md convention 5). Draft hunch: a TOML/JSON config +file pointing at the alkstore backend URI and alkvault references; +paths for the alkstore/alkblobs stores. Config values that are secret +must never live in the file, only alkvault references. Decides whether +config lives in alkstore rows vs a static file per deployment. + +### OQ-FG-07: Migration path from the gitea deployment? + +Sequence the cutover: git data (alkgit import path exists? gitea's +bare repos are ordinary git repos, so a clone-push migration through +protocol halves is likely), account/key data regeneration into +alkvault/alkstore, and the reverse-proxy endpoint swap. Sized by +OQ-FG-01's findings. + +### OQ-FG-08: Where does TLS terminate? + +reverse-proxy at the edge (hunch) vs alkhttp carrying alktls. Also +whether any internal leg needs TLS (alk/git and alk/ssh over loopback +or a private network). Confirm with the reverse-proxy deployment's +actual posture. + +### OQ-FG-09: What bounded-resource posture does the web surface need? + +The doors carry their own limits (alkssh per-session, alkhttp body/ +streaming); the assembly still owns page-render and walk-request +limits (per-request wall clock, max rendered size, per-peer concurrency +limits) at alkhttp's op registry level. + +### OQ-FG-10: Does anything in the web surface need alkcall ops vs direct wiring? + +The assembly has a choice: expose forge operations as registered +alkcall ops (uniform ACL) or wire alkhttp handlers directly to +internal functions. Draft hunch: web-view rendering is direct wiring +with ACL at the HTTP layer, while transport-critical state changes +(repo create/delete, permission grants) go through alkcall ops so the +ACL model covers them uniformly. + +## 8. POC candidates + +None committed yet. The candidates the seeded OQs imply, in rough +priority (each run per the POC-placement rules in +`docs/sdd_process.md`; findings merge back here): + +1. **Deployment walk** (not a POC; pure research): the §3 gitea + surface enumeration. Blocks OQ-FG-01 and most sizing decisions. +2. **End-to-end git round trip through the assembled doors** (likely + thin over alkssh's and alkgit's existing test coverage; only run + what the crates do not already prove): clone + push over alkssh git + mapping and over alkhttp's git feature against an + alkgit-substrate backend, with an ACL grant/decline matrix. Validates + §4's composition and the asks list items 1/2/4. +3. **SSR first-consumer spike**: one repo-landing page through alkgen + 0.1.0 behind an alkhttp op, with strict-mode failure and + determinism tests. Validates item 5 (OQ-GN-09 residue) from + alkforge's side and turns up the first alkgen asks. + +## 9. Convergence checklist (what Phase 0 must produce) + +- [ ] Load-bearing gitea surface enumerated (OQ-FG-01; §3 template + filled with real findings) +- [ ] 0.1 feature freeze list agreed from those findings +- [ ] Stack mapping validated (§4) against the real surface; updated + with door gaps +- [ ] Upstream asks list (OQ-07) finalized and filed in the owning + repos +- [ ] Repo/permission model (§5) and identity wiring settled enough + for the Phase 1 spec +- [ ] Config/secrets layout (OQ-FG-06) dispositioned +- [ ] Migration path (OQ-FG-07) sketched +- [ ] OQ-FG seeds dispositioned or carried to Phase 1's + open-questions.md +- [ ] Family OQ-06/OQ-07 updates written back to + `/workspace/@alkdev/alkfamily/docs/architecture/open-questions.md` + +## 10. References + +- Family ADR-002: + `/workspace/@alkdev/alkfamily/docs/architecture/decisions/002-alkforge-gitea-replacement.md` + (the structural decision: scope, no-deformation, security posture) +- Family docs: `ledger.md` (wire-stable contracts incl. `alk/git`, + `alk/ssh` status), `layering.md` (where alkforge sits), + `family-map.md` (crate versions/roles), + `architecture/open-questions.md` (OQ-06/OQ-07), + `tasks/architecture/oq-06-alkforge-phase-0.md` (the tracker task) +- alkgit: AGENTS.md, `docs/architecture/` (esp. `doors.md` for the + alkhttp/alkssh door shapes, `backend.md`/`substrate.md` for the + alkgit-substrate composition, `lfs.md`) +- alkssh: AGENTS.md, `docs/research/vision.md` / + `auth-bridge.md` / `mapping-table.md` (the git feature and auth + surface alkforge consumes) +- alkhttp: AGENTS.md, `docs/architecture/` (`http-adapters.md`, + `http-server.md` for the gate shape alkforge mounts) +- alkgen: `docs/research/phase-0.md` (the SSR branch, OQ-GN-09) and + ADR-006 (SSR engine decision) +- alkstore: docs + contract suite (backend acceptance gate) +- alkcall: docs/architecture (identity/ACL model: `AccessControl`, + `Visibility`) +- reverse-proxy: `/workspace/@alkdev/reverse-proxy` (front door + precedent) +- The gitea deployment itself: live inspection target for §3 + (behavior only)