Files
crossbar/docs/plans/v1/02-fingerprint-config.md
T

89 lines
4.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# v1 task 02: the conversation fingerprint; config additions
**Branch:** `v1` (run `git switch v1`; `git status --short` must be empty, otherwise stop)
**Commit subject:** `Add the conversation fingerprint and the v1 config keys`
## Goal
Two small things. `fingerprint.Of` turns a chat-completions body into a stable key for the
conversation it belongs to (`PLAN.md` §4a), and `config` learns `db`, `lease_idle` and
`retention`, with durations that accept a `d` suffix.
## Context
OpenCode and Hermes send no session id. A conversation's system prompt and its **first user
message** do not change from turn to turn, so hashing those two identifies the conversation
without client support. Only the first 4 KiB of each is hashed, so a huge first message does
not make every turn slow, and a body with no user message has no fingerprint (`""`): such
requests fall back to the route-level lease (task 04).
## Files
- Copy: `internal/fingerprint/fingerprint_test.go`, `internal/config/config_v1_test.go`
- Copy (**replaces** v0's): `internal/config/config_test.go`, `internal/config/testdata/bad-unknown-key.toml`
(v0's unknown-key example was `lease_idle`, which this task makes valid; the replacements use `bogus_key`)
- Create: `internal/fingerprint/fingerprint.go`
- Modify: `internal/config/config.go`, `docs/implementer-log.md`
## Interfaces
`internal/fingerprint`, package `fingerprint`:
```go
// Of returns the lowercase hex SHA-256 of the system prompt and the first user message of a
// chat-completions body (first 4 KiB of each, joined with "\n"), or "" when the body is not a
// JSON object with a "messages" array containing a user message.
func Of(body []byte) string
```
Rules the tests check:
1. Decode `{"messages":[{"role":…,"content":…}, …]}`. `content` is either a string or an array
of parts `[{"type":"text","text":"…"}, …]`; for an array, join the `text` of the text parts
with `""` (other part types are ignored). Use `json.Unmarshal` into a struct with
`Content json.RawMessage`, then decide.
2. System prompt = content of the **first** message with `role == "system"` (or `""` if none).
First user message = content of the **first** message with `role == "user"`; **no user
message → return `""`**. Not JSON, or no `messages` → `""`.
3. Truncate each of the two strings to its first 4096 **bytes**, hash `system + "\n" + user`
with `crypto/sha256`, return `hex.EncodeToString`.
`internal/config` gains, in `Config`:
```go
DB string `toml:"db"` // default "crossbar.db"; empty string is an error (field "db")
LeaseIdle Duration `toml:"lease_idle"` // default 30m; less than 1m is an error (field "lease_idle")
Retention Duration `toml:"retention"` // default 180d; less than 1d is an error (field "retention")
```
and `Duration.UnmarshalText` accepts an integer followed by `d` (`"7d"` = 7 × 24 h) **in addition
to** `time.ParseDuration` syntax. Exactly: if the text matches `^[0-9]+d$`, multiply; otherwise
`time.ParseDuration`. `"1.5d"`, `"d"`, `"1d2h"` are errors. Validation order of the new fields:
after `queue_max`, before `hosts`.
## Steps
- [ ] **1. Copy.**
```sh
git switch v1
mkdir -p internal/fingerprint
cp docs/plans/v1/_files/internal/fingerprint/fingerprint_test.go internal/fingerprint/
cp docs/plans/v1/_files/internal/config/config_v1_test.go internal/config/
```
- [ ] **2. See them fail.** `go test ./internal/fingerprint/ ./internal/config/`. Expected: compile errors.
- [ ] **3. Write `fingerprint.go`; extend `config.go`.** `gofmt -w internal/`.
- [ ] **4. See them pass.** `go test -race -count=1 ./internal/fingerprint/ ./internal/config/`. Expected: both `ok` (the v0 config tests must still pass).
- [ ] **5. Run the gate.** `make gate`. Expected last line: `gate: ok`.
- [ ] **6. Log and commit.** Row `v1/02-fingerprint-config`.
```sh
git add internal/fingerprint internal/config docs/implementer-log.md
git commit
```
## Done when
- Both packages pass with `-race`; `make gate` prints `gate: ok`; both copied files are byte-identical to `_files/`.