This recipe shows the host-centric path for plugin discovery, trust evaluation, registration, and runtime communication.
Use it when your host loads plugins from disk and needs explicit allow/block policy before any plugin code runs.
For the smaller plugin-author path, see Plugin And Custom Node Recipe. For the v1 manifest and trust-policy contract, see Plugin Manifest and Trust Policy Contract v1.
dotnet add package AsterGraph.Editor --prerelease
dotnet add package AsterGraph.Abstractions --prereleaseAdd AsterGraph.Avalonia only if the host also embeds the shipped UI.
Use AsterGraphEditorFactory.DiscoverPluginCandidates(...) to enumerate candidates without loading plugin assemblies.
var discoveryOptions = new GraphEditorPluginDiscoveryOptions
{
DirectorySources =
[
new GraphEditorPluginDirectoryDiscoverySource(@"C:\MyHost\Plugins")
],
PackageDirectorySources =
[
new GraphEditorPluginPackageDiscoverySource(@"C:\MyHost\PluginPackages")
],
TrustPolicy = myHostTrustPolicy, // optional pre-filter
};
var candidates = AsterGraphEditorFactory.DiscoverPluginCandidates(discoveryOptions);What you get:
GraphEditorPluginCandidateSnapshotper candidateManifest(id, display name, version, compatibility, provenance)ProvenanceEvidence(package identity, signature status, signer fingerprint)TrustEvaluation(decision, source, reason code, reason message)PackagePathwhen the candidate comes from a package directory
Discovery does not load assemblies. It is safe to run against untrusted directories.
Implement IGraphEditorPluginTrustPolicy to make host-governed decisions.
public sealed class MyHostTrustPolicy : IGraphEditorPluginTrustPolicy
{
public GraphEditorPluginTrustEvaluation Evaluate(GraphEditorPluginTrustPolicyContext context)
{
// context.Registration — the candidate registration
// context.Manifest — the visible manifest
// context.ProvenanceEvidence — signature and package identity
// context.PackagePath — absolute local package path, when present
if (IsInHostAllowlist(context.Manifest, context.ProvenanceEvidence))
{
return new GraphEditorPluginTrustEvaluation(
GraphEditorPluginTrustDecision.Allowed,
GraphEditorPluginTrustEvaluationSource.HostPolicy,
reasonCode: "myhost.allowlist.allowed",
reasonMessage: $"Allowed '{context.Manifest.Id}' via host allowlist.");
}
return new GraphEditorPluginTrustEvaluation(
GraphEditorPluginTrustDecision.Blocked,
GraphEditorPluginTrustEvaluationSource.HostPolicy,
reasonCode: "myhost.allowlist.blocked",
reasonMessage: $"Blocked '{context.Manifest.Id}': not in host allowlist.");
}
}If no policy is configured, the runtime returns GraphEditorPluginTrustEvaluation.ImplicitAllow().
Trust decisions are evaluated before any plugin contribution code executes.
Keep the allowlist host-owned:
- Persist entries by manifest id + fingerprint (hash of id, display name, package id, version, signer fingerprint, signature status, target framework)
- Support import/export paths so the same allowlist can move between environments
- Keep provenance snapshots alongside allowlist entries for audit
ConsumerSamplePluginAllowlistTrustPolicy in tools/AsterGraph.ConsumerSample.Avalonia is the bounded sample path. Copy the shape, replace the storage format and persistence policy with your host's own.
Turn approved candidates into GraphEditorPluginRegistration and pass them to AsterGraphEditorOptions.PluginRegistrations.
Direct instance:
var registration = GraphEditorPluginRegistration.FromPlugin(
new MyPlugin(),
manifest,
provenanceEvidence);From assembly path:
var registration = GraphEditorPluginRegistration.FromAssemblyPath(
assemblyPath,
pluginTypeName: null,
manifest,
provenanceEvidence);From package path:
var registration = GraphEditorPluginRegistration.FromPackagePath(
packagePath,
manifest,
provenanceEvidence);From staged package (after StagePluginPackage):
var stageResult = AsterGraphEditorFactory.StagePluginPackage(
new GraphEditorPluginPackageStageRequest(candidate));
if (stageResult.Registration is not null)
{
// use stageResult.Registration
}Compose the editor:
var editor = AsterGraphEditorFactory.Create(new AsterGraphEditorOptions
{
Document = document,
NodeCatalog = catalog,
CompatibilityService = compatibilityService,
PluginTrustPolicy = myHostTrustPolicy,
PluginRegistrations = approvedRegistrations,
});After the editor is running, inspect plugin outcomes through the session.
var session = editor.Session;
// Loaded, trusted, blocked outcomes
var loadSnapshots = session.Queries.GetPluginLoadSnapshots();
// Session diagnostics include plugin load events
var diagnostics = session.Diagnostics.GetRecentDiagnostics();Plugin-contributed commands surface through the same shared command route:
var descriptors = session.Queries.GetCommandDescriptors();
// plugin commands are included in the descriptor listExecute through the canonical path:
session.Commands.TryExecuteCommand(new GraphEditorCommandInvocationSnapshot(
commandId, parameters));Plugin loading is in-process. AsterGraph does not sandbox plugin code or isolate untrusted execution.
For public beta hosts:
- keep plugin directories explicit
- prefer allowlists
- validate provenance with signatures or hashes in host policy
- do not treat plugin loading as an isolation boundary
Run the defended hosted proof:
dotnet run --project tools/AsterGraph.ConsumerSample.Avalonia/AsterGraph.ConsumerSample.Avalonia.csproj --nologo -- --proofExpect:
CONSUMER_SAMPLE_TRUST_OK:TrueCONSUMER_SAMPLE_PLUGIN_OK:TrueCONSUMER_SAMPLE_OK:True