Skip to content

Knowledge state (projections)

Epic #262 Phase 4 (#266).

Knowledge State is a projection of the Knowledge Model, not a set of dashboards. Every number a user sees — debt, balance, discoveries, open questions, the heatmap, the evolution timeline — is computed by a pure function of the model, and the Experience views (Home, Health, Discovery, Graph) consume those projections rather than computing anything themselves.

Metrics are consequences, not inventions (the manifesto): a metric exists because a projection derives it from the model, never because a view invented it inline.

The State surface

All projections are exposed through one import surface — architecture/knowledge/state — so the Experience layer has a single door to "what the model says", and no view reaches into an individual analysis by a deep path. This is the §XI boundary made concrete: the surface is pure and Obsidian-free (guarded by test/architecture/knowledge/pure-is-obsidian-free.test.ts over state/), and a structural test (test/architecture/components/core/experienceUsesStateSurface.test.ts) enforces that components/core only imports from architecture/knowledge or architecture/knowledge/state.

Every projection follows one contract:

export type StateProjection<Params extends unknown[] = [], Result = unknown> =
    (model: KnowledgeModel, ...params: Params) => Result;

The projection inventory

Projection Result Consumed by
buildHome HomeModel Home
computeKnowledgeDebt KnowledgeDebt Health
computeKnowledgeBalance KnowledgeBalance Health
buildKnowledgeDashboard DashboardModel Dashboard
findDiscoveries Discovery[] Discovery
openQuestions / proposeAnswers OpenQuestion[] / answers Open questions
buildEvidenceMap EvidenceMap Evidence map
buildKnowledgeMap KnowledgeMap Knowledge map
conceptNeighbors ConceptNeighbors Concept navigation
computeWeeklyReview WeeklyReview Review
buildHeatmapGrid HeatmapGrid Thinking heatmap
deriveOutline Outline Projects / synthesis
classifyHealth HealthResult Health (orphans / dead-ends, over the model's edges)
deriveRecommendations KnowledgeRecommendation[] Home / Health / Discovery (via #268)

The recommendation pipeline — Query → State → Recommendation → Command

Every metric proposes an action (the manifesto). The last leg of the pipeline is one primitive, KnowledgeRecommendation (#267, architecture/knowledge/state/recommendation.ts):

interface KnowledgeRecommendation {
    reason: RecommendationReason;      // closed why-union (add-source, connect, resolve-contradiction, …)
    target: string[];                  // the note path(s) it concerns; empty = vault-wide
    command: CommandActionId | null;   // the kind:"command" action that resolves it, or null (no built-in yet)
    priority: number;                  // urgency in [0,1]
}

deriveRecommendations(model) is a pure projection (offline, deterministic, §XI obsidian-free) that composes the debt / balance / discovery / question / state signals into one prioritized list.

One primitive unifies six vocabularies. Before #267 each surface invented its own "next-step" tokens — dashboard RecommendationToken, debt RemediationToken, balance BalanceSuggestion, review ReviewAction, home NextSession.reason, and the action-layer NextMoveToken. Pure from* mappers collapse every one of their cases onto a single RecommendationReason, proven by a coverage test — so the whole system speaks one recommendation language.

command is a declarative pointer, not an invocation: it names a kind:"command" action id (Phase 3) — add-source → attach-source, connect → create-semantic-relation — or null when no built-in command applies yet. Actually running a recommendation's command from a view (via the

264 KnowledgeContext), and rendering recommendation widgets on Home/Health/Discovery, is deferred

to the view-collapse phase (#268). The dashboard keeps its current per-panel presentation unchanged; Phase 5 delivers the primitive + the unification, not new displayed output.

Health derives from the model (#274)

classifyHealth(model) classifies each idea over the model's typed edges — an orphan has no outgoing edge, a dead-end has no incoming edge (self-edges excluded). Because the model's edges include semantic relations (up::, supports, inline key:: [[X]]) on top of raw wikilinks, a note connected only semantically is no longer a false orphan/dead-end. This is an intended number change vs the earlier raw-resolvedLinks classifier: counts drop for vaults that use relations, and are identical for pure-wikilink vaults.