aliases sind ein totes Feld: drei Read-Pfade, kein einziger Write-Pfad, null Daten #7

Open
opened 2026-07-17 09:37:43 +00:00 by mAi · 1 comment
Collaborator

Problem

aliases wird überall gelesen, kann aber nirgends gesetzt werden. Live-Check (2026-07-17, main @ bcf3412): alle 88 Items haben "aliases":[] — nicht als Zufall, sondern weil es keinen Schreibweg gibt.

Aufgefallen beim Triage von #1: m will dort Mental mit Alias Psychologie. Das Projekt health.mental ist angelegt — der Alias ist nicht setzbar.

Belege

Read-Pfade (alle vorhanden, alle laufen ins Leere):

  • store/store.go:837 — Suche matcht exists (select 1 from unnest(u.aliases) a where a ilike ...)
  • store/store.go:891 — Ranking-Bucket 3 ist explizit alias (zwischen title-contains und content)
  • web/tree_filter.go:333 — Tree-Filter iteriert it.Aliases
  • mcp/tools.go:631,678 — MCP gibt aliases in jedem Item zurück

Write-Pfade (keine):

  • mcp__projax__create_item — kein aliases-Parameter
  • mcp__projax__update_item — kein aliases-Parameter
  • Web-Edit-Form — grep -n "aliases" web/templates/*.tmplnull Treffer
  • store/mbrian_writer.go — sendet aliases in keinem Payload

Der Kicker: die scoped mBrian-Write-API kann es längst. Laut Vertrag in store/mbrian_writer.go:36-37 (m/mBrian#73):

POST   /api/projax/nodes      {title, content_md?, aliases?, ...}
PATCH  /api/projax/nodes/{id} {title?, content_md?, aliases?, ...}

aliases? ist auf beiden Endpoints akzeptiert. Der projax-Writer schickt es einfach nie. Das ist kein API-Gap wie G1/G2/G3 — die Server-Seite ist fertig, nur die Client-Seite fehlt.

Scope

  1. store/mbrian_writer.go: aliases in den POST- und PATCH-Payload aufnehmen (die API nimmt es schon — kein Cross-Repo-Ask nötig).
  2. Web-Edit-Form: Alias-Feld auf der Detail-Seite (Phase 8 Direction A: gehört hinter Edit, zu den Settings — nicht auf die Read-View).
  3. MCP: aliases als optionalen Parameter auf create_item + update_item.
  4. Danach: Alias Psychologie auf health.mental setzen → schließt die letzte offene Anforderung aus #1.

Constraints

  • Writes ausschließlich über die scoped /api/projax-Surface (Phase-6-Vertrag) — kein rohes mBrian-SQL, sonst reißt der projax_origin-Ownership-Vertrag (m/mBrian#73).
  • Eingabeformat: eine Zeile, komma-separiert, ist konsistent mit dem bestehenden Tag-Feld — nicht neu erfinden.
  • Read-Seite ist fertig und korrekt. Nicht anfassen — sie funktioniert ab dem Moment, wo Daten da sind.

Verify

go build ./... + go vet clean. Live: Alias auf einem Item setzen → Suche findet das Item über den Alias (Bucket 3) → Tree-Filter matcht ihn. Deploy-verify per CLAUDE.md (healthz-SHA == HEAD).

Bekanntes Rauschen: TestProjectFilter*/TestTimeline* in web/ sind vorbestehender Route-Drift — nicht Teil dieses Issues.

Refs

  • #1 — braucht den Alias Psychologie auf health.mental (Auslöser)
  • m/mBrian#73 — projax_origin-scoped writes, der API-Vertrag
  • store/mbrian_writer.go:36-37 — die API-Signatur, die aliases? längst zusagt
