The HTTP door + the load-bearing I1 gate. Wire CaptureService (#51) behind POST /capture with OAuth2/JWT auth, implement the server-derived harness-origin → trust-zone mapping (Q2/§4.2), and enforce the I1 sovereignty gate using the classification tags (#50). After this, capture works end-to-end for harnesses that can run against the REST endpoint directly (Claude Code CLI, Agentsquad) — the relay (#49f) is not required for these.
This is where the gate stops being theoretical, so it's downstream of the taxonomy, the use-case, and the tracker.
What to build
POST /capture adapter — JWT-authed (reuse existing Authentik/JWT middleware), thin: decode CaptureRequest → call CaptureService.Capture → encode CaptureReceipt. No business logic in the handler.
Server-derived harness origin (Q2/§4.2): map the authenticated principal/origin → trust-zone (sovereign-soil / us-nexus). context.harness is descriptive-only, NEVER a gate input. A caller-asserted label that disagrees with the derived origin is logged as a security event.
I1 sovereignty gate: resolve effective classification (caller-declared cross-checked against #50's derived tag, stricter wins); if effective = confidential AND origin = us-nexus → refuse before any write, name the invariant.
HTTP semantics: 200 all-ok; 207-style body on partial; 4xx/5xx all-failed; dry_run validates + returns would-be receipt.
Route registered; OpenAPI/handler docs updated.
Acceptance criteria
POST /capture JWT-authed, thin adapter (decode → use-case → encode)
Harness origin server-derived from authenticated principal; context.harness not a gate input
Asserted-vs-derived origin mismatch logged as a security event
I1 gate: confidential + us-nexus origin → refuse before any write, names the invariant
If deployed as a service: manifest under infra/k3s/apps/** (I3) — coordinate with infra
Notes
Gherkin scenarios this makes live: the entire I1 sovereignty block + classification-model-(C) block in the spec. This is the milestone where "capture works" is true for direct-REST harnesses. The audit path (#49e) and relay (#49f) build on top.
**Parent:** #49 · **Spec:** `specs/capture-bdd-spec.md` §1, §4.1–4.2 · **Depends on:** #50 (tags), #51 (use-case), #52 (tracker) · **Blocks:** #49f
## Scope
The HTTP door + the load-bearing I1 gate. Wire `CaptureService` (#51) behind `POST /capture` with OAuth2/JWT auth, implement the **server-derived harness-origin → trust-zone** mapping (Q2/§4.2), and enforce the I1 sovereignty gate using the classification tags (#50). After this, capture works end-to-end for harnesses that can run against the REST endpoint directly (Claude Code CLI, Agentsquad) — **the relay (#49f) is not required for these.**
This is where the gate stops being theoretical, so it's downstream of the taxonomy, the use-case, and the tracker.
## What to build
1. **`POST /capture` adapter** — JWT-authed (reuse existing Authentik/JWT middleware), thin: decode `CaptureRequest` → call `CaptureService.Capture` → encode `CaptureReceipt`. No business logic in the handler.
2. **Server-derived harness origin (Q2/§4.2):** map the authenticated principal/origin → trust-zone (sovereign-soil / us-nexus). **`context.harness` is descriptive-only, NEVER a gate input.** A caller-asserted label that disagrees with the derived origin is logged as a security event.
3. **I1 sovereignty gate:** resolve effective classification (caller-declared cross-checked against #50's derived tag, stricter wins); if effective = confidential AND origin = us-nexus → **refuse before any write**, name the invariant.
4. **HTTP semantics:** 200 all-ok; 207-style body on partial; 4xx/5xx all-failed; `dry_run` validates + returns would-be receipt.
5. Route registered; OpenAPI/handler docs updated.
## Acceptance criteria
- [ ] `POST /capture` JWT-authed, thin adapter (decode → use-case → encode)
- [ ] Harness origin **server-derived** from authenticated principal; `context.harness` not a gate input
- [ ] Asserted-vs-derived origin mismatch logged as a security event
- [ ] I1 gate: confidential + us-nexus origin → refuse before any write, names the invariant
- [ ] Effective classification = stricter(declared, #50-derived); mismatch logged
- [ ] HTTP status semantics (200 / 207-partial / 4xx-5xx); `dry_run` writes nothing
- [ ] Tests: happy path, confidential-via-us-nexus-refused, confidential-via-sovereign-allowed, asserted-label-ignored, stricter-wins, partial-failure status, dry-run
- [ ] `task check` green; secret-handling respected
- [ ] If deployed as a service: manifest under `infra/k3s/apps/**` (I3) — coordinate with infra
## Notes
Gherkin scenarios this makes live: the entire I1 sovereignty block + classification-model-(C) block in the spec. This is the milestone where "capture works" is true for direct-REST harnesses. The audit path (#49e) and relay (#49f) build on top.
I1 gate (in the use-case):CaptureContext.Origin (server-derived Zone); confidential + us-nexus → refuse before any write (ErrSovereigntyRefused), refusal audited. Asserted-vs-derived harness mismatch → security event. Gate fires only on explicit us-nexus so the unset default can't misfire.
REST adapter (capturehttp): auth (static wins, then Dex JWT) returns the principal + auth path the chassis middleware hides → derives origin via OriginResolver. Principal/origin server-set, overwriting any forged body (TestCallerCannotForgeOrigin). Status: 200 / 207 partial / 502 all-failed / 403 refusal / 400 validation.
Origin → zone (spec §4.2, made concrete): static token + allowlisted JWT subjects → sovereign; every other principal → us-nexus (fail safe). Sovereign JWT subjects via BRAIN_CAPTURE_SOVEREIGN_PRINCIPALS.
Audit: minimal audit.SlogSink (I5 record to logs); #54 swaps in loki+buffer+degradation behind the same interface.
Wiring:/capture reuses the MCP server's graph-wired brain store (one impl, new BrainStore() accessor) + classification tags; mounts only when Gitea is configured.
All ACs met; task check green; token only ever in the auth header (never logged/argv).
Testability note: handler depends on a small Validator interface (chassis *JWTValidator satisfies it) so the JWT/origin path is testable without a live JWKS.
Deferred per the epic: real audit sink + degradation → #54; ai-sessions SummaryWriter → nil for now (summary item fails as partial, rest lands); k3s manifest (I3) → infra/#55.
Milestone: with #50–#53 merged, capture works end-to-end for direct-REST harnesses (Claude Code CLI, Agentsquad). Remaining: #54 (I5 audit path + classification-aware degradation) → #55 (central relay + I2 ledger, the heaviest security weight).
## Implemented — PR #59 (open, base main)
**PR:** https://git.d-ma.be/mathias/hyperguild/pulls/59 · branches from `main` (#50/#51/#52 merged).
**I1 gate (in the use-case):** `CaptureContext.Origin` (server-derived `Zone`); confidential + us-nexus → refuse **before any write** (`ErrSovereigntyRefused`), refusal audited. Asserted-vs-derived harness mismatch → security event. Gate fires only on explicit us-nexus so the unset default can't misfire.
**REST adapter (`capturehttp`):** auth (static wins, then Dex JWT) returns the **principal + auth path** the chassis middleware hides → derives origin via `OriginResolver`. Principal/origin server-set, overwriting any forged body (`TestCallerCannotForgeOrigin`). Status: 200 / 207 partial / 502 all-failed / 403 refusal / 400 validation.
**Origin → zone (spec §4.2, made concrete):** static token + allowlisted JWT subjects → sovereign; **every other principal → us-nexus (fail safe)**. Sovereign JWT subjects via `BRAIN_CAPTURE_SOVEREIGN_PRINCIPALS`.
**Audit:** minimal `audit.SlogSink` (I5 record to logs); #54 swaps in loki+buffer+degradation behind the same interface.
**Wiring:** `/capture` reuses the MCP server's graph-wired brain store (one impl, new `BrainStore()` accessor) + classification tags; mounts only when Gitea is configured.
All ACs met; `task check` green; token only ever in the auth header (never logged/argv).
**Testability note:** handler depends on a small `Validator` interface (chassis `*JWTValidator` satisfies it) so the JWT/origin path is testable without a live JWKS.
**Deferred per the epic:** real audit sink + degradation → #54; ai-sessions `SummaryWriter` → nil for now (summary item fails as partial, rest lands); k3s manifest (I3) → infra/#55.
Brain: `wiki/hyperguild/decisions/gate-on-server-derived-signals-fail-safe`.
**Milestone:** with #50–#53 merged, capture works end-to-end for direct-REST harnesses (Claude Code CLI, Agentsquad). Remaining: **#54** (I5 audit path + classification-aware degradation) → **#55** (central relay + I2 ledger, the heaviest security weight).
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.
Parent: #49 · Spec:
specs/capture-bdd-spec.md§1, §4.1–4.2 · Depends on: #50 (tags), #51 (use-case), #52 (tracker) · Blocks: #49fScope
The HTTP door + the load-bearing I1 gate. Wire
CaptureService(#51) behindPOST /capturewith OAuth2/JWT auth, implement the server-derived harness-origin → trust-zone mapping (Q2/§4.2), and enforce the I1 sovereignty gate using the classification tags (#50). After this, capture works end-to-end for harnesses that can run against the REST endpoint directly (Claude Code CLI, Agentsquad) — the relay (#49f) is not required for these.This is where the gate stops being theoretical, so it's downstream of the taxonomy, the use-case, and the tracker.
What to build
POST /captureadapter — JWT-authed (reuse existing Authentik/JWT middleware), thin: decodeCaptureRequest→ callCaptureService.Capture→ encodeCaptureReceipt. No business logic in the handler.context.harnessis descriptive-only, NEVER a gate input. A caller-asserted label that disagrees with the derived origin is logged as a security event.dry_runvalidates + returns would-be receipt.Acceptance criteria
POST /captureJWT-authed, thin adapter (decode → use-case → encode)context.harnessnot a gate inputdry_runwrites nothingtask checkgreen; secret-handling respectedinfra/k3s/apps/**(I3) — coordinate with infraNotes
Gherkin scenarios this makes live: the entire I1 sovereignty block + classification-model-(C) block in the spec. This is the milestone where "capture works" is true for direct-REST harnesses. The audit path (#49e) and relay (#49f) build on top.
Implemented — PR #59 (open, base main)
PR: #59 · branches from
main(#50/#51/#52 merged).I1 gate (in the use-case):
CaptureContext.Origin(server-derivedZone); confidential + us-nexus → refuse before any write (ErrSovereigntyRefused), refusal audited. Asserted-vs-derived harness mismatch → security event. Gate fires only on explicit us-nexus so the unset default can't misfire.REST adapter (
capturehttp): auth (static wins, then Dex JWT) returns the principal + auth path the chassis middleware hides → derives origin viaOriginResolver. Principal/origin server-set, overwriting any forged body (TestCallerCannotForgeOrigin). Status: 200 / 207 partial / 502 all-failed / 403 refusal / 400 validation.Origin → zone (spec §4.2, made concrete): static token + allowlisted JWT subjects → sovereign; every other principal → us-nexus (fail safe). Sovereign JWT subjects via
BRAIN_CAPTURE_SOVEREIGN_PRINCIPALS.Audit: minimal
audit.SlogSink(I5 record to logs); #54 swaps in loki+buffer+degradation behind the same interface.Wiring:
/capturereuses the MCP server's graph-wired brain store (one impl, newBrainStore()accessor) + classification tags; mounts only when Gitea is configured.All ACs met;
task checkgreen; token only ever in the auth header (never logged/argv).Testability note: handler depends on a small
Validatorinterface (chassis*JWTValidatorsatisfies it) so the JWT/origin path is testable without a live JWKS.Deferred per the epic: real audit sink + degradation → #54; ai-sessions
SummaryWriter→ nil for now (summary item fails as partial, rest lands); k3s manifest (I3) → infra/#55.Brain:
wiki/hyperguild/decisions/gate-on-server-derived-signals-fail-safe.Milestone: with #50–#53 merged, capture works end-to-end for direct-REST harnesses (Claude Code CLI, Agentsquad). Remaining: #54 (I5 audit path + classification-aware degradation) → #55 (central relay + I2 ledger, the heaviest security weight).