docs: publish the Web Foundations application onramp
verify / verify (push) Successful in 2m58s

Sanitized snapshot of private source 144ca0a9544042b0477ae732c1356cf0b9d62b3f. Add package selection, adoption workflow, and optional Sandwich Hime integration guidance.

AI-Assistance: OpenAI Codex assisted documentation, verification, and publication.
Signed-off-by: Cole Speelman <crspeelman@gmail.com>
This commit is contained in:
2026-08-16 21:10:18 -04:00
parent a18f1dd22a
commit 206d09e4cd
10 changed files with 282 additions and 15 deletions
+9 -7
View File
@@ -2,16 +2,17 @@
# Architecture
The dependency direction is intentionally one-way:
The package dependency direction is intentionally one-way:
```text
net/http application
-> requestmeta
-> requestlog / websec / abuse / authhttp
-> auth and analytics interfaces
-> optional authsqlite and JSONL adapters
analytics ──> requestlog ──> requestmeta
abuse ─────────────────────> requestmeta
authhttp ──> websec ───────> requestmeta
authhttp ──> auth <───────── authsqlite
```
An ordinary `net/http` application composes whichever branches it needs.
Packages never own application routes, templates, authorization policy, cache
policy, or deployment. Middleware communicates through typed request context.
Storage and reporting surfaces are interfaces so an application can retain its
@@ -20,4 +21,5 @@ time.
The package model is developed from explicit threat and data contracts, not by
moving an existing application's internals into a shared directory. See
[ADOPTION.md](ADOPTION.md).
[ADOPTION.md](ADOPTION.md), [GETTING_STARTED.md](GETTING_STARTED.md), and the
[module-boundary policy](MODULES.md).
+7
View File
@@ -13,6 +13,13 @@ Applications that do not import `auth` or `authsqlite` do not link those
implementations into their binaries. Optional GeoIP enrichment is an interface
only; the base toolkit performs no lookup and adds no GeoIP dependency.
All packages currently share one Go module, so these requirements remain
visible in the module graph even when an application imports only
`requestmeta`. Go still avoids compiling or linking unused packages. A future
nested module may isolate a heavyweight adapter such as `authsqlite` when its
independent dependency and release lifecycle justify the additional tags,
vanity metadata, and CI. See [MODULES.md](MODULES.md).
`go.sum`, `go mod verify`, checksum-database verification, vulnerability
scanning, and the public snapshot allowlist are release gates. Binary
distributors remain responsible for preserving all applicable upstream notices.
+80
View File
@@ -0,0 +1,80 @@
<!-- SPDX-License-Identifier: MPL-2.0 -->
# Getting started
Gamertan Web Foundations is adopted one boundary at a time. Start with the
smallest package that solves a problem the application actually has; do not
install an imagined framework lifecycle around it.
## Choose a first slice
| Application need | Begin with | What remains application-owned |
| --- | --- | --- |
| Request IDs and trustworthy client addresses | `requestmeta` | Proxy configuration and operational logs |
| Bounded structured request evidence | `requestmeta`, `requestlog` | Route names, sensitive-field policy, rotation, retention, and access |
| Browser and HTTP safety primitives | `requestmeta`, `websec` | Exact CSP, route authorization, and response policy |
| Persistent request-abuse decisions | `requestmeta`, `abuse` | Route classification, storage, appeals, and operator policy |
| Users, credentials, permissions, and sessions | `auth` | Roles, permissions, login UX, and account policy |
| Secure browser cookies around `auth` | `authhttp` | Login routes, redirects, pages, and authorization decisions |
| SQLite persistence for `auth` | `authsqlite` | Database placement, backup, migration approval, and recovery |
| Aggregate projections over request records | `analytics` | Collection policy, access control, report UI, and retention |
The packages are ordinary Go imports. Pin the current preview and verify its
module checksum:
```bash
go get gamertan.com/web/requestmeta@v0.1.0-preview.1
go mod verify
```
## Preserve middleware order
Packages that consume request metadata must run inside the resolver. Build the
handler from the application outward; the final resolver assignment becomes
the first middleware to receive a request:
```go
var handler http.Handler = router
handler = requestlog.Middleware(sink, logPolicy)(handler)
handler = websec.Headers(headerPolicy)(handler)
handler = resolver.Middleware(handler)
```
The complete, copyable composition is in [`starters/basic`](../starters/basic).
It binds to loopback, shuts down gracefully, and keeps request logging optional.
Configure trusted proxy networks narrowly. A forwarding header is not evidence
by itself; it becomes usable only when the immediate peer and skipped proxy
hops satisfy the resolver's trust policy. Metadata, authentication, or storage
failures that affect security decisions should stop the request rather than
quietly changing identity or policy.
## Add HTML without merging responsibilities
Handlers should convert request and service state into typed display data.
They may then render those values with any HTML system. Gamertan's preferred
companion is [Sandwich Hime](SANDWICH_HIME.md), whose generated components keep
templates typed while leaving this middleware stack and the `net/http`
application in control.
## Verify the application boundary
After adopting a package:
```bash
go mod verify
go test ./...
go test -race ./...
go vet ./...
go build ./...
```
Test the composed handler with `httptest`, not only the package in isolation.
Include a normal request, malformed or spoofed metadata, a downstream failure,
and the application's intended response headers. Existing applications should
follow the differential and rollback sequence in [ADOPTION.md](ADOPTION.md).
Deeper tutorials for accounts, analytics, and persistent abuse policy will be
written after multiple application migrations have validated those seams. The
preview documentation describes demonstrated contracts rather than prescribing
an unfinished application framework.
+72
View File
@@ -0,0 +1,72 @@
<!-- SPDX-License-Identifier: MPL-2.0 -->
# Packages, modules, and repositories
These boundaries solve different problems:
- a **package** owns one Go responsibility and import path;
- a **module** owns dependency selection and semantic versions; and
- a **repository** owns contribution, security, and release operations.
The first preview uses one repository and one module, `gamertan.com/web`, with
several independently importable packages. An application may write:
```go
import "gamertan.com/web/requestmeta"
```
and request the containing module at an exact version:
```bash
go get gamertan.com/web/requestmeta@v0.1.0-preview.1
```
Only imported packages are compiled and linked. The packages nevertheless
share the module's version and dependency graph.
## Why not one repository per package?
Separate repositories would multiply release credentials, security updates,
vanity-import records, tags, CI, issue tracking, and coordinated API changes.
A focused pull request can already change and test one package directory. A
repository boundary is reserved for software with an independently operated
lifecycle, such as a future standalone `authd` service.
## When a nested module is justified
A package may become a nested module inside this repository when all of these
are true:
1. it introduces materially heavier or different dependencies;
2. consumers can usefully version it independently;
3. its API boundary has survived real application adoption; and
4. separate tags, release ordering, vanity metadata, and CI are less costly
than keeping it in the root module.
`authsqlite` is the clearest current candidate because it carries the optional
SQLite implementation and its transitive module graph. A future split could
retain the import path `gamertan.com/web/authsqlite` while giving that directory
its own `go.mod` and tags such as `authsqlite/v0.1.0-preview.1`.
Do not split merely to make an architecture diagram look modular. Package
interfaces provide source-level modularity today; modules are introduced only
for an independent dependency and release lifecycle.
## Session boundaries
Authenticated sessions currently belong to three deliberate packages:
- `auth` owns opaque token creation, digest-backed session lookup, revocation,
and the storage interface;
- `authhttp` binds those sessions to secure browser cookies and request
context; and
- `authsqlite` persists the storage contract.
A separate `session` package would be appropriate only for a genuinely
identity-neutral need, such as anonymous application sessions with no user,
role, or credential semantics. It should not duplicate `auth` under a more
general name.
This policy may evolve before a stable release. Any split must include a
migration guide and preserve already published versions at their original
module coordinates.
+71
View File
@@ -0,0 +1,71 @@
<!-- SPDX-License-Identifier: MPL-2.0 -->
# HTML with Sandwich Hime
Gamertan Web Foundations owns reusable web-application boundaries; it does not
own HTML or a template language. [Sandwich Hime](https://sandwichhime.com/) is
the preferred companion for Gamertan applications that want HTML-first,
ahead-of-time templates with typed Go composition.
The relationship is intentionally optional:
| Application responsibility | Owner |
| --- | --- |
| Request identity, logging, security primitives, sessions, and analytics | Web Foundations packages selected by the application |
| Routing, authorization decisions, status, headers, caching, and deployment | The application |
| Visible HTML and typed component composition | Authored `.sando` templates |
| Template parsing, contextual analysis, and Go generation | Hime-san during development or CI |
| Rendering generated components | The small `sando` runtime in production |
Web Foundations does not import Sandwich Hime. Sandwich Hime does not import
Web Foundations. An application chooses both and provides the seam between
them.
## Request flow
```text
request
-> requestmeta / selected middleware
-> application router and handler
-> typed view data
-> generated Sandwich Hime component
-> buffered sando.Render
-> application-owned HTTP response
```
Buffer the component before committing a successful response so a rendering
error can still become a clean application error:
```go
func renderHTML(response http.ResponseWriter, request *http.Request, status int, component sando.Component) {
var output bytes.Buffer
if err := sando.Render(request.Context(), &output, component); err != nil {
log.Printf("render page: %v", err)
response.Header().Set("Cache-Control", "no-store")
http.Error(response, "could not render page", http.StatusInternalServerError)
return
}
response.Header().Set("Content-Type", "text/html; charset=utf-8")
response.WriteHeader(status)
_, _ = response.Write(output.Bytes())
}
```
The handler—not the template—should interpret request metadata, principals,
permissions, analytics, or storage errors. It passes only the resulting typed
display data into the component. Templates should not acquire an implicit
request global or turn middleware context into an inheritance framework.
Handwritten `sando.Component` implementations and `Trust*` values are explicit
trusted-output capabilities. Keep them conspicuous and review them separately
from ordinary untrusted values.
## Continue with the official lessons
- [Build a component, page, and small site](https://sandwichhime.com/docs/tutorial/).
- [Follow a request through a larger Go application](https://sandwichhime.com/docs/tutorial/application/).
- [Review the Sandwich Hime security boundary](https://sandwichhime.com/docs/security/).
Those tutorials own the template syntax and compiler workflow. This repository
documents only the application seam so the two projects do not drift into a
single mandatory framework.