# Uploaded Understand Anything Dashboard

> Canonical path: `specs/kb/uploaded-ua-dashboard.md`
> Status: living product-engineering spec
> Scope: source-scoped import, publication, reading, and rendering of Understand Anything `.ua` Dashboard data.

## 1. Purpose

Knowledge Base exposes a source-scoped **Dashboard** next to Documents. The
Dashboard renders an existing Understand Anything graph uploaded by a Human; it
does not generate a graph, wiki, or other model output.

MeshX owns product authorization, import validation, current-version selection,
and the Dashboard API. AGS owns immutable repository objects. The selected KB
source's `insights` repository stores published `.ua` files, while the selected
source's `canonical` or `mirror` repository remains the source-code read plane.

This contract complements:

- [`workspace-knowledge-base.md`](./workspace-knowledge-base.md) for product
  navigation and source selection;
- [`repo-backed-knowledge-base.md`](./repo-backed-knowledge-base.md) for
  repository roles and revision binding;
- [`kb-permissions.md`](./kb-permissions.md) for KB roles; and
- [`kb-ags-permission-matrix.md`](./kb-ags-permission-matrix.md) for the MeshX
  control-plane / AGS execution-plane boundary.

## 2. Product Contract

The KB detail tabs for a selected source are ordered so that Dashboard appears
immediately after Documents. Its canonical route state is:

```text
/kb/:kbId?view=dashboard&source=:sourceId
```

Dashboard always has one selected source. Sources do not share Dashboard data:
switching sources resolves that source's independent current publication.

The public source state is intentionally small:

| State | Meaning |
| --- | --- |
| `empty` | The current source revision has no published UA Dashboard commit. |
| `ready` | The current source revision points to a strict exact-five publication whose files passed archive, size, and JSON-syntax validation at import time. The publication may represent the current or an earlier source commit; renderability is determined by the viewer. |
| `reimport_required` | The current source revision points to an older, well-formed publication whose file set is not canonical; it must be replaced before rendering or file reads. |

An import is synchronous. Request and browser progress describe the temporary
upload operation; it does not create a durable job or an additional public state.
While an overwrite is in progress, every reader continues to see the old
`ready` publication. A failed first import remains `empty`; a failed overwrite
remains on the previous `ready` publication; and a failed replacement from
`reimport_required` remains `reimport_required` with its old pointer intact.

V1 supports importing and overwriting only. It does not:

- invoke a service-side or user-agent model;
- generate Documents wiki pages;
- expose publication history, rollback, or deletion;
- accept a URL or a bare `.gz` file; or
- read a local source-code directory.

When the current source commit differs from the source commit recorded in a
`ready` publication, the old Dashboard remains readable and status reports that
an update is available. The Human may generate an incremental Understand
Anything update outside AgentTeam and upload the resulting archive; AgentTeam does not
run that generation itself.

## 3. Authorization

All Dashboard endpoints authenticate and authorize the KB and selected source
before reading a current pointer, repository mapping, cache validator, or AGS
object.

- A workspace `owner` or `admin` with KB `owner`, `manage`, or `edit` may
  import or overwrite Dashboard data. A KB grant never substitutes for the
  workspace governance role.
- KB `view` may read Dashboard data but cannot see an enabled import action.
- `none` and callers without KB visibility receive no Dashboard metadata.

The import mutation is a MeshX product operation. After authorizing the Human,
MeshX uses the stable `agentteam / meshx-internal` AGS workload identity for
server-owned repository writes. The browser does not receive an AGS write
token, and direct Human access to the system-managed `insights` repository is
not required.

Read authorization remains a product check. Direct knowledge-graph reads do
not become generic AGS repository browse access and do not materialize a new
repository collaborator grant.

## 4. Accepted Archive Contract

The multipart import accepts exactly one file. Filename extension is not the
format authority; the server identifies one of these formats from its bounded
magic/header and parser:

