CreateIssue / CommentIssue / CloseIssue(+optional closing comment) over the Gitea REST API.
Owner is the const "mathias" — never a parameter, so a caller can never redirect a write to another owner's repo.
Token handling (AGENTS.md secret-handling; the 2026-06-11 masking-defeat precedent): read once at construction from BRAIN_GITEA_TOKEN, held in the struct, travels only in the Authorization: token … header — never logged, never in argv. Error messages carry status + truncated body, never the token (regression-tested in TestErrorPathDoesNotLeakToken). gitea.New returns nil when URL or token is unset → "tracker disabled" via one nil check.
Responses mapped to IssueRef{Repo, Number, URL} → the Ticket receipt fields.
Injection:mcp.Server.WithIssueTracker(capture.IssueTracker) (constructor injection, swappable/testable) + an IssueTracker() accessor; main wires it from BRAIN_GITEA_URL (default https://git.d-ma.be) + BRAIN_GITEA_TOKEN. The use-case consumes it in #53 — wired here so the dependency is constructed once and stays swappable.
Port refinement (1 commit):IssueTracker.CloseIssue gains a comment param to match #52's spec (CloseIssue(repo, number, comment)); the service passes the ticket body as the closing comment. Touches #51's port — forward change on this stacked branch.
Env / deployment note
New env: BRAIN_GITEA_TOKEN (required to enable), BRAIN_GITEA_URL (default https://git.d-ma.be). The 1Password item is DMABE_GITEA_API_TOKEN — the k3s deployment maps it to BRAIN_GITEA_TOKEN (manifest is #55/infra, not here). Secret never enters the repo, argv, or logs.
Acceptance criteria
Concrete IssueTracker: create / close / comment
Owner forced to mathias; never read from caller input
Injected into the brain server behind the interface (constructor injection)
Token via env only; no secret in argv/logs; verified with a token-leak test
Errors → per-item ok:false + message (best-effort; the service already maps a tracker error to a per-ticket failure without aborting)
Tests via faked transport (httptest): create, close (+/- comment), comment, error path, auth header, owner forcing
task check green (lint 0 issues, vet clean, -race green)
Implements #52 (capture 49c) — the real Gitea client behind the `IssueTracker` port, the new outbound dependency the brain server gains for capture.
> **Stacked on #57 (#51).** Base is `feat/capture-service` so this diff is #52-only. Merge order: **#56 → #57 → #58**.
## What landed
**`ingestion/internal/gitea/`** — `Client` implementing `capture.IssueTracker`:
- `CreateIssue` / `CommentIssue` / `CloseIssue(+optional closing comment)` over the Gitea REST API.
- **Owner is the const `"mathias"`** — never a parameter, so a caller can never redirect a write to another owner's repo.
- **Token handling** (AGENTS.md secret-handling; the 2026-06-11 masking-defeat precedent): read once at construction from `BRAIN_GITEA_TOKEN`, held in the struct, travels only in the `Authorization: token …` header — never logged, never in argv. Error messages carry status + truncated body, **never the token** (regression-tested in `TestErrorPathDoesNotLeakToken`). `gitea.New` returns `nil` when URL or token is unset → "tracker disabled" via one nil check.
- Responses mapped to `IssueRef{Repo, Number, URL}` → the `Ticket` receipt fields.
**Injection:** `mcp.Server.WithIssueTracker(capture.IssueTracker)` (constructor injection, swappable/testable) + an `IssueTracker()` accessor; `main` wires it from `BRAIN_GITEA_URL` (default `https://git.d-ma.be`) + `BRAIN_GITEA_TOKEN`. The use-case consumes it in **#53** — wired here so the dependency is constructed once and stays swappable.
**Port refinement (1 commit):** `IssueTracker.CloseIssue` gains a `comment` param to match #52's spec (`CloseIssue(repo, number, comment)`); the service passes the ticket body as the closing comment. Touches #51's port — forward change on this stacked branch.
## Env / deployment note
New env: `BRAIN_GITEA_TOKEN` (required to enable), `BRAIN_GITEA_URL` (default `https://git.d-ma.be`). The 1Password item is `DMABE_GITEA_API_TOKEN` — the k3s deployment maps it to `BRAIN_GITEA_TOKEN` (manifest is #55/infra, not here). Secret never enters the repo, argv, or logs.
## Acceptance criteria
- [x] Concrete `IssueTracker`: create / close / comment
- [x] Owner forced to `mathias`; never read from caller input
- [x] Injected into the brain server behind the interface (constructor injection)
- [x] Token via env only; no secret in argv/logs; verified with a token-leak test
- [x] Errors → per-item `ok:false` + message (best-effort; the service already maps a tracker error to a per-ticket failure without aborting)
- [x] Tests via faked transport (`httptest`): create, close (+/- comment), comment, error path, auth header, owner forcing
- [x] `task check` green (lint 0 issues, vet clean, `-race` green)
Not auto-merged — review + merge from claude.ai.
Closes #52
🤖 Generated with [Claude Code](https://claude.com/claude-code)
#52's IssueTracker spec is CloseIssue(repo, number, comment). Refine the
#51 port signature to match and have the service pass the ticket body as
the closing comment (empty ⇒ close only). Keeps the close-with-comment
flow first-class rather than forcing two separate ticket items.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Implements the IssueTracker port as a real Gitea REST client (#49c) — the
new outbound dependency the brain server gains for capture.
- CreateIssue / CommentIssue / CloseIssue(+optional closing comment) over
the Gitea API. Owner is the const "mathias", never caller-supplied, so
a caller cannot redirect a write to another owner's repo.
- Token read once at construction (BRAIN_GITEA_TOKEN), held in the struct,
travels only in the Authorization header — never logged or in argv.
Error messages carry status + truncated body, never the token
(regression-tested). gitea.New returns nil when URL or token is unset,
so missing config = tracker disabled via one nil check.
- Injected into the MCP server behind the capture.IssueTracker interface
via WithIssueTracker (constructor injection, swappable/testable); main
wires it from BRAIN_GITEA_URL (default https://git.d-ma.be) +
BRAIN_GITEA_TOKEN. Consumed by the capture use-case in #53.
Tests use httptest transports: create (owner+auth header asserted),
comment, close with/without comment, error path that proves the token
never leaks into an error string.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
mathias
merged commit aad90f2dfe into main2026-06-22 21:32:02 +00:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Implements #52 (capture 49c) — the real Gitea client behind the
IssueTrackerport, the new outbound dependency the brain server gains for capture.What landed
ingestion/internal/gitea/—Clientimplementingcapture.IssueTracker:CreateIssue/CommentIssue/CloseIssue(+optional closing comment)over the Gitea REST API."mathias"— never a parameter, so a caller can never redirect a write to another owner's repo.BRAIN_GITEA_TOKEN, held in the struct, travels only in theAuthorization: token …header — never logged, never in argv. Error messages carry status + truncated body, never the token (regression-tested inTestErrorPathDoesNotLeakToken).gitea.Newreturnsnilwhen URL or token is unset → "tracker disabled" via one nil check.IssueRef{Repo, Number, URL}→ theTicketreceipt fields.Injection:
mcp.Server.WithIssueTracker(capture.IssueTracker)(constructor injection, swappable/testable) + anIssueTracker()accessor;mainwires it fromBRAIN_GITEA_URL(defaulthttps://git.d-ma.be) +BRAIN_GITEA_TOKEN. The use-case consumes it in #53 — wired here so the dependency is constructed once and stays swappable.Port refinement (1 commit):
IssueTracker.CloseIssuegains acommentparam to match #52's spec (CloseIssue(repo, number, comment)); the service passes the ticket body as the closing comment. Touches #51's port — forward change on this stacked branch.Env / deployment note
New env:
BRAIN_GITEA_TOKEN(required to enable),BRAIN_GITEA_URL(defaulthttps://git.d-ma.be). The 1Password item isDMABE_GITEA_API_TOKEN— the k3s deployment maps it toBRAIN_GITEA_TOKEN(manifest is #55/infra, not here). Secret never enters the repo, argv, or logs.Acceptance criteria
IssueTracker: create / close / commentmathias; never read from caller inputok:false+ message (best-effort; the service already maps a tracker error to a per-ticket failure without aborting)httptest): create, close (+/- comment), comment, error path, auth header, owner forcingtask checkgreen (lint 0 issues, vet clean,-racegreen)Not auto-merged — review + merge from claude.ai.
Closes #52
🤖 Generated with Claude Code