Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 48 additions & 6 deletions src/FS.GG.Coord.Cli/TelemetryStoreApplication.fs
Original file line number Diff line number Diff line change
Expand Up @@ -7529,8 +7529,44 @@ WHERE n.source_ref=$source;
if Encoding.UTF8.GetByteCount encoded > 4 * 1024 * 1024 then invalidOp "efficiency-export-byte-bound"
encoded

// Same authenticated workspace/read transaction as the item snapshot. Raw
// native identities and source metadata never enter this display allowlist.
let private passiveDashboardRows (connection: SqliteConnection) (transaction: SqliteTransaction) =
use command = connection.CreateCommand()
command.Transaction <- transaction
command.CommandText <- """
SELECT f.canonical,t.accepted_at FROM current_ingest_facts f
JOIN fact_admissions a ON a.identity=f.identity AND a.authority_role='generic'
JOIN fact_acceptance_times t ON t.identity=f.identity AND t.fact_revision=f.revision AND t.content_digest=f.content_digest
JOIN receipt_admissions r ON r.receipt_key=t.receipt_key AND r.producer=a.producer AND r.stream=a.stream AND r.authority_role='generic'
JOIN transport_receipts tr ON tr.producer=r.producer AND tr.batch=r.batch AND tr.digest=r.envelope_digest AND tr.state='applied'
WHERE f.kind='provider-usage-observation/1' AND f.item_id IS NULL
ORDER BY f.identity LIMIT 257;
"""
use reader = command.ExecuteReader()
let observations = JsonArray()
let mutable truncated = false
while reader.Read() do
if observations.Count = 256 then truncated <- true
else
let canonical = reader.GetString 0
if Encoding.UTF8.GetByteCount canonical > 16384 then invalidOp "passive-display-row-byte-bound"
use document = JsonDocument.Parse canonical
let row = JsonObject()
for name in ["revision";"capturedAt";"occurredAt";"input";"cachedInput";"cacheWriteInput";"output";"reasoning";"total"] do
row[name] <- JsonNode.Parse(document.RootElement.GetProperty(name).GetRawText())
row["receivedAt"] <- JsonValue.Create(reader.GetString 1)
observations.Add row
let result = JsonObject()
result["schema"] <- JsonValue.Create "fsgg.telemetry.passive-display/1"
result["limit"] <- JsonValue.Create 256
result["truncated"] <- JsonValue.Create truncated
result["observations"] <- observations
result

let private dashboardSnapshotBound
compactCi
includePassive
path
assessment
(workspaceId: (string * (unit -> unit)) option)
Expand Down Expand Up @@ -7703,6 +7739,8 @@ WHERE n.source_ref=$source;
journalMode = journal
|}

if includePassive then content["passiveUsage"] <- passiveDashboardRows connection transaction

content["items"] <- arrayOfStrings selectedItems
content["summaries"] <- summaries

Expand Down Expand Up @@ -7776,7 +7814,7 @@ WHERE n.source_ref=$source;

let revision = CanonicalJson.sha256 canonicalBytes
let envelope = JsonObject()
envelope["schema"] <- JsonValue.Create(if compactCi then "fsgg.telemetry.item-detail/3" else "fsgg.telemetry.item-detail/2")
envelope["schema"] <- JsonValue.Create(if includePassive then "fsgg.telemetry.item-detail/4" elif compactCi then "fsgg.telemetry.item-detail/3" else "fsgg.telemetry.item-detail/2")
envelope["workspaceId"] <- JsonValue.Create(snapshotWorkspace)
envelope["observedAt"] <- JsonValue.Create(DateTimeOffset.UtcNow.ToString("O"))
envelope["revision"] <- JsonValue.Create(revision)
Expand Down Expand Up @@ -7826,11 +7864,11 @@ WHERE n.source_ref=$source;
Error [ error.Message ]

let efficiencyExport path assessment expectedRevision maxItems maxMetrics =
dashboardSnapshotBound true path assessment None ignore None
dashboardSnapshotBound true false path assessment None ignore None
(Some(efficiencyExportInSnapshot expectedRevision maxItems maxMetrics))

