## Who Owns What?

Every piece of the web publishing system, sorted by layer

### Layer 1: CDK / CloudFormation (cogtainer stack)

Infrastructure — deployed once per cogent
Web Gateway Lambda
Python function + Function URL. Routes HTTP to file store (static) or channels (dynamic). This is the only new AWS resource.
Cloudflare DNS
Points {name}.softmax-cogents.com at the Function URL. Access policy already exists.
IAM Role
Gateway needs: read file store tables, write channel tables, push to SQS ingress queue.

That's it. No ALB changes, no new ECS tasks, no S3 buckets.

### Layer 2: Image Boot (CogOS init scripts)

CogOS runtime — runs at cogent boot, like Discord channels
web capability
Registered in `init/capabilities.py`. Provides publish(), unpublish(), respond(), list() methods. Same as how discord capability is registered.
io:web:request channel
Created in `init/capabilities.py` (or `init/processes.py`). System channel for inbound HTTP requests. Same as io:discord:dm.

These are CogOS concerns baked into the image. Every cogent gets them automatically.

### Layer 3: App Init (cogent's cog definition)

Cogent application — optional, cogent decides if/how to use web
Web cog + handler coglet
Defined in `apps/website/init/cog.py` (or wherever the cogent wants).
`cog = add_cog("website")
cog.make_default_coglet(
entrypoint="main.md",
mode="daemon",
handlers=["io:web:request"], # ← this is the subscription
capabilities=["web", "channels", "file", ...],
...
)`
Handler prompt
The coglet's main.md — LLM prompt that decides what to do with each request. Reads the request from io:web:request, calls web.respond(). This is where route logic lives.
Published files
Any process with the `web` capability can call `web.publish("index.html", content)`. The files just sit in the file store under `web/`.

A cogent that doesn't want a web presence just... doesn't create a web cog. The infra is there but idle.

### End-to-End: What Happens When a Request Arrives

Static: GET /dashboard/index.html
CDK → Gateway Lambda receives request
CDK → Path doesn't start with /api, so: static path
CogOS → FileStore.get("web/dashboard/index.html")
CDK → Return content with Content-Type: text/html
No processes, no channels, no capability involved.
Dynamic: POST /api/status
CDK → Gateway Lambda receives request
CDK → Path starts with /api, so: dynamic path
CogOS → append_channel_message("io:web:request", {request_id, method, path, ...})
CogOS → auto-delivery created, handler marked RUNNABLE
CDK → push to SQS ingress queue
CogOS → ingress Lambda → executor dispatches handler process
App → handler coglet wakes, reads request, does work
App → calls web.respond(request_id, 200, {"content-type": "application/json"}, body)
CogOS → response written to io:web:response:{request_id} channel
CDK → Gateway reads response channel, returns HTTP 200