Add M2a plan: thirteen tasks, tests, fake server and recordings
The tasks build the inference path: emsha-backed SHA-256, inferproxy, config, a hand-written HTTP and SSE client, request building, delta assembly, the chat state machine, the thinking cap, the slot gate with retry, the startup self-test and on-device verification. Everything the tasks copy in was checked against a private reference implementation: the gate passes after each task in order, the timing tests pass repeatedly under CPU load, and the reference passes the self-test and all four device checks on straylight. Expected results for the recorded streams were derived by a separate script. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
# M2a task 01: SHA-256 in `proto`
|
||||
|
||||
**Branch:** `m2a` (create it from `master`: `git switch master && git switch -c m2a`)
|
||||
**Commit subject:** `Add SHA-256 to proto, wrapping the emsha crate`
|
||||
|
||||
## Goal
|
||||
|
||||
Give the workspace one SHA-256 function. It wraps the `emsha` crate, which the project's owner
|
||||
wrote. Nothing else in the workspace may use `emsha` directly, so that the choice of implementation
|
||||
stays in one file.
|
||||
|
||||
## Context
|
||||
|
||||
The hash is used to check that the inference server runs the chat template we expect, and later
|
||||
for the audit log. An earlier release of `emsha` hashed every message of 63, 127, 191, … bytes
|
||||
wrongly, because of a mistake at a padding boundary. That is fixed in 1.0.4, and it is why the test
|
||||
file checks lengths on both sides of every boundary and feeds each message in two pieces.
|
||||
|
||||
## Files
|
||||
|
||||
- Copy: `crates/proto/tests/hash.rs`
|
||||
- Create: `crates/proto/src/hash.rs`
|
||||
- Modify: `Cargo.toml`, `crates/proto/Cargo.toml`, `crates/proto/src/lib.rs`,
|
||||
`docs/dependencies.md`, `docs/implementer-log.md`, `Cargo.lock` (generated)
|
||||
|
||||
## Interfaces
|
||||
|
||||
Consumes: `proto::Hash32` (`Hash32::from_bytes([u8; 32])`).
|
||||
|
||||
Produces, in `crates/proto/src/hash.rs`, re-exported from the crate root:
|
||||
|
||||
```rust
|
||||
pub struct HashError; // Debug, Clone, Copy, PartialEq, Eq; Display; std::error::Error
|
||||
|
||||
pub struct Sha256(/* private */);
|
||||
impl Sha256 {
|
||||
pub fn new() -> Self;
|
||||
pub fn update(&mut self, data: &[u8]) -> Result<(), HashError>; // may be called many times
|
||||
pub fn finish(self) -> Result<Hash32, HashError>;
|
||||
}
|
||||
impl Default for Sha256 { /* same as new() */ }
|
||||
|
||||
pub fn sha256(data: &[u8]) -> Result<Hash32, HashError>; // new, update, finish
|
||||
```
|
||||
|
||||
Every `emsha` error becomes `HashError`. No `unwrap` or `expect`.
|
||||
|
||||
## API notes (`emsha` 1.0.4, read from its source on 2026-09-17)
|
||||
|
||||
```rust
|
||||
use emsha::Hash; // the trait that has update and finalize
|
||||
let mut h = emsha::sha256::SHA256::new();
|
||||
h.update(b"bytes")?; // fn update(&mut self, msg: &[u8]) -> emsha::Result<()>
|
||||
let mut out = [0u8; emsha::sha256::SIZE]; // SIZE is 32
|
||||
h.finalize(&mut out)?; // fn finalize(&mut self, digest: &mut [u8]) -> emsha::Result<()>
|
||||
```
|
||||
|
||||
`emsha::Result<T>` is `Result<T, emsha::Error>`. The crate has no dependencies and is `no_std`.
|
||||
|
||||
## Steps
|
||||
|
||||
- [ ] **1. Branch, copy the test, add the dependency.**
|
||||
|
||||
```sh
|
||||
git switch master && git switch -c m2a
|
||||
cp docs/plans/M2a/files/crates/proto/tests/hash.rs crates/proto/tests/
|
||||
```
|
||||
|
||||
Add to `[workspace.dependencies]` in the root `Cargo.toml`: `emsha = "1.0.4"`.
|
||||
Add to `[dependencies]` in `crates/proto/Cargo.toml`: `emsha.workspace = true`.
|
||||
Add this row to `docs/dependencies.md`:
|
||||
|
||||
```markdown
|
||||
| `emsha` | 1.0.4 | `proto` | SHA-256. Written by the owner; no dependencies, no `unsafe`. Checked against `sha256sum` on 3,204 inputs. |
|
||||
```
|
||||
|
||||
- [ ] **2. See the test fail.** `cargo test -p proto --test hash`. Expected: it does not compile.
|
||||
|
||||
- [ ] **3. Write `hash.rs`,** and in `lib.rs` add `pub mod hash;` and
|
||||
`pub use hash::{HashError, Sha256, sha256};`. Run `cargo fmt --all`.
|
||||
|
||||
- [ ] **4. See the test pass.** `cargo test -p proto --test hash`. Expected: `4 passed`.
|
||||
|
||||
- [ ] **5. Run the gate.** `cargo build`, then `make gate`. Expected last line: `gate: ok`.
|
||||
`cargo deny` prints a `no-license-field` warning for `emsha`; that is expected, because the
|
||||
crate's licence is a file and not an SPDX name. Do not edit `deny.toml`.
|
||||
|
||||
- [ ] **6. Log and commit.**
|
||||
|
||||
```sh
|
||||
git add Cargo.toml Cargo.lock crates/proto docs/dependencies.md docs/implementer-log.md
|
||||
git commit
|
||||
```
|
||||
|
||||
## Done when
|
||||
|
||||
- `cargo test -p proto --test hash` reports 4 passed; `make gate` prints `gate: ok`.
|
||||
- `grep -rn emsha crates/*/src` shows `crates/proto/src/hash.rs` only.
|
||||
|
||||
## Stop and report if
|
||||
|
||||
- A vector in `hash.rs` fails. Do not change the vector: it would mean `emsha` is wrong again.
|
||||
- `cargo deny` reports an error, not a warning, for `emsha`.
|
||||
Reference in New Issue
Block a user