A customer portal that can configure, prove, fill and watch a deployment - #504
Open
bjmeetsfo wants to merge 5 commits into
Open
A customer portal that can configure, prove, fill and watch a deployment#504bjmeetsfo wants to merge 5 commits into
bjmeetsfo wants to merge 5 commits into
Conversation
Seven pages served by the gateway itself, so a customer running MatrixArk as a managed service can set it up, confirm it works, fill it, use it, watch it, and retry what failed -- without shell access to the host. Setup writes a closed registry of model settings (secrets write-only, stored owner-only, never returned by a read); an endpoint probe calls the configured providers, because extraction and embedding both fall back to a deterministic path that answers 200 and is otherwise indistinguishable from a healthy deployment. The readiness checklist carries the steps to fix each unfinished item. /v1/metrics gains the edge's own request, latency and configuration-health series, with a route-template label so a client cannot create unbounded series. Ingestion shows what an import is doing and retries what is worth retrying. Six tests compare a claim against the source rather than asserting it, and each failed on its first run: the live/restart label against an AST walk of every env read; the route list against the gateway's own path literals, with every documented example executed; the scope catalogue against MATRIXARK_TOOL_SCOPES; the generated pages against the generator; the required-settings mark; and a timed guard on the readiness report's concurrency. Five defects found on the way. The largest: post_document gave up immediately on any 4xx, which is right for a 400 and exactly backwards for a 429 -- the gateway rate-limits ingest and answers 429 with retry-after, so a large import against a busy deployment abandoned precisely the documents it had been asked to resend. The job reported 1000 documents; the store held fewer. Also X-Scope on /v1/ingest_file was passed through unparsed and filed uploads under the wrong scope silently, an unreachable blob tier escaped as a bare 500, the help-text colour token was below AA in both schemes, and one CSS class meant two things. 270 tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ingest can defer encoding, so between the write and the drainer catching up a chunk exists and cannot be matched on meaning -- a retrieve over that window returns less than it should and says nothing about why. matrixark_embedding_status counts encoded against waiting, with the models and vector widths in use; GET /v1/admin/embeddings serves it and the portal shows it moving. The configured encoder travels with the counts (with none, nothing is waiting because nothing will ever be encoded), mixed vector widths are called out (they cannot be compared, so some memories can never match), and a backend that cannot answer is an error rather than an empty backlog. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
GET /v1/admin/events is a server-sent stream carrying traffic, imports in
progress, the encoding backlog and the configuration-warning count; the overview
and setup pages read it and show "live". Three pages were each running timers
against three endpoints, so an import that finished between two polls left a
stale bar until the next one.
Read over fetch rather than EventSource, which cannot set an Authorization
header -- the alternative puts the key in a query string, where it lands in every
access log between the browser and the gateway. X-Accel-Buffering is set because
nginx buffers a proxied response by default, which turns a live stream into one
delivery at the end. A stream is excluded from the latency histogram: a
ten-minute subscription left in there gives a p99 of ten minutes, describing
nothing anyone waited for.
Monitoring: a test compares the dashboards against the metrics this build emits,
both ways. A panel querying a series nobody emits is a blank panel, and a blank
panel reads as "no traffic"; a metric emitted and charted nowhere is work nobody
sees. It caught six, including documents waiting for a retry and when the
configuration last changed. The dashboards and alert rules are now served at
GET /v1/admin/monitoring/{gateway|ingestion|alerts} rather than named as a repo
path a managed customer cannot reach.
305 tests.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
bjmeetsfo
force-pushed
the
feat/portal-setup-metrics
branch
from
August 31, 2026 07:24
447b8b7 to
b96f0f4
Compare
… a paste as a real job Ported from the private tree. Commits in this branch, newest first: - Choose the models and drive the memory API from the portal, and batch a paste as a real job - Stop scoring a vector against a query from a different encoder - Hand the customer the dashboards instead of naming a file they cannot reach - Push the live state instead of polling for it, and chart what was emitted and shown nowhere - Say how much of the store is encoded, and show it moving - Say which twelve of the seventy-nine settings actually have to be set - Show a running import, and the failures waiting, on the page people land on - Let a running upload be cancelled, and stop polling a page nobody is watching - Show an upload happening, let it be retried, and say how to fix each setup item - Retry an import's failures, and stop dropping the ones the gateway asked us to resend - Make the key list answer the two questions anyone asks of it - Put the page generator in the repo, and hold the pages to it - Give the readiness rows a class of their own - Put back the executable bit on files this work never touched - Stop the readiness page paying three times over - Record what each configuration write changed, and from what - Match the repo's file mode for the new tools modules - Stop the settings audit reading the API documentation as a reader - Serve the API contract from the process that implements it, and execute the examples - Move a configuration between deployments, and say when one changed Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ported from the private tree. Commits in this branch, newest first: - A vector nobody can compare must not count as one that was scored - Choose the models and drive the memory API from the portal, and batch a paste as a real job - Stop scoring a vector against a query from a different encoder - Hand the customer the dashboards instead of naming a file they cannot reach - Push the live state instead of polling for it, and chart what was emitted and shown nowhere - Say how much of the store is encoded, and show it moving - Say which twelve of the seventy-nine settings actually have to be set - Show a running import, and the failures waiting, on the page people land on - Let a running upload be cancelled, and stop polling a page nobody is watching - Show an upload happening, let it be retried, and say how to fix each setup item - Retry an import's failures, and stop dropping the ones the gateway asked us to resend - Make the key list answer the two questions anyone asks of it - Put the page generator in the repo, and hold the pages to it - Give the readiness rows a class of their own - Put back the executable bit on files this work never touched - Stop the readiness page paying three times over - Record what each configuration write changed, and from what - Match the repo's file mode for the new tools modules - Stop the settings audit reading the API documentation as a reader - Serve the API contract from the process that implements it, and execute the examples Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The portal could show which model endpoints a deployment talks to and could not change any of them, so configuring a managed deployment meant shell access to the host. It had no way to list the skills and resources it holds, no way to run the one check that covers the whole pipeline, and
/v1/metricscarried only the bulk-import registry — the customer-facing API surface was invisible to the dashboards while the engine below it was fully instrumented.Seven pages now, all served by the gateway itself.
/v1/admin/v1/admin/setup/v1/admin/catalog/v1/admin/explore/v1/admin/ingestion/v1/admin/portal/v1/admin/apicurlthat runsSix tests that verify rather than assert
Documentation that drifts is worse than none, because it is trusted. Each of these compares a claim against the source and failed on its first run:
live/needs restartlabel is checked by AST-walking every module undertools/and classifying each environment read as import-time or per-call. It caught 20 mislabelled settings and one phantom knob that nothing outside the display function reads./v1/admin/routesundocumented and/v1/memory/by-key's example missing its required parameter.MATRIXARK_TOOL_SCOPESboth ways. It caughtadmin:sso.Five defects found by building on top of the code
post_documentgave up immediately on any 4xx — right for a400, exactly backwards for a429. The gateway rate-limits ingest and answers429withretry-after, so a large import against a busy deployment was abandoning precisely the documents it had been asked to resend. The job reported 1000 documents; the store held fewer, and nothing said so. Mutation-checked: 5 of the 6 new tests fail without the fix.X-Scopeon/v1/ingest_fileis documented as a JSON scope object and was passed through unparsed. It reached the string branch of_apply_identity, which reads a bare string as a namespace label, so an upload carrying{"user_id":"alice"}was filed underacme/{"user_id":"alice"}with the user_id dropped. Silent; the only symptom is a retrieve that later cannot find it.500with a stack trace. Now a502naming the URL it could not reach.--faint, the token carrying the help text under every field, measured 3.85:1 in dark and 3.04:1 in light — below AA, on exactly the prose explaining what each setting does..checkmeant both a checkbox label and a readiness row; the collision was already papered over with inline styles.Safety posture
400. Storage locations, cluster addresses and the auth mode are a read-only inventory — repointing a storage directory from a browser form does not reconfigure a running deployment, it strands its data.0600), pushed into the variable named by the matching*_API_KEY_ENV, never returned by any read, and omitted rather than blanked from an export, because a blank is a write that would clear the target's key.GET /v1/metricsstays credential-free: aggregate counters only, and theroutelabel is a template, so a client cannot create unbounded series in an operator's Prometheus.Also
GET /v1/skills,GET /v1/resources,GET /v1/usersandPOST /v1/skills/update— the backend has had the listing tools since the skill lane landed, reachable only through/v1/mcp. Grafana dashboard and alert rules for the edge. Accessibility:role="status"+aria-liveon every region that changes, tab roles on Explore, both colour schemes checked at 375 px with no horizontal overflow.Five of the seven pages are generated by
tools/portal/build_portal_pages.py; the other two are hand-maintained and only have their nav refreshed.270 tests across the portal, ingestion and gateway suites.
Choosing models, driving the memory API, and batching a paste
Both model names were free text on the wire, and a name that is merely misspelt does not fail
loudly: extraction falls back to the local rules and embedding to hash vectors, and both answer
200. The deployment looks healthy while storing far less than it was asked to.
/models`, so asking it turns a guess into a choice), and for embeddings what the stored vectors were actually made with. The probe is opt-in; it is a request against somebody else's service.GET /v1/admin/modelsanswers three questions per role — what is worth suggesting, what theconfigured endpoint says it actually serves (an OpenAI-compatible server answers `GET
Changing the encoder is the one setting on the page that can silently invalidate data already
held. The dangerous case is not a width change — that at least has a width to notice — but two
different encoders of the same width, where nothing in the stack sees a mismatch:
all-MiniLM-L6-v2andparaphrase-multilingual-MiniLM-L12-v2are both 384,BAAI/bge-m3andvoyage-3are both 1024. Each catalogue entry states its width and names the others it collideswith, derived from the widths rather than written into prose — the hand-written note called out the
384 pair and said nothing about the 1024 one, which a test caught. The warning appears while the
choice is being made, and only when it would actually strand something.
Three engine guards make that change survivable rather than silent. All three were broken and none
had a test:
across two unrelated spaces, worse than no number because it ranks. It now refuses;
provider.model, the chat model, while the drainer hashedprovider.embedding_model, so the same encoder produced two hashes depending on which pathembedded the node;
whose recorded encoder is not the active one now falls through to the hybrid lexical pass. A
hash of
0means unknown, not foreign, and is still scored.Six Rust tests, each verified by breaking its guard again on purpose — including the
over-correction that would treat unknown as foreign. Every mutation turns exactly one test red.
A console over the memory API on Explore, listing every method by the name it is called by
rather than by the URL that serves it, generated from one table so an operation that gains an
argument gains a field. Tests hold the table to the routes in both directions, and check each
declared scope against what the gateway gates on. Destructive operations are marked from the
route's own scope and will not run until their name is typed.
Batch ingest takes a paste or a file, one memory per line, and runs it as a server-side job
rather than a loop in the tab — a browser loop over ten thousand records is one closed laptop away
from a half-finished import nobody can resume or count. Job items are now either a path or a
record, so a batch inherits the progress, failure classification, retry-only-the-retryable, cancel
and metrics the directory import already had. Blank lines are dropped and counted rather than
queued to fail forever.
Also fixes the settings audit, which matched a startup function by the outermost segment of a
qualname; the request handler is nested inside the app factory, so every per-request read inside it
read as captured-at-boot. Nothing had noticed because no tracked setting was read there until now.
361 tests.