fix: hyperguild brain write requires wing/hall, not legacy type/slug
PR #131 removed WriteNote's legacy brain/knowledge/ fallback branch that activated when wing/hall were both empty, but never updated the CLI that was the only caller relying on it. Since `hyperguild brain write` never sent wing/hall, every invocation of the CLI's most load-bearing brain command started hard-erroring "wing and hall are required" — a live, README-documented command broken on every call. Separately, writeRequest never had a `slug` field (pre-existing, unrelated to #131): the CLI's slug value was silently dropped server-side even before this PR, falling back to a timestamp filename. Fix: - `hyperguild brain write <wing> <hall> <slug>` replaces the old `<type> <slug>` shape. Positional args, mirroring the existing `brain query <topic>` pattern and matching writeHallNote's own wing/hall/slug parameter order server-side (handler.go's brain.NotePath(brainDir, wing, hall, slug)) — no new flags needed since wing/hall/slug are all required, not optional filters. - brainClient.Write now sends {wing, hall, filename, content}, matching writeRequest exactly (writeRequest already gained a Filename field in #131, so no server-side change was needed — only the CLI was sending the wrong shape). This also fixes the dead slug bug: slug now arrives server-side as filename instead of being silently dropped. - Added TestWrite_CLIPayloadRoundTrips in ingestion/internal/api, which POSTs the CLI's exact JSON envelope to the real Handler.Write (not a mock) and asserts a real file with real wing/hall frontmatter lands on disk. cmd/hyperguild and ingestion are separate Go modules (no go.work), so this can't be a single cross-module test — instead the wire contract is pinned from the server side, closing the gap where cmd/hyperguild/http_test.go's TestBrainClient_Write_Success used only a hand-rolled httptest mock and stayed green through the wing/hall regression. - Updated cmd/hyperguild/README.md and the 2026-05-03 CLI design spec's stale `<type> <slug>` examples. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -251,6 +251,50 @@ func TestWrite_GeneratesFilenameIfAbsent(t *testing.T) {
|
||||
assert.True(t, strings.HasSuffix(entries[0].Name(), ".md"))
|
||||
}
|
||||
|
||||
// TestWrite_CLIPayloadRoundTrips exercises the real Handler.Write (not a
|
||||
// hand-rolled mock) over an actual HTTP round trip, POSTing the exact JSON
|
||||
// envelope cmd/hyperguild's brainClient.Write sends: {wing, hall, filename,
|
||||
// content}. cmd/hyperguild lives in a separate Go module from ingestion
|
||||
// (no shared go.work), so a Go-level test can't import brainClient
|
||||
// directly — this test instead pins the wire contract from the server
|
||||
// side and proves a real `hyperguild brain write <wing> <hall> <slug>`
|
||||
// invocation writes a real file with real frontmatter under wiki/<wing>/<hall>/.
|
||||
// A prior version of this contract test on the client side (in
|
||||
// cmd/hyperguild/http_test.go) used only an httptest.NewServer with a
|
||||
// hand-rolled mock body, so it stayed green through a regression where the
|
||||
// server required wing/hall but the client never sent them (hyperguild#79
|
||||
// PR review).
|
||||
func TestWrite_CLIPayloadRoundTrips(t *testing.T) {
|
||||
dir, h := setup(t)
|
||||
srv := httptest.NewServer(http.HandlerFunc(h.Write))
|
||||
defer srv.Close()
|
||||
|
||||
payload, err := json.Marshal(struct {
|
||||
Wing string `json:"wing"`
|
||||
Hall string `json:"hall"`
|
||||
Filename string `json:"filename"`
|
||||
Content string `json:"content"`
|
||||
}{Wing: "homelab", Hall: "facts", Filename: "cli-roundtrip", Content: "# CLI roundtrip\n\nReal write.\n"})
|
||||
require.NoError(t, err)
|
||||
|
||||
resp, err := http.Post(srv.URL+"/write", "application/json", bytes.NewReader(payload)) //nolint:noctx
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close() //nolint:errcheck
|
||||
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
var out struct {
|
||||
Path string `json:"path"`
|
||||
}
|
||||
require.NoError(t, json.NewDecoder(resp.Body).Decode(&out))
|
||||
assert.Equal(t, "wiki/homelab/facts/cli-roundtrip.md", out.Path)
|
||||
|
||||
written, err := os.ReadFile(filepath.Join(dir, "wiki", "homelab", "facts", "cli-roundtrip.md"))
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, string(written), "wing: homelab")
|
||||
assert.Contains(t, string(written), "hall: facts")
|
||||
assert.Contains(t, string(written), "Real write.")
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// POST /ingest
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user