Logo FS-Skia-UI

Governance System Comprehensive Analysis

Executive Summary

The governance system is best understood as an agent operating contract for a repository whose changes often affect generated products, public package surfaces, evidence claims, local skills, and Spec Kit artifacts. It is not mainly a user-facing documentation system. Its job is to turn an agent's free-form development work into deterministic routing, typed gates, durable readiness artifacts, failure ownership, and machine-checkable claims.

The current architecture is heavy but coherent. The decisive design move has already happened: the old script/Python/Bash governance center was moved into compiled F# under build/Governance/**, with fake.sh delegating to a compiled build/Build.fsproj executable. Targets, route selection, evidence graph/audit, generated guidance checks, skill synchronization, package-surface checks, template validation, preflight checks, generated-product validation, publishing checks, and generated-product evidence execution now live mostly as typed F# modules.

The best parts are:

The main risks are:

The recommendation is not a rewrite. The system should continue moving mechanical governance into F#, but with a sharper boundary: F# should own identities, facts, rules, validators, generators, reports, route scopes, concurrency locks, and provenance; Markdown should remain for agent judgement, examples, specs, plans, tasks, and human-readable evidence. The next high-value step is a small F# governance knowledge system: a typed facts-and-rules layer that can answer "what changed?", "which gates?", "why?", "which artifacts?", "which skills?", "what is stale?", "is another governance run active?", and "what should the agent do next?" as structured data and rendered Markdown.

What The Governance System Is

