Files
Andy Dixon 89edbb7c06 Phase 13: integrations (research folder, git, Prometheus, signed webhooks)
Adds the integration layer that turns external sources into provenance-tagged
observations without ever executing what it reads.

- research folder poller: os.OpenRoot, text allowlist, binary/archive/symlink
  rejection, content fingerprints, duplicate tracking
- git poller: bare shallow mirror with hooks disabled and ext protocol refused,
  commit observations, co-change pairs
- Prometheus poller: threshold crossings and relative-change detection
- webhooks: HMAC over timestamp.body, +/-300s window, replay cache, 256 KiB cap,
  per-IP rate limit; refused entirely without a secret
- capabilities read_metrics (allow) and repository.read (ask), scoped to the
  invoking mind's integrations
- migrations 0013, API, UI pages, noemactl integrations, docs/integrations.md
2026-06-29 21:04:50 +01:00

31 lines
3.0 KiB
HTML

{{define "content"}}
<div class="pagehead"><h1>Integrations</h1><p class="muted">External sources feeding observations into minds. Everything ingested is data: tagged with source, trust and reference, never obeyed.</p></div>
{{$admin := .Principal.User.Role.AtLeast "administrator"}}
<section class="grid cols-3-1">
<div class="card">
{{if .Data.Integrations}}<table class="table compact"><thead><tr><th>Name</th><th>Kind</th><th>Mind</th><th>Trust</th><th>Status</th><th>Last run</th><th class="num">Ingested</th><th></th></tr></thead>
<tbody>{{range .Data.Integrations}}<tr><td><a href="/integrations/{{.ID}}"><strong>{{.Name}}</strong></a></td><td class="small">{{.Kind}}</td><td class="small">{{index $.Data.MindNames .MindID}}</td><td><span class="badge">{{.Trust}}</span></td>
<td><span class="badge is-{{.Status}}">{{.Status}}</span>{{with .LastError}} <span class="small danger" title="{{.}}">{{trunc . 40}}</span>{{end}}</td><td class="small muted">{{if .LastRunAt}}{{ago .LastRunAt}}{{else}}never{{end}}</td><td class="num">{{.IngestedCount}}</td>
<td>{{if and ($.Principal.User.Role.AtLeast "operator") (ne .Kind "webhook")}}<form method="post" action="/integrations/{{.ID}}/run" class="inline"><input type="hidden" name="_csrf" value="{{$.CSRF}}"><button class="btn small">Run now</button></form>{{end}}</td></tr>{{end}}</tbody></table>
{{else}}<p class="muted">No integrations yet.</p>{{end}}
</div>
{{if $admin}}
<div class="card">
<h2>Add integration</h2>
<form method="post" action="/integrations" class="stack">
<input type="hidden" name="_csrf" value="{{.CSRF}}">
<label>Mind <select name="mind">{{range .Data.Minds}}<option value="{{.Slug}}">{{.DisplayName}}</option>{{end}}</select></label>
<label>Kind <select name="kind"><option value="research_folder">research folder (watched directory)</option><option value="git">git repository</option><option value="prometheus">Prometheus / VictoriaMetrics</option><option value="webhook">signed webhook</option></select></label>
<label>Name <input name="name" required maxlength="64"></label>
<label>Path / URL / endpoint <input name="target" placeholder="./research · https://host/repo.git · http://prom:9090"></label>
<label>Interval (seconds) <input type="number" name="interval_seconds" value="300" min="30"></label>
<label>Metric queries <span class="muted small">(prometheus: one per line, name = expr [gt|lt threshold])</span> <textarea name="queries" rows="3" class="mono" placeholder="cpu = 100 - avg(rate(node_cpu_seconds_total{mode=&quot;idle&quot;}[5m]))*100 gt 80"></textarea></label>
<label>Secret <input type="password" name="secret" autocomplete="off" placeholder="webhook HMAC secret or bearer token; env:NAME works"></label>
<label class="check"><input type="checkbox" name="allow_private"> Endpoint may be a private address</label>
<div><button class="btn btn-primary" type="submit">Add</button></div>
</form>
</div>
{{end}}
</section>
{{end}}