Files
alkhttp/AGENTS.md
T
deepseek-v4-flash 604b7c7c63 docs: document conventional commits style and unit-of-work commits in AGENTS.md
- Explicitly require small, focused commits (one per unit of work) so
  work is easy to revert and review
- Specify conventional commits format: <type>(<scope>): <summary> with
  feat/fix/docs/chore/refactor/test/perf/build/ci types, matching the
  alkcall repo's established pattern
2026-08-25 08:51:33 +00:00

248 lines
12 KiB
Markdown

# AGENTS.md
Operating instructions for opencode agents working in this repo. opencode
auto-loads this file as instructions, overriding the built-in defaults for
this project. Custom agents in `.opencode/agents/` inherit these rules
unless their own prompts say otherwise.
## Git Workflow
**Commit and push when reasonable.** When a change is complete and
verified (build + lint + tests pass), commit and push to `origin/main`
without asking. This overrides the built-in default of "only commit when
explicitly asked."
Commit in small, focused units — one commit per unit of work (a fix, a
feature, a doc change), not one large commit covering many topics. This
keeps each unit of work isolated and easy to revert or review if
something goes wrong. Push regularly so work is never stranded locally.
The workflow:
1. Make the change
2. Verify: `cargo test`, `cargo clippy --all-targets -- -D warnings`,
`cargo fmt --check`, `cargo doc --no-deps` if docs changed
3. Inspect `git status` and `git diff` before staging — stage only the
intended files, never secrets
4. Write a concise commit message in **conventional commits** style:
`<type>(<scope>): <summary>` — types are `feat`, `fix`, `docs`,
`chore`, `refactor`, `test`, `perf`, `build`, `ci`; the scope is
optional (e.g. `docs(review 002): ...`). For multi-point changes, use
a summary line plus a body with bullet points and a verification
block.
5. `git push origin main`
6. Report the commit hash and the verification summary
Exceptions — **do not** commit or push without asking:
- The change is exploratory / speculative (you're not sure the user wants
it kept)
- The user is actively reviewing the diff and may ask for changes
- The change touches published wire formats or semver-relevant public API
(this crate will be on crates.io; the HTTP surface and the
HTTP-backed call-protocol adapters are the stable contract — see the
ADRs in `docs/architecture/decisions/`)
- You'd be force-pushing, amending a published commit, creating an empty
commit, or skipping hooks
Never commit secrets, keys, or credentials. If a commit fails or hooks
reject it, fix the issue and create a new commit — do not amend the
failed one.
## Project Conventions (Rust / HTTP interface crate)
This is the HTTP interface crate — the extraction of `alknet-http` from
the alknet mono-repo (`/workspace/@alkdev/alknet`). It serves HTTP/1.1 +
HTTP/2 on standard ALPNs (with WebSocket upgrade for browser bidirectional
access to the call protocol) and hosts the HTTP-backed call-protocol
adapters (`from_openapi`, `to_openapi`, `from_mcp`, `to_mcp`). The
conventions below apply to all work in `src/` and `tests/`. They mirror
`.opencode/agents/implementation-specialist.md` §Project Conventions and
are repeated here so they apply to every session, not just spawned
implementation agents.
1. **No comments in code** unless the user explicitly asks. This is a
project-wide convention. Doc comments (`///`, `//!`) are fine and
expected on public API. Inline `//` comments only when the user asks
or when a non-obvious safety/correctness constraint would otherwise be
missed.
2. **Error handling**`thiserror` for library error types. No panics
in library code. No `unwrap()` or `expect()` outside tests. If you
reach for `unwrap`, the error path wasn't specified — stop and decide
what should actually happen. For poisoned `RwLock`/`Mutex`, use
`unwrap_or_else(|e| e.into_inner())` so a panic in one operation does
not cascade to other operations.
3. **`tokio` is the async runtime** — all I/O is async. The HTTP
server, the WebSocket upgrade path, and the reqwest-backed adapters
are all async. Do not introduce blocking I/O on the async path. Use
`tokio::sync` primitives (`oneshot`, `mpsc`) for request correlation
and subscription channels; `parking_lot` for short-held internal locks.
4. **No secret material on the wire** — the HTTP surface carries no
private keys, API keys, mnemonics, or decrypted credentials in
request/response payloads or headers. Outbound credentials flow
through `Capabilities` injected at the assembly layer →
`HandlerRegistration.capabilities``OperationContext.capabilities`
→ handler. The `from_openapi`/`from_mcp` adapters are the credential
injection point. See the no-env-vars invariant below.
5. **No-env-vars invariant** — no handler reads outbound credentials
from any source other than `OperationContext.capabilities`. The
credential injection path is vault → assembly layer → `Capabilities`
`HandlerRegistration.capabilities``OperationContext.capabilities`
→ handler. Downstream consumers' `std::env::var` reads are
unreachable because the assembly layer never calls `Default::default()`.
This is a spec-level invariant, not a runtime convention.
6. **`OperationEnv` must remain a trait** — the trait-based design
enables registry layering (session overlays, connection overlays,
peer-keyed composition). Making `OperationEnv` concrete or hardcoding
the global registry into the dispatch path would close the
session-overlay and connection-overlay patterns. This is the same
integration-point pattern as `IdentityProvider`.
7. **The HTTP surface is the stable contract** — the server serves
REST APIs, the `to_openapi`/`to_mcp` projections of local
call-protocol operations, the `/healthz` operational endpoint, and
the decoy surface for stealth mode. The gateway endpoints
(`/search`/`/schema`/`/call`/`/batch`/`/subscribe`) are the sole
invoke path for HTTP callers; the WebSocket path carries the native
call-protocol session, not the gateway shape. HTTP/3 + WebTransport
(`h3`) is deferred — browsers use WebSocket. See ADR-044, ADR-047,
ADR-048.
8. **Producer/consumer, not server/client** — both sides of a call or
channels connection can initiate. A producer exposes operations
(call) or opens data channels (channels); a consumer calls operations
or opens channels. Both sides can be both simultaneously — connection
direction (who opened it) is independent of call/channel direction
(who calls/opens). Avoid "server" and "client" framing in docs and
API names; use "producer" and "consumer," or "accept side" / "connect
side" for the connection-establishment half specifically.
9. **Dependency on the call crate** — this crate consumes the call
protocol from the alkcall crate (`/workspace/@alkdev/alkcall`), which
owns the vendored core types (`Connection`, `ProtocolHandler`,
`BiStream`, `BidiStreamSource`, `AuthContext`, `IdentityProvider`,
`Identity`, `AuthToken`, `Capabilities`, `OwnershipProvider`,
`HandlerError`, `StreamError`) and the `EventEnvelope` wire format.
Do not re-implement or fork those types here; do not add a separate
`alkcore` dependency. Keep this crate lean (no TLS, no transport
coupling, no endpoint/accept-loop); the dial and the TLS config are
concerns of the consumer, not of this crate.
10. **Feature flags** — the HTTP transports are feature-gated: `h2` and
`http1` are default features (hyper), `mcp` gates the
`from_mcp`/`to_mcp` adapters (rmcp). The base crate should compile
lean (no `rmcp` unless the `mcp` feature is on). Verify both
`cargo test` (default) and `cargo test --all-features` pass if
features are added.
11. **Naming** — Rust standard: `snake_case` for functions/variables/
modules, `PascalCase` for types/traits, `SCREAMING_SNAKE_CASE` for
constants.
12. **Module structure** — one module per file under `src/`, re-exported
from `src/lib.rs`. Public API surface is `lib.rs` re-exports. The
crate has five subsystems: `server` (HttpAdapter, auth, stealth,
`/healthz`, gateway routes), `websocket` (upgrade, native session
overlay), `adapters` (`from_openapi`, `to_openapi`, `from_mcp`,
`to_mcp`, `from_jsonschema`), `client` (reqwest-backed HTTP client
host), and `gateway` (dispatch, error mapping).
13. **Adapter-registered ops are `Internal` by default** — operations
registered by the adapters are `Visibility::Internal` unless
explicitly marked otherwise. Peer authorization is via
`AccessControl::check(peer_identity)` — no `remote_safe` flag, no
`trusted_peer` bypass. See ADR-015, ADR-024.
14. **Error fidelity across the HTTP boundary**`from_openapi`/
`from_jsonschema`/`to_openapi` map call-protocol errors to HTTP
status codes with `HTTP_<status>` error codes. The gateway is the
sole invoke path; per-caller `AccessControl`-filtered `/search` is
the discovery. See ADR-023, ADR-047.
## Verification Commands
Run these before committing. All must pass.
```bash
cargo test # full suite
cargo clippy --all-targets -- -D warnings
cargo fmt --check
cargo doc --no-deps # if docs changed
cargo publish --dry-run --allow-dirty # before a release
```
If feature flags are added, also run `cargo test --all-features` and
`cargo clippy --all-features --all-targets -- -D warnings`.
## Architecture Context
- `docs/architecture/` — the authoritative spec. Read it before
non-trivial changes. ADRs are numbered 001..066; OQs (open questions)
track resolved/deferred decisions.
- This crate is the extraction of `alknet-http` from the alknet mono-repo
(`/workspace/@alkdev/alknet`). The source architecture docs were ported
from `/workspace/@alkdev/alknet/docs/architecture/crates/http/` and
renumbered as alkhttp ADRs. The ALPN strings (`h2`, `http/1.1`) and
the gateway endpoint contract are wire-stable going forward.
- Key ADRs that inform this crate's design:
**Server:**
- ADR-001 — ALPN-based protocol dispatch (`HttpAdapter` registers on
standard HTTP ALPNs)
- ADR-002 — `ProtocolHandler` trait (`HttpAdapter` implements it)
- ADR-004 — auth as shared core (Bearer → `resolve_from_token`)
- ADR-010 — ALPN router and endpoint (stealth mode = HTTP handler on
standard ALPNs)
- ADR-039 — HTTP server and client host colocated in one crate
- ADR-046 — assembly-layer custom HTTP routes on `HttpAdapter`
(`extra_routes: Option<Router>`)
- ADR-047 — remove the direct-call HTTP surface; the 5 gateway
endpoints are the sole invoke path
- ADR-048 — WebSocket carries the native call-protocol session, not
the gateway shape
**WebSocket:**
- ADR-044 — defer h3/WebTransport; browsers use WebSocket ("browser
is not a peer" rationale)
- ADR-048 — WebSocket native session (framing, dispatch,
bidirectionality, connection-local Layer 2 overlay)
**Adapters:**
- ADR-014 — secret material flow (`from_openapi`/`from_mcp` are the
credential injection point)
- ADR-015 — privilege model (adapter-registered ops are `Internal` by
default)
- ADR-017 — call protocol client and adapter contract
(`OperationAdapter` trait; `to_*` are projections)
- ADR-022 — handler registration, provenance, and composition
authority (adapters produce leaf bundles)
- ADR-023 — operation error schemas (`HTTP_<status>` error codes)
- ADR-041 — MCP tool-gateway pattern for `to_mcp` (4 fixed gateway
tools, not one tool per operation)
- ADR-042 — OpenAPI gateway pattern for `to_openapi` (5 fixed gateway
endpoints, not one path per operation)
- ADR-045 — `to_openapi` gateway-spec versioning (`info.version`
tracks the gateway endpoint contract)
- ADR-049 — streaming handler for subscription operations
(`HandlerKind::Stream`; SSE → `BoxStream<ResponseEnvelope>`)
- ADR-051 — YAML input format for `from_openapi` (`yaml_serde` 0.10.x
is YAML 1.2)
- ADR-066 — `from_jsonschema` as HTTP-backed single-endpoint adapter
**Client:**
- ADR-039 — one crate for server + client host (shared HTTP deps,
shared mapping)
- If a TODO references a "Phase B" or a design direction that an ADR
has since decided against, the TODO is stale — remove it and align
the comments with the ADR. Do not implement the rejected design.
- The call protocol's `EventEnvelope` shape is owned by the alkcall
crate (`/workspace/@alkdev/alkcall`), which was extracted from the
alknet mono-repo alongside this crate. The WebSocket path carries the
native `EventEnvelope` session.