contract surface · 70-control-plane.md · 71-console.md · guide · card
The operator console
The operator console turns governed store reads into answers and store-published state into administrative views. Its hosted server and embeddable client library expose those paths to operators and third-party pages.
Pages run in the browser, the turn loop and redactor run in the console server, and only the server reaches the store engine and the model endpoint:
An operator’s question passes the identity perimeter once, and every store read and model call on its way to an answer runs on the server under that operator’s credential:
Governed rows reach the model only through the turn loop, model prose reaches the page only through the redactor, and memory receives only distilled conclusions:
open-console
The hosted console’s two pages, their API routes, identity gates, page grants and engine capabilities.
page-routestestThe console serves Query at/querywith/query/api/*, Admin at/adminwith/admin/api/*, and, underpublic-data, Data at/datawith/data/api/*.identity-gatetestCloudflare Access gates the hosted Query and Admin pages by default; a deployment without Access uses Cognito managed password login and first-party sessions.page-grantstestThe server maps each verified Access application audience or Cognito group to Query and Admin grants and verifies the matching grant on every page and API request.query-surfacetestQuery presents one composer, one transcript and widgets for returned rows.saved-transcripttestQuery restores a saved transcript only under the verified operator and reading session that wrote it, and only for a store the stores route still lists.Why
because a shared browser tab otherwise hands one operator’s answer rows to the nextnarrow-sidebartestOn a viewport too narrow for the sidebar, a header control opens it as a drawer, keeping chat creation, switching and deletion reachable.Why
because a hidden sidebar with no replacement strands every chat control on a phoneengine-facestestQuery reaches the governed read face; Admin reaches workflow state and sends edit or apply through a server-held control capability.public-datatestA deployment settingCONTEXTFUL_CONSOLE_PUBLIC_DATAto1serves Data:GETreads of Admin’s workflows, runs, lineage, trace and search, to an anonymous operator, and no record, answer-model or edit route. The setting is off by default.Why
because a public sample shows workflows and lineage over public data, and an anonymous caller changes nothingwrong-pagetestA Query or Admin page or API request without a verified Access assertion or Cognito session granting that page raisesConsolePageForbiddenand dispatches nothing.admin-granttestAn Admin edit or apply without a server-held admin capability raisesConsoleAdminGrantMissingand reaches no control call.source-listtestAdmin lists ingest sources from the applied control document, naming each connector, destination, schedule or trigger, applied version and latest run outcome.Why
because the applied document owns source identity and configurationsource-drafttestAdmin adds a source through the version-bound edit and apply path ofengine-faces, with a source form and a raw TOML fallback.source-preservationtestAdding a source throughsource-draftpreserves the applied jobs, models and external steps.source-validationtestSaving and applying a source validates connector availability, required configuration, destination compatibility and duplicate identity undervalidation; refusals name the affected field and a correction, and leave the applied version unchanged.source-first-runtestAdmin shows the applied version’s first and latest source fires, aggregating rows and failures across destinations; absent matching run evidence, the source remains pending after apply.Why
because claiming configuration establishes no execution outcomesource-run-evidencetestA source fire binds its loaded applied version, source identity, configuration digest and destination run identities in the operational record; first and latest summaries travel throughrun-state.Why
because a pipeline name or start time alone can associate an older configuration’s run
Scenarios
surface.open-console.saved-transcript: WHEN a second operator opens Query in the tab where another saved a transcript, THEN Query restores none of its answers or rows.surface.open-console.source-validation: WHEN an HTTP source draft lacks its endpoint, THEN Admin identifiessource.config.endpointand the applied document retains its version.surface.open-console.source-first-run: WHEN an identical source configuration claims another version, THEN older-version runs leave its first-run state pending.
register-store
Which stores a deployment serves, how each store’s credential and binding names derive, and how a request reaches a store’s origin.
registry-unreadabletestACONTEXTFUL_STORES_JSONvalue that does not parse raisesStoreRegistryUnreadableat startup, and no built-in store stands in for the configured set.Why
P3malformed-entrytestOne entry that does not decode raisesStoreEntryMalformednaming it, and is dropped while its siblings are served.reserved-idtestA configured entry claiming either built-in id raisesStoreIdReserved.Why
because the entry otherwise reads the fixture’s rows under its own nameauthored-nametestAn entry carrying its own credential or binding name raisesStoreNameAuthored.
visualize
The Admin page’s operational views: the workflow canvas, run history and data lineage.
workflow-canvastestAdmin projects the store-published pipelines, jobs, models, publications and external steps as one node–edge graph underdataflow-edges, with each pipeline’s schedule, steps and latest run outcome.dataflow-edgestestThe published workflow lists edges{from, to, via}between typed node ids: pipeline and build-jobafter, pipeline to produced table, table or model to its reading model or job, build job to model, job to written table and publication.Why
because a graph the client infers from names draws edges the store never declaredexternal-steptestAdmin draws each[[external]]declaration as a node marked as run outside the engine, joined by its inputs and outputs; the engine runs, schedules and records nothing for it.Why
because work around the store belongs on the canvas without becoming a job kindjob-kind-unknownrefusesexternal-shapetestAn[[external]]declaresExternal, itsinputsandoutputstyped referencestable:<name>,pipeline:<id>orjob:<name>; any other key failsvalidation.Why
because a typed reference keeps a table and a pipeline sharing a name distinct, and no command key makes it runnableexternal-unboundtestAn[[external]]input or output lacking atable:,pipeline:orjob:prefix, or naming no declared table, model, pipeline or job, raisesExternalStepUnboundat validation.Why
because an edge to nothing draws a line the operator cannot traceexternal-collisiontestAn[[external]]whosenamerepeats another external’s name, a job’s name or a pipeline’s id raisesExternalStepCollisionat validation.Why
because one name labelling two graph nodes leaves the reader to guess which one ranrun-historytestAdmin lists recorded runs newest first, each with its pipeline, table, status, rows and duration, filtered by pipeline, through the server-held control capability.Why
because the latest outcome on a node hides a run that failed and a later run that recoveredrun-history-pagetestOne/control/runspage carries at most 1000 runs, flags truncation and counts the runs it declines.lineage-viewtestAdmin drawscallunder the operator’s reader credential and showswithheldas a count.Why
because the control capability reads every table, so a graph fetched under it names tables the operator cannot readstore-searchtestAdmin’s search callscorpus.retrieveunder the operator’s reader credential and lists each hit’s heading, score, snippet, path and run.Why
because an operator checks what the store holds without leaving the page that shows how it arrivedrow-tracetestAdmin traces one row throughcontext.referenceunder the operator’s reader credential, showing the verdict the resolver returns.Why
because a trace composed in the console becomes a second read policy
package
The client library, its four deployment shapes and transports, and the credential each shape carries.
stdio-credentialtestThe process transport requires a verified capability token;--ownerselects its signed owner claim. An absent token raisesStdioCredentialMissingbefore protocol framing and never resolves to owner context.store-selectortestThe child’s working directory selects the store by walking up to the project manifest; finding none raisesStoreSelectorAbsentand exits before writing any protocol framing.owner-flagtestA process--owneradmits the local owner context only with a verified signed claim bound to the selected store and an unrestricted read grant; any other credential raisesOwnerCredentialInvalidbefore protocol framing.
speak
The language every Query-visible string carries, the audience contract, and the deterministic backstops beneath it.
redactor-lookaheadtestThe streaming redactor buffers 128 chars across each chunk boundary, and no denylist entry is longer than the buffer.Why
because an identifier split between two chunks otherwise passes unredactedunanswerable-suggestiontestA suggested prompt whose answer under these rules is a decline raisesConsoleSuggestionUnanswerablewhen the suggestion set is built.stream-redactiontestThe server redacts each streamed model chunk before Query emits prose to the operator.
ground
Where an answer’s material comes from: the closed tool set, the two trust layers, the per-reader credential, the web supplement and the sources block.
ungrounded-answertestA path composing answer prose in a turn holding no tool result raisesConsoleUngroundedAnswer.Why
P2unadmitted-tooltestA call naming a tool the turn’s packs do not admit raisesConsoleToolNotAdmittedand dispatches nothing.mutating-tooltestThe console’s query endpoint admits a read subset: a client-reachable path naming a write raisesConsoleMutatingToolRequested. The one write a turn performs is authored on the server.org-face-read-onlytestA pack registering a write tool on an organization-wide face raisesConsoleWriteToolOnOrgFaceat startup, naming the pack and the tool, and the face serves nothing.direct-file-readtestA table function resolving a path straight against stored bytes raisesConsoleFileAccessDirecton this surface.Why
P5mint-refusedtestA refused mint raisesConsoleTokenExchangeRefused. A store carrying a shared credential falls back to it; one carrying none surfaces the refusal to the reader.Why
P2answer-modeltestA verified Admin operator sets Query’s answer model to one the deployment lists, or to off, and the choice persists across restarts; a model outside the list refuses withConsoleAnswerModelUnlisted.Why
because a model spends on the deployment’s account, so the operators who own that account choose it from a reviewed listmodel-credentialtestEach model call carries the endpoint’s bearer credential when the deployment names one.Why
because a hosted inference endpoint admits no call without its keypassage-groundedtestWith an answer model, a turn whose planner finds no rows answers from the passagescorpus.retrievereturns, citing them, and lists them when the model returns no prose.Why
because a store of documents answers through ranked passages, not through table rowspassage-answertestA deployment naming no model endpoint, or with answers off, answers a turn with the closest passagescorpus.retrievereturns, stating that no model wrote them, and composes no prose of its own.Why
because a store still answers from its own text, and a raw adapter error tells the operator nothingsources-per-turntestA source list carries at most 8 entries.sources-blocktestEvery grounded answer includes a sources block citing only the governed results its admitted read tools returned.
plan-turn
The shape of one turn: planner scaffolding, the replanning round, the answerability test, the code path, and the store’s prompt overlay.
planner-reached-memorytestScaffolding and every deterministic fallback cover data tables; scaffolding naming a memory relation raisesConsolePlannerReachedMemory.code-path-boundstestThe code path reads at most 5000 rows per data table and stops after 10 s.overlay-cachetestThe overlay caches for 5 min, a miss included.overlay-lengthtestAn overlay is truncated at 8000 chars.turn-flowtestA Query turn plans over admitted read tools, replans once after an unanswered first round, and synthesizes from the resulting governed rows.overlay-audiencetestA store’s prompt overlay reaches the operator-facing synthesis text and stays outside the planner’s text.
set-vantage
A session’s time basis: its vantage, the bound each leg carries, the snapshot timeline and the arrivals strip.
unparseabletestA vantage that is neither a calendar day nor an instant raisesConsoleVantageUnparseable, answered400.Why
P2web-bound-unparseabletestA web leg whose bound fails to parse fails that leg and raisesConsoleWebBoundUnparseable.Why
P2sample-labelstestA table’s arrivals contribute at most 3 entries of sampled label.
browse
What a store advertises before a question: discovered chips, humanized labels, the insights panel and the file gallery.
discovered-chipstestQuery builds pre-question chips from described tables admitted to the reader’s zone and humanizes their table labels.insights-paneltestThe insights panel presents row counts returned by the reader’s table descriptions.Why
because a row count from the read face already reflects the reader’s relationfile-gallerytestThe file gallery presents only paths returned by the reader’s governed file listing and previews them through the read face.Why
P5gallery-path-unlistedtestA gallery preview naming a path absent from the governed file listing under the request’sas_ofraisesConsoleGalleryPathUnlistedbefore preview dispatch.Why
because the browser must not turn an arbitrary path into a store readfull-text-searchtestQuery’s search callscorpus.retrieveunder the operator’s reader credential, composes no prose and reaches no model endpoint.Why
because a ranked read answers from the store alone, so a deployment without a model still searches
Scenarios
surface.browse.gallery-path-unlisted: WHEN a Query client requests a path absent from the reader’s bound file listing, THEN it raisesConsoleGalleryPathUnlistedwithout a preview call.
learn
The reading loop’s memory: recall ahead of planning, the per-turn distillation and the labels its conclusions inherit.
distillationtestAfter the answer streams, a second pass distils the exchange into at most 3 entries shaped{subject, key, learning}, zero included.unscopedtestA distilled conclusion landing without the reading-session scope raisesConsoleLearningUnscoped.write-refusedtestA served claim write that refuses or returns no scope-bound receipt raisesConsoleLearningWriteRefusedto Query before answer publication.Why
because a failed write cannot count as a durable learningsubject-resolutiontesttestDistillation preserves the subject it observes; recall resolves that subject through entity matching.
render
The three channels, the component union, deterministic component choice, view hints and the sanitizer walk.
client-authored-viewtestA view specification arriving from a client, or composed by the model, raisesConsoleViewNotServerBuilt.component-choicetestOne row with one measure draws a metric; a date column with a measure over 3 rows on distinct days draws a line; anything else draws a table offering a bar view.older-transcripttestA client reading a transcript with an unknown component-union member renders its view as a table.
brief
The proactive greeting card: its derivation, payload, matching tiers, client-side clock and the absence it earns.
absence-is-earnedtestThe card needs a session with no turns, at present time, a live conclusion, an arrived row matching an interest, and a derivation inside its budget. An error or timeout raisesConsoleBriefUnavailable, and no card renders.topic-tiertestThe topic tier needs at least 2 tokens shared between the conclusion’s subject and text and the row’s label and topics, one of them naming the subject.card-subjectstestA card carries at most 3 subjects.articles-per-subjecttestA subject carries at most 3 entries of matched article.catchup-windowtestThe backend caps the requested window at 7 d.greetingtestQuery derives a greeting card from arrived rows matching live conclusions in a turnless present-time session.
publish-answer
The moment an answer computed for one operator reaches a place other people read.
askerless-audiencetestA scheduled job posting to an audience under a service identity raisesVisibilityAskerlessAudience, naming the destination; a scheduled post’s corpus is narrowed in the reviewed manifest to what the destination reaches.one-operator-principaltestAn answer uses one operator’s credential; the console offers no room-intersection principal.
Shapes
A store registry entry, and the names derived from it:
[
{
"id": "field-notes",
"label": "Field notes",
"endpoint": "https://field-notes.example.net",
"auth": "exchange",
"exchangeRoute": "/auth/exchange",
"packPrefix": "packs/field-notes/"
}
]
secret FIELD_NOTES_QUERY_TOKEN
binding FIELD_NOTES
An external step, and the edges it adds to the published workflow:
[[external]]
name = "review"
label = "Operator review"
inputs = ["table:normalize_records"]
outputs = ["pipeline:enrich"]
{
"externals": [{ "name": "review", "label": "Operator review", "inputs": ["table:normalize_records"], "outputs": ["pipeline:enrich"] }],
"edges": [
{ "from": "table:normalize_records", "to": "external:review", "via": "input" },
{ "from": "external:review", "to": "pipeline:enrich", "via": "output" }
]
}
A tool return, split three ways:
{
"grounding": "3 records landed on 2 Mar, the newest from Northwind at 09:14.",
"view": {
"component": "table.v1",
"props": { "columns": ["Published on", "Publisher"], "rows": [["2 Mar", "Northwind"]] }
},
"internals": { "tool": "context.query", "rows": 3, "elapsed_ms": 41, "redactions": 0 }
}
A table’s view hint:
[tables.daily_levels.view]
component = "band.v1"
x = "observed_on"
measure = "midpoint"
low = "band_low"
high = "band_high"
series = "resource"
unit = "units"
The catch-up payload one greeting renders from:
{
"since": "<instant>",
"clamped": false,
"totalNew": 214,
"items": [
{
"subject": "northwind",
"display": "Northwind",
"learning": "Northwind has new records this week.",
"tier": "derived",
"articles": [
{ "title": "Northwind publishes new records", "source": "example-press.com",
"url": "https://example-press.com/northwind-records", "published_on": "<day>" }
],
"followUp": "What changed for Northwind this week?"
}
]
}