- `.zip`;
- `.tar.gz`; or
- `.tgz`.

If a filename extension is present, it must be compatible with the identified
format. Encrypted ZIP entries and trailing ambiguous archive payloads are
rejected.

Exactly one of these input layouts is accepted:

```text
.ua/knowledge-graph.json
.ua/domain-graph.json
.ua/meta.json
.ua/config.json
.ua/fingerprints.json
```

or:

```text
knowledge-graph.json
domain-graph.json
meta.json
config.json
fingerprints.json
```

The first layout requires `.ua` at the archive root. A wrapping directory such
as `project/.ua/`, the legacy `.understand-anything/` root, a mixture of root
files and `.ua/` files, or more than one candidate root is rejected. Both
accepted layouts normalize to repository paths under `.ua/`.

The publication allowlist is exactly:

| Path | Required | Validation |
| --- | --- | --- |
| `knowledge-graph.json` | yes | UTF-8, one complete JSON value, depth and byte limits |
| `domain-graph.json` | yes | UTF-8, one complete JSON value, depth and byte limits |
| `meta.json` | yes | UTF-8, one complete JSON value, depth and byte limits |
| `config.json` | yes | UTF-8, one complete JSON value, depth and byte limits |
| `fingerprints.json` | yes | UTF-8, one complete JSON value, depth and byte limits |

Directories are permitted only as the single `.ua/` structural root.
`diff-overlay.json`, `intermediate/`, `.trash-*`, cache files, and every other
file or directory are outside the product contract. Missing any of the five
required files, or adding any other content, rejects the archive rather than
silently producing a partial publication.

All JSON files must be UTF-8 without a byte-order mark, parse as exactly one
complete JSON value, and satisfy a maximum nesting depth of 64. Import does not
validate graph schemas, node or edge references, metadata versions, fingerprint
contents, source-commit fields, graph kinds, or the presence of a Domain node.
The viewer owns runtime interpretation. If syntactically valid JSON is not
renderable, it shows a bounded, recoverable graph error and preserves the
import/re-upload controls rather than crashing the page.

A newer source revision carries the old Dashboard pointer and bounded manifest
forward so readers do not lose the last successful publication. The manifest's
`sourceCommitSha` remains the immutable source commit represented by that
Dashboard; it is not rewritten to the newer source commit. This binding is
independent from commit-like fields inside the uploaded graph files.

## 5. Resource And Complexity Limits

Defaults are operator-configurable only toward a deliberate deployment policy;
the server always publishes its effective limits in the status/import metadata
used by the upload dialog.

| Limit | Default |
| --- | ---: |
| HTTP archive payload | 64 MiB |
| archive entries, including directories | 1,000 |
| extracted regular-file bytes | 256 MiB |
| one extracted file | 128 MiB |
| JSON nesting depth | 64 |
Limits are checked while streaming and during the JSON token pass. The importer
must not allocate based only on attacker-controlled declared sizes. It rejects
an archive as soon as a compressed-size, entry-count, per-entry, extracted-byte,
or JSON-depth limit is exceeded.

## 6. Archive Safety

Every entry is normalized once to a slash-separated relative path. Import
rejects:

- empty paths, absolute paths, drive/UNC paths, NUL, `.` or `..` segments;
- backslashes, percent-encoded path ambiguity, or two names that normalize to
  the same path;
- symlinks, hardlinks, devices, FIFOs, sockets, and any non-regular file type;
- duplicate archive entries, encrypted ZIP entries, invalid size metadata, and
  decompression beyond the configured bounds; and
- a normalized file path outside the single accepted UA root or allowlist.

The request body is streamed to a private temporary file. Extraction writes
only to a private temporary directory with no-follow semantics and closes every
archive reader and file on success, error, cancellation, and disconnect.

All archive, path, size, UTF-8, and JSON-syntax checks complete before the first
AGS mutation. Error responses use stable,
display-safe categories and never include archive contents, local temporary
paths, raw AGS bodies, or source contents.

