chore: bootstrap repo with AGENTS.md, gitignore, and project-specific agent specs
- AGENTS.md adapted from alkcall: shared Rust conventions (no comments, thiserror, tokio, no-env-vars, OperationEnv trait) plus alkhttp-specific rules (HTTP surface as stable contract, gateway endpoints, feature flags h2/http1/mcp, five-subsystem module structure, adapter Internal-by-default) - Root .gitignore (target/, node_modules/, .worktrees/) - Purged alkcall/alknet residuals from agent specs: coordinator prompt template now targets @alkdev/alkhttp with h2/http1/mcp feature flags, architect deferral examples reference alkhttp, implementation-specialist conventions rewritten for the HTTP crate (no BAST/chunk-header/abort-cascade rules), code-reviewer feature flags updated, sdd_process.md package name fixed - Dropped the preconfigured git-identity note (glm-specific, not applicable) Verification: no cargo code changed; grep confirms no residual alkcall/alknet/glm/iroh/quinn/acme/BAST references outside intentional alkcall-dependency and alknet-extraction-origin mentions
This commit is contained in:
@@ -0,0 +1,239 @@
|
||||
# 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."
|
||||
|
||||
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 matching the repo style (see `git log
|
||||
--oneline -10`). 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.
|
||||
Reference in New Issue
Block a user