The governance system is a layered contract across six domains:

  1. Constitutional policy: .specify/memory/constitution.md defines the repository's principles: Spec -> FSI -> tests -> implementation, .fsi visibility, simplicity, MVU/effect boundaries, synthetic-evidence disclosure, mandatory test evidence, and observable safe failure.
  2. Spec Kit authoring artifacts: spec.md, plan.md, tasks.md, tasks.deps.yml, and readiness/** translate those principles into per-feature obligations.
  3. Agent skills: .agents/skills/**, .claude/skills/**, src/*/skill/SKILL.md, and template/fragments/*/skill/SKILL.md describe specialized work instructions. Skill loading is part of implementation readiness, not optional reading.
  4. Compiled build/governance engine: FS.Skia.UI.Build under build/Governance/** owns targets, routing, validation, evidence graph/audit, generated products, skill checks, preflight, publishing checks, and single-source generation.
  5. Generated product contract: template/**, template/capabilities.yml, generated API-surface docs, generated product checks, and template/base/build.fsx govern what consumers receive from dotnet new fs-skia-ui.
  6. Reviewable evidence: active feature readiness files record logs, task graphs, audit output, preflight diagnostics, generated product validation, FSI transcripts, package surfaces, template checks, and focused gate summaries.

For agents, this is a deterministic shell around nondeterministic work. The agent may choose how to implement, but it must prove completion through typed gates and durable artifacts.

How It Works

Entry Point: Compiled FAKE Front-End

The root fake.sh runs:

dotnet run --project build/Build.fsproj -- "$@"

build/Program.fs registers FAKE targets from Targets.dispatchTargets, wires dependency edges from Targets.targetDependencyRows, and delegates each target body to Engine.Interpret.runTarget.

The important design choice is that target identity is a closed F# union:

This makes a mistyped target in the source policy a compile error. It also gives target metadata one canonical model.

The Route Selector

The operational rule in AGENTS.md is correct: run ./fake.sh build -t Route before validation, then run only the gates it prints.

Route works as follows:

  1. The interpreter edge reads git state: branch-vs-main/master merge-base diff plus uncommitted/untracked working tree paths.
  2. Routing.selectForFeature receives a pure Diff.
  3. Compiled Routing.rules match path globs and select the highest applicable tier.
  4. Required gates are de-duplicated in Targets.allTargets registry order.
  5. The route report prints developer-class, tier, gates, dogfood-forced, and matched-rules.
  6. --enforce additionally checks whether the selected rule's expected artifact paths exist.

Current tiers include inner-loop, focused-authority, agent-ready, maintainer-verify, and automation-final.

The rule set currently includes:

validation.contract.yml is a generated compatibility view rendered by ContractView.render from Routing.rules, not an independent policy file.

The Target Graph

The target graph mixes fast inner-loop work, focused proof, generated product checks, and broad aggregates.

Key targets:

The target bodies are modeled through a local MVU/effect algebra:

This is a strong use of the repository constitution's Principle IV. The build workflow is stateful and I/O-heavy, so it is expressed as data and interpreted at the edge.

Evidence Graph And Audit

build/Governance/Evidence/** is the most important formal subsystem.

Inputs are gathered at the interpreter edge:

The pure engine then:

This belongs in F#. The semantics are mechanical, review-blocking, and hard to express safely in prose.

Skill Governance

Skills are not advisory-only. Task generation must write structured skillist metadata in tasks.deps.yml; tasks.md must mirror it visibly. Implementation must load the listed skills before code changes.

The skill system has several layers:

This is heavy but directly addresses a real agent failure mode: local specialized guidance is easy to skip unless task artifacts force it.

Generated Product Governance

The generated product is treated as a consumer contract.

template/capabilities.yml maps capability ids to:

Capabilities validates the catalog. ApiSurfaceGen derives the generated template/base/docs/api-surface/** tree from the catalog's contracts: entries. GeneratedProduct packs, installs, instantiates, scans, and validates the generated product matrix.

Generated products do not copy the full governance implementation. Their template/base/build.fsx resolves the single <FsSkiaUiVersion> from Directory.Packages.props, reflection-loads the matching FS.Skia.UI.Build assembly, and invokes Evidence.GeneratedRunner.run. That keeps consumer evidence behavior aligned with the package version without copying Python or F# source into generated products.

Single-Source Generation And Currency

This repository repeatedly uses the same pattern:

canonical source -> generated view -> currency check

Examples:

This is stronger than drift comparison alone. It reduces "two sources of truth" into one source plus rendered views.

Inventory

Main Source Homes

Area

Source

Target identity and dependency graph

build/Governance/Targets.fsi, Targets.fs

Route selection

build/Governance/Routing.fsi, Routing.fs

Generated validation contract

build/Governance/ContractView.fs, validation.contract.yml

Build model/update/interpret

build/Governance/Engine/**, build/Governance/Front/**

Evidence graph/audit

build/Governance/Evidence/**

Capability catalog

build/Governance/Capabilities.*, template/capabilities.yml

Generated product validation

build/Governance/GeneratedProduct.*, GeneratedProductContract.*

Generated product evidence runner

build/Governance/Evidence/GeneratedRunner.*, template/base/build.fsx

Skill sync/quality/path checks

SkillTreeGen.*, SkillSync.*, SkillQuality.*, SkillContractPath.*

Generated guidance

Guidance.*

Target metadata

TargetMetadata.*, Front/Helpers.fs

Package surface diff

PerPackageSurface.*

Publish/pre-publish

Publish.*, PrePublish.*

Governed prose and constitution

GovernedBlocks.fs, ConstitutionFragments.*

Skill helper library

src/SkillSupport/**

Current Size

Measured from tracked files on 2026-06-05:

These numbers are large enough that "governance as text" cannot be trusted by attention alone. The typed gates are necessary.

Pros

Determinism For Agent Work

The strongest argument for the system is that it does not ask a model to remember the rules. It asks a model to run route-selected gates. That shifts compliance from memory to execution.

Minimal Validation Instead Of Always-Verify

Route lets routine internal work run the light path while escalating contract changes. This keeps the system usable. Without Route, the governance burden would incentivize skipping validation.

Compile-Time Gate Identity

Targets.Target and Routing.RoutingRule.RequiredGates: Targets.Target list are materially better than stringly typed YAML. A bad gate name fails at compile time in the source of truth.

Pure Core, I/O Edge

The build engine, evidence engine, route selector, publish checks, surface diff, skill sync, and generated-guidance evaluators all try to keep deterministic logic pure and push filesystem/git/process reads to the edge. That makes the rules testable and explainable.

Durable Readiness Memory

Feature readiness artifacts give reviewers a stable audit trail. This matters more for agent work than human-only development because the chat transcript is not a reliable system of record.

Single-Source Generation

Generated views reduce drift across agents, templates, contracts, docs, and generated consumers. The controls catalog generation added after the prior report shows the pattern is spreading into product-facing metadata too.

Failure Ownership

Target metadata and focused gate contracts carry failure ownership and category. Preflight and bootstrap checks help separate environment failures from product failures.

Generated Consumer Alignment

Generated products use the packaged build engine for evidence graph/audit. That keeps consumer validation from becoming a stale fork of repository validation.

Explicit Synthetic-Evidence Policy

Synthetic evidence is not merely discouraged; it is modeled, disclosed, propagated, and audited. The narrow [SEH] exception is structured and still visible.

Cons And Risks

Active Guidance Is Still Too Large

The active Markdown guidance corpus is large enough to hide contradictions. This is not fatal because many mechanical checks are in F#, but it is still costly for agents. The governance system should keep reducing prose that exists only to encode machine-checkable policy.

Stale Or Ambiguous Root build.fsx References Remain

The root build.fsx is gone, but active files still mention it. Some references are historical or legitimate for generated products (template/base/build.fsx). Others are active prompts or route rules that still describe root build-governance paths in old terms.

The most important current issue is routing: build-target-contract names build.fsx and scripts/build/**, while the actual repository governance code lives in build/Governance/** and build/Program.fs. Unknown governance-path edits default-deny to broad Verify, so this is safe, but it is not explicit enough.

Route Output Is Not Structured Enough

The plain text is readable:

developer-class=framework-author
tier=focused-authority
gates=Dev, EvidenceGraph

But the system is mature enough to need:

That would let agents and future tools consume the route without parsing prose.

--enforce Is Presence-Oriented

Route --enforce checks whether expected artifact paths exist. It does not prove they were generated for the current diff, current commit, current gate set, or current feature state.

This is acceptable as a low-cost guard, but it should be documented precisely. A future provenance extension could store route selection, git SHA, command, exit code, started/finished timestamps, and input hash in each readiness artifact.

Active Feature Coupling

Many gates write to the feature from .specify/feature.json. That is right for Spec Kit work, but it can surprise ad hoc report/documentation work. A docs-only report can route to EvidenceGraph, which validates the active feature's tasks, not the report itself.

Possible fix: let focused gates accept --feature <path> or let Route emit the feature it will validate.

Whole-Workspace Routing Is Too Coarse For Report Authoring

The observed shortcoming is that Route intentionally reasons over the union of:

That is a good model for merge-readiness. It answers "what must this worktree prove before it is safe to land?" It is a poor model for "what should I run after adding one report file?"

In a dirty feature workspace, a report-only change can inherit unrelated product, package-surface, controls, template, or Spec Kit changes made by another agent. The route can then escalate from a lightweight documentation check into Dev, package-surface checks, generated-product checks, controls catalog checks, EvidenceGraph, and EvidenceAudit. If those gates fail, the failure may be caused by the in-progress feature, not by the report. The agent then burns time validating and debugging work it did not touch.

The issue is not that the route selector is wrong. It is answering a different question than the report author needs. The system currently lacks a first-class distinction between:

For report creation, the proper local check should usually be a scoped docs/report check: Markdown structure, frontmatter, broken local links when cheap, retired active-instruction terms when relevant, and maybe a route explanation artifact. It should not automatically validate the active feature task graph unless the report edits active Spec Kit artifacts or readiness evidence.

Concrete improvements:

This would make the governance system less burdensome without weakening merge safety. Agents could do quick local validation for their current artifact, then run full route-selected gates only when the branch is ready or when explicitly asked.

AgentValidation Has A Parallel Selector

AgentValidation.ValidationSelection.selectRules parses validation.contract.yml and performs simplified pattern matching. The authoritative selector is Routing.select. Because validation.contract.yml is generated from Routing.fs, this is mostly a compatibility layer, but it is still a second implementation of selection semantics.

If external agents need the public AgentValidation API, consider making it call the same compiled routing facts directly or generating a richer structured rule model rather than re-parsing a YAML compatibility view.

Heuristic Scanners Are Still Present

Some checks necessarily scan Markdown. The risk is when a human-prose heuristic becomes a hard gate:

These can be pragmatic, but they should stay small, tested, and easy to explain. When a scanner starts modeling structured intent, move the intent into structured data.

Public Surface Of FS.Skia.UI.Build

The governance library is packable because generated products consume the evidence engine. That is useful, but it means .fsi files are public-looking. There should be a clear split between:

Large .fsi surfaces such as AgentValidation.fsi should be reviewed with that in mind.

FAKE State Serialization Is A Persistent Footgun

The docs correctly say FAKE-backed commands share .fake state and must not run concurrently. This is necessary but operationally brittle for agents, which otherwise parallelize aggressively. The route output could help by printing a serialized command plan and marking any non-FAKE checks as parallel-safe.

Concurrent Governance Runs In One Worktree Are Unsafe

There are real thread-safety concerns when another agent is running Spec Kit implementation or governance gates in the same branch/worktree. This is not limited to theoretical deadlocks. The more likely problems are races, stale reads, interleaved writes, build-server contention, and nondeterministic failures, but hangs are also plausible when multiple build/test processes contend over shared state.

The shared resources include:

If two agents run gates such as Dev, PackageSurfaceCheck, PerPackageSurfaceDiff, TemplateCheck, GeneratedProductCheck, FsiTranscripts, EvidenceGraph, or EvidenceAudit at the same time, either run can observe a moving workspace or partially written artifacts from the other. A failure can then be falsely attributed to the current task. In the worst case, one process can clean, overwrite, regenerate, or shut down build-server state while the other process expects it to exist.

During this report update, a non-governance process inspection found another agent already running a FAKE-backed package-surface gate in this worktree. That is exactly the scenario in which this report should not start any more governance gate activity.

Recommended controls:

The immediate operational rule is simple: if another agent is running Spec Kit implementation or route-selected gates in this worktree, do not start governance gates. Continue with read-only investigation or use a separate worktree.

Generated Product Reflection Is Clever

The generated build.fsx avoids a second version literal by reading <FsSkiaUiVersion> and reflection-loading the matching build engine. That solves a real pin-drift problem, but it is complex and can fail through global package cache or assembly-resolution issues. This is probably the right tradeoff for now, but it should remain heavily tested.

Alternatives

Keep Current Architecture And Harden It

This is the recommended path.

Pros:

Cons:

Full Rewrite

A new governance tool or new Spec Kit fork could be cleaner on paper.

Pros:

Cons:

Verdict: not warranted. The existing system has already completed the major rewrite from scripts to compiled F#.

YAML/JSON Policy Files

Move more policy to structured data and interpret it.

Pros:

Cons:

Verdict: good for feature-authored instance data; not ideal for framework-owned policy that can be compiled.

CI-Only Governance

Make GitHub Actions the main enforcement point.

Pros:

Cons:

Verdict: CI should run the same gates, not replace local route-selected governance.

External Policy Engine

Use OPA/Rego, Datalog, Prolog, Nix, Bazel/Starlark, or another rule engine.

Pros:

Cons:

Verdict: not a good fit now. If the system needs Datalog-like inference, implement a small F# facts/rules layer first.

Markdown-Only Agent Prompting

Shrink the system to instructions and trust agents.

Pros:

Cons:

Verdict: reject.

Could More Become F#?

Yes, but the right question is not "can prose be moved into F#?" It is "which decisions are facts and rules that agents should query rather than remember?"

What Should Move Further Into F#

Move these further into F#:

What Should Stay Markdown

Keep these in Markdown:

F# can generate headers, tables, inventories, and required sections, but it should not become a dumping ground for long string-literal manuals.

A Practical F# Knowledge System

The repository already has pieces of a knowledge system. The next step is to make that explicit.

Proposed Model

Define a central governance facts model:

type GovernanceFact =
    | ChangedPath of string
    | ActiveFeature of string
    | Target of target: Targets.Target
    | RouteRule of id: string
    | RequiredGate of ruleId: string * target: Targets.Target
    | ExpectedArtifact of ruleId: string * artifactId: string
    | Skill of id: string * path: string * source: SkillSource
    | Capability of id: string * packageId: string option
    | Task of id: string * status: DeclaredStatus
    | TaskDependsOn of taskId: string * dependencyId: string
    | TaskSkill of taskId: string * skillId: string
    | TaskOwnsEvidence of taskId: string * evidence: EvidenceClass
    | ArtifactProducedBy of artifactId: string * target: Targets.Target
    | ArtifactObserved of artifactId: string * path: string * provenance: ArtifactProvenance

Then define rules as pure functions:

type GovernanceConclusion =
    | SelectedTier of Routing.Tier
    | SelectedGate of Targets.Target * reason: string
    | MissingArtifact of artifactId: string * reason: string
    | StaleArtifact of artifactId: string * reason: string
    | MissingSkill of taskId: string * skillId: string
    | SuggestedSkill of taskId: string * skillId: string * confidence: string
    | FailureOwner of owner: string * reason: string
    | NextAction of command: string * reason: string

This does not need a general-purpose Prolog. A small, typed rule engine can be enough:

Performance Benefits Of A Pure F# Expert System

A pure F# expert system could bring meaningful performance benefits, but mainly by avoiding unnecessary work rather than by making expensive proof work magically cheap.

The current governance path often answers planning questions by starting FAKE targets or by re-running target logic that performs filesystem walks, git reads, Markdown parsing, YAML parsing, package-surface discovery, skill discovery, and readiness scans. Many of those operations are repeated across targets. A pure facts/rules layer could gather a single immutable GovernanceSnapshot, then answer multiple questions from that snapshot:

That can be fast because the heavy work becomes one bounded scan and parse pass, followed by in-memory pure evaluation. The likely savings are:

This is especially relevant for agents. A human can often decide "this is just a report" from context. An agent follows the written contract and may escalate to branch-wide validation unless the system gives it a structured, scoped answer. A fast F# expert layer could return that answer in milliseconds or low seconds, while reserving heavy FAKE-backed gates for proof.

The performance boundary is important:

The best implementation shape is a compiled, pure GovernanceSnapshot plus typed rule functions. Expensive effects remain at the edge:

type GovernanceSnapshot =
    { Git: GitFacts
      ActiveFeature: FeatureFacts option
      ChangedPaths: ChangedPath list
      Targets: TargetFacts list
      RouteRules: RouteRuleFacts list
      Skills: SkillFacts list
      Capabilities: CapabilityFacts list
      Artifacts: ArtifactFacts list
      ConcurrentRuns: ConcurrentRunFacts list }

type GovernanceQuery =
    | ExplainRoute of scope: RouteScope
    | ExplainArtifacts of scope: RouteScope
    | ExplainSkills of feature: string option
    | ExplainConcurrency
    | ExplainNextActions of scope: RouteScope

The interpreter would gather facts once. The pure engine would answer queries repeatedly. Renderers would emit JSON and Markdown. FAKE targets would still execute the expensive proofs, but agents would be less likely to start them unnecessarily.

Agent-Facing Commands

Useful commands could include:

Possible JSON shape:

{
  "feature": "067-keyed-reconciliation",
  "changed_paths": ["src/Controls/Reconcile.fs"],
  "tier": "focused-authority",
  "matched_rules": [
    {
      "id": "controls-public-surface",
      "matched_paths": ["src/Controls/Reconcile.fs"],
      "required_gates": ["ControlsCatalogCheck", "PackageSurfaceCheck"],
      "expected_artifacts": ["readiness/typed-controls-front-door.md"]
    }
  ],
  "gate_plan": [
    {
      "target": "ControlsCatalogCheck",
      "command": "./fake.sh build -t ControlsCatalogCheck",
      "parallel_safe": false,
      "why": "controls-public-surface"
    }
  ]
}

Why This Helps Agents

An agent needs answers more than prose:

A typed knowledge system gives those answers without asking the model to infer them from hundreds of lines of instructions.

Risks Of Overdoing It

Do not build a giant expert system that tries to judge everything. The bad version would:

The good version is modest: typed facts, typed conclusions, provenance, JSON/Markdown rendering, and reuse of existing validators.

Recommended Roadmap

Near Term

  1. Add explicit route coverage for current governance paths. Include build/Governance/**, build/Program.fs, build/Build.fsproj, and wrapper/tooling paths under a governance/build-target rule. Keep template/base/build.fsx under distribution/template rules.
  2. Add Route --json. Render selection, changed paths, matched rules, gates, expected artifacts, dogfood status, and developer class.
  3. Add route scope and intent. Support path-scoped authoring decisions such as Route --paths ..., Route --staged, and Route --intent docs-report, while keeping whole-worktree merge validation available and conservative.
  4. Add a report/document authoring gate. A DocsReportCheck or MarkdownReportCheck should validate report files without starting product, package-surface, generated-product, or active-feature evidence gates.
  5. Add governance-run locking. Protect FAKE-backed targets with a worktree-level advisory lock and make read-only route/explain commands report active lock ownership.
  6. Write readiness/route-selection.json when Route --enforce runs. Include commit/merge-base, working-tree dirty indicator, route scope, and whether unrelated dirty paths contributed to escalation.
  7. Clean active stale guidance. Replace root build.fsx mentions in active prompts with compiled front-end wording, while preserving legitimate generated-product template/base/build.fsx references.
  8. Document enforce semantics. Be explicit that it is artifact-presence unless/until provenance is added.
  9. Unify AgentValidation selection with Routing. Avoid a simplified second selector where possible.

Medium Term

  1. Introduce an artifact registry. Each artifact should know producer target, expected path, route rule, and freshness fields.
  2. Add provenance to readiness artifacts. Record target, command, feature, git SHA, merge-base, start/end time, exit code, and selected route.
  3. Create an active guidance registry. Distinguish active instructions, generated views, historical reports, tests, and fixtures; apply retired-term scans only to active instruction homes.
  4. Typed skill facts. Generate a skill inventory JSON from the live registry and use it in task generation and skill checks.
  5. Shared GovernanceSnapshot. Gather git, route, skill, capability, artifact, active-feature, and concurrency facts once per command and feed route/explain/check logic from that snapshot.
  6. Public/internal split for FS.Skia.UI.Build. Keep generated-product APIs stable and mark repository-internal APIs more deliberately.

Long Term

  1. Governance knowledge base. Gather facts from route, targets, capabilities, skills, tasks, artifacts, and readiness. Produce explanations and next actions.
  2. External tool integration. Let CI and agents consume the same JSON route/evidence outputs.
  3. Policy explainability. Every hard failure should be able to answer: rule id, fact matched, expected state, actual state, owning target, remediation command.

Final Recommendation

Keep the system. It is heavy because it is doing real work for agents: route selection, evidence validation, generated-product proof, public-surface safety, skill loading, synthetic-evidence policing, and failure ownership. Replacing it with lighter prose would make it easier to read and easier to lie to.

The correct refinement is to make the heavy parts more structured:

The governance system is already closer to a useful expert system than to a docs folder. The next step is to admit that and give agents a queryable F# facts/rules layer with explainable outputs, scoped validation, concurrency awareness, and fast authoring checks, while resisting the temptation to move all natural-language guidance into code.

Multiple items
val string: value: 'T -> string

--------------------
type string = System.String
val id: x: 'T -> 'T
type 'T option = Option<'T>
type 'T list = List<'T>

Type something to start searching.