## 7. Revision And Publication Model

`knowledge_base_source_revisions` is the MeshX authority for the relationship
between a source snapshot and its Dashboard publication. It adds:

```text
ua_dashboard_commit_sha VARCHAR(128) NOT NULL DEFAULT ''
ua_dashboard_manifest_json VARBINARY(16384) NOT NULL DEFAULT ''
ua_dashboard_published_at TIMESTAMP NULL
```

The 16,384-byte binary capacity exactly matches the service's maximum encoded
manifest size; it is bytes, not utf8mb4 characters, and the column is not
indexed. The store rejects an oversized manifest before issuing a database
write. The store model uses `UADashboardCommitSHA`, `UADashboardManifestJSON`, and
`UADashboardPublishedAt` with no JSON tags. A canonical manifest contains exactly the five
allowlisted paths, blob SHAs, and byte counts shown by the status DTO. API DTOs expose only the fields required by the Dashboard
contract.

The MeshX `SourceRevision` model and normal schema migration own the canonical
`VARBINARY(16384) NOT NULL DEFAULT ''` shape. A fresh database is created in
that final shape, and the default keeps ordinary inserts that omit an
unpublished manifest valid. Existing revisions whose Dashboard metadata was
cleared by earlier source-advance behavior are not backfilled. Retention applies
only when a new source revision is advanced by the current implementation.

The complete MeshX schema migration is serialized across application replicas
by one backend-appropriate migration mutex held for its full lifetime. The TiDB
implementation follows the pinned-session `GET_LOCK` / `RELEASE_LOCK` pattern
from `internal/coredb/bootstrap_lock.go`, extracting a dependency-neutral shared
helper only if both Go modules can consume it without reversing boundaries; it
must not invent different lock semantics. It creates one `*sql.Conn`, assigns
that connection as the GORM session `ConnPool`, and runs every metadata query, DDL,
AutoMigrate call and final verification through that derived
GORM handle. Release uses `context.WithTimeout(context.WithoutCancel(ctx), 5s)`
before the connection is closed. A replica that cannot acquire or retain the
lock fails startup rather than racing an inspect-then-add sequence. A canonical
column, including type, capacity, nullability, and default, is a startup no-op.
This makes repeated and concurrent startup safe. Deployments that disable automatic migration must run the same
serialized MeshX migration entry point before starting a binary that needs new
schema; disabling it does not weaken the canonical shape or compatibility
contract.

Import requires an existing current source revision with a non-empty immutable
`source_commit_sha`. Source import or mirror synchronization owns creating and
advancing source revisions; Dashboard import does not infer a source snapshot
from a mutable branch and does not create a competing minimal revision. A
source revision created by later source sync or materialization copies the
previous current revision's Dashboard commit, bounded manifest, and publication
timestamp. The source-row lock and current-revision row lock serialize this
copy with Dashboard publication so a concurrent import either becomes the
carried publication or loses its current-revision CAS and can be retried.

Mirror completion, canonical source provisioning, and canonical default-branch
push handling are source-revision producers. Mirror completion uses
the immutable `last_mirrored_commit` and its queued current-revision fence;
canonical provisioning resolves the initialized default-branch commit; and a
push fast path uses its predecessor SHA as a stale-delivery fence. Feature
branches, tags, deletions, and insights-repository writes do not advance a
source revision. Same-commit retries preserve an attached Dashboard, while a
different commit creates a current revision that retains the previous Dashboard
pointer. The unchanged manifest source commit makes the retained publication
detectably out of date without another database row or AGS read.

While a source lacks a current revision, status returns the stable retryable
`dashboard-source-revision-unavailable` error without an AGS call or write.

