Product architecture map + gap ledger

Mainark observes a product’s architecture; it does not own it.

ConcernSource of truth
Code dependenciesNx project graph (posted in the snapshot)
Architectural intentProduct repo .mainark/architecture.yaml (parsed into manifest)
Engineering standardsProduct docs (e.g. Fulreach .ai/fulreach-standards.md) — rules in Mainark mirror them
Detected gapsMainark, derived on ingest
Engineering workMainark issues (via idempotent open-task)

Ingest ordering

Snapshots carry ingestSequence (monotonic integer from CI or the exporter). Git SHAs are not used for ordering.

On POST /api/architecture/snapshot:

  1. Validate the full payload (reject with 400 — no partial write).
  2. If ingestSequence is less than stored → 409 ignored_stale, DB unchanged.
  3. If equal sequence and same gitSha → 200 unchanged (idempotent re-POST).
  4. If equal sequence and different gitSha → 409 sequence_conflict, DB unchanged (CI must bump the sequence).
  5. Otherwise, in one transaction: upsert the snapshot, upsert gaps by stable id, mark missing gaps open = 0, preserve linkedIssueId on gaps that remain.

Stable gap id: full hex digest of sha256(product + ":" + ruleId + ":" + subject). Dependency-rule subjects use the full Nx project ids (@fulreach/web->@fulreach/api), not bare basenames.

Gap granularity

Standards rules that inspect dependencies use Nx project → project edges only. Intra-project module violations are out of scope for phase 1–2.

Open Task

Orchestrator: POST /api/architecture/gaps/:id/open-task (bearer MAINARK_API_TOKEN) creates or returns the linked issue in one shot.

Web UI: call same-origin POST /api/architecture/gaps/:id/open-task (Next.js route). That route checks the session cookie and proxies with the server-only token — never put MAINARK_API_TOKEN in next.config env (it would ship to the browser). The Next server must be able to reach MAINARK_API_URL (local dev, or a reachable API URL — not Tailscale-only from Vercel).

UI

Authenticated /architecture is a single grouped explorer + inspector over the same snapshot.

Architecture is the map. Issues are the work. Agents execute the work using context from the map.

PaneRole
Left explorerAlways visible (desktop). Three-level tree: PRODUCT / IMPLEMENTATION (section headers, no counts) → collapsible categories (Product Areas, Workflows, Shared Capabilities, Services, Nx Projects) → selectable entities. Default: Product Areas open; other categories collapsed. Empty Shared Capabilities hidden. Selecting an entity (including via relationships / deep links) auto-expands its category and scrolls the row into view when needed. Client-side search filters entities and opens matching categories.
Right workspaceArchitecture overview (no selection), entity inspector, findings panel, or Build graph (Nx substrate) with a shared selection side panel
HeaderSnapshot status · open-gaps badge (opens findings) · List | Build graph view control

Entities

EntityManifest fieldRole
DomaindomainsWhat the software implements
ComponentservicesWhere it runs / deploys
WorkflowworkflowsCross-cutting flows (authored links)
Build graphnxProject→project substrate only — not deployable services

Findings (gaps) are not explorer entities. They open from the open-gaps badge into a findings panel (filters for open / resolved / unassigned).

Selection and URL

Every architecture entity is independently selectable. Relationships navigate by selecting the destination — no Product/Components page toggle.

URL: ?entity=<type>:<id> (e.g. domain:campaigns, project:@fulreach/api). Optional view=graph, panel=findings. Browser Back/Forward restores selection. Unknown or removed ids show an unavailable state with Back to Architecture (do not silently clear). Search and group expansion do not write the URL. Relationship clicks preserve explorer scroll position.

Default (no entity): lightweight Architecture overview (counts, open findings, last synced, short needs-attention list).

domain.kind (optional)

  • product (default when omitted) — Product Areas (e.g. Campaigns, Inbox, Billing)
  • capability — Shared Capabilities (e.g. Infrastructure, Observability); first-class, not second-class

Product repos should mark shared platforms (e.g. Fulreach infra) as kind: capability so they leave Product Areas. That YAML change is separate from this UI.

Domain title convention (UI)

Prefer a short navigation label. If title contains " / " (space-slash-space), the Architecture UI uses the part before as the explorer label and the part after as Scope in the inspector — e.g. Sequences / funnel sends → nav Sequences, inspector Funnel sends · Scope. Scope is a short focus phrase for the area (not a workflow name and not a technical id). Avoid " / " inside a title unless you intend that split. Longer prose belongs in a future dedicated description field, not the title.

Domain ↔ service associations

UI cross-links use manifest nxProjects overlap only (empty ingested Nx graph does not clear associations).

Each link is:

  • relation: "associated_with" — inferred; not proof of implementation
  • evidence: "nx_project_overlap"
  • matchedProjects: string[]

Future explicit ownership can use relation: "implemented_by" without replacing this model. Inspector rows show provenance as via <matchedProjects> (full explanation in the tooltip). Gap badges / open counts on explorer rows appear only when open count > 0.

Gap open-counts still use direct evidence only (domain= / workflow=), not these associations.

Inspector (context-first)

  1. Identity — type, title, owner/description
  2. Relationships — typed, clickable, with provenance
  3. Implementation — code paths (full paths)
  4. Engineering health — findings for this entity
  5. Actions — Open task beside a specific finding only (no generic “Dispatch AI” on entities)

Future agent context (not this surface)

When assembling agent context from the map, distinguish:

TierMeaning
Explicit scopeEntities directly attached to the issue
Related contextAuthored relationships (e.g. workflow domainIds / serviceIds)
Inferred contextCandidates from weak evidence (associated_with / nx overlap)

Agents may inspect beyond suggested paths. Inferred edges never imply implementation ownership. Architecture-aware issues and agent dispatch are later stages; issues remain the unit of work.

CI reachability

GitHub-hosted runners cannot reach a Tailscale-only Mainark API. Upload from a self-hosted runner on the tailnet (or Tailscale’s GitHub Action), after Mainark’s API is live.

Related HTTP routes

See HTTP API — Architecture section.