let efficiencyAnalysisInspect path assessment requestId =
dashboardSnapshotBound true path assessment None ignore None (Some(fun connection transaction baseSnapshot revision ->
dashboardSnapshotBound true false path assessment None ignore None (Some(fun connection transaction baseSnapshot revision ->
let record,packet,_,_,_,_ = efficiencyQueueRow connection requestId
record["packetBase64"] <- JsonValue.Create(Convert.ToBase64String packet)
record["rawDigest"] <- JsonValue.Create("sha256:"+CanonicalJson.sha256 packet)
Expand Down Expand Up @@ -7876,19 +7914,19 @@ WHERE n.source_ref=$source;
efficiencyQueueResult record))

let dashboardSnapshotWithHooks path assessment (hooks: DashboardSnapshotHooks) itemId =
dashboardSnapshotBound false path assessment None hooks.AfterFirstRead itemId None
dashboardSnapshotBound false false path assessment None hooks.AfterFirstRead itemId None

let dashboardSnapshot path assessment itemId =
dashboardSnapshotWithHooks path assessment ({ AfterFirstRead = ignore }: DashboardSnapshotHooks) itemId

let compactDashboardSnapshotWithHooks path assessment (hooks: DashboardSnapshotHooks) itemId =
dashboardSnapshotBound true path assessment None hooks.AfterFirstRead itemId None
dashboardSnapshotBound true false path assessment None hooks.AfterFirstRead itemId None

let compactDashboardSnapshot path assessment itemId =
compactDashboardSnapshotWithHooks path assessment ({ AfterFirstRead = ignore }: DashboardSnapshotHooks) itemId

let scopedDashboardSnapshotWithHooks path assessment workspaceId (hooks: ScopedDashboardSnapshotHooks) itemId =
dashboardSnapshotBound false path assessment (Some(workspaceId, hooks.ReceiptKeyComputed)) hooks.AfterFirstRead itemId None
dashboardSnapshotBound false false path assessment (Some(workspaceId, hooks.ReceiptKeyComputed)) hooks.AfterFirstRead itemId None

let scopedDashboardSnapshot path assessment workspaceId itemId =
scopedDashboardSnapshotWithHooks
Expand All @@ -7901,6 +7939,10 @@ WHERE n.source_ref=$source;
}
itemId

// Workspace-authorized current passive facts, sharing the item snapshot transaction.
let scopedDashboardSnapshotWithPassive path assessment workspaceId itemId =
dashboardSnapshotBound false true path assessment (Some(workspaceId, ignore)) ignore itemId None

type private ActivationRow =
{
Id: string
Expand Down
5 changes: 5 additions & 0 deletions src/FS.GG.Coord.Cli/TelemetryStoreApplication.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,11 @@ module TelemetryStoreApplication =
itemId: string option ->
Result<string, string list>

/// Workspace-authorized item and bounded unassigned usage snapshot from one read transaction.
val scopedDashboardSnapshotWithPassive:
path: string -> assessment: TelemetryStore.DurabilityAssessment -> workspaceId: string -> itemId: string option ->
Result<string, string list>

val exportPublic:
path: string ->
assessment: TelemetryStore.DurabilityAssessment ->
Expand Down
34 changes: 32 additions & 2 deletions src/FS.GG.Telemetry.Dashboard/Assets/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,25 @@
}
return section;
};
const passiveCounters = ["input","cachedInput","cacheWriteInput","output","reasoning","total"];
function renderPassive(data){
const target=$("unassigned-rows");target.replaceChildren();
if(data.schema!=="fsgg.telemetry.private-dashboard/3"){
text($("unassigned-status"),"Unassigned usage projection unavailable in this snapshot.");return;
}
const usage=data.unassignedUsage;
text($("unassigned-status"),`${usage.observations.length} displayed observations · partial population coverage · item attribution and billed cost: unknown.${usage.truncated?` Display limited to ${usage.limit} observations.`:""} Counters are shown per observation without a combined total.`);
if(!usage.observations.length)target.append(element("li","No receiver-applied unassigned observations are available in this bounded snapshot."));
const counter=value=>value===null?"unknown":BigInt(value).toLocaleString();
for(const row of usage.observations){
const item=element("li","","passive-row");
item.append(element("h3",`Observation ${row.ordinal} · revision ${row.revision}`));
item.append(element("p",`Input ${counter(row.input)} · cached input ${counter(row.cachedInput)} · cache writes ${counter(row.cacheWriteInput)} · output ${counter(row.output)} · reasoning ${counter(row.reasoning)}`));
item.append(element("p",`Observed total: ${counter(row.total)}`,"passive-total"));
item.append(element("p",`Captured ${row.capturedAt} · received ${row.receivedAt} · occurred ${row.occurredAt??"unknown"}`,"passive-clocks"));
target.append(item);
}
}
function renderOverview(data){
text($("process-verdict"),"Insufficient evidence");
text($("process-reason"),"Health requires delivery, rework, coordination, cost and reliability evidence. This snapshot does not supply a diagnostic assessment or comparable baseline.");
Expand All @@ -137,6 +156,7 @@
}
function render(data) {
renderOverview(data);
renderPassive(data);
text($("freshness"),data.observedAt?`Snapshot read ${new Date(data.observedAt).toLocaleString()} · source freshness unestablished`:"Observation time unavailable");
const receipts=data.operational.appliedReceipts==null||data.operational.rejectedReceipts==null?"receipt outcomes unavailable":`${data.operational.appliedReceipts} applied receipts · ${data.operational.rejectedReceipts} rejected receipts`;
text($("health"),`${count(data.operational.pendingBatches)} pending receipts · ${receipts} · ${data.operational.consistency}`);
Expand Down Expand Up @@ -170,7 +190,7 @@
}
target.append(card);
});
if(!data.items.length)target.append(element("p","No scoped observations are available."));
if(!data.items.length)target.append(element("p","No attributed item observations are available."));
applySelection();
}
const object = value => value !== null && typeof value === "object" && !Array.isArray(value);
Expand Down Expand Up @@ -205,12 +225,22 @@
&& (!item.steps || (object(item.steps) && ["runtimeCount","activityCount","ciStepCount","limitPerKind"].every(name=>Number.isSafeInteger(item.steps[name])&&item.steps[name]>=0)
&& typeof item.steps.truncated==="boolean" && Array.isArray(item.steps.rows) && item.steps.rows.length<=192 && item.steps.rows.every(validStep)))
&& validHealth(item.processHealth);
const validSnapshot = (data, workspace) => object(data) && data.schema==="fsgg.telemetry.private-dashboard/2"
const exactKeys = (value,names) => object(value) && Object.keys(value).length===names.length && names.every(name=>Object.hasOwn(value,name));
const decimal = value => typeof value==="string" && /^(0|[1-9][0-9]{0,18})$/.test(value) && BigInt(value)<=9223372036854775807n;
const validPassive = usage => exactKeys(usage,["schema","populationCoverage","attribution","cost","counterEncoding","limit","truncated","observations"])
&& usage.schema==="fsgg.telemetry.unassigned-usage/1" && usage.populationCoverage==="partial" && usage.attribution==="unknown" && usage.cost==="unknown"
&& usage.counterEncoding==="decimal-int64" && usage.limit===256 && typeof usage.truncated==="boolean" && Array.isArray(usage.observations) && usage.observations.length<=256
&& usage.observations.every((row,index)=>exactKeys(row,["ordinal","revision","capturedAt","occurredAt","receivedAt",...passiveCounters])
&& row.ordinal===index+1 && decimal(row.revision) && row.revision!=="0" && row.capturedAt!==null && row.receivedAt!==null
&& ["capturedAt","occurredAt","receivedAt"].every(name=>timestamp(row[name])) && passiveCounters.every(name=>row[name]===null||decimal(row[name])));
const validSnapshot = (data, workspace) => object(data) && ["fsgg.telemetry.private-dashboard/2","fsgg.telemetry.private-dashboard/3"].includes(data.schema)
&& (data.schema!=="fsgg.telemetry.private-dashboard/3"||validPassive(data.unassignedUsage))
&& data.workspaceId===workspace && string(data.revision,64) && /^[0-9a-f]{64}$/.test(data.revision) && timestamp(data.observedAt)
&& object(data.processHealth) && data.processHealth.schema==="fsgg.telemetry.process-health/1" && data.processHealth.verdict==="insufficient-evidence"
&& object(data.operational) && ["pendingBatches","appliedReceipts","rejectedReceipts"].every(name=>quantity(data.operational[name])) && string(data.operational.consistency)
&& Array.isArray(data.items) && data.items.length<=200 && data.items.every(validItem) && new Set(data.items.map(item=>item.id)).size===data.items.length;
function clearSnapshot(){
$("unassigned-rows").replaceChildren();text($("unassigned-status"),"");
lastValid=null;selectedOutcome="";openOutcomes.clear();openEvidence.clear();$("window").value="24h";$("items").replaceChildren();$("categories").replaceChildren();
const option=element("option","All recorded outcomes");option.value="";$("outcome").replaceChildren(option);
for(const id of ["freshness","health","process-verdict","process-reason","process-coverage","window-note"])text($(id),"");
Expand Down
2 changes: 1 addition & 1 deletion src/FS.GG.Telemetry.Dashboard/Assets/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="referrer" content="no-referrer"><title>FS-GG private telemetry</title><link rel="stylesheet" href="/private/dashboard/styles.css"><script defer src="/private/dashboard/app.js"></script></head>
<body><header><p>FS-GG</p><h1>Process health</h1><button id="logout" type="button" hidden>Sign out</button></header><main>
<form id="login"><h2>Sign in</h2><label>Principal <input name="principalId" required maxlength="128" autocomplete="username"></label><label>Access key <input name="accessKey" required type="password" autocomplete="current-password"></label><button type="submit">Sign in</button><p id="login-error" role="alert" hidden>Sign-in failed.</p></form><p id="local-ended" role="alert" hidden>This local session ended. Restart telemetry dashboard serve for a fresh URL.</p>
<div id="dashboard" hidden><section aria-labelledby="workspace-heading"><h2 id="workspace-heading">Workspace</h2><label>Select workspace <select id="workspace"></select></label><p id="freshness"></p></section><section aria-labelledby="process-heading"><h2 id="process-heading">Process health overview</h2><p id="process-verdict" class="verdict"></p><p id="process-reason"></p><p id="process-coverage"></p><label>Outcome <select id="outcome"><option value="">All recorded outcomes</option></select></label><label>Step window <select id="window"><option value="24h">24 hours</option><option value="7d">7 days</option><option value="all">All recorded context</option></select></label><button id="refresh" type="button">Refresh</button><p id="window-note"></p><div id="categories" class="categories"></div><p>Highest-impact anomalies and interventions: unavailable until a canonical diagnostic assessment is recorded.</p></section><section aria-labelledby="health-heading"><h2 id="health-heading">Storage</h2><p id="health">Loading…</p></section><section aria-labelledby="items-heading"><h2 id="items-heading">Observed items</h2><div id="items" role="list"></div></section><p id="error" role="alert" hidden></p></div>
<div id="dashboard" hidden><section aria-labelledby="workspace-heading"><h2 id="workspace-heading">Workspace</h2><label>Select workspace <select id="workspace"></select></label><p id="freshness"></p></section><section aria-labelledby="process-heading"><h2 id="process-heading">Process health overview</h2><p id="process-verdict" class="verdict"></p><p id="process-reason"></p><p id="process-coverage"></p><label>Outcome <select id="outcome"><option value="">All recorded outcomes</option></select></label><label>Step window <select id="window"><option value="24h">24 hours</option><option value="7d">7 days</option><option value="all">All recorded context</option></select></label><button id="refresh" type="button">Refresh</button><p id="window-note"></p><div id="categories" class="categories"></div><p>Highest-impact anomalies and interventions: unavailable until a canonical diagnostic assessment is recorded.</p></section><section aria-labelledby="health-heading"><h2 id="health-heading">Storage</h2><p id="health">Loading…</p></section><section aria-labelledby="unassigned-heading"><h2 id="unassigned-heading">Unassigned observed usage</h2><p id="unassigned-status"></p><ul id="unassigned-rows"></ul></section><section aria-labelledby="items-heading"><h2 id="items-heading">Observed items</h2><div id="items" role="list"></div></section><p id="error" role="alert" hidden></p></div>
</main></body></html>
Loading
Loading