chore(brain): remove brain_write legacy params domain/type/knowledge-path (ADR-0012)
hyperguild#79's caller audit (done in a prior session) found the removal was safe: - The promote/pending flow (#38/#81, ingestion/internal/api/promote.go) operates exclusively on brainDir/raw/ -> brainDir/wiki/<wing>/<hall>/. It never touches brainDir/knowledge/, so removing the knowledge/ write path does not break it. - No live caller uses domain/type or the knowledge/ path: checked hyperguild itself, mathias/skills, mathias/agentsquad, mathias/dispatch, mathias/cad, mathias/cad-atlas. - internal/skills/brain (a skill.go + handlers.go pair) defined a third, even-more-legacy brain_write shape (type/domain/filename, no wing/hall) for an old "supervisor" MCP server whose cmd/supervisor/main.go no longer exists. Nothing outside its own test imported it; deleted entirely as it encodes the exact legacy shape this issue removes. Changes: - ingestion/internal/mcp/handlers.go: brain_write's tool schema and brainWriteArgs drop type/domain; wing+hall are now marked required in the schema. - ingestion/internal/api/handler.go: WriteNote has one behaviour — wing+ hall required, routing to wiki/<wing>/<hall>/. writeLegacyNote and the knowledge/-path branch are deleted. Omitting wing or hall now returns "wing and hall are required" (400 over HTTP, a tool error over MCP) — the only sane failure mode left once there is no other valid destination. - ingestion/internal/capture/ports.go, internal/brainstore/store.go: drop Type/Domain from capture.Note and its WriteNoteOptions construction. pipeline/*.go's unrelated "domain" field (a different, legitimate concept for brain_ingest_raw) is untouched. - internal/skills/brain/: deleted (orphaned legacy tool schema, dead since cmd/supervisor was removed). Tests: added TestWriteNote_RequiresWingAndHall (api), TestBrainWriteRequiresWingAndHall + TestBrainWriteIgnoresLegacyTypeAndDomainParams (mcp) for the new single behaviour; removed tests that only exercised the deleted legacy path (TestWrite_IncludesFrontmatterWhenTypeProvided, TestWriteNote_HallRouteExistingTypeWinsOverOptsType, TestBrainWriteRejectsTraversal, TestBrainWriteAcceptsDoubleDotInName — the latter two's filename-sanitisation behaviour is covered independently by internal/brain/path_test.go's TestNotePath_SanitisesWingAndSlug / TestSanitise, which is unaffected by this change). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -54,8 +54,6 @@ type queryRequest struct {
|
||||
type writeRequest struct {
|
||||
Content string `json:"content"`
|
||||
Filename string `json:"filename,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Domain string `json:"domain,omitempty"`
|
||||
Wing string `json:"wing,omitempty"`
|
||||
Hall string `json:"hall,omitempty"`
|
||||
SourceType string `json:"source_type,omitempty"` // "external" opts a hall=facts entry out of the internal default
|
||||
@@ -110,17 +108,13 @@ func (h *Handler) Query(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// WriteNoteOptions configures how a brain note is written.
|
||||
//
|
||||
// When both Wing and Hall are non-empty, the note routes into the
|
||||
// structured wiki at brain/wiki/<wing>/<hall>/<slug>.md and gets
|
||||
// wing/hall/created_at injected into its YAML frontmatter.
|
||||
//
|
||||
// When either is empty, the note falls back to brain/knowledge/<filename>
|
||||
// with optional type/domain frontmatter (legacy behaviour).
|
||||
// Wing and Hall are both required: the note routes into the structured
|
||||
// wiki at brain/wiki/<wing>/<hall>/<slug>.md and gets wing/hall/created_at
|
||||
// injected into its YAML frontmatter. There is no other valid destination
|
||||
// (the legacy brain/knowledge/ fallback was removed — ADR-0012 / #79).
|
||||
type WriteNoteOptions struct {
|
||||
Content string
|
||||
Filename string
|
||||
Type string
|
||||
Domain string
|
||||
Wing string
|
||||
Hall string
|
||||
SourceType string // "internal" marks a first-party observation (e.g. claudewatcher) that needs no external citation
|
||||
@@ -132,6 +126,9 @@ func WriteNote(brainDir string, opts WriteNoteOptions) (string, error) {
|
||||
if opts.Content == "" {
|
||||
return "", fmt.Errorf("content is required")
|
||||
}
|
||||
if opts.Wing == "" || opts.Hall == "" {
|
||||
return "", fmt.Errorf("wing and hall are required")
|
||||
}
|
||||
|
||||
// In the primitive, not in a caller: POST /write reaches here directly,
|
||||
// without the BrainStore, and a gate one door wide is not a gate.
|
||||
@@ -143,13 +140,7 @@ func WriteNote(brainDir string, opts WriteNoteOptions) (string, error) {
|
||||
}
|
||||
opts.Content = clean
|
||||
|
||||
if opts.Wing != "" && opts.Hall != "" {
|
||||
return writeHallNote(brainDir, opts)
|
||||
}
|
||||
if opts.Wing != "" || opts.Hall != "" {
|
||||
return "", fmt.Errorf("wing and hall must be set together")
|
||||
}
|
||||
return writeLegacyNote(brainDir, opts)
|
||||
return writeHallNote(brainDir, opts)
|
||||
}
|
||||
|
||||
// writeHallNote routes a note into brain/wiki/<wing>/<hall>/ and injects
|
||||
@@ -207,9 +198,6 @@ func writeHallNote(brainDir string, opts WriteNoteOptions) (string, error) {
|
||||
fmt.Fprintf(&fm, "%s: %s\n", key, fallback)
|
||||
}
|
||||
}
|
||||
writeField("type", opts.Type)
|
||||
writeField("domain", opts.Domain)
|
||||
|
||||
sourceType := opts.SourceType
|
||||
if sourceType == "" && opts.Hall == "facts" {
|
||||
// Most hall=facts entries are first-party (an eval/benchmark the
|
||||
@@ -300,53 +288,7 @@ func splitFrontmatter(content string) (fields []frontmatterField, body string) {
|
||||
return fields, body
|
||||
}
|
||||
|
||||
// writeLegacyNote preserves the original brain/knowledge/ behaviour for
|
||||
// callers that have not adopted the wing/hall taxonomy.
|
||||
func writeLegacyNote(brainDir string, opts WriteNoteOptions) (string, error) {
|
||||
filename := opts.Filename
|
||||
if filename == "" {
|
||||
filename = fmt.Sprintf("%s-auto.md", time.Now().UTC().Format("2006-01-02-150405"))
|
||||
}
|
||||
|
||||
rawDir := filepath.Join(brainDir, "knowledge")
|
||||
if err := os.MkdirAll(rawDir, 0o755); err != nil {
|
||||
return "", fmt.Errorf("create raw dir: %w", err)
|
||||
}
|
||||
|
||||
finalContent := opts.Content
|
||||
if opts.Type != "" || opts.Domain != "" {
|
||||
var fm strings.Builder
|
||||
fm.WriteString("---\n")
|
||||
if opts.Type != "" {
|
||||
fmt.Fprintf(&fm, "type: %s\n", opts.Type)
|
||||
}
|
||||
if opts.Domain != "" {
|
||||
fmt.Fprintf(&fm, "domain: %s\n", opts.Domain)
|
||||
}
|
||||
fm.WriteString("---\n")
|
||||
finalContent = fm.String() + opts.Content
|
||||
}
|
||||
|
||||
if strings.ContainsAny(filename, `/\`) {
|
||||
return "", fmt.Errorf("invalid filename")
|
||||
}
|
||||
base := filepath.Base(filename)
|
||||
if base == "." || base == ".." || base == "" {
|
||||
return "", fmt.Errorf("invalid filename")
|
||||
}
|
||||
if !strings.HasSuffix(base, ".md") {
|
||||
base += ".md"
|
||||
}
|
||||
dest := filepath.Join(rawDir, base)
|
||||
if err := os.WriteFile(dest, []byte(finalContent), 0o644); err != nil {
|
||||
return "", fmt.Errorf("write: %w", err)
|
||||
}
|
||||
|
||||
rel, _ := filepath.Rel(brainDir, dest)
|
||||
return filepath.ToSlash(rel), nil
|
||||
}
|
||||
|
||||
// Write handles POST /write — write raw content to brain/knowledge/.
|
||||
// Write handles POST /write — write a note into brain/wiki/<wing>/<hall>/.
|
||||
func (h *Handler) Write(w http.ResponseWriter, r *http.Request) {
|
||||
var req writeRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
@@ -359,10 +301,10 @@ func (h *Handler) Write(w http.ResponseWriter, r *http.Request) {
|
||||
writeError(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
if req.Wing != "" && req.Hall != "" {
|
||||
if err := brain.BuildWingIndex(h.brainDir, req.Wing); err != nil {
|
||||
h.logger.Warn("auto-index failed", "wing", req.Wing, "err", err)
|
||||
}
|
||||
// WriteNote errors out above unless both are set, so this always runs —
|
||||
// no legacy fallback path left to guard against.
|
||||
if err := brain.BuildWingIndex(h.brainDir, req.Wing); err != nil {
|
||||
h.logger.Warn("auto-index failed", "wing", req.Wing, "err", err)
|
||||
}
|
||||
writeJSON(w, map[string]string{"path": relPath})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user