Mirror configuration mutation and sync enqueue serialize through the same
source-row lock. Enqueue may preflight external connectivity before opening the
transaction, but it must re-read and match the authoritative enabled state,
upstream type, remote, default ref, and credential reference under that lock;
only then may it read the current revision, create the job, and set queued
state. A stale preflight is rejected rather than writing an older mirror row.
Mirror configuration mutation follows the same rule: its lock-time
authoritative mirror generation must still match the snapshot used for
preflight and credential validation. If it changed, the mutation returns a
conflict instead of applying a stale full-row update over newer configuration
or over a job already created for that newer generation.

Publication proceeds as follows:

1. Authorize the KB/source mutation and load exactly one source, its current
   source revision, and its `resource_repositories(repo_role=insights)` mapping.
2. Snapshot `revision_id`, `source_commit_sha`, and the previous
   `ua_dashboard_commit_sha` as the compare-and-swap fence.
3. Fully stage and validate the normalized allowlist without an AGS write.
4. Resolve the insights repository default-branch head and base tree.
5. Create a tree from that base which deletes the existing `.ua` subtree and
   adds only the normalized allowlist. Wiki and all non-`.ua` files from the
   base tree remain unchanged.
6. Create a commit object with the resolved insights head as parent. Creating a
   commit object does not move the default branch.
7. Create an immutable retention ref below
   `refs/meshx/kb-dashboard/<source-id>/<commit-sha>`. It makes the object
   reachable but is not the product's current pointer.
8. Atomically update the Dashboard commit, bounded manifest, and publication
   timestamp only where workspace, KB, source, current revision id,
   `is_current=true`, source commit, and previous Dashboard commit still match
   the fence. The same transaction appends the successful
   `kb.source.dashboard.import` audit event.

Only the MeshX CAS makes a publication visible. A concurrent loser returns
`409 dashboard-import-conflict`; it never overwrites the winner. AGS objects and
immutable retention refs created before a losing or interrupted CAS are
unpublished history. V1 retains them and previous successful commits and does
not provide history UI or garbage collection.

An AGS write failure, request cancellation, or database/audit failure leaves the
old MeshX pointer unchanged. A response may report success only after the CAS
and audit transaction commits. If that transaction commits but the HTTP
response is lost, a status read reveals the committed version; retrying import
is an explicit new overwrite, not an idempotent replay of the old request.

Dashboard readers never infer current from the insights default branch or a
retention ref. They read only the SHA stored on the current source revision.

## 8. API Contract

### Import

```text
POST /api/v1/workspaces/:workspaceId/kb/{kbId}/sources/{sourceId}/dashboard/import
Content-Type: multipart/form-data
file: <one archive>
```

The success response is `200` and contains `state=ready`, the immutable
Dashboard commit SHA, selected source commit SHA, manifest, and published
timestamp. Expected validation and CAS failures use stable codes:

- `dashboard-archive-unsupported` (`400`);
- `dashboard-archive-unsafe` (`400`);
- `dashboard-archive-layout-invalid` (`400`);
- `dashboard-archive-limit-exceeded` (`413`);
- `dashboard-data-invalid` (`422`) for invalid UTF-8 or JSON syntax;
- `dashboard-import-conflict` (`409`); and
- `dashboard-insights-unavailable` (`409` or `503`, depending on whether the
  mapping is absent or the dependency is temporarily unavailable).

### Status and manifest

```text
GET /api/v1/workspaces/:workspaceId/kb/{kbId}/sources/{sourceId}/dashboard
```

The bounded response is:

```ts
type UADashboardStatus = {
  state: "empty" | "ready" | "reimport_required"
  sourceId: string
  sourceCommitSha?: string
  dashboardSourceCommitSha?: string
  dashboardCommitSha?: string
  updateAvailable: boolean
  publishedAt?: string
  canImport: boolean
  acceptedFormats: ["zip", "tar.gz", "tgz"]
  limits: UADashboardImportLimits
  manifest?: { files: Array<{ path: string; bytes: number; blobSha: string }> }
}
```

The state-specific wire shape is:

