Governance Kernel Extraction Implementation Plan
- Timestamp: 2026-06-06T10:55:00+02:00
- Author: Codex
- Status: Implementation plan, not implemented
- Audience: Maintainers and agents working on the FS.Skia.UI governance system
- Related analysis:
docs/reports/2026-06-05-2237-governance-system-comprehensive-analysis.md
Executive Summary
The governance system should be split into a dedicated F# governance kernel project,
consumed by the current FS.Skia.UI.Build package and FAKE front-end. The split should
start inside this repository, not as an external repository. The goal is not to create a
generic policy-engine product on day one. The goal is to make the repository's governance
facts, rules, path classifiers, route decisions, artifact expectations, and explanations
explicitly testable outside the already complex build interpreter.
The recommended shape is:
|
The implementation should be staged as an assembly extraction first. Namespace cleanup,
public package positioning, generalized rule DSLs, and external repository extraction are
later decisions. The first successful milestone is boring: Route, generated validation
contract rendering, evidence graph/audit outputs, and generated-product evidence behavior
remain byte-compatible while the pure logic compiles and tests in a smaller project.
After that boundary is stable, the kernel should grow into a small typed fixed-point governance engine: supplied facts in, derived conclusions out, and every conclusion explained by rule provenance. This is the useful expert-system shape for agent collaboration: the agent can ask which actions are allowed, which gates or artifacts are required, why something is blocked, and what next action is justified without the kernel running FAKE, git, filesystem scans, or product validation itself.
Problem Statement
FS.Skia.UI.Build currently carries several responsibilities in one packable project:
- target identity and target metadata;
- route selection;
- generated validation contract rendering;
- evidence graph and audit algorithms;
- skill discovery and validation models;
- capability catalog validation;
- generated product validation;
- package surface checks;
- generated guidance checks;
- preflight and process health checks;
- publish/pre-publish checks;
- FAKE target interpretation;
- filesystem, git, process, and report-writing effects.
Many modules already follow a pure-core / I/O-edge style, but the project boundary does not make that separation visible. This makes the governance logic harder to review because a reader must mentally distinguish pure policy from build orchestration while navigating a large build package.
The current structure also makes tests less focused. tests/Governance.Tests validates a
wide mix of pure algorithms, generated-product behavior, process diagnostics, docs
guidance, and integration fixtures. That is useful for end-to-end confidence, but it is
not ideal for evaluating a governance knowledge system on its own terms.
Decision
Create a separate local F# project named FS.Skia.UI.Governance.Core for the pure
governance kernel. FS.Skia.UI.Build will reference it and keep the current build
front-end and generated-product facade. The new project should be packable if it becomes
a runtime dependency of the packable FS.Skia.UI.Build package.
This is intentionally a local solution split first:
- one repository;
- one branch;
- one version line;
- one CI/build route;
- no cross-repository release choreography;
- no promise yet that the kernel is a reusable product outside FS.Skia.UI.
The implementation should preserve existing behavior first, then introduce clearer knowledge-system APIs such as route explanations and JSON outputs.
Design Principles
1. Extract the Pure Kernel, Not the Build Front-End
Move logic that is deterministic over supplied data:
- target identity and metadata;
- path classification;
- route selection;
- generated validation contract views;
- route explanations;
- artifact expectation and provenance models;
- evidence graph/audit algorithms;
- skill registry facts over supplied file lists/text;
- capability catalog validation over supplied YAML/text;
- generated guidance checks over supplied documents;
- package-surface comparison over supplied snapshots.
Keep effectful work in FS.Skia.UI.Build:
- git discovery;
- filesystem walking and file reads;
- process execution;
dotnetcommands;- FAKE target registration and execution;
- package packing and template installation;
- generated product instantiation and smoke execution;
- report and readiness file writes;
- publish/pre-publish process interactions;
- concurrency locks.
2. Prefer Typed Fixed-Point F# Rules Over an External Rule Engine
The kernel should use ordinary F# modules, records, discriminated unions, active patterns, and pure functions. It should not start as a generalized Datalog, Prolog, OPA/Rego, CLIPS, NRules, or custom untyped rules DSL.
The useful rule-engine shape is still worth adopting, but as typed F#:
- collect supplied facts from the build edge;
- derive new facts and conclusions to a deterministic fixed point;
- keep monotonic derivations for route, artifact, evidence, and agent-planning decisions;
- attach provenance to every derived fact and conclusion;
- expose explainable outputs rather than only pass/fail booleans.
Good:
type GovernanceFact =
| ChangedPath of string
| ActiveFeature of string
| RouteRule of RouteRuleFacts
| ExpectedArtifact of ArtifactFacts
| Skill of SkillFacts
type GovernanceConclusion =
| SelectedGate of target: Target * reason: string
| MissingArtifact of artifactId: string * reason: string
| StaleArtifact of artifactId: string * reason: string
| BlocksAction of action: string * reason: string
| NextAction of command: string * reason: string
Risky:
type Rule =
{ Name: string
When: obj list -> bool
Then: obj list -> obj list }
The second version hides too much behind untyped plumbing and makes the system harder to debug than the current direct code.
An eventual dependency on an external engine is only justified if the typed F# kernel first proves a clear semantic boundary and then hits a real scalability or authoring problem. Until then, direct F# is easier to review, easier to test, and safer for generated-product compatibility.
3. Use Active Patterns Where They Clarify Classification
Active patterns and partial active patterns are a good fit for path and artifact classification. They should make policy read like policy:
let (|PublicFsiSurface|_|) path = ...
let (|TemplateContractPath|_|) path = ...
let (|GovernanceImplementationPath|_|) path = ...
let (|GeneratedGuidancePath|_|) path = ...
let (|HistoricalReport|ActiveGuidance|GeneratedView|) path = ...
let classifyChangedPath path =
match normalizeRepoPath path with
| PublicFsiSurface packageId -> PublicSurface packageId
| TemplateContractPath capability -> TemplateContract capability
| GovernanceImplementationPath area -> GovernanceImplementation area
| HistoricalReport -> Documentation Historical
| ActiveGuidance -> Documentation ActiveInstruction
| GeneratedView -> GeneratedView
| other -> UnknownPath other
They should not be used as ornament. If a record field or a simple helper is clearer, use that.
4. Keep Compatibility Facades Until the Split Is Proven
The safest first extraction keeps public module names stable where practical. That can
mean the new assembly initially contains modules under the existing FS.Skia.UI.Build
namespace, even though the assembly/package is named FS.Skia.UI.Governance.Core.
This avoids a high-churn namespace rewrite in the same change as the assembly split. A
later cleanup can introduce FS.Skia.UI.Governance namespaces plus compatibility wrappers
if that proves valuable.
5. Make Tests Smaller and More Direct
The new test project should prove the kernel without running FAKE targets or touching
the real working tree. Integration tests should stay in tests/Governance.Tests.
Core tests should answer questions like:
- Does this path classify as active guidance, generated view, template contract, or historical report?
- Does adding a changed path ever lower the selected tier?
- Are selected gates de-duplicated in target registry order?
- Does the generated contract match the route-rule table?
- Does a fixture task graph produce the expected synthetic propagation?
- Does a stale artifact conclusion identify the producer target and route rule?
- Does route explanation name the matched paths and rule ids?
Proposed Project Layout
New Core Project
|
This is the target shape, not the first commit shape. Extraction should proceed in dependency order and stop after each phase once parity tests pass.
Existing Build Project After Extraction
|
The exact final ownership of GeneratedProductContract, Guidance, PerPackageSurface,
and SkillistReference should be decided by dependency pressure. If a module is pure over
supplied data and useful for explanations, it belongs in Core. If it shells out, writes
files, instantiates templates, resolves packages, or depends on build model state, it
belongs in Build.
New Core Test Project
|
tests/Governance.Tests should remain, but it should gradually become the integration
layer around FS.Skia.UI.Build.
Packaging Decision
Because FS.Skia.UI.Build is packable and generated products consume it through a
reflected generated-product runner, a real project reference from FS.Skia.UI.Build to
FS.Skia.UI.Governance.Core changes the package dependency graph.
Recommended initial package stance:
-
FS.Skia.UI.Governance.Coreis packable from the first commit whereFS.Skia.UI.Builddepends on it. - It shares the repository version line with the rest of
FS.Skia.UI.*. FS.Skia.UI.Builddeclares a normalProjectReferenceto it.-
The packed
FS.Skia.UI.Build.nupkgshould carry a dependency onFS.Skia.UI.Governance.Corewith the same version. -
Generated products should still reference only
FS.Skia.UI.Build; NuGet should restore the core package transitively. -
GeneratedRunner.runremains inFS.Skia.UI.Buildso generatedtemplate/base/build.fsxdoes not need to learn a new reflection entry point.
This does introduce one more package. That cost is preferable to hiding the core assembly
inside the build package or relying on a non-packable project reference that may fail at
runtime when generated products restore only FS.Skia.UI.Build.
Acceptance tests must explicitly prove the generated-product reflection path still works.
Compile And Dependency Strategy
Core Dependencies
Start with the minimum existing dependency set:
FSharp.Corefrom central package management.YamlDotNetonly for pure parsers that already parse YAML from supplied strings.DiffPlexonly if the extracted pure package-surface comparison needs it.FS.Skia.UI.SkillSupportonly if extracted skill checks need shipped helper APIs.
Avoid dependencies on:
- FAKE packages;
System.Diagnostics.Processhelpers;dotnetcommand wrappers;- repository-local path discovery modules;
- Skia, Silk.NET, UI runtime packages;
- test-only packages.
Build Dependencies
FS.Skia.UI.Build keeps:
Fake.Core.Targetindirectly throughbuild/Build.fsproj, not through Core;- process and filesystem code;
- generated-product execution;
- publish/pre-publish code;
- report writing;
- route command-line parsing;
- concurrency lock acquisition.
F# Compile Order
The extraction should preserve explicit compile order. A practical initial core order is:
|
This list should be adjusted by actual dependencies during implementation. The important
constraint is that Front/*, Engine/Model, Engine/Update, and Engine/Interpret
should not leak into Core.
Feature Scope
In Scope
- Create
FS.Skia.UI.Governance.Coreas a local F# library project. - Create
Governance.Core.Testsas a fast unit/property test project. - Move pure governance modules from
FS.Skia.UI.Buildinto Core in phases. - Keep
FS.Skia.UI.Buildas the packable generated-product and FAKE-facing package. - Add or preserve facades where needed so current commands and generated products keep working.
- Introduce path classification types and active patterns for route and retired-term decisions.
- Add a typed route explanation model usable by future
Route --json. - Preserve current route text output and generated
validation.contract.ymlparity. - Preserve evidence graph/audit artifact parity for representative fixtures.
-
Prove the generated-product evidence runner still loads and runs from the packed
FS.Skia.UI.Buildpackage. - Add a typed fact/conclusion/provenance model after route and evidence parity are stable.
- Add pure agent-facing queries for action authorization, required evidence, blockers, and justified next actions.
Out Of Scope For The First Implementation
- Moving governance into a separate Git repository.
- Replacing FAKE.
- Replacing direct F# validators with a generic rule engine.
- Integrating NRules, CLIPS, OPA, Cedar, Souffle, Prolog, or Datalog as the first implementation of the kernel.
- Authoring governance rules as untyped
objpredicates or stringly runtime rules. - Renaming every public namespace to
FS.Skia.UI.Governance. - Changing generated-product
template/base/build.fsxreflection entry points. - Changing the route-selected gate policy except where explicit governance path coverage is already needed.
- Adding a full stale-artifact provenance system.
- Adding worktree-level governance locks, unless the implementation naturally touches the target entry point and can do it safely.
Implementation Phases
Phase 0: Baseline And Safety Check
Purpose: capture current behavior before moving anything.
Tasks:
-
Run
./fake.sh build -t Routeand follow only the printed gates for the actual branch before implementation starts. - Capture current
Routetext output for focused fixture diffs in tests. -
Capture current generated
validation.contract.ymloutput in existing contract-view tests. - Capture current evidence graph/audit golden outputs for representative fixtures.
- Capture generated-product evidence runner behavior through the existing generated product tests.
- Record current package dependency behavior for
FS.Skia.UI.Build.
Deliverables:
- No code movement yet.
- A short readiness note for baseline commands and results.
- A list of parity tests that must stay green throughout extraction.
Acceptance:
- Baseline tests are green before extraction begins.
- Any failing existing test is either fixed first or explicitly excluded from this feature with a documented reason.
Phase 1: Add Empty Core Project And Test Harness
Purpose: introduce the project boundary without moving policy.
Tasks:
- Add
build/Governance.Core/FS.Skia.UI.Governance.Core.fsproj. -
Add package metadata:
PackageId=FS.Skia.UI.Governance.CoreAssemblyName=FS.Skia.UI.Governance.CoreIsPackable=true- description = pure governance kernel consumed by
FS.Skia.UI.Build
- Add
build/Governance.Core/README.md. - Add
tests/Governance.Core.Tests/Governance.Core.Tests.fsproj. - Add both projects to
FS-Skia-UI.slnunder the existing build/test solution folders. - Add a trivial smoke module and test to prove project wiring.
- Add a
ProjectReferencefromtests/Governance.Core.Teststo the core project. - Do not reference Core from
FS.Skia.UI.Buildyet unless the smoke module is needed.
Deliverables:
- Empty but compiling core project.
- Empty but compiling test project.
- No behavior changes.
Acceptance:
- Solution builds.
- Core test smoke passes.
- No package dependency behavior changes yet.
Phase 2: Move Target Identity And Routing
Purpose: extract the highest-value pure policy while preserving route behavior.
Candidate moved modules:
FindingsTargetsRoutingContractView
New modules:
PathPatternsPathClassificationRouteExplain
Tasks:
- Move
Findings,Targets,Routing, andContractViewsource files to Core. - Keep their module names stable initially if that reduces churn.
- Add
ProjectReferencefromFS.Skia.UI.Buildto Core. - Remove moved compile includes from
FS.Skia.UI.Build.fsproj. - Update
tests/Governance.Teststo reference Core directly where needed. -
Move pure tests for targets, routing, and contract rendering into
tests/Governance.Core.Tests. - Keep integration tests for the
RouteFAKE target intests/Governance.Tests. -
Add path-classification tests for:
build/Governance/**build/Program.fsbuild/Build.fsproj- root
fake.sh template/base/build.fsxsrc/**/*.fsi.specify/**.agents/skills/**.claude/skills/**- historical
docs/reports/**
- Add route explanation types, but do not change the default
Routetext output.
Acceptance:
- Existing
Routeoutput is unchanged. validation.contract.ymlrendering is unchanged.- Core routing tests pass without filesystem or git access.
- Build front-end still compiles and dispatches typed targets.
Routecan still be run through./fake.sh.
Phase 3: Move Evidence Graph And Audit Core
Purpose: isolate the most formal governance subsystem in Core.
Candidate moved modules:
Evidence/EvidenceFormatSchemaEvidence/TaskParserEvidence/DepsParserEvidence/SkillRegistryEvidence/GraphEvidence/StatusRegionEvidence/ScansEvidence/DiffScanEvidence/AuditEvidence/RenderEvidence/Engine
Tasks:
- Move evidence modules to Core in compile order.
- Keep
Evidence/GeneratedRunnerinFS.Skia.UI.Buildas a stable facade. - Update
GeneratedRunnerto callFS.Skia.UI.Governance.Coreevidence APIs. -
Move pure parser, graph, audit, scan, render, and golden tests to
Governance.Core.Tests. - Keep generated-product runner tests in
Governance.TestsorPackage.Tests. - Ensure every evidence input remains supplied as data. Do not move filesystem reads into Core.
-
Add property tests for graph invariants:
- cycle detection reports at least one participating task;
- topological order respects dependencies;
- synthetic propagation is monotonic;
- accepted
[SEH]summaries are explicit and do not hide blocking statuses.
Acceptance:
- Evidence graph/audit artifacts are byte-compatible for fixtures.
- Generated-product evidence runner still works through
FS.Skia.UI.Build. - Core evidence tests require no git repository and no FAKE target.
- No active feature state is read by Core directly.
Phase 4: Move Skill, Capability, Contract, And Generated-View Pure Logic
Purpose: expand the kernel to the rest of the deterministic governance model.
Candidate moved modules:
SkillTreeGenSkillSyncSkillQualitySkillContractPathSkillistViewSkillistReferenceCapabilitiesApiSurfaceGenTemplateUpdatePackageConstitutionFragmentsGovernedBlocksCatalogGenSymbolCrossCheck- pure portions of
Guidance - pure portions of
PerPackageSurface
Tasks:
- Move one dependency cluster at a time.
-
Split any mixed module before moving it:
- pure evaluator to Core;
- filesystem/process wrapper remains in Build.
- Move corresponding tests into
Governance.Core.Testswhere they are pure. - Keep generated file writing and currency-check command execution in Build.
-
Add tests that distinguish:
- canonical source;
- generated view;
- active guidance;
- historical report;
- fixture.
- Add retired-term policy tests using path roles rather than repo-wide raw string scans.
Acceptance:
GeneratedGuidanceCheckbehavior is unchanged from the outside..agentsto.claudeskill sync behavior is unchanged.- Capability catalog validation behavior is unchanged.
- Generated API-surface docs are unchanged for current catalog fixtures.
- Core tests cover the rule decisions directly.
Phase 5: Introduce The Governance Snapshot Model
Purpose: make the knowledge-system boundary explicit without changing existing gates.
New core concepts:
type RouteScope =
| WholeWorkspace
| ExplicitPaths of string list
| StagedChanges
| SinceBase of string
type GovernanceSnapshot =
{ ChangedPaths: string list
ActiveFeature: string option
Targets: TargetFacts list
RouteRules: RouteRuleFacts list
Skills: SkillFacts list
Capabilities: CapabilityFacts list
Artifacts: ArtifactFacts list
ConcurrentRuns: ConcurrentRunFacts list }
type GovernanceFact =
| SnapshotFact of GovernanceSnapshot
| ChangedPathFact of string
| MatchedRouteRule of ruleId: string * paths: string list
| RequiredGateFact of target: Target * reason: string
| RequiredArtifactFact of artifactId: string * reason: string
| EvidenceStatusFact of artifactId: string * status: string
| AgentIntentFact of action: string * scope: RouteScope
| BlockerFact of blockerId: string * reason: string
type GovernanceQuery =
| ExplainRoute of RouteScope
| ExplainArtifacts of RouteScope
| ExplainSkills of string option
| ExplainNextActions of RouteScope
| AuthorizeAgentAction of action: string * scope: RouteScope
type Explanation =
{ Summary: string
Conclusions: GovernanceConclusion list
Provenance: ExplanationProvenance list }
Tasks:
- Add typed fact models for targets, route rules, skills, artifacts, capabilities, and changed paths.
-
Add a minimal fixed-point evaluator over typed facts:
- deterministic rule order;
- stable de-duplication;
- iteration limit with a diagnostic if a rule set does not converge;
- no filesystem, git, process, FAKE, or package execution.
- Encode route, artifact, and evidence conclusions as typed derivation rules.
- Add pure query functions over
GovernanceSnapshotand derived facts. - Add JSON-friendly DTO renderers in Core or a small rendering submodule.
- Add Markdown rendering for human route explanations.
- Wire only read-only build commands to gather snapshots at the edge.
- Keep current
Routeoutput stable by default. -
Add tests for explanation provenance:
- every selected gate names a rule or default-deny reason;
- every missing artifact names its expected producer or requiring rule;
- every path-scoped decision lists the scoped paths used.
-
Add tests for fixed-point behavior:
- repeated evaluation is idempotent;
- rule order is deterministic;
- selected tier and required gates are monotonic as facts are added;
- every derived blocker carries at least one source fact and rule id.
Acceptance:
- Snapshot evaluation is pure and testable.
- Fixed-point derivation is deterministic, idempotent, and provenance-rich.
- Existing route behavior remains the default.
- New explanation APIs can support future
Route --jsonandRoute --explain. - No gate starts expensive product validation merely to answer explain-only questions.
- Agent-facing authorization queries can deny unsafe actions before the build edge executes them.
Phase 5A: Add Agent Governance Queries
Purpose: make the extracted kernel useful to an agent as an enforcement and planning layer, without letting the kernel execute tools or commands.
New core concepts:
type AgentAction =
| RunTarget of Target
| EditPath of string
| WriteArtifact of string
| CommitChanges
| PushBranch
| RequestHumanInput of reason: string
type AgentDecision =
| Allowed of reason: string
| Denied of reason: string
| NeedsEvidence of artifactId: string * reason: string
| NeedsHuman of reason: string
type AgentPlan =
{ Decisions: (AgentAction * AgentDecision) list
RequiredEvidence: string list
RequiredGates: Target list
Blockers: string list
NextActions: GovernanceConclusion list
Provenance: ExplanationProvenance list }
Tasks:
-
Add pure authorization queries that evaluate proposed agent actions against a supplied
GovernanceSnapshot. - Distinguish read-only actions from effectful actions in the model.
- Treat FAKE-backed targets as effectful actions that can only be recommended, never run, by Core.
- Return denied decisions for actions that would bypass required artifacts, route-selected gates, or evidence ownership rules.
- Return next-action suggestions that are descriptive, not imperative execution.
-
Add tests for:
- commit/push requests with missing required artifacts;
- target execution requests outside the selected route;
- write attempts under generated-view paths;
- safe read-only explanation requests;
- human-input recommendations when a blocker cannot be resolved from facts.
Acceptance:
- The agent plan API is pure and side-effect free.
- A denied action names the blocking rule and required remedy.
- The kernel never shells out, edits files, commits, pushes, or invokes FAKE.
- Build or agent edges can consume
AgentPlanbefore deciding whether to execute an action.
Phase 6: Build Front-End Integration
Purpose: consume the kernel through stable build commands.
Tasks:
- Update
Front/Governance.fsandEngine/Interpret.fsto gather facts and call Core. - Keep command-line parsing and filesystem reads in Build.
- Add
Route --jsonusing Core renderers. - Optionally add
Route --paths <path...>if the scope model is ready. -
Keep
Route --enforcebehavior as artifact presence unless a provenance feature is implemented in the same change. - Add a route report artifact only if the implementation includes the required writer and tests.
- Ensure FAKE-backed target execution remains serialized and does not get hidden inside the core project.
Acceptance:
./fake.sh build -t Routetext output remains stable../fake.sh build -t Route --jsonproduces deterministic JSON.Route --jsondoes not run FAKE-backed gates.-
Route --paths docs/reports/example.md --jsoncan explain scoped authoring decisions if implemented. Route --enforcediagnostics remain clear and tested.
Phase 7: Package And Generated Product Validation
Purpose: prove the package dependency graph and reflection runner.
Tasks:
- Pack
FS.Skia.UI.Governance.Core. - Pack
FS.Skia.UI.Build. -
Inspect
FS.Skia.UI.Build.nuspecto confirm it depends onFS.Skia.UI.Governance.Coreat the same version. - Install or instantiate a generated product that references
FS.Skia.UI.Build. - Confirm NuGet restores the transitive Core package.
-
Confirm
template/base/build.fsxstill reflection-loadsFS.Skia.UI.Build.Evidence.GeneratedRunner.run. - Confirm
GeneratedRunner.runcan call into Core at runtime.
Acceptance:
- Generated product does not reference Core directly.
- Transitive restore brings Core into the generated product dependency closure.
- Generated evidence graph/audit still runs.
- No duplicate assembly or binding conflict appears in the generated product.
Phase 8: Documentation And Guidance Update
Purpose: make the new boundary visible to maintainers and agents.
Tasks:
-
Update
build/Governance/README.mdto explain the split:- Core owns pure rules and evidence algorithms;
- Build owns FAKE/effects/generated-product facade.
- Add
build/Governance.Core/README.md. - Update active guidance that references governance source homes.
- Update generated validation contract documentation if route explanation fields are added.
-
Update
docs/reports/build.mdor another active architecture page if it describes the old single-project model. - Avoid changing historical reports except to add a new superseding report link if the docs pattern requires it.
Acceptance:
- Active docs name the new source homes.
- Historical report content is not rewritten as if it were active policy.
- Generated guidance checks pass.
Test Plan
Core Unit Tests
Add focused tests in Governance.Core.Tests:
-
PathClassificationTests- normalizes slash direction;
- handles root-relative and
./paths; - distinguishes
template/base/build.fsxfrom retired rootbuild.fsx; - classifies
build/Governance/**as governance implementation; - classifies
.agentsas canonical skills and.claudeas generated skills; - classifies timestamped reports as historical docs unless explicitly active.
-
TargetRegistryTestsTargets.specis total over dispatch targets;- all target names are unique;
- runnable targets exclude non-registry dispatch-only targets;
- dependency rows are deterministic.
-
RoutingKernelTests- highest tier wins;
- unmatched paths default-deny;
- consumer-agent floor composes with path escalation;
- dogfood override forces full pipeline;
- gates are de-duplicated in registry order;
- adding changed paths never lowers the selected tier.
-
ContractViewTests- rendered contract is stable;
- every rendered rule corresponds to a typed route rule;
- every rendered gate is a typed target.
-
RouteExplainTests- every matched rule has matched paths;
- every selected gate has a reason;
- default-deny conclusions are explicit;
- JSON rendering is deterministic.
-
GovernanceInferenceTests- fixed-point evaluation is idempotent;
- adding facts cannot lower a selected tier or remove required gates;
- every derived conclusion carries rule provenance;
- non-converging rules fail with a bounded diagnostic rather than looping.
-
AgentGovernanceTests- read-only explanation queries are allowed without requiring gate execution;
- effectful target execution requests are denied when outside the selected route;
- commit/push plans surface missing evidence and route-selected gates;
- generated-view write attempts are blocked with the canonical source path;
- next-action suggestions do not execute commands.
-
ArtifactRegistryTests- route-required artifacts know their producer target where known;
- feature-relative artifacts resolve with the active feature;
- presence-only and freshness-aware checks are distinct.
-
EvidenceGraphTestsandEvidenceAuditTests- fixture parity for graph JSON/Markdown;
- cycle detection;
- synthetic propagation;
[SEH]accepted summary behavior;- diff-scan blocking/advisory classification.
Property Tests
Use FsCheck where invariants are clearer as properties:
- route tier monotonicity as paths are added;
- gate de-duplication preserves first registry occurrence;
- path normalization is idempotent;
- contract rendering and parsed compatibility views agree on rule ids;
- task graph topological order respects every dependency;
- artifact requirement union is order-stable.
- fixed-point derivation reaches the same fact set regardless of repeated runs;
- derived conclusions always have non-empty provenance;
- agent authorization is conservative: unknown effectful actions deny by default.
Integration Tests
Keep these in Governance.Tests, Package.Tests, or existing generated-product tests:
./fake.sh build -t Routecommand behavior;- FAKE target dispatch and dependency wiring;
- generated-product reflection runner;
- template pack/install/instantiate paths;
- package nuspec dependency inspection;
- report writing and readiness artifact locations;
- preflight, process health, publish/pre-publish checks.
Golden Parity
Before and after each movement phase, verify:
- route text output for representative diffs;
validation.contract.yml;- target metadata output where affected;
- evidence graph JSON/Markdown for fixtures;
- audit hit JSON for fixtures;
- generated API-surface docs if capability modules move;
- skill sync generated tree if skill modules move.
Validation Plan
For the actual implementation branch, follow the repository rule:
- Run
./fake.sh build -t Route. - Run only the gates it prints, sequentially.
-
If the route escalates to maintainer verification, use the serialized order:
./fake.sh build -t Dev./fake.sh build -t GeneratedGuidanceCheck./fake.sh build -t TemplateCheck./fake.sh build -t GeneratedProductCheck./fake.sh build -t EvidenceGraph./fake.sh build -t EvidenceAudit
Expected route characteristics:
-
Edits under
build/Governance/**, newbuild/Governance.Core/**, project files, tests, package metadata, and active guidance should route beyond the inner loop. - Because the current route rules do not yet explicitly cover all governance implementation paths, the implementation may default-deny to broad verification until route coverage is added.
-
If route coverage is added in the same feature, its contract rendering and
validation.contract.ymlcurrency must be part of the evidence.
Additional manual checks for this feature:
- Inspect packed
FS.Skia.UI.Build.nupkgdependencies. - Instantiate a generated product and run its evidence command.
- Confirm no runtime UI package references appear in Core.
- Confirm no FAKE package references appear in Core.
- Confirm no filesystem/process code appears in Core except unavoidable BCL value types.
Migration Strategy
Keep Existing Names First
First move files, not concepts. It is acceptable for the new assembly to contain modules under the old namespace temporarily. This keeps the diff reviewable and focuses on project boundary correctness.
Later, introduce FS.Skia.UI.Governance namespaces if there is a clear benefit. If that
happens, provide compatibility wrappers in FS.Skia.UI.Build for any external or
generated-product API that should remain stable.
Move Tests With The Modules
When a pure module moves, move its direct tests to Governance.Core.Tests. Keep tests that
exercise FAKE, filesystem, generated products, packaging, or command-line behavior in the
existing integration test project.
Split Mixed Modules Before Moving
Do not move mixed modules wholesale if they perform filesystem or process effects. Split them first:
|
Preserve Generated Product Entry Points
Generated products should not need to change during the extraction. Keep:
|
as the reflection-invoked facade. Internally it can delegate to Core.
Risks And Mitigations
Risk: Package Dependency Breaks Generated Products
If FS.Skia.UI.Build depends on Core but the Core assembly is not restored into generated
products, the reflection runner can fail at runtime.
Mitigation:
- make Core packable;
- verify
FS.Skia.UI.Build.nuspecdependency; - run generated-product validation from packed packages;
- keep generated products referencing only
FS.Skia.UI.Build.
Risk: Namespace Rename Churn Hides Behavior Changes
Renaming modules while moving assemblies can make review noisy.
Mitigation:
- keep namespaces stable in the first extraction;
- add namespace cleanup only after parity is proven;
- keep compatibility wrappers for generated-product APIs.
Risk: Core Slowly Gains Effects
A separate project is not useful if it starts reading the real repository or shelling out.
Mitigation:
- ban FAKE/process dependencies in Core;
- write tests using supplied strings and in-memory facts;
- review dependencies in the project file;
- keep filesystem reads in Build.
Risk: Generic Expert-System Abstraction Becomes Opaque
A generalized rule engine could make direct policy harder to understand.
Mitigation:
- use typed F# records/unions/functions first;
- use active patterns only for classification clarity;
- require every conclusion to carry provenance;
- prefer ordinary exhaustive pattern matches over stringly runtime rules.
Risk: Duplicate Source During Migration
Moving modules between projects can accidentally leave duplicate module definitions.
Mitigation:
- remove compile includes from Build in the same patch that adds them to Core;
- keep compile order explicit;
- build after each cluster;
- avoid large "move everything" commits.
Risk: Tests Become Fragmented
Splitting tests can make it unclear where a failure belongs.
Mitigation:
- Core tests own pure behavior;
- Build tests own integration/effects;
- test names should state the boundary they prove;
- keep a small parity matrix in the readiness evidence.
Acceptance Criteria
The feature is complete when:
FS.Skia.UI.Governance.Coreexists and contains the pure governance kernel.FS.Skia.UI.Buildreferences Core and keeps effectful build/front-end work.GeneratedRunner.runremains available fromFS.Skia.UI.Build.- Core has focused unit/property/golden tests.
- Existing integration tests still validate FAKE commands and generated products.
Routetext output is unchanged unless an intentional route feature is included.validation.contract.ymlremains generated from typed route facts.- Evidence graph/audit fixture outputs remain compatible.
- Packed
FS.Skia.UI.Buildrestores Core transitively. - Generated products can still run evidence validation.
- Active guidance documents name the new source homes.
- Route-selected gates pass in the order printed by
Route.
Recommended Task Breakdown
- Create the Spec Kit feature and classify it as governance/build-contract work.
- Add baseline route/contract/evidence/generated-product parity tests if any are missing.
- Add empty Core project and Core test project.
- Move
Findings,Targets,Routing, andContractView. - Add path classification active patterns and tests.
- Add route explanation models and tests, without changing default
Routeoutput. - Move evidence parser/graph/audit core and tests.
- Keep
GeneratedRunnerin Build and delegate to Core. - Move skill/capability/generated-view pure modules cluster by cluster.
-
Introduce
GovernanceSnapshot, typed facts, fixed-point derivation, and pure explanation queries. - Add agent-facing authorization and planning queries over the derived facts.
- Wire optional
Route --jsonthrough the build edge. - Prove pack dependency and generated-product reflection behavior.
- Update active docs and guidance.
- Run route-selected gates sequentially.
- Review the diff for accidental namespace churn, dependency leaks, and generated-product package regressions.
Final Recommendation
The split makes sense, but it should be framed as a local kernel extraction rather than a new generalized governance platform. The first implementation should make the current rules easier to test and explain without weakening the existing route-selected gates.
The most valuable early outcome is a small, fast test surface for governance decisions:
path classification, target identity, route selection, contract rendering, artifact
expectations, evidence graph/audit algorithms, and explanation provenance. Once that is
stable, higher-level features such as Route --json, scoped authoring validation,
artifact freshness, agent action authorization, and concurrency-aware explanations become
much easier to add without turning the build front-end into an even larger policy module.
val string: value: 'T -> string
--------------------
type string = System.String