## Problem `aliases` wird überall **gelesen**, kann aber **nirgends gesetzt werden**. Live-Check (2026-07-17, `main` @ bcf3412): **alle 88 Items haben `"aliases":[]`** — nicht als Zufall, sondern weil es keinen Schreibweg gibt. Aufgefallen beim Triage von #1: m will dort `Mental` mit Alias `Psychologie`. Das Projekt `health.mental` ist angelegt — der Alias ist nicht setzbar. ## Belege **Read-Pfade (alle vorhanden, alle laufen ins Leere):** - `store/store.go:837` — Suche matcht `exists (select 1 from unnest(u.aliases) a where a ilike ...)` - `store/store.go:891` — Ranking-Bucket 3 ist explizit `alias` (zwischen title-contains und content) - `web/tree_filter.go:333` — Tree-Filter iteriert `it.Aliases` - `mcp/tools.go:631,678` — MCP gibt `aliases` in jedem Item zurück **Write-Pfade (keine):** - `mcp__projax__create_item` — kein `aliases`-Parameter - `mcp__projax__update_item` — kein `aliases`-Parameter - Web-Edit-Form — `grep -n "aliases" web/templates/*.tmpl` → **null Treffer** - `store/mbrian_writer.go` — sendet `aliases` in **keinem** Payload **Der Kicker:** die scoped mBrian-Write-API kann es längst. Laut Vertrag in `store/mbrian_writer.go:36-37` (m/mBrian#73): ``` POST /api/projax/nodes {title, content_md?, aliases?, ...} PATCH /api/projax/nodes/{id} {title?, content_md?, aliases?, ...} ``` `aliases?` ist auf **beiden** Endpoints akzeptiert. Der projax-Writer schickt es einfach nie. Das ist kein API-Gap wie G1/G2/G3 — die Server-Seite ist fertig, nur die Client-Seite fehlt. ## Scope 1. `store/mbrian_writer.go`: `aliases` in den POST- und PATCH-Payload aufnehmen (die API nimmt es schon — kein Cross-Repo-Ask nötig). 2. Web-Edit-Form: Alias-Feld auf der Detail-Seite (Phase 8 Direction A: gehört hinter **Edit**, zu den Settings — nicht auf die Read-View). 3. MCP: `aliases` als optionalen Parameter auf `create_item` + `update_item`. 4. Danach: Alias `Psychologie` auf `health.mental` setzen → schließt die letzte offene Anforderung aus #1. ## Constraints - Writes ausschließlich über die scoped `/api/projax`-Surface (Phase-6-Vertrag) — **kein** rohes mBrian-SQL, sonst reißt der `projax_origin`-Ownership-Vertrag (m/mBrian#73). - Eingabeformat: eine Zeile, komma-separiert, ist konsistent mit dem bestehenden Tag-Feld — nicht neu erfinden. - Read-Seite ist fertig und korrekt. **Nicht anfassen** — sie funktioniert ab dem Moment, wo Daten da sind. ## Verify `go build ./...` + `go vet` clean. Live: Alias auf einem Item setzen → Suche findet das Item über den Alias (Bucket 3) → Tree-Filter matcht ihn. Deploy-verify per CLAUDE.md (healthz-SHA == HEAD). **Bekanntes Rauschen:** `TestProjectFilter*`/`TestTimeline*` in `web/` sind vorbestehender Route-Drift — nicht Teil dieses Issues. ## Refs - #1 — braucht den Alias `Psychologie` auf `health.mental` (Auslöser) - m/mBrian#73 — projax_origin-scoped writes, der API-Vertrag - `store/mbrian_writer.go:36-37` — die API-Signatur, die `aliases?` längst zusagt
mAi self-assigned this 2026-07-17 09:37:43 +00:00
Author
Collaborator

Erledigt — aliases hat jetzt einen Write-Pfad

Commit: e859a98cd0
Branch: mai/apollo/issue-7-aliases-write (gepusht, wartet auf head-Merge)

Was drin ist

  1. store/mbrian_writer.goaliases ist jetzt im Node-POST und im PATCH-Payload. Diff bleibt bewusst auf die zwei Payload-Zeilen beschränkt (icarus ist parallel in derselben Datei auf #6).
  2. Web-Edit-Form — Alias-Feld auf der Detail-Seite hinter Edit, direkt neben Tags (Phase 8 Direction A: Settings hinter Edit, nicht auf die Read-View). Gleiche Shape wie das Tag-Feld: eine Zeile, komma-separiert. Auch auf der New-Item-Form.
  3. mcp/tools.go — optionaler aliases-Parameter auf create_item + update_item.
  4. Alias Psychologie auf health.mental gesetzt — siehe unten. Schließt die letzte offene Anforderung aus #1.

Bonus: der Legacy-Writer + zwei stille Datenverlust-Fallen

  • projax.items.aliases existiert seit 0001_init.sql:23 als Spalte, der Legacy-*Store hat sie nur nie geschrieben. Jetzt schreibt er sie — kein halbtotes Feld auf dem Fallback-Pfad (der greift, wenn PROJAX_MBRIAN_API_URL nicht gesetzt ist).
  • Update ist full-replace. Jeder Caller, der ein einzelnes Feld ändert, muss die aktuellen Aliases mitschicken, sonst löscht ein Tag-Toggle sie still weg. Entsprechend nachgezogen: updateInputFromItem (bulk chip edits) und der update_item-Patch im MCP.

Parse-Regeln: Aliases ≠ Tags

parseCSV (Tags) lowercased und splittet auch an Leerzeichen. Für Aliases falsch — ein Alias ist ein Name, kein Token:

  • Psychologie darf nicht zu psychologie werden (du hast in #1 explizit die Großschreibung verlangt)
  • Mental Health darf nicht in zwei Aliases zerfallen

Deshalb parseAliasCSV: splittet nur an Kommas, erhält Groß-/Kleinschreibung, dedupliziert case-insensitiv (Suche + Tree-Filter matchen ohnehin per ilike/ToLower, zwei Schreibweisen wären ein toter Duplikat-Eintrag). Das Eingabeformat bleibt identisch zu Tags — nur die Parse-Regeln unterscheiden sich. Unit-Test: web/alias_form_test.go.

Rename-Interaktion (geprüft, nicht vermutet)

mBrians PATCH-Handler ruft renameSlug (hängt den alten Slug als Alias an) nach updateNode auf (src/routes/api/projax/nodes/[id]/+server.ts:50-59). Unser Alias-Write landet also zuerst, der Cascade-Append danach — ein Slug-Rename kann den Alias-Write nicht überschreiben. Kein Handlungsbedarf.

Außerdem gegengeprüft statt dem Vertragskommentar zu glauben: buildCreateInput/buildUpdateInput in src/lib/server/projax.ts:175,235-238 akzeptieren aliases tatsächlich auf beiden Endpoints. Server-Seite war wie beschrieben fertig.

Verify

go build ./... + go vet ./... clean. Die 10 TestProjectFilter*/TestTimeline*-Failures habe ich gegen einen gestashten Tree gegengeprüft — identisches Failure-Set vor und nach meiner Änderung, also vorbestehender Route-Drift. Wichtig war das, weil TestTimelineExcludeDetailFormShowsCheckboxes detail.tmpl anfasst, die ich geändert habe — auch die failt vorher wie nachher.

Live-Round-Trip (gegen die deployte Read-Seite, die ich nicht angefasst habe):

BEFORE: Mental (mental) aliases=[]
AFTER:  Mental (mental) aliases=[Psychologie]

Geschrieben über den echten MBrianWriter gegen die scoped /api/projax-Surface — kein rohes mBrian-SQL, projax_origin-Ownership unangetastet. Titel/Slug/Status/Parents/Content nach dem full-replace unverändert.

Die Suche nach Psychologie findet das Item — aber das ist kein Beweis, weil Psychologie auch im content_md steht. Sauber nachgewiesen mit einem Token, das ausschließlich in aliases existiert:

search "Zzqxtestalias" → count:1, Mental (health.mental)

Gefunden über den Alias, nicht über Titel/Slug/Content. Probe-Alias danach wieder entfernt; live steht jetzt exakt aliases:["Psychologie"].

Befund: der Bucket-3-Beleg im Issue gilt für den Legacy-Pfad

Eine Korrektur zur Beweislage — kein Blocker, aber du solltest es wissen:

Die im Issue zitierte Ranking-Logik (store.go:891, Bucket 3 = alias) läuft gegen projax.items_unified. Produktion läuft aber PROJAX_BACKEND=mbrian, und health.mental liegt nur in mbrian.nodesitems_unified liefert für die id null Zeilen. Der live genutzte Suchpfad ist MBrianReader.Search (store/mbrian.go:670), und der ist ein flacher Substring-Match über itemMatchesSubstring, sortiert nach Slug — ohne jedes Ranking. Aliases werden dort gematcht (deshalb der Beweis oben), aber Buckets gibt es auf dem mBrian-Backend schlicht nicht.

Heißt: „Alias landet in Bucket 3" trifft auf den Legacy-Pfad zu, nicht auf Produktion. Das Feature dieses Issues funktioniert vollständig; nur die Ranking-Zusage ist auf dem mBrian-Backend ein Read-Seiten-Gap. Wenn dir Ranking wichtig ist (Alias-Treffer vor Content-Treffern), ist das ein eigenes Issue auf der Read-Seite — ich fasse sie hier vertragsgemäß nicht an.

Deploy

Live ist noch bcf3412. Deploy-Verify (healthz-SHA == HEAD) nach dem head-Merge. Der Alias ist jetzt schon gesetzt und über die deployte Read-Seite auffindbar — der Alias lebt in den Daten, nicht im Binary.

## Erledigt — aliases hat jetzt einen Write-Pfad Commit: https://mgit.msbls.de/m/projax/commit/e859a98cd0dc50c9a11ac0f3ecbb8c5cf1b39d80 Branch: `mai/apollo/issue-7-aliases-write` (gepusht, wartet auf head-Merge) ### Was drin ist 1. **`store/mbrian_writer.go`** — `aliases` ist jetzt im Node-POST **und** im PATCH-Payload. Diff bleibt bewusst auf die zwei Payload-Zeilen beschränkt (icarus ist parallel in derselben Datei auf #6). 2. **Web-Edit-Form** — Alias-Feld auf der Detail-Seite hinter **Edit**, direkt neben Tags (Phase 8 Direction A: Settings hinter Edit, nicht auf die Read-View). Gleiche Shape wie das Tag-Feld: eine Zeile, komma-separiert. Auch auf der New-Item-Form. 3. **`mcp/tools.go`** — optionaler `aliases`-Parameter auf `create_item` + `update_item`. 4. **Alias `Psychologie` auf `health.mental` gesetzt** — siehe unten. Schließt die letzte offene Anforderung aus #1. ### Bonus: der Legacy-Writer + zwei stille Datenverlust-Fallen - `projax.items.aliases` existiert seit `0001_init.sql:23` als Spalte, der Legacy-`*Store` hat sie nur nie geschrieben. Jetzt schreibt er sie — kein halbtotes Feld auf dem Fallback-Pfad (der greift, wenn `PROJAX_MBRIAN_API_URL` nicht gesetzt ist). - `Update` ist **full-replace**. Jeder Caller, der ein einzelnes Feld ändert, muss die aktuellen Aliases mitschicken, sonst löscht ein Tag-Toggle sie still weg. Entsprechend nachgezogen: `updateInputFromItem` (bulk chip edits) und der `update_item`-Patch im MCP. ### Parse-Regeln: Aliases ≠ Tags `parseCSV` (Tags) lowercased und splittet auch an Leerzeichen. Für Aliases falsch — ein Alias ist ein **Name**, kein Token: - `Psychologie` darf nicht zu `psychologie` werden (du hast in #1 explizit die Großschreibung verlangt) - `Mental Health` darf nicht in zwei Aliases zerfallen Deshalb `parseAliasCSV`: splittet nur an Kommas, erhält Groß-/Kleinschreibung, dedupliziert case-insensitiv (Suche + Tree-Filter matchen ohnehin per `ilike`/`ToLower`, zwei Schreibweisen wären ein toter Duplikat-Eintrag). Das Eingabeformat bleibt identisch zu Tags — nur die Parse-Regeln unterscheiden sich. Unit-Test: `web/alias_form_test.go`. ### Rename-Interaktion (geprüft, nicht vermutet) mBrians PATCH-Handler ruft `renameSlug` (hängt den alten Slug als Alias an) **nach** `updateNode` auf (`src/routes/api/projax/nodes/[id]/+server.ts:50-59`). Unser Alias-Write landet also zuerst, der Cascade-Append danach — ein Slug-Rename kann den Alias-Write nicht überschreiben. Kein Handlungsbedarf. Außerdem gegengeprüft statt dem Vertragskommentar zu glauben: `buildCreateInput`/`buildUpdateInput` in `src/lib/server/projax.ts:175,235-238` akzeptieren `aliases` tatsächlich auf beiden Endpoints. Server-Seite war wie beschrieben fertig. ### Verify `go build ./...` + `go vet ./...` clean. Die 10 `TestProjectFilter*`/`TestTimeline*`-Failures habe ich gegen einen gestashten Tree gegengeprüft — **identisches Failure-Set vor und nach meiner Änderung**, also vorbestehender Route-Drift. Wichtig war das, weil `TestTimelineExcludeDetailFormShowsCheckboxes` `detail.tmpl` anfasst, die ich geändert habe — auch die failt vorher wie nachher. **Live-Round-Trip** (gegen die deployte Read-Seite, die ich nicht angefasst habe): ``` BEFORE: Mental (mental) aliases=[] AFTER: Mental (mental) aliases=[Psychologie] ``` Geschrieben über den echten `MBrianWriter` gegen die scoped `/api/projax`-Surface — kein rohes mBrian-SQL, `projax_origin`-Ownership unangetastet. Titel/Slug/Status/Parents/Content nach dem full-replace unverändert. Die Suche nach `Psychologie` findet das Item — aber das ist **kein** Beweis, weil `Psychologie` auch im `content_md` steht. Sauber nachgewiesen mit einem Token, das ausschließlich in `aliases` existiert: ``` search "Zzqxtestalias" → count:1, Mental (health.mental) ``` Gefunden **über den Alias**, nicht über Titel/Slug/Content. Probe-Alias danach wieder entfernt; live steht jetzt exakt `aliases:["Psychologie"]`. ### Befund: der Bucket-3-Beleg im Issue gilt für den Legacy-Pfad Eine Korrektur zur Beweislage — **kein Blocker**, aber du solltest es wissen: Die im Issue zitierte Ranking-Logik (`store.go:891`, Bucket 3 = alias) läuft gegen `projax.items_unified`. Produktion läuft aber `PROJAX_BACKEND=mbrian`, und `health.mental` liegt **nur** in `mbrian.nodes` — `items_unified` liefert für die id null Zeilen. Der live genutzte Suchpfad ist `MBrianReader.Search` (`store/mbrian.go:670`), und der ist ein flacher Substring-Match über `itemMatchesSubstring`, **sortiert nach Slug — ohne jedes Ranking**. Aliases werden dort gematcht (deshalb der Beweis oben), aber Buckets gibt es auf dem mBrian-Backend schlicht nicht. Heißt: „Alias landet in Bucket 3" trifft auf den Legacy-Pfad zu, nicht auf Produktion. Das Feature dieses Issues funktioniert vollständig; nur die Ranking-Zusage ist auf dem mBrian-Backend ein Read-Seiten-Gap. Wenn dir Ranking wichtig ist (Alias-Treffer vor Content-Treffern), ist das ein eigenes Issue auf der Read-Seite — ich fasse sie hier vertragsgemäß nicht an. ### Deploy Live ist noch `bcf3412`. Deploy-Verify (healthz-SHA == HEAD) nach dem head-Merge. Der Alias ist **jetzt schon** gesetzt und über die deployte Read-Seite auffindbar — der Alias lebt in den Daten, nicht im Binary.
mAi added the
status:done
label 2026-07-17 09:53:52 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: m/projax#7
No description provided.