feat(brain): audit-log every tools/call; server-owned write provenance (#147)
Closes #144 (audit line per tools/call) and #145 (origin/written_by stamped on every model-reachable write, surfaced in brain_query/brain_get). Passed pr-gatekeeper after four rounds of adversarial review, verified by execution: #147 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Mathias <mthbqv@gmail.com>
This commit was merged in pull request #147.
This commit is contained in:
@@ -5,7 +5,9 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/mathiasbq/hyperguild/ingestion/internal/api"
|
||||
"log/slog"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -103,9 +105,40 @@ type brainEditArgs struct {
|
||||
// hard the change is to reconstruct. Auditing every note edit would also bury
|
||||
// the rare governance record under routine traffic, which is how an alert
|
||||
// stops being read.
|
||||
func (s *Server) brainEdit(_ context.Context, args json.RawMessage) (json.RawMessage, error) {
|
||||
_, out, err := s.editWithScope(args, notes.ScopeNotes)
|
||||
return out, err
|
||||
func (s *Server) brainEdit(ctx context.Context, args json.RawMessage) (json.RawMessage, error) {
|
||||
// Snapshot before editing: if the edited note cannot be stamped and
|
||||
// verified, the edit is rolled back rather than left on disk unattributed
|
||||
// (or carrying whatever provenance the edit wrote).
|
||||
var a brainEditArgs
|
||||
if err := json.Unmarshal(args, &a); err != nil {
|
||||
return nil, fmt.Errorf("parse args: %w", err)
|
||||
}
|
||||
target, err := notes.Resolve(s.brainDir, a.Path, notes.ScopeNotes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// No snapshot, no edit: without one a failed stamp could not be undone.
|
||||
prior, err := os.ReadFile(target.Abs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("snapshot before edit: %w", err)
|
||||
}
|
||||
res, out, err := s.editWithScope(args, notes.ScopeNotes)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
// A byte-level replace can rewrite any line, including provenance, and
|
||||
// whoever edits a note is the author of what a reader now sees: re-stamp
|
||||
// to the editor after every edit (PR #147 review, escape E1).
|
||||
origin, writtenBy := provenanceFrom(ctx)
|
||||
if serr := api.StampProvenanceFile(s.brainDir, res.Path, origin, writtenBy); serr != nil {
|
||||
if werr := os.WriteFile(target.Abs, prior, 0o644); werr != nil {
|
||||
// Say exactly what state the note is in: edited, unverified.
|
||||
return nil, fmt.Errorf("stamp provenance failed (%v) AND the edit could NOT be rolled back: %w; "+
|
||||
"%s carries the edit under its previous attribution", serr, werr, res.Path)
|
||||
}
|
||||
return nil, fmt.Errorf("edit rolled back, stamp provenance: %w", serr)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// brainEditRoot edits a governance file (CLAUDE.md). De-surfaced AND gated on
|
||||
|
||||
Reference in New Issue
Block a user