- `empty`: includes `sourceId`, `sourceCommitSha` when available, import
  permission/formats/limits, `updateAvailable=false`, and no Dashboard source
  commit, Dashboard commit, publication timestamp, or manifest;
- `ready`: includes the current source commit, the Dashboard's source commit,
  Dashboard commit SHA, `updateAvailable`, `publishedAt`, and the canonical
  exact-five manifest;
- `reimport_required`: includes the current and Dashboard source commits,
  Dashboard commit SHA, `updateAvailable`, and
  `publishedAt` so the UI can explain and confirm replacement, but omits the
  legacy manifest entirely.

`files` contains exactly the five allowlisted paths in lexical order. MeshX
persists this bounded manifest with the publication
pointer in the same CAS transaction, so status does not reconstruct metadata
by reading AGS. An empty, ready, or re-import-required status read performs no AGS request or database write.

A stored publication created under an older contract is decoded only from its
bounded database manifest; status never scans revision rows or probes AGS. An
exact-five legacy publication remains `ready`. A well-formed older manifest
that lacks any required path or contains a retired path returns
`reimport_required`, preserves its immutable commit, and exposes no files until
an import-capable workspace owner/admin imports a canonical exact-five archive.
Malformed metadata, Dashboard pointer mismatches, duplicate paths, and
unknown paths remain `dashboard-publication-invalid` failures. A manifest
source commit that differs from the current source revision is not corruption:
it sets `updateAvailable=true` and remains readable. The Dashboard commit in
the manifest must still match the stored Dashboard pointer.

### File read

```text
GET /api/v1/workspaces/:workspaceId/kb/{kbId}/sources/{sourceId}/dashboard/files/{path}
```

`path` must be one exact allowlisted basename, with no `.ua/` prefix or nested
path. MeshX maps it to `.ua/{path}`, re-checks current source revision and read
authorization, verifies the file against the persisted current manifest, and
reads it from AGS at `dashboardCommitSha`. It never accepts a branch or
caller-selected ref. The response uses the declared JSON content type, a
private cache policy, and an ETag derived from the Dashboard commit and blob
SHA. Authorization and current-pointer resolution run before `If-None-Match`
can produce `304`.

When status is `reimport_required`, every file endpoint returns the stable
`dashboard-reimport-required` conflict code before AGS access. It never serves
or projects a legacy manifest as canonical.

First paint requests status/manifest and `knowledge-graph.json` only. The
required `domain-graph.json` is loaded on explicit Domain intent, so its
required publication presence does not add first-paint I/O.

## 9. Dashboard UI

The Dashboard tab retains the KB source selector. It renders:

- `empty`: import explanation, accepted formats/limits, and an import button for
  workspace owners/admins with `canImport=true`;
- `reimport_required`: the older publication is not rendered; import-capable
  workspace owners/admins see an explicit replacement-import action and other
  callers see upgrade guidance;
- `ready`: the Dashboard immediately, plus **Re-upload** for import-capable
  callers. When `updateAvailable=true`, the old Dashboard stays visible and a
  warning explains both source commits; import-capable callers also receive an
  **Upload update** action for an externally generated incremental archive; and
- import in flight: the old Dashboard remains usable while the dialog shows
  progress. Success replaces query data; failure leaves it unchanged.

The import dialog supports drag/drop and file selection, shows the effective
limits, and requires overwrite confirmation for `ready`. Browser-side checks
improve feedback but never replace server validation.

AgentTeam keeps one graph viewer. The existing `KnowledgeBaseGraphExplorer` and its
KB-wide `view=graph` behavior are removed or replaced by the source-scoped
Dashboard. The port is pinned to an immutable Understand Anything upstream
revision and records upstream source and MIT license provenance.

The ready-state header exposes exactly **Structural**, **Domain**, and **Start
Tour**. Structural derives one visible entity per `knowledge-graph.json.layers`
entry. It aggregates cross-layer edges while never rendering, naming, or
providing drill-down to member nodes, file paths, or source. Selecting a layer
opens its description plus deterministic metadata already present in the
primary graph: at most eight 320-code-point capabilities, twelve tags, and
eight adjacent-layer dependencies plus an overflow count. Multiple edge types
to one neighbor remain one dependency item with incoming, outgoing, or
bidirectional direction.

