memorystand / app.js
tomyimkc's picture
Sync exact MemoryStand judge demo bundle
8170d59 verified
Raw History Blame Contribute Delete
82.5 kB
// SPDX-License-Identifier: Apache-2.0 // // MemoryStand dashboard — no framework, no build step, no external CDN or font. // Deployable to AWS Amplify Hosting as a static site exactly as it sits in this // directory. Talks to the backend over four JSON endpoints; see the API CONTRACT // block below for exactly what this file sends and expects back. // // ----------------------------------------------------------------------------- // API CONTRACT this dashboard assumes (backend/handler.py implements it; that // file did not exist yet when this dashboard was written, so this block is the // source of truth for what to build against). Every request/response shape // below is a thin pass-through of an existing backend/*.py function — nothing // here invents new business logic. // // This is a live contract, taken from backend/handler.py's ROUTES table and its // docstring (not guessed) as of the last time this file was updated: // // POST {API_BASE}/decide — requires header x-memorystand-secret // body: { tenant_id, agent_id, target_entity, action, rationale, query, k, task_id, // produced_memory_ids: string[], requires_approval } // -> backend.memory.recall(tenant_id, agent_id, query, k) for "consulted", // then backend.decisions.decide(tenant_id, agent_id, action, rationale, // [m.memory_id for m in consulted], produced_memory_ids, // requires_approval, task_id). `action` is optional: supplying it skips // handler.py's reasoning fallback entirely (reasoning_source: // "caller_supplied"), which is this dashboard's default (the "Proposed // action" field is pre-filled but not required -- see panel 1). Leaving // it blank lets backend.agent.propose() decide instead: it tries the // configured Bedrock model first (reasoning_source:"bedrock:<model>"), // and on ModelUnavailable -- guaranteed on this deployment, Bedrock quota // is 0 -- falls back deterministically to the highest-trust recalled // memory that names an action (reasoning_source:"fallback_memory"), or a // fixed keyword table if none do (reasoning_source:"fallback_heuristic"). // No AWS creds are required for either fallback path. // response: { decision_id, decided_at, action, status ("taken" | // "held_for_approval"), produced: string[], rationale, // reasoning_source, model_calls, cited_memory_ids: string[], // consulted: [ { memory_id, content, entity, attribute_key, // attribute_value, trust_tier, confidence, source, distance, // verdict } ] } // // POST {API_BASE}/ingest — requires header x-memorystand-secret // body: { tenant_id, agent_id, content, entity, attribute_key, // attribute_value, memory_type, source, task_id } // -> backend.memory.remember(...) // response: { memory_id, verdict ("accepted" | "quarantined"), // verdict_reasons: string[], checked_against: string[], // superseded: string|null } // // POST {API_BASE}/confirm_outcome — requires header x-memorystand-secret // body: { tenant_id, decision_id, // outcome ("success" | "rollback" | "false_positive"), // source ("pagerduty" | "metric" | "human"), external_ref, // metric_delta: number|null } // -> backend.trust.grant_standing(tenant_id, decision_id, evidence) // NOTE: this was once ungated and documented here as "read-adjacent". It is not: // it is the route that grants a memory its standing. Ungated, it let any caller // promote any tenant's memories to 'verified'. // response: { decision_id, outcome, source, external_ref, metric_delta, // promoted: string[], demoted: string[], model_calls: number, // trust_tier: "attested"|"verified"|"disputed"|null, // verification: { status, detail, observed, claimed } } // `verification.status` is one of confirmed | contradicted | unavailable | // not_verifiable | entity_mismatch | entity_unbound. Only "confirmed" earns // trust_tier "verified". Contradicted and wrong-entity claims are refused; evidence // that cannot be checked can reach only "attested". // // GET {API_BASE}/timemachine?tenant_id=...&decision_id=... — public demo tenant, // operator auth otherwise // -> backend.replay.cross_examine(tenant_id, decision_id) // response: { decision: { decision_id, action, rationale, decided_at, // outcome, consulted_memory_ids, produced_memory_ids }, // believed_at_decision_time: [ {memory_id, content, entity, // attribute_key, attribute_value, trust_tier, confidence, // source} ], changed_since: [ {...same fields..., delta: // "added"|"removed"|"changed", was_trust_tier?} ], // consulted: string[] } // // GET {API_BASE}/health — open route; used for the connection pill // // A non-2xx response is JSON shaped { error|degraded|held, detail? } (from // handler.py's centralised error mapping); this file renders `detail` when // present, else the raw body. // // Auth: /ingest, /decide and /confirm_outcome are gated behind a shared secret compared with // hmac.compare_digest server-side (MEMORYSTAND_SHARED_SECRET). This dashboard // never hardcodes it -- the operator pastes it into the top bar, and it is // sent only on those two routes, only in memory, never persisted to storage. // // CORS: re-verified 2026-08-04 against a running handler.py dev server (curl -i on // every route, including POST /decide, /ingest and /confirm_outcome, plus an OPTIONS // preflight). Every response -- reads, writes, and error paths alike -- now sends // Access-Control-Allow-Origin: * (handler.py's `_response(..., cors=True)` is // unconditional). A cross-origin deployment (Amplify Hosting calling a Lambda Function // URL, or this dashboard served on a different local port than the API) works. If a // future backend change reintroduces a read/write split here, the symptom from this // file's side will be indistinguishable from "API unreachable" -- the browser hides the // actual response from a CORS-opaque POST -- so this comment exists to make that failure // mode easy to recognize again if it ever comes back. // ----------------------------------------------------------------------------- (function () { "use strict"; var qs = new URLSearchParams(window.location.search); // Default matches scripts/run-local.sh's MEMORYSTAND_LOCAL_PORT (8077), not the more // common 8000 -- port 8000 is documented as taken by an unrelated service on this // project's own dev machines, and run-local.sh --serve always binds 8077. A judge // running the Quickstart never has to pass ?api= for the local flow to work. A judge // hitting a deployed Amplify URL always needs ?api=<lambda-function-url> explicitly -- // there is no way to bake a not-yet-deployed Lambda URL in here honestly. // Default to the deployed API so a judge opening the hosted page needs no query // parameter and no local setup. ?api= still overrides, which is what `run-local.sh // --serve` relies on when pointing this same file at http://127.0.0.1:8077. var DEPLOYED_API = "https://ojao6oaxlk26mqfjwpuy7g4dy40tglyi.lambda-url.us-west-2.on.aws"; var LOCAL_API = "http://127.0.0.1:8077"; var IS_LOCAL_PAGE = location.protocol === "file:" || /^(localhost|127\.0\.0\.1)$/.test(location.hostname); var API_BASE = (qs.get("api") || (IS_LOCAL_PAGE ? LOCAL_API : DEPLOYED_API)).replace(/\/+$/, ""); // Every fetch below is capped so a stalled connection (conference wifi, a Lambda cold // start behind a dead security group, a typo'd ?api=) turns into a clear timeout error // within a few seconds instead of a spinner that spins forever with no explanation. var FETCH_TIMEOUT_MS = 10000; // Requests start concurrently on page load. Generic API calls may update only // transport reachability; /health owns the richer database/credential state. var requestEpoch = 0; var connectionStateEpoch = 0; var healthStateEpoch = 0; function fetchWithTimeout(url, opts) { var controller = typeof AbortController !== "undefined" ? new AbortController() : null; var timer = null; if (controller) { opts = Object.assign({}, opts, { signal: controller.signal }); timer = setTimeout(function () { controller.abort(); }, FETCH_TIMEOUT_MS); } return fetch(url, opts).finally(function () { if (timer) clearTimeout(timer); }); } // ---------------------------------------------------------------- helpers function $(id) { return document.getElementById(id); } function el(tag, attrs, children) { var node = document.createElement(tag); attrs = attrs || {}; Object.keys(attrs).forEach(function (k) { if (k === "class") node.className = attrs[k]; else if (k === "text") node.textContent = attrs[k]; else if (k === "html") node.innerHTML = attrs[k]; else node.setAttribute(k, attrs[k]); }); (children || []).forEach(function (c) { if (c) node.appendChild(c); }); return node; } function clear(node) { while (node.firstChild) node.removeChild(node.firstChild); } function placeholder(text) { return el("p", { class: "placeholder", text: text }); } function shortId(v) { if (!v) return "-"; return String(v).slice(0, 8); } function fmtNum(v, digits) { if (v === null || v === undefined || Number.isNaN(v)) return "-"; return Number(v).toFixed(digits === undefined ? 4 : digits); } function trustBadgeClass(tier) { if (tier === "verified") return "green"; if (tier === "disputed") return "red"; if (tier === "attested") return "amber"; return "gray"; // unconfirmed / unknown / no trust tier yet } function verdictBadgeClass(verdict) { if (verdict === "accepted") return "green"; if (verdict === "quarantined" || verdict === "held") return "amber"; if (verdict === "superseded") return "purple"; return "gray"; } function verdictLabel(verdict) { if (verdict === "quarantined") return "held for review"; if (verdict === "accepted") return "accepted"; if (verdict === "superseded") return "corrected by a newer fact"; return verdict || "unknown"; } function outcomeBadgeClass(outcome) { if (outcome === "success") return "green"; return "red"; } function outcomeLabel(outcome) { if (outcome === "rollback") return "rolled back"; if (outcome === "false_positive") return "false positive"; return outcome || "unknown"; } function trustTierLabel(tier) { if (tier === "verified") return "verified"; if (tier === "attested") return "attested"; if (tier === "disputed") return "disputed"; return "unconfirmed"; } // Ranking weight for the ladder, for DISPLAY ONLY -- the backend ranking is the authority. // // backend/agent.py::_TIER_RANK contains only verified and attested. Verified is action- // authoritative; attested is advisory and always held for approval. Unconfirmed and disputed // remain visible but do not steer an action. This display weight shows standing, not permission. function tierWeight(tier) { if (tier === "verified") return 2; if (tier === "attested") return 1; return 0; // unconfirmed, disputed, or unknown } // The attested-vs-verified distinction is the whole point of this project -- this is the // one sentence of explanation that travels with the badge every place it is shown. function trustTierExplain(tier) { if (tier === "verified") return "Re-queried against the external system of record (CloudWatch), which agreed."; if (tier === "attested") return "An outcome was reported but not independently corroborated. It can advise, but any resulting action is held for human approval."; if (tier === "disputed") return "The reported outcome was a rollback or a false positive."; return "No outcome has been reported for this decision yet."; } function verificationStatusLabel(status) { if (status === "confirmed") return "confirmed"; if (status === "contradicted") return "contradicted"; if (status === "unavailable") return "unavailable"; if (status === "not_verifiable") return "not verifiable"; if (status === "entity_mismatch") return "wrong entity"; if (status === "entity_unbound") return "entity missing"; return status || "unknown"; } function verificationStatusExplain(status) { if (status === "confirmed") return "The external system of record was re-queried and agreed with the claim."; if (status === "contradicted") return "The external system of record was re-queried and disagreed with the claim."; if (status === "unavailable") return "The external system of record could not be reached, or had no datapoints in the relevant window yet -- expected right after a fresh decision."; if (status === "not_verifiable") return "This source (PagerDuty / human) has no machine-checkable record here to re-query."; if (status === "entity_mismatch") return "The telemetry belongs to a different entity than the memory, so the outcome is refused."; if (status === "entity_unbound") return "The memory has no entity to bind to the telemetry, so it cannot reach verified."; return ""; } // reasoning_source says which path actually picked /decide's action -- shown next to // every decision so "did a model do this?" never requires reading the raw response. function reasoningSourceLabel(source) { if (source === "caller_supplied") return "supplied by caller"; if (source === "fallback_memory") return "memory decided"; if (source === "fallback_heuristic") return "keyword table decided"; if (typeof source === "string" && source.indexOf("bedrock:") === 0) return "model decided"; return source || "unknown"; } // On THIS deployment reasoning_source is never a bedrock: value -- Bedrock quota is 0, // see docs/BEDROCK_QUOTA.md -- so that branch is included for completeness, honestly // labelled as not what actually happens here. function reasoningSourceExplain(source) { if (source === "caller_supplied") { return "This request handed /decide a ready-made action, so the agent's own reasoning path (model or memory fallback) never ran."; } if (source === "fallback_memory") { return "A memory decided: no model was consulted (Bedrock quota is 0 on this deployment) -- a recalled memory named this action and outranked the keyword table because it has trust standing from a real outcome."; } if (source === "fallback_heuristic") { return "A keyword table decided: no model was consulted, and no recalled memory named an action either, so a fixed keyword table picked this one instead."; } if (typeof source === "string" && source.indexOf("bedrock:") === 0) { return "The model decided: " + source.slice("bedrock:".length) + " proposed this action, grounded in the recalled memories below. This is not what happens on this deployment -- Bedrock quota is 0 here -- shown for completeness only."; } return ""; } // Shared by the success path (verification.status === "confirmed") and the refusal path // (HTTP 400, verification.status === "contradicted") -- both show the same claimed-vs-observed // pair, just with a different border colour (see .observed-claimed.match / .mismatch in CSS). function observedClaimedRow(v, modifierClass) { return el("div", { class: "observed-claimed" + (modifierClass ? " " + modifierClass : "") }, [ el("div", { class: "oc-item" }, [ el("div", { class: "oc-label", text: "claimed" }), el("div", { class: "oc-value", text: fmtNum(v.claimed, 2) }), ]), el("div", { class: "oc-item" }, [ el("div", { class: "oc-label", text: "observed" }), el("div", { class: "oc-value", text: fmtNum(v.observed, 2) }), ]), ]); } // `opts.rank` (1-based) and `opts.isChosen` are optional decorations used by panel 1's // ranked consulted-memories list (see renderDecideResult below); every other caller // (the seeded-demo preview, panel 4's "believed at decision time") omits `opts` and // gets exactly the row this always rendered. function memoryRow(m, opts) { opts = opts || {}; var topChildren = []; if (opts.rank !== undefined && opts.rank !== null) { topChildren.push(el("span", { class: "memory-rank", text: "#" + opts.rank })); } topChildren.push(el("span", { class: "badge " + trustBadgeClass(m.trust_tier), text: m.trust_tier || "unconfirmed" })); topChildren.push(el("span", { class: "memory-id", text: shortId(m.memory_id) })); if (m.distance !== undefined && m.distance !== null) { topChildren.push(el("span", { class: "memory-attr", text: "distance " + fmtNum(m.distance) })); } if (m.source) topChildren.push(el("span", { class: "memory-attr", text: "source: " + m.source })); if (opts.isChosen) { topChildren.push(el("span", { class: "badge green", text: "chose this action" })); } if (opts.eligibility) { topChildren.push(el("span", { class: "badge " + (opts.eligibility === "decision eligible" ? "green" : "amber"), text: opts.eligibility, })); } var top = el("div", { class: "memory-top" }, topChildren); var attrLine = null; if (m.entity || m.attribute_key) { attrLine = el("div", { class: "memory-attr", text: (m.entity || "-") + "." + (m.attribute_key || "-") + " = " + (m.attribute_value || "-"), }); } var content = el("div", { class: "memory-content", text: m.content || "" }); return el("div", { class: "memory-row" + (opts.isChosen ? " chosen" : "") }, [top, attrLine, content]); } // -------------------------------------------------------------- API layer function apiPost(path, body, extraHeaders) { var epoch = ++requestEpoch; var url = API_BASE + path; var headers = { "Content-Type": "application/json" }; Object.keys(extraHeaders || {}).forEach(function (k) { if (extraHeaders[k]) headers[k] = extraHeaders[k]; }); return fetchWithTimeout(url, { method: "POST", headers: headers, body: JSON.stringify(body), }) .then(function (resp) { return resp .text() .then(function (text) { var data = null; try { data = text ? JSON.parse(text) : null; } catch (e) { data = null; } return { ok: resp.ok, status: resp.status, data: data, rawText: text }; }); }) .then(function (result) { markConnected(true, epoch); return result; }) .catch(function (err) { markConnected(false, epoch); return { ok: false, status: 0, data: null, rawText: "", networkError: err }; }); } function apiGet(path, params) { var epoch = ++requestEpoch; var qsStr = new URLSearchParams(params || {}).toString(); var url = API_BASE + path + (qsStr ? "?" + qsStr : ""); return fetchWithTimeout(url, { method: "GET" }) .then(function (resp) { return resp.text().then(function (text) { var data = null; try { data = text ? JSON.parse(text) : null; } catch (e) { data = null; } return { ok: resp.ok, status: resp.status, data: data, rawText: text }; }); }) .then(function (result) { markConnected(true, epoch); return result; }) .catch(function (err) { markConnected(false, epoch); return { ok: false, status: 0, data: null, rawText: "", networkError: err }; }); } function errorMessage(result) { if (result.networkError) { if (result.networkError.name === "AbortError") { return ( "Timed out waiting for " + API_BASE + " (no response within " + (FETCH_TIMEOUT_MS / 1000) + "s). The API may be down, cold-starting, or unreachable from this network -- " + "check the address bar's ?api= value, or the operator's Lambda Function URL." ); } return ( "Could not reach " + API_BASE + " (" + (result.networkError.message || "network error") + "). Is the API running, and does it allow CORS from this origin?" ); } if (result.data && result.data.detail) return result.data.detail; if (result.data && result.data.error) return String(result.data.error); if (result.rawText) return "HTTP " + result.status + ": " + result.rawText.slice(0, 300); return "HTTP " + result.status + ": request failed"; } function renderError(container, result) { clear(container); container.appendChild(el("div", { class: "error-banner", text: errorMessage(result) })); } // ---------------------------------------------------------- status pill var statusDot = $("statusDot"); var statusText = $("statusText"); var heroStatus = $("heroStatus"); var heroStatusDot = $("heroStatusDot"); var heroStatusText = $("heroStatusText"); var SEEDED_TENANT_ID = "9c8f6e5a-9d1a-4a1c-8f2e-3b6d1c7a4e10"; var SEEDED_AGENT_ID = "1a2b3c4d-5e6f-4708-9a0b-1c2d3e4f5061"; var publicDemoCredential = ""; var publicDemoTenantId = SEEDED_TENANT_ID; $("apiBaseLabel").textContent = API_BASE; function markConnected(ok, epoch, health) { var hasHealth = Boolean(health); // Once a current /health response established database + credential state, // a later generic /recall completion must not downgrade the pill to the // weaker "HTTP API reachable" label. Network failure is still allowed to // override it, because that is new evidence that the public surface is down. if (ok && !hasHealth && healthStateEpoch > 0) return; if (epoch !== undefined && epoch < connectionStateEpoch) return; if (epoch !== undefined) connectionStateEpoch = epoch; if (hasHealth && epoch !== undefined) healthStateEpoch = epoch; statusDot.className = "status-dot " + (ok ? "ok" : "down"); var databaseReachable = hasHealth && health.database === "reachable"; var interactiveReady = Boolean(databaseReachable && health.demo && health.demo.credential); if (!ok) { statusText.textContent = "API unreachable"; } else if (hasHealth && !databaseReachable) { statusText.textContent = "API reachable · database unavailable"; } else if (hasHealth && !interactiveReady) { statusText.textContent = "Read-only API ready · demo credential unavailable"; } else { statusText.textContent = hasHealth ? "Interactive demo ready" : "HTTP API reachable"; } if (heroStatus && heroStatusDot && heroStatusText) { heroStatus.className = "hero-note" + (interactiveReady ? "" : " degraded"); heroStatusDot.className = "status-dot " + (interactiveReady ? "ok" : "down"); heroStatusText.textContent = !ok ? "Live API unavailable · the explanation remains readable, but interactive proof cannot run" : (!hasHealth ? "HTTP API reachable · health details unavailable, so interactive proof is not confirmed" : (!databaseReachable ? "HTTP API reachable · CockroachDB unavailable, so live memory proof cannot run" : (!interactiveReady ? "CockroachDB reachable · read-only proof works, but no public interactive credential is available" : "Live CockroachDB-backed demo · public tenant-scoped credential · no account needed"))); } } // Fill in the published demo credential, and say plainly what it can and cannot do. A judge // arriving cold should be able to press the buttons; the honest framing is that this credential // is deliberately public AND deliberately powerless outside one tenant. function applyDemoCredential(demo) { if (!demo || !demo.credential) return; publicDemoCredential = String(demo.credential); if (demo.tenant_id) publicDemoTenantId = String(demo.tenant_id); var secretInput = $("sharedSecret"); var tenantInput = $("tenantId"); if (secretInput && !secretInput.value) secretInput.value = publicDemoCredential; if (tenantInput && (!tenantInput.value || tenantInput.value.trim() === SEEDED_TENANT_ID)) { tenantInput.value = publicDemoTenantId; } if (runGuidedDemoBtn) runGuidedDemoBtn.disabled = false; var label = document.querySelector('label[for="sharedSecret"] .hint'); if (label) { label.textContent = "(public demo credential, filled in for you — writes only to the demo tenant)"; } } function checkConnection() { var epoch = ++requestEpoch; statusDot.className = "status-dot"; statusText.textContent = "checking…"; return fetchWithTimeout(API_BASE + "/health", { method: "GET" }) .then(function (res) { // Auto-fill the public demo credential so a reviewer never has to be handed one, or // type one, to drive the write routes. It is safe to publish precisely because the // server scopes it to a single tenant (handler._scope_tenant) -- 401 for anything else. // Nothing is overwritten if the visitor has already typed their own secret. return res.json().then(function (h) { markConnected(true, epoch, h); applyDemoCredential(h && h.demo); return h; }).catch(function () { markConnected(true, epoch, null); return null; }); }) .catch(function () { // A same-origin-policy / network failure / timeout means truly unreachable. // Any HTTP response proves only that the HTTP surface answered. The // parsed health body separately decides whether CockroachDB and the // public interactive credential are ready. markConnected(false, epoch); return null; }); } var initialHealthCheck = checkConnection(); // --------------------------------------------------------- guided judge path // The default page is deliberately not the four-form operator dashboard. It // first shows the one contrast a judge must understand, then lets the visitor // exercise the real decision route, then opens the recorded CockroachDB // retrieval receipt. Raw JSON and all manual controls remain below under // "Advanced workspace". var GUIDED_QUERY = "payments-service p99 latency above 2s, error rate climbing"; var GUIDED_ENTITY = "payments-service"; var guidedResult = $("guidedResult"); var runGuidedDemoBtn = $("runGuidedDemo"); var inspectGuidedReceiptBtn = $("inspectGuidedReceipt"); var guidedInstruction = $("guidedInstruction"); var lastGuidedDecisionId = null; function guidedTenantId() { return publicDemoTenantId || SEEDED_TENANT_ID; } function guidedSecretHeaders() { return publicDemoCredential ? { "x-memorystand-secret": publicDemoCredential } : {}; } function actionLabel(value) { return String(value || "unknown").replace(/_/g, " "); } function findGuidedMemories(rows) { var list = rows || []; var restart = list.find(function (m) { return matchesGuidedEntity(m) && (String(m.attribute_value || "").toLowerCase() === "restart_service" || String(m.content || "").toLowerCase().indexOf("restart_service") !== -1); }) || null; var verified = list.find(function (m) { return m.trust_tier === "verified" && (String(m.attribute_value || "").toLowerCase() === "scale_up" || String(m.content || "").toLowerCase().indexOf("scale_up") !== -1); }) || null; return { restart: restart, verified: verified }; } function normalizedEntity(value) { return String(value || "") .trim() .toLowerCase() .replace(/[\s_-]+/g, ""); } function matchesGuidedEntity(memory) { return Boolean(memory) && normalizedEntity(memory.entity) === normalizedEntity(GUIDED_ENTITY); } function guidedMemoryCard(memory, opts) { opts = opts || {}; var action = opts.action || actionLabel(memory && memory.attribute_value); return el("article", { class: "guided-memory" + (opts.stronger ? " stronger" : "") }, [ el("div", { class: "guided-memory-head" }, [ el("span", { class: "badge " + trustBadgeClass(memory && memory.trust_tier), text: trustTierLabel(memory && memory.trust_tier), }), el("span", { class: "distance", text: memory && typeof memory.distance === "number" ? "distance " + fmtNum(memory.distance) : "distance unavailable", }), ]), el("h4", { text: action }), el("p", { text: opts.explain || (memory && memory.content) || "The expected seeded memory is unavailable.", }), ]); } function renderGuidedComparison(rows) { clear(guidedResult); var pair = findGuidedMemories(rows); if (!pair.restart || !pair.verified) { guidedResult.appendChild( el("div", { class: "error-banner", text: "The live tenant did not return both seeded comparison memories. Reload the page or use the Advanced workspace to inspect the raw recall response." }) ); return; } guidedResult.appendChild( el("div", { class: "guided-compare" }, [ guidedMemoryCard(pair.restart, { action: "Restart service", explain: "Closest word match. The page cleared, but the outcome was never independently corroborated.", }), guidedMemoryCard(pair.verified, { action: "Scale up · " + String(pair.verified.entity || "service missing"), stronger: true, explain: matchesGuidedEntity(pair.verified) && pair.verified.action_eligible === true ? "Farther word match. This matching receipt-backed row is eligible for decision context." : "Farther word match, but it is not eligible for this service. It remains visible for audit only.", }), ]) ); guidedInstruction.textContent = "Closeness is not proof, and a different service is not the same subject. Click “Run the live decision” to inspect eligibility and exclusions."; } function loadGuidedComparison() { clear(guidedResult); guidedResult.appendChild(placeholder("Loading the decisive memories from the live deployment…")); apiGet("/recall", { tenant_id: guidedTenantId(), q: GUIDED_QUERY, k: 5, }).then(function (result) { if (!result.ok || !result.data) { renderError(guidedResult, result); guidedInstruction.textContent = "The live comparison could not load. Check the connection status, then retry."; return; } renderGuidedComparison(result.data.results || []); }); } function assessGuidedDecision(data) { data = data || {}; var consulted = Array.isArray(data.consulted) ? data.consulted : []; var pair = findGuidedMemories(consulted); var citedIds = Array.isArray(data.cited_memory_ids) ? data.cited_memory_ids : []; var chosenId = citedIds[0] || null; var chosen = chosenId ? consulted.find(function (m) { return m.memory_id === chosenId; }) || null : null; var eligibleIds = Array.isArray(data.eligible_memory_ids) ? data.eligible_memory_ids : []; var excluded = Array.isArray(data.excluded_memories) ? data.excluded_memories : []; var memoryActuallyDecided = data.reasoning_source === "fallback_memory" && Boolean(chosen); var restartExcluded = Boolean(pair.restart) && excluded.some(function (row) { return row.memory_id === pair.restart.memory_id && row.reason === "trust_tier_not_eligible"; }); var wrongRowExcluded = Boolean(pair.verified) && excluded.some(function (row) { return row.memory_id === pair.verified.memory_id && row.reason === "entity_mismatch"; }); var passed = memoryActuallyDecided && data.target_entity === GUIDED_ENTITY && data.action === "scale_up" && data.status === "held_for_approval" && data.model_calls === 0 && Boolean(pair.restart) && matchesGuidedEntity(pair.restart) && chosen.trust_tier === "verified" && eligibleIds.indexOf(chosen.memory_id) !== -1 && matchesGuidedEntity(chosen) && chosen.memory_id !== pair.restart.memory_id && typeof pair.restart.distance === "number" && typeof chosen.distance === "number" && pair.restart.distance < chosen.distance; var wrongEntityRefused = restartExcluded && wrongRowExcluded && data.target_entity === GUIDED_ENTITY && Boolean(pair.restart) && Boolean(pair.verified) && !matchesGuidedEntity(pair.verified) && eligibleIds.length === 0 && citedIds.length === 0 && !chosen && data.reasoning_source === "fallback_heuristic" && data.action === "scale_up" && data.status === "held_for_approval" && data.model_calls === 0; return { passed: passed, wrongEntityRefused: wrongEntityRefused, consulted: consulted, pair: pair, chosen: chosen, excluded: excluded, }; } function assessGuidedReceipt(data, expectedDecisionId) { data = data || {}; var decision = data.decision || {}; var hasRankedReceipt = Array.isArray(data.recalled_as_of); var recalled = hasRankedReceipt ? data.recalled_as_of : []; var pair = findGuidedMemories(recalled); var excludedAsOf = Array.isArray(data.excluded_memories_as_of) ? data.excluded_memories_as_of : []; var eligibleAsOf = Array.isArray(data.eligible_memory_ids_as_of) ? data.eligible_memory_ids_as_of : null; var baseReceipt = hasRankedReceipt && !data.recall_note && !data.policy_note && decision.query_text === GUIDED_QUERY && decision.target_entity === GUIDED_ENTITY && (!expectedDecisionId || decision.decision_id === expectedDecisionId) && Boolean(pair.restart) && matchesGuidedEntity(pair.restart); var wrongEntityRefused = baseReceipt && Boolean(pair.verified) && Array.isArray(eligibleAsOf) && eligibleAsOf.length === 0 && excludedAsOf.some(function (row) { return row.memory_id === pair.restart.memory_id && row.reason === "trust_tier_not_eligible"; }) && excludedAsOf.some(function (row) { return row.memory_id === pair.verified.memory_id && row.reason === "entity_mismatch"; }); var matchingAuthority = baseReceipt && Boolean(pair.verified) && matchesGuidedEntity(pair.verified) && Array.isArray(eligibleAsOf) && eligibleAsOf.indexOf(pair.verified.memory_id) !== -1; var passed = (wrongEntityRefused || matchingAuthority) && typeof pair.restart.distance === "number" && typeof pair.verified.distance === "number" && pair.restart.distance < pair.verified.distance; return { passed: passed, wrongEntityRefused: wrongEntityRefused, matchingAuthority: matchingAuthority, recalled: recalled, pair: pair, }; } function guidedDecisionCopy(data) { var assessment = assessGuidedDecision(data); if (assessment.passed) { return { headline: "scale up wins on trust", explanation: "The closer unconfirmed restart remained visible, but it did not steer the action. " + "The verified memory supplied the recommendation, and the high-risk action stayed behind human approval.", }; } if (assessment.wrongEntityRefused) { return { headline: "wrong service — proof refused", explanation: "The checkout-api scale-up memory stayed visible in recall, but target_entity=" + GUIDED_ENTITY + " excluded it from decision context. The disclosed fixed fallback selected scale up; no mismatched memory was cited.", }; } if (assessment.chosen && !matchesGuidedEntity(assessment.chosen)) { return { headline: "policy failure — wrong service cited", explanation: "The cited memory belongs to " + String(assessment.chosen.entity || "an unknown entity") + ", not " + GUIDED_ENTITY + ". This response is unsafe and is shown as a failure, not proof.", }; } return { headline: actionLabel(data && data.action) + " returned", explanation: "This live response did not prove the seeded trust-over-proximity path. " + "It returned reasoning_source=" + String((data && data.reasoning_source) || "unknown") + ", status=" + String((data && data.status) || "unknown") + ", and action=" + String((data && data.action) || "unknown") + ". The raw receipt is preserved below without rewriting the result as a success.", }; } function renderGuidedDecision(data) { clear(guidedResult); var assessment = assessGuidedDecision(data); var pair = assessment.pair; var chosen = assessment.chosen; var expectedTrustOverrule = assessment.passed; var expectedRefusal = assessment.wrongEntityRefused; var expectedPolicy = expectedTrustOverrule || expectedRefusal; var copy = guidedDecisionCopy(data); var statusText = data.status === "held_for_approval" ? "Held for human approval" : "Cleared to proceed"; guidedResult.appendChild( el("div", { class: "guided-verdict" + (expectedPolicy ? "" : " unexpected") }, [ el("div", {}, [ el("span", { class: "badge " + (expectedPolicy ? "green" : "amber"), text: expectedPolicy ? statusText : "Unexpected live result", }), el("h4", { text: copy.headline }), el("p", { text: copy.explanation }), ]), el("div", { class: "guided-metric" }, [ el("span", { text: "model calls" }), el("strong", { text: String(typeof data.model_calls === "number" ? data.model_calls : 0) }), ]), ]) ); if (expectedTrustOverrule) { guidedResult.appendChild( el("div", { class: "guided-compare" }, [ guidedMemoryCard(pair.restart, { action: "Closer: restart", explain: "Stored and inspectable, but unconfirmed — no keys.", }), guidedMemoryCard(chosen, { action: "Chosen: " + actionLabel(data.action), stronger: true, explain: "Standing from outside evidence outranked word proximity.", }), ]) ); } else if (expectedRefusal) { guidedResult.appendChild( el("div", { class: "guided-compare" }, [ guidedMemoryCard(pair.restart, { action: "Closer: restart", explain: "Unconfirmed, so it cannot steer.", }), guidedMemoryCard(pair.verified, { action: "Excluded · " + String(pair.verified.entity || "wrong service"), stronger: true, explain: "Verified label, wrong service. Visible in recall; barred from decision context.", }), ]) ); } guidedResult.appendChild(rawJsonBlock(data)); guidedInstruction.textContent = expectedTrustOverrule ? "Result: matching, receipt-backed trust outranked proximity, and safety held the risky action. Inspect the CockroachDB receipt next." : (expectedRefusal ? "Result: wrong-service evidence was refused; the fixed fallback is labeled and the risky action remains held. Inspect the CockroachDB receipt next." : "The live response differed from the expected candidate path. Inspect the raw receipt; this page will not relabel it as proof."); } function renderGuidedReceipt(data) { clear(guidedResult); var decision = data.decision || {}; var assessment = assessGuidedReceipt(data, lastGuidedDecisionId); var recalled = assessment.recalled; var pair = assessment.pair; var receiptMatchesExpected = assessment.passed; guidedResult.appendChild( el("div", { class: "guided-verdict" + (receiptMatchesExpected ? "" : " unexpected") }, [ el("div", {}, [ el("span", { class: "badge " + (receiptMatchesExpected ? "purple" : "amber"), text: receiptMatchesExpected ? "CockroachDB receipt" : "Receipt incomplete", }), el("h4", { text: receiptMatchesExpected ? (assessment.wrongEntityRefused ? "The ranked context and refusal were preserved" : "The ranked context was preserved") : "The expected ranked comparison was not available", }), el("p", { text: receiptMatchesExpected ? (assessment.wrongEntityRefused ? "MemoryStand re-ran the recorded query at the decision timestamp and preserved the wrong-service exclusion. Nothing was overwritten to make the decision look cleaner." : "MemoryStand re-ran the recorded query at the decision timestamp. The old and new beliefs remain inspectable; nothing was overwritten to make the decision look cleaner.") : (data.recall_note || "The response did not include both seeded memories with ranked distances, so this page does not claim a complete time-travel receipt."), }), ]), el("div", { class: "guided-metric" }, [ el("span", { text: "recalled rows" }), el("strong", { text: String(recalled.length) }), ]), ]) ); guidedResult.appendChild( el("div", { class: "guided-receipt" }, [ el("div", { class: "receipt-line" }, [ el("span", { text: "Decision" }), el("strong", { text: decision.decision_id || lastGuidedDecisionId || "unknown" }), ]), el("div", { class: "receipt-line" }, [ el("span", { text: "Recorded query" }), el("strong", { text: decision.query_text || GUIDED_QUERY }), ]), el("div", { class: "receipt-line" }, [ el("span", { text: "Target service" }), el("strong", { text: decision.target_entity || "not recorded" }), ]), el("div", { class: "receipt-line" }, [ el("span", { text: "Closer memory" }), el("strong", { text: pair.restart ? "restart_service · " + trustTierLabel(pair.restart.trust_tier) + " · distance " + fmtNum(pair.restart.distance) : "not returned", }), ]), el("div", { class: "receipt-line" }, [ el("span", { text: assessment.wrongEntityRefused ? "Excluded memory" : "Stronger memory" }), el("strong", { text: pair.verified ? "scale_up · " + trustTierLabel(pair.verified.trust_tier) + " · " + String(pair.verified.entity || "entity missing") + " · distance " + fmtNum(pair.verified.distance) : "not returned", }), ]), el("div", { class: "receipt-line" }, [ el("span", { text: "Time-travel basis" }), el("strong", { text: decision.decided_at ? "AS OF SYSTEM TIME at " + decision.decided_at : "decision timestamp unavailable", }), ]), ]) ); guidedResult.appendChild(rawJsonBlock(data)); guidedInstruction.textContent = receiptMatchesExpected ? (assessment.wrongEntityRefused ? "Complete: the unconfirmed restart and wrong-service scale-up stayed visible, neither guided the action, and CockroachDB preserved the refusal." : "Complete: a non-causal restart stayed visible, matching receipt-backed memory guided, the action was held, and CockroachDB preserved the receipt.") : "The receipt was returned, but it did not prove every expected comparison. Review the raw response rather than treating this as a pass."; } if (runGuidedDemoBtn) { runGuidedDemoBtn.addEventListener("click", function () { runGuidedDemoBtn.disabled = true; runGuidedDemoBtn.innerHTML = '<span class="spinner"></span> connecting…'; inspectGuidedReceiptBtn.disabled = true; clear(guidedResult); guidedResult.appendChild(placeholder("Checking the tenant-scoped public demo credential…")); var credentialReady = publicDemoCredential ? Promise.resolve(true) : checkConnection().then(function () { return Boolean(publicDemoCredential); }); credentialReady.then(function (ready) { if (!ready) return null; runGuidedDemoBtn.innerHTML = '<span class="spinner"></span> running live decision…'; clear(guidedResult); guidedResult.appendChild(placeholder("Calling the deployed /decide route…")); return apiPost("/decide", { tenant_id: guidedTenantId(), agent_id: SEEDED_AGENT_ID, query: GUIDED_QUERY, target_entity: GUIDED_ENTITY, action: null, rationale: "Guided demo: compare trust standing against vector proximity.", k: 5, // task_id is UUID-typed in CockroachDB. A friendly slug used here // previously reached the database as invalid input and made the // guided button look broken, so omit it on the public path. task_id: null, produced_memory_ids: [], requires_approval: false, }, guidedSecretHeaders()); }).then(function (result) { runGuidedDemoBtn.disabled = false; runGuidedDemoBtn.textContent = result ? "Run again" : "Retry live decision"; if (!result) { clear(guidedResult); guidedResult.appendChild( el("div", { class: "error-banner", text: "The API did not publish its tenant-scoped demo credential. The read-only comparison still works; reload the page or retry this button." }) ); guidedInstruction.textContent = "The write path is unavailable until the public demo credential loads from /health."; return; } if (!result.ok || !result.data) { renderError(guidedResult, result); guidedInstruction.textContent = "The live decision did not complete. Review the error above, check the connection status, and retry."; return; } lastGuidedDecisionId = result.data.decision_id || null; if (lastGuidedDecisionId) { $("confirmDecisionId").value = lastGuidedDecisionId; $("timemachineDecisionId").value = lastGuidedDecisionId; inspectGuidedReceiptBtn.disabled = false; } renderGuidedDecision(result.data); }); }); } if (inspectGuidedReceiptBtn) { inspectGuidedReceiptBtn.addEventListener("click", function () { if (!lastGuidedDecisionId) return; inspectGuidedReceiptBtn.disabled = true; inspectGuidedReceiptBtn.innerHTML = '<span class="spinner"></span> loading receipt…'; clear(guidedResult); guidedResult.appendChild(placeholder("Reading the decision-time receipt from CockroachDB…")); apiGet("/timemachine", { tenant_id: guidedTenantId(), decision_id: lastGuidedDecisionId, }).then(function (result) { inspectGuidedReceiptBtn.disabled = false; inspectGuidedReceiptBtn.textContent = "Inspect CockroachDB receipt"; if (!result.ok || !result.data) { renderError(guidedResult, result); return; } renderGuidedReceipt(result.data); }); }); } // ---------------------------------------------------- seeded-demo preview strip // Values match the seeded tenant/agent this repo ships with (db/seed/seed.py via // scripts/run-local.sh) -- the same defaults already pre-filled into the topbar // inputs below. This exists so a first-time visitor never has to know or type a UUID: // click the button (or just load the page, since it auto-runs once) and see real // data, or a plain-language explanation of why there isn't any yet. var previewResult = $("previewResult"); var loadSeededDemoBtn = $("loadSeededDemo"); function runPreview(previewTenantId) { clear(previewResult); previewResult.appendChild( el("p", { class: "placeholder" }, [ document.createTextNode(""), ]) ); previewResult.lastChild.appendChild(el("span", { class: "spinner" })); previewResult.lastChild.appendChild(document.createTextNode(" checking " + previewTenantId + " for existing memories…")); // /recall is an open, read-only route (no shared secret needed) -- this preview // works even before an operator has configured MEMORYSTAND_SHARED_SECRET, which // /decide and /ingest below require. apiGet("/recall", { tenant_id: previewTenantId, q: "on-call incident", k: 3 }).then(function (result) { clear(previewResult); if (!result.ok || !result.data) { renderError(previewResult, result); return; } var results = result.data.results || []; if (results.length === 0) { previewResult.appendChild( el("p", { class: "preview-summary" }, [ document.createTextNode( "No memories found yet for tenant " + shortId(previewTenantId) + ". This is an empty " + "database, not a broken page -- seed it with " ), el("code", { text: "./scripts/run-local.sh" }), document.createTextNode( " (adds 101 example on-call incidents), or submit your first memory with panel 2 below." ), ]) ); return; } previewResult.appendChild( el("p", { class: "preview-summary" }, [ document.createTextNode( "Real data: " + results.length + " memory(ies) recalled for tenant " + shortId(previewTenantId) + ". " + "Panels 1 and 4 below use this same tenant ID." ), ]) ); results.forEach(function (m) { previewResult.appendChild(memoryRow(m)); }); }); } loadSeededDemoBtn.addEventListener("click", function () { $("tenantId").value = guidedTenantId(); $("agentId").value = SEEDED_AGENT_ID; runPreview(guidedTenantId()); }); // Wait for /health before the initial reads so a rotated public demo tenant is // adopted before any tenant-scoped request is sent. If /health is unavailable, // checkConnection resolves to null and these reads still try the committed seed. initialHealthCheck.then(function () { runPreview($("tenantId").value.trim() || guidedTenantId()); loadGuidedComparison(); }); function tenantId() { return $("tenantId").value.trim(); } function agentId() { return $("agentId").value.trim(); } function sharedSecret() { return $("sharedSecret").value; } function secretHeaders() { var s = sharedSecret(); return s ? { "x-memorystand-secret": s } : {}; } function isUuid(value) { return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i .test(String(value || "").trim()); } function validateOptionalUuid(fieldId, resultNode, label) { var value = $(fieldId).value.trim(); if (!value || isUuid(value)) return true; clear(resultNode); resultNode.appendChild( el("div", { class: "error-banner", text: label + " must be a UUID or left blank.", }) ); $(fieldId).focus(); return false; } // ============================================================ Panel 1 // Incident feed -> POST /decide var decideForm = $("form-decide"); var decideResult = $("decideResult"); var decideSubmit = $("decideSubmit"); decideForm.addEventListener("submit", function (evt) { evt.preventDefault(); if (!validateOptionalUuid("decideTaskId", decideResult, "Task / incident ID")) return; if (!$("decideAction").value.trim() && !$("decideTargetEntity").value.trim()) { clear(decideResult); decideResult.appendChild( el("div", { class: "error-banner", text: "Target service is required when MemoryStand selects the action.", }) ); $("decideTargetEntity").focus(); return; } var produced = $("decideProduced").value .split(",") .map(function (s) { return s.trim(); }) .filter(Boolean); var body = { tenant_id: tenantId(), agent_id: agentId(), query: $("decideQuery").value, target_entity: $("decideTargetEntity").value.trim() || null, // Blank on purpose is a real, supported request: omitting `action` lets the agent's // own reasoning path (memory fallback or model) pick one instead of the caller // dictating it -- see the reasoning_source explanation rendered below. action: $("decideAction").value.trim() || null, rationale: $("decideRationale").value, k: parseInt($("decideK").value, 10) || 5, task_id: $("decideTaskId").value.trim() || null, produced_memory_ids: produced, requires_approval: $("decideApproval").checked, }; decideSubmit.disabled = true; decideSubmit.innerHTML = '<span class="spinner"></span> posting…'; clear(decideResult); decideResult.appendChild(placeholder("waiting for response…")); apiPost("/decide", body, secretHeaders()).then(function (result) { decideSubmit.disabled = false; decideSubmit.textContent = "Post alert to /decide"; if (!result.ok || !result.data) { renderError(decideResult, result); return; } renderDecideResult(result.data); // Convenience: carry the new decision id into panels 3 and 4. if (result.data.decision_id) { $("confirmDecisionId").value = result.data.decision_id; $("timemachineDecisionId").value = result.data.decision_id; } }); }); // Older deployments did not return cited_memory_ids, so keep a rationale parser only as a // compatibility fallback. Current deployments return the cited ids structurally. function chosenMemoryIdFromRationale(rationale) { if (!rationale) return null; var m = /Chosen from memory (\S+) \(trust_tier=/.exec(rationale); return m ? m[1] : null; } function renderDecideResult(data) { clear(decideResult); var statusBadge = el("span", { class: "badge " + (data.status === "held_for_approval" ? "amber" : "green"), text: data.status === "held_for_approval" ? "held for approval" : "cleared to proceed", }); var hero = el("div", { class: "verdict-hero" }, [ statusBadge, el("div", {}, [ el("div", { text: "action: " + data.action }), el("div", { class: "verdict-id", text: "decision " + shortId(data.decision_id) + (data.decided_at ? " · " + data.decided_at : "") }), ]), ]); decideResult.appendChild(hero); // model_calls and reasoning_source belong right next to the decision itself, not // only next to the outcome gate in panel 3 -- this is the honest answer to "did a // model make this call?" for the specific decision above. var modelCalls = typeof data.model_calls === "number" ? data.model_calls : 0; decideResult.appendChild( el("div", { class: "kv-line" }, [ el("span", { class: "badge " + (modelCalls === 0 ? "green" : "red"), text: "model calls: " + modelCalls }), document.createTextNode(" reasoning_source: "), el("b", { text: reasoningSourceLabel(data.reasoning_source) + " (" + (data.reasoning_source || "unknown") + ")" }), ]) ); var reasoningExplain = reasoningSourceExplain(data.reasoning_source); if (reasoningExplain) { decideResult.appendChild(el("p", { class: "verification-status-line", text: reasoningExplain })); } if (data.target_entity) { decideResult.appendChild( el("div", { class: "kv-line" }, [ document.createTextNode("target service: "), el("b", { text: data.target_entity }), ]) ); } var consulted = (data.consulted || []).slice(); var eligibleIds = Array.isArray(data.eligible_memory_ids) ? data.eligible_memory_ids.slice() : []; var excluded = Array.isArray(data.excluded_memories) ? data.excluded_memories.slice() : []; var excludedById = {}; excluded.forEach(function (row) { if (row && row.memory_id) excludedById[row.memory_id] = row.reason || "excluded"; }); decideResult.appendChild( el("div", { class: "kv-line" }, [ el("b", { text: String(consulted.length) }), document.createTextNode(" memory(ies) consulted, ranked nearest first:"), ]) ); if (consulted.length === 0) { decideResult.appendChild(el("p", { class: "tier-empty", text: "no accepted memories matched this query yet" })); } else { // Rank by vector distance ourselves rather than trusting response order -- recall() // happens to already return nearest-first, but the ranking shown on screen should // hold even if that ordering ever changes upstream. var ranked = consulted.slice().sort(function (a, b) { var da = typeof a.distance === "number" ? a.distance : Infinity; var db = typeof b.distance === "number" ? b.distance : Infinity; return da - db; }); var cited = Array.isArray(data.cited_memory_ids) ? data.cited_memory_ids : []; var chosenId = data.reasoning_source === "fallback_memory" ? (cited[0] || chosenMemoryIdFromRationale(data.rationale)) : null; var chosen = chosenId ? ranked.filter(function (m) { return m.memory_id === chosenId; })[0] || null : null; var nearest = ranked[0] || null; ranked.forEach(function (m, i) { decideResult.appendChild( memoryRow(m, { rank: i + 1, isChosen: !!(chosen && m.memory_id === chosen.memory_id), eligibility: eligibleIds.indexOf(m.memory_id) !== -1 ? "decision eligible" : (excludedById[m.memory_id] ? "excluded: " + excludedById[m.memory_id].replace(/_/g, " ") : null), }) ); }); if (excluded.length) { var mismatch = excluded.find(function (row) { return row.reason === "entity_mismatch"; }); decideResult.appendChild( el("div", { class: "overrule-banner" }, [ el("div", { class: "overrule-title" }, [ el("span", { class: "badge green", text: "subject policy enforced" }), el("span", { text: excluded.length + " recalled row(s) stayed visible but could not steer", }), ]), el("p", { class: "overrule-explain", text: mismatch ? "A row for " + String(mismatch.entity || "another service") + " was excluded from target " + String(data.target_entity || "the requested service") + ". Recall preserves it for audit without treating its trust label as authority here." : "The response lists each excluded row and reason. Recall remains an audit surface; decision authority is narrower.", }), ]) ); } // A named memory may be described as overruling proximity only when the backend // explicitly cited it from the eligible decision context. if (chosen && eligibleIds.indexOf(chosen.memory_id) !== -1 && nearest && chosen.memory_id !== nearest.memory_id) { decideResult.appendChild( el("div", { class: "overrule-banner" }, [ el("div", { class: "overrule-title" }, [ el("span", { class: "badge green", text: "trust beat proximity" }), el("span", { text: "a closer memory was overruled by higher trust" }), ]), el("p", { class: "overrule-explain" }, [ document.createTextNode( "Memory " + shortId(nearest.memory_id) + " (distance " + fmtNum(nearest.distance) + ", " + trustTierLabel(nearest.trust_tier) + ") was the closer vector match, but memory " + shortId(chosen.memory_id) + " (distance " + fmtNum(chosen.distance) + ", " + trustTierLabel(chosen.trust_tier) + ") supplied the recommendation instead, because it has " + "standing the closer memory does not. " + (data.status === "held_for_approval" ? "Because that standing is attested rather than verified, the recommendation is held for human approval." : "No model was consulted.") ), ]), ]) ); } else if (chosen && eligibleIds.indexOf(chosen.memory_id) !== -1) { decideResult.appendChild( el("p", { class: "kv-line", text: "The memory that decided the action was also the nearest match here -- proximity and trust agreed." }) ); } else if (nearest && eligibleIds.length) { // WHEN A MODEL ANSWERED, we cannot name the deciding memory: only // agent.py::_fallback_action writes "Chosen from memory <id>" into its rationale, and a // model returns free prose. Claiming an overrule here would be a guess, and this file // already refuses to guess one rung up. // // Staying silent was worse, and it was a regression: wiring the standby provider meant // reasoning_source stopped being "fallback_memory" on live calls, so the dashboard's best // moment -- trust outranking proximity -- quietly stopped rendering for every judge who // clicked. What IS provable from the response alone is what the ranking says: which // candidate is nearest, and which carries the most standing. Show exactly that, labelled // as a property of the candidates rather than a claim about what decided. var strongest = ranked.filter(function (row) { return eligibleIds.indexOf(row.memory_id) !== -1; }).sort(function (a, b) { return tierWeight(b.trust_tier) - tierWeight(a.trust_tier); })[0]; // A REAL DISTANCE GAP IS REQUIRED, not just a different row. On the seeded demo tenant // several near-duplicate memories come back at byte-identical distance, and calling one // of them "the closest vector match" when nothing is closer than anything would be a // claim the numbers do not support -- proximity is not losing to trust there, proximity // simply has nothing to say. Ties get the honest line below instead. var gap = (typeof strongest?.distance === "number" && typeof nearest.distance === "number") ? strongest.distance - nearest.distance : 0; if (strongest && strongest.memory_id !== nearest.memory_id && tierWeight(strongest.trust_tier) > tierWeight(nearest.trust_tier) && gap > 1e-9) { decideResult.appendChild( el("div", { class: "overrule-banner" }, [ el("div", { class: "overrule-title" }, [ el("span", { class: "badge green", text: "trust vs proximity" }), el("span", { text: "the nearest memory is not the most trusted one" }), ]), el("p", { class: "overrule-explain" }, [ document.createTextNode( "Memory " + shortId(nearest.memory_id) + " (distance " + fmtNum(nearest.distance) + ", " + trustTierLabel(nearest.trust_tier) + ") is the closest vector match, but memory " + shortId(strongest.memory_id) + " (distance " + fmtNum(strongest.distance) + ", " + trustTierLabel(strongest.trust_tier) + ") carries standing earned from a real outcome. " + "Every tier here was set by the outcome gate with zero model calls. A model chose " + "this action, so this panel does not claim which memory decided it -- with no model " + "reachable, the memory path names it outright." ), ]), ]) ); } else if (strongest && tierWeight(strongest.trust_tier) > tierWeight(nearest.trust_tier)) { // Same tier disagreement, but no usable distance gap to attribute it to. Say what is // true -- the ladder differs across candidates that retrieval could not separate -- // rather than dressing a tie up as proximity losing. decideResult.appendChild( el("p", { class: "kv-line", text: "These candidates are indistinguishable by distance, so proximity does not choose " + "between them -- but their standing differs: " + shortId(strongest.memory_id) + " is " + trustTierLabel(strongest.trust_tier) + " while the others are not. " + "That difference was set by the outcome gate, with zero model calls." }) ); } } } if (data.produced && data.produced.length) { decideResult.appendChild( el("div", { class: "kv-line" }, [ document.createTextNode("produced: "), el("b", { text: data.produced.map(shortId).join(", ") }), ]) ); } decideResult.appendChild(rawJsonBlock(data)); } // ============================================================ Panel 2 // Memory admission -> POST /ingest var ingestForm = $("form-ingest"); var ingestResult = $("ingestResult"); var ingestSubmit = $("ingestSubmit"); ingestForm.addEventListener("submit", function (evt) { evt.preventDefault(); if (!validateOptionalUuid("ingestTaskId", ingestResult, "Task / incident ID")) return; var body = { tenant_id: tenantId(), agent_id: agentId(), content: $("ingestContent").value, entity: $("ingestEntity").value.trim() || null, attribute_key: $("ingestKey").value.trim() || null, attribute_value: $("ingestValue").value.trim() || null, memory_type: $("ingestType").value, source: $("ingestSource").value.trim() || null, task_id: $("ingestTaskId").value.trim() || null, }; ingestSubmit.disabled = true; ingestSubmit.innerHTML = '<span class="spinner"></span> submitting…'; clear(ingestResult); ingestResult.appendChild(placeholder("waiting for response…")); apiPost("/ingest", body, secretHeaders()).then(function (result) { ingestSubmit.disabled = false; ingestSubmit.textContent = "Submit to /ingest"; if (!result.ok || !result.data) { renderError(ingestResult, result); return; } renderIngestResult(result.data); }); }); function renderIngestResult(data) { clear(ingestResult); var isHeld = data.verdict === "quarantined"; var badge = el("span", { class: "badge " + verdictBadgeClass(data.verdict), text: verdictLabel(data.verdict), }); var hero = el("div", { class: "verdict-hero" }, [ badge, el("div", {}, [ el("div", { class: "verdict-id", text: "memory " + shortId(data.memory_id) }), ]), ]); ingestResult.appendChild(hero); if (isHeld) { ingestResult.appendChild(el("p", { class: "kv-line", html: "<b>Held for review</b> — this memory is NOT recallable until a human resolves the conflict below." })); } else if (data.superseded) { ingestResult.appendChild( el("p", { class: "kv-line" }, [ document.createTextNode("Admitted — replaces the earlier memory "), el("b", { text: shortId(data.superseded) }), ]) ); } else { ingestResult.appendChild(el("p", { class: "kv-line", text: "Admitted — recallable immediately." })); } var reasons = data.verdict_reasons || []; if (reasons.length) { var title = isHeld ? "Why it was held:" : "Why:"; ingestResult.appendChild(el("div", { class: "kv-line", text: title })); var ul = el("ul", { class: "reason-list" }); reasons.forEach(function (r) { ul.appendChild(el("li", { text: r })); }); ingestResult.appendChild(ul); } var checked = data.checked_against || []; ingestResult.appendChild( el("div", { class: "kv-line" }, [ document.createTextNode("checked against " + checked.length + " existing memory(ies)" + (checked.length ? ": " : "")), checked.length ? el("span", { class: "mono", text: checked.map(shortId).join(", ") }) : null, ]) ); if (data.memory_id) { var useBtn = el("button", { class: "ghost", type: "button", text: "use as produced in panel 1" }); useBtn.addEventListener("click", function () { var field = $("decideProduced"); var existing = field.value.split(",").map(function (s) { return s.trim(); }).filter(Boolean); if (existing.indexOf(data.memory_id) === -1) existing.push(data.memory_id); field.value = existing.join(", "); }); ingestResult.appendChild(useBtn); } ingestResult.appendChild(rawJsonBlock(data)); } // ============================================================ Panel 3 // MemoryStand (outcome gate) -> POST /confirm_outcome var confirmForm = $("form-confirm"); var confirmResult = $("confirmResult"); var confirmSubmit = $("confirmSubmit"); var confirmSourceSelect = $("confirmSource"); var confirmDeltaField = $("confirmDeltaField"); var modelCallsNumberEl = $("modelCallsNumber"); var modelCallsNoteEl = $("modelCallsNote"); // Updates the persistent hero in the panel header, not something inside confirmResult // -- it is the single most important number on the page, so it stays visible before, // during, and after every confirm attempt rather than living only in the result area. function updateModelCallsHero(modelCalls, data) { var n = typeof modelCalls === "number" ? modelCalls : 0; modelCallsNumberEl.textContent = String(n); modelCallsNumberEl.className = "mc-number " + (n === 0 ? "zero" : "nonzero"); if (data) { modelCallsNoteEl.textContent = "From the last confirmed outcome (decision " + shortId(data.decision_id) + "): the promotion path made " + n + (n === 1 ? " model call." : " model calls."); } } function syncDeltaRequirement() { var needsDelta = confirmSourceSelect.value === "metric"; confirmDeltaField.style.opacity = needsDelta ? "1" : "0.55"; } confirmSourceSelect.addEventListener("change", syncDeltaRequirement); syncDeltaRequirement(); confirmForm.addEventListener("submit", function (evt) { evt.preventDefault(); var deltaRaw = $("confirmDelta").value; var body = { // tenant_id is required now: the server scopes the decision lookup by it, so a // decision id alone can no longer promote a memory (it previously could, for ANY // tenant). tenant_id: tenantId(), decision_id: $("confirmDecisionId").value.trim(), outcome: $("confirmOutcome").value, source: confirmSourceSelect.value, external_ref: $("confirmRef").value.trim(), metric_delta: deltaRaw === "" ? null : parseFloat(deltaRaw), }; if (body.source === "metric" && body.metric_delta === null) { clear(confirmResult); confirmResult.appendChild(el("div", { class: "error-banner", text: "source=metric requires a metric delta." })); return; } confirmSubmit.disabled = true; confirmSubmit.innerHTML = '<span class="spinner"></span> confirming…'; clear(confirmResult); confirmResult.appendChild(placeholder("waiting for response…")); // Sends the shared secret. This route used to be described here as "read-adjacent" // and was left ungated -- but it is the route that GRANTS TRUST, and an ungated one // let anyone promote memories to 'verified'. Classifying a route by how it reads // rather than by what it changes is how that gap opened. apiPost("/confirm_outcome", body, secretHeaders()).then(function (result) { confirmSubmit.disabled = false; confirmSubmit.textContent = "Confirm outcome"; // HTTP 400 from this route is not a broken request -- it is the outcome gate refusing // to record a claim the external system of record disagrees with. Render it as a // deliberate, first-class outcome instead of falling through to the generic error banner. if (result.status === 400 && result.data) { renderConfirmRefusal(confirmResult, result); return; } if (!result.ok || !result.data) { renderError(confirmResult, result); return; } renderConfirmResult(result.data); }); }); function tierList(ids) { var ul = el("ul"); if (!ids || !ids.length) { return el("p", { class: "tier-empty", text: "none" }); } ids.forEach(function (id) { ul.appendChild(el("li", { text: shortId(id) })); }); return ul; } function renderConfirmResult(data) { clear(confirmResult); var outcomeBadge = el("span", { class: "badge " + outcomeBadgeClass(data.outcome), text: outcomeLabel(data.outcome), }); confirmResult.appendChild( el("div", { class: "verdict-hero" }, [ outcomeBadge, el("div", {}, [ el("div", { text: "decision " + shortId(data.decision_id) }), el("div", { class: "verdict-id", text: "source: " + data.source + " · ref: " + data.external_ref }), ]), ]) ); var columns = el("div", { class: "tier-columns" }, [ el("div", { class: "tier-col promoted" }, [ el("h3", { text: "promoted → verified (" + ((data.promoted || []).length) + ")" }), tierList(data.promoted), ]), el("div", { class: "tier-col demoted" }, [ el("h3", { text: "demoted → disputed (" + ((data.demoted || []).length) + ")" }), tierList(data.demoted), ]), ]); confirmResult.appendChild(columns); // -------- resulting trust tier + verification -- the feature this whole submission // leads with, so it gets its own prominent block rather than living only in "raw response". var tier = data.trust_tier; var tierRow = el("div", { class: "verification-row" }, [ el("span", { class: "badge " + trustBadgeClass(tier), text: tier ? trustTierLabel(tier) : "no memories produced", }), el("p", { class: "verification-explain", text: tier ? trustTierExplain(tier) : "This decision did not produce any memories, so there is nothing to promote or demote.", }), ]); var verificationChildren = [tierRow]; var verification = data.verification || null; if (verification && verification.status) { verificationChildren.push( el("p", { class: "verification-status-line" }, [ document.createTextNode("verification: "), el("b", { text: verificationStatusLabel(verification.status) }), document.createTextNode(" — " + verificationStatusExplain(verification.status)), ]) ); if (verification.observed !== undefined || verification.claimed !== undefined) { verificationChildren.push( observedClaimedRow(verification, verification.status === "confirmed" ? "match" : "") ); } if (verification.detail) { verificationChildren.push(el("p", { class: "verification-status-line", text: verification.detail })); } } confirmResult.appendChild(el("div", { class: "verification-block" }, verificationChildren)); // The model-calls-hero itself lives outside confirmResult (see #modelCallsHero in // index.html) so it stays visible -- showing the honest static "0" -- through every // loading/error state this panel goes through, instead of only appearing after a // successful response. updateModelCallsHero(data.model_calls, data); confirmResult.appendChild(rawJsonBlock(data)); } // A 400 here means the outcome gate did its job: the claimed result did not match what the // external system of record shows, so nothing was promoted and nothing was recorded. This // gets its own renderer -- reusing the generic red .error-banner would make the single most // persuasive state this dashboard can show look like a broken request instead of the feature // working as designed. function renderConfirmRefusal(container, result) { clear(container); var data = result.data || {}; var verification = data.verification || {}; var detail = data.detail || data.error || verification.detail || errorMessage(result); container.appendChild( el("div", { class: "refusal-banner" }, [ el("div", { class: "refusal-title" }, [ el("span", { class: "badge red", text: "refused" }), el("span", { text: "the outcome gate rejected this claim" }), ]), el("p", { class: "refusal-explain", text: "This is the trust-promotion feature working as designed: the claimed outcome did " + "not match the external system of record, so the request was refused with HTTP 400 " + "and nothing was recorded. Nothing to fix here.", }), el("p", { class: "refusal-detail", text: String(detail) }), ]) ); if (verification.observed !== undefined || verification.claimed !== undefined) { container.appendChild(observedClaimedRow(verification, "mismatch")); } container.appendChild(rawJsonBlock(data)); } // ============================================================ Panel 4 // Cross-examine -> POST /timemachine var timemachineForm = $("form-timemachine"); var timemachineResult = $("timemachineResult"); var timemachineSubmit = $("timemachineSubmit"); timemachineForm.addEventListener("submit", function (evt) { evt.preventDefault(); var params = { tenant_id: tenantId(), decision_id: $("timemachineDecisionId").value.trim(), }; timemachineSubmit.disabled = true; timemachineSubmit.innerHTML = '<span class="spinner"></span> calling…'; clear(timemachineResult); timemachineResult.appendChild(placeholder("waiting for response…")); apiGet("/timemachine", params).then(function (result) { timemachineSubmit.disabled = false; timemachineSubmit.textContent = "Call /timemachine"; if (!result.ok || !result.data) { renderError(timemachineResult, result); return; } renderTimemachineResult(result.data); }); }); function renderTimemachineResult(data) { clear(timemachineResult); var decision = data.decision || {}; timemachineResult.appendChild( el("div", { class: "verdict-hero" }, [ el("span", { class: "badge " + (decision.outcome ? outcomeBadgeClass(decision.outcome) : "gray"), text: decision.outcome ? outcomeLabel(decision.outcome) : "outcome not yet confirmed", }), el("div", {}, [ el("div", { text: "action: " + (decision.action || "-") }), el("div", { class: "verdict-id", text: "decision " + shortId(decision.decision_id) + (decision.decided_at ? " · decided " + decision.decided_at : "") }), ]), ]) ); if (decision.rationale) { timemachineResult.appendChild(el("p", { class: "kv-line" }, [document.createTextNode("rationale at the time: "), el("b", { text: decision.rationale })])); } var consultedSet = {}; (data.consulted || []).forEach(function (id) { consultedSet[id] = true; }); timemachineResult.appendChild(el("div", { class: "kv-line", text: "What the agent knew when it acted, versus what is true now:" })); var changes = data.changed_since || []; if (!changes.length) { timemachineResult.appendChild(el("p", { class: "tier-empty", text: "nothing has changed since that instant" })); } else { var diffWrap = el("div", { class: "diff-columns" }); changes.forEach(function (row) { var delta = row.delta || "unchanged"; var body = el("div", { class: "diff-body" }, [ el("div", { class: "memory-id", text: shortId(row.memory_id) + (consultedSet[row.memory_id] ? " (consulted)" : "") }), el("div", { text: (row.entity || "-") + "." + (row.attribute_key || "-") + " = " + (row.attribute_value || row.content || "-") + (delta === "changed" && row.was_trust_tier ? " (" + row.was_trust_tier + " → " + row.trust_tier + ")" : ""), }), ]); diffWrap.appendChild( el("div", { class: "diff-row " + delta }, [ el("div", { class: "diff-tag " + delta, text: delta }), body, ]) ); }); timemachineResult.appendChild(diffWrap); } var believed = data.believed_at_decision_time || []; var believedDetails = el("details", { class: "raw-json" }, [ el("summary", { text: "everything believed at decision time (" + believed.length + " memories)" }), ]); var believedWrap = el("div", {}); believed.forEach(function (m) { believedWrap.appendChild(memoryRow(m)); }); believedDetails.appendChild(believedWrap); timemachineResult.appendChild(believedDetails); timemachineResult.appendChild(rawJsonBlock(data)); } // ---------------------------------------------------------------- shared function rawJsonBlock(data) { var d = el("details", { class: "raw-json" }, [el("summary", { text: "raw response" })]); var pre = el("pre", { text: JSON.stringify(data, null, 2) }); d.appendChild(pre); return d; } // Browser-test seam: observable claim policy only, with no network or DOM // dependency. The production page does not read this object. window.MemoryStandJudge = Object.freeze({ assessDecision: assessGuidedDecision, assessReceipt: assessGuidedReceipt, decisionCopy: guidedDecisionCopy, matchesGuidedEntity: matchesGuidedEntity, }); })();
// SPDX-License-Identifier: Apache-2.0
//
// MemoryStand dashboard — no framework, no build step, no external CDN or font.
// Deployable to AWS Amplify Hosting as a static site exactly as it sits in this
// directory. Talks to the backend over four JSON endpoints; see the API CONTRACT
// block below for exactly what this file sends and expects back.
//
// -----------------------------------------------------------------------------
// API CONTRACT this dashboard assumes (backend/handler.py implements it; that
// file did not exist yet when this dashboard was written, so this block is the
// source of truth for what to build against). Every request/response shape
// below is a thin pass-through of an existing backend/*.py function — nothing
// here invents new business logic.
//
// This is a live contract, taken from backend/handler.py's ROUTES table and its
// docstring (not guessed) as of the last time this file was updated:
//
// POST {API_BASE}/decide — requires header x-memorystand-secret
// body: { tenant_id, agent_id, target_entity, action, rationale, query, k, task_id,
// produced_memory_ids: string[], requires_approval }
// -> backend.memory.recall(tenant_id, agent_id, query, k) for "consulted",
// then backend.decisions.decide(tenant_id, agent_id, action, rationale,
// [m.memory_id for m in consulted], produced_memory_ids,
// requires_approval, task_id). `action` is optional: supplying it skips
// handler.py's reasoning fallback entirely (reasoning_source:
// "caller_supplied"), which is this dashboard's default (the "Proposed
// action" field is pre-filled but not required -- see panel 1). Leaving
// it blank lets backend.agent.propose() decide instead: it tries the
// configured Bedrock model first (reasoning_source:"bedrock:<model>"),
// and on ModelUnavailable -- guaranteed on this deployment, Bedrock quota
// is 0 -- falls back deterministically to the highest-trust recalled
// memory that names an action (reasoning_source:"fallback_memory"), or a
// fixed keyword table if none do (reasoning_source:"fallback_heuristic").
// No AWS creds are required for either fallback path.
// response: { decision_id, decided_at, action, status ("taken" |
// "held_for_approval"), produced: string[], rationale,
// reasoning_source, model_calls, cited_memory_ids: string[],
// consulted: [ { memory_id, content, entity, attribute_key,
// attribute_value, trust_tier, confidence, source, distance,
// verdict } ] }
//
// POST {API_BASE}/ingest — requires header x-memorystand-secret
// body: { tenant_id, agent_id, content, entity, attribute_key,
// attribute_value, memory_type, source, task_id }
// -> backend.memory.remember(...)
// response: { memory_id, verdict ("accepted" | "quarantined"),
// verdict_reasons: string[], checked_against: string[],
// superseded: string|null }
//
// POST {API_BASE}/confirm_outcome — requires header x-memorystand-secret
// body: { tenant_id, decision_id,
// outcome ("success" | "rollback" | "false_positive"),
// source ("pagerduty" | "metric" | "human"), external_ref,
// metric_delta: number|null }
// -> backend.trust.grant_standing(tenant_id, decision_id, evidence)
// NOTE: this was once ungated and documented here as "read-adjacent". It is not:
// it is the route that grants a memory its standing. Ungated, it let any caller
// promote any tenant's memories to 'verified'.
// response: { decision_id, outcome, source, external_ref, metric_delta,
// promoted: string[], demoted: string[], model_calls: number,
// trust_tier: "attested"|"verified"|"disputed"|null,
// verification: { status, detail, observed, claimed } }
// `verification.status` is one of confirmed | contradicted | unavailable |
// not_verifiable | entity_mismatch | entity_unbound. Only "confirmed" earns
// trust_tier "verified". Contradicted and wrong-entity claims are refused; evidence
// that cannot be checked can reach only "attested".
//
// GET {API_BASE}/timemachine?tenant_id=...&decision_id=... — public demo tenant,
// operator auth otherwise
// -> backend.replay.cross_examine(tenant_id, decision_id)
// response: { decision: { decision_id, action, rationale, decided_at,
// outcome, consulted_memory_ids, produced_memory_ids },
// believed_at_decision_time: [ {memory_id, content, entity,
// attribute_key, attribute_value, trust_tier, confidence,
// source} ], changed_since: [ {...same fields..., delta:
// "added"|"removed"|"changed", was_trust_tier?} ],
// consulted: string[] }
//
// GET {API_BASE}/health — open route; used for the connection pill
//
// A non-2xx response is JSON shaped { error|degraded|held, detail? } (from
// handler.py's centralised error mapping); this file renders `detail` when
// present, else the raw body.
//
// Auth: /ingest, /decide and /confirm_outcome are gated behind a shared secret compared with
// hmac.compare_digest server-side (MEMORYSTAND_SHARED_SECRET). This dashboard
// never hardcodes it -- the operator pastes it into the top bar, and it is
// sent only on those two routes, only in memory, never persisted to storage.
//
// CORS: re-verified 2026-08-04 against a running handler.py dev server (curl -i on
// every route, including POST /decide, /ingest and /confirm_outcome, plus an OPTIONS
// preflight). Every response -- reads, writes, and error paths alike -- now sends
// Access-Control-Allow-Origin: * (handler.py's `_response(..., cors=True)` is
// unconditional). A cross-origin deployment (Amplify Hosting calling a Lambda Function
// URL, or this dashboard served on a different local port than the API) works. If a
// future backend change reintroduces a read/write split here, the symptom from this
// file's side will be indistinguishable from "API unreachable" -- the browser hides the
// actual response from a CORS-opaque POST -- so this comment exists to make that failure
// mode easy to recognize again if it ever comes back.
// -----------------------------------------------------------------------------