Domain is the only intent-loaded ready-state read and loads the required
`domain-graph.json` on explicit intent. Its default projection renders only domain nodes and valid
cross-domain relationships. Clicking one domain expands only its owned flows
and their steps in the same canvas; one domain may be expanded at a time, and
re-click or Escape collapses it. Pointer, Enter, and Space share the same node
intent, while flow/step activation never switches the expanded domain.
Overview and per-domain viewports use collision-free structured keys and
restore independently, while expansion never performs another read or infers
ownership from id prefixes. Domain loading, selection, viewport, and failure state remain
independent from Structural. Start Tour maps authored member ids to their
first owning visible layer, deduplicates them in authored order, skips unmapped
steps, and retains start, previous, next, and close behavior. Search, node-type
and complexity filters, layer filters, diff/config controls, source preview,
and member-node drill-down are not part of this UI.

The port reuses the Understand Anything Dashboard's ELK/layout worker and tour
interaction while deliberately excluding the standalone TokenGate, Vite/local
server, local fetch contract, theme shell, and i18n shell. Network loading is
injected through the authenticated MeshX Dashboard API. UI controls, spacing,
colors, dialogs, empty/loading/error states, focus behavior, and notifications
use AgentTeam primitives and tokens. New copy has matching `en`, `zh-CN`, and `ja`
keys.

Mobile uses a single-column graph with the ported narrow-screen drawer; no
important action may depend on hover.

## 10. Cost And Observability

Runtime cost is bounded by explicit user intent:

- status loads one source/current pointer and, only for `ready`, bounded
  allowlist metadata;
- each file request reads exactly one allowlisted file; and
- no Dashboard read enumerates KBs, unrelated sources, repository history,
  workspace members, all tree descendants, or `intermediate` data.

Successful imports append a KB audit event containing actor id, source id,
source commit, Dashboard commit, archive format, and accepted byte/file counts.
Logs and metrics may record the same bounded metadata and stable
error code, but not graph text, uploaded filenames outside the normalized
allowlist, source contents, raw archive paths, or high-cardinality node ids.

## 11. Required Verification

Implementation must cover:

- real UA fixtures plus syntactically valid arbitrary JSON values;
- both accepted layouts and all three formats;
- any required file missing, multiple/mixed roots, unknown files or
  directories, legacy root, invalid UTF-8, BOM, trailing JSON, excessive JSON
  depth, and valid but otherwise arbitrary JSON content;
- traversal, absolute/ambiguous/duplicate paths, encryption, links and special
  files, declared-size lies, compressed/extracted/file/entry/JSON limits, and
  cancellation cleanup;
- KB roles, absent/unusable insights mapping, cross-workspace/KB/source access,
  and authorization before ETag/AGS reads;
- tree replacement preserving non-`.ua` content, detached commit creation,
  retention ref creation, CAS concurrency, source-revision races, AGS/DB/audit
  failures, and old-publication visibility;
- current-row Dashboard-SHA-only status/file reads with no branch fallback,
  bounded manifest, retained publication visibility and update detection after
  a source advance, exact-five legacy readiness, explicit re-import state for
  older noncanonical publications, zero status AGS/write amplification, runtime
  graph-error containment, intent-only Domain loading, and domain-only overview
  plus one owned flow/step expansion for renderable inputs;
- one source-scoped viewer, removal of the old graph route/viewer, AgentTeam UI
  primitives, accessibility, mobile drawer behavior, and `en`/`zh-CN`/`ja`;
  and
- focused Go/Web tests, changed-file ESLint, typecheck, production build,
  `git diff --check`, canonical relative-link validation, and the fast aggregate
  harness when feasible.
