Plugin API¶
Extension points for domain-specific behaviour without modifying engine code.
Custom PhaseExtractor¶
Subclass PhaseExtractor and implement extract() and quality_score():
from scpn_phase_orchestrator.oscillators.base import PhaseExtractor, PhaseState
class MyExtractor(PhaseExtractor):
def extract(self, signal, sample_rate):
# signal: NDArray, sample_rate: float
# Return: list[PhaseState]
...
def quality_score(self, phase_states):
# Return: float in [0, 1]
...
Register in the binding spec:
oscillator_families:
my_sensor:
channel: P
extractor_type: my_module.MyExtractor
config:
param1: value1
The loader resolves extractor_type by dotted import path when it is not one of the built-in types (physical, informational, symbolic).
Custom GeometryConstraint¶
Implement a callable that constrains the Knm matrix:
def my_constraint(knm, params):
# Enforce sparsity, band structure, etc.
# Return: modified knm (NDArray)
...
Register in the binding spec:
Called after CouplingBuilder.build() and after every template switch.
Custom DriverSpec¶
The drivers section of the binding spec configures per-channel external drive parameters:
Custom driver logic can override the default zeta * sin(Psi - theta) by providing a driver class:
class MyDriver:
def drive(self, phases, t):
# Return: NDArray of drive contributions per oscillator
...
Registration¶
For one-off local experiments, custom classes may still be resolved at
binding-spec load time via Python's importlib. The module must be importable
from the Python path.
For reusable extensions, publish a plugin manifest through the
scpn_phase_orchestrator.plugins Python entry-point group. The manifest
declares versioned capabilities before runtime code is imported:
from scpn_phase_orchestrator.plugins import PluginCapability, PluginManifest
def spo_plugin_manifest():
return PluginManifest(
name="my_domain_pack",
version="0.1.0",
package="my_domain_pack",
capabilities=(
PluginCapability(
kind="extractor",
name="my_sensor",
target="my_domain_pack.extractors:MyExtractor",
channels=("P",),
),
),
)
validate_plugin_manifest() and compatibility_report() provide the stable
CI gate for domainpack, extractor, actuator, and bridge extensions.
Runtime loading is available only through the explicit Python-owned loader. It is disabled by default and must be enabled with a policy at the deployment boundary that owns the risk decision:
from scpn_phase_orchestrator.plugins import (
PluginRuntimeExecutionPolicy,
PluginRuntimeLoadPolicy,
execute_plugin_capability,
load_plugin_capability,
)
loaded = load_plugin_capability(
manifest,
"extractor",
"my_sensor",
policy=PluginRuntimeLoadPolicy(loading_permitted=True),
)
extractor_cls = loaded.target_object
audit_record = loaded.audit_record
The loader validates manifest compatibility before import, resolves only the
declared capability, keeps targets inside the manifest package by default,
requires callable runtime targets, and records scpn_plugin_runtime_load_v1
audit metadata. Domainpack metadata entries remain manifest/catalogue records
rather than directly runtime-loadable callables unless an explicit future policy
expands that boundary.
Calling a loaded target requires a second opt-in policy:
executed = execute_plugin_capability(
manifest,
"extractor",
"my_sensor",
args=(signal,),
kwargs={"sample_rate": 100.0},
policy=PluginRuntimeExecutionPolicy(
loading_permitted=True,
execution_permitted=True,
),
)
The execution audit record stores the load hash, target hash, argument count, keyword names, result type, and deterministic execution hash. It deliberately does not store argument values, because runtime payloads may contain proprietary measurements or credentials.
For production deployments, bind execution to reviewed target hashes through a request stage:
policy = PluginRuntimeExecutionPolicy(
loading_permitted=True,
execution_permitted=True,
approved_target_hashes=(reviewed_target_hash,),
require_target_hash_approval=True,
)
This approval check happens before the implementation module is imported. It is the intended guard for promoting reviewed plugin metadata into a deployment request stage, not an execution gate by itself.
Approval artefacts alone do not execute plugins.
build_plugin_execution_request() consumes both the reviewed plan and operator
approval artefacts together. It verifies plan_hash, target_hash, plugin,
kind, name, and operator binding before returning a request envelope.
The request envelope remains import-free and non-invoking; it sets policy such
that loading_permitted and execution_permitted are true only when
require_target_hash_approval=True and approved_target_hashes contains the
reviewed target_hash.
For traceability, keep operator ownership in a deployment artefact alongside
that approval check. The deployment record should include the reviewer/operator
identity or reference, the plan_hash, and the target_hash. Missing or altered
identity/hash pairing should keep execution in fail-closed mode.
Review-only runtime execution planning¶
Runtime planning is a separate non-executing boundary.
build_plugin_execution_plan() builds deterministic metadata from manifest
compatibility, invocation shape, and target-hash policy without importing any
plugin module or invoking any target.
The planning primitive records immutable audit-relevant shape rather than runtime state:
- required capability identity and kind
- compatible/incompatible status and rejection reasons
- target hashes precomputed from manifest and policy
- reviewed
plan_hashfor immutable operator sign-off - positional argument count and keyword argument names for each candidate call
- policy flags that gate loading and execution
Reviewers can approve execution using policy outputs and reviewed target hashes
before any load_plugin_capability/execute_plugin_capability path is enabled.
If an approved hash is missing or invalid, the surface must fail closed.
Execution-path fail-closed conditions are enforced before import:
- execution disabled by policy
- missing capability declaration
- unsupported capability kind
- target outside plugin package when package boundary is required
- missing approval hash when
require_target_hash_approval=True
No payload values are stored in the plan artifact; only argument count and keyword names remain.
No argument values are part of this plan output. Argument payloads are only
handled at execution time and remain outside deterministic plan artifacts. The
operator-facing command is
spo plugins plan-execution <plugin> <kind> <capability> with optional
--approved-target-hash and --require-target-hash-approval flags.
The operator request artifact is produced with:
Runtime code consumes that request through execute_plugin_execution_request().
The helper rebuilds the plan for the supplied argument shape, verifies the
request plan_hash and target_hash, and then invokes the declared capability
through the explicit runtime execution policy. Mismatched argument counts,
keyword names, manifests, or target hashes fail before plugin import.
Deployment storage must run validate_plugin_execution_request() before use.
That validator recomputes the request hash from the stored envelope and rejects
tampered audit records or request hashes present in a deployment-owned revocation
set. Approval rotation therefore happens by revoking old request hashes before a
new request envelope is consumed.
Storage custody is represented by
build_plugin_execution_request_storage_manifest(). The manifest records the
request hash, approval hash, target hash, storage URI, backend, retention
policy, manifest creator, revoked request hashes, and deterministic manifest
hash. validate_plugin_execution_request_storage_manifest() rejects stale or
tampered storage records before the request is handed to runtime code.
The local-file persistence adapter is
write_plugin_execution_request_storage_bundle(). It first builds and validates
scpn_plugin_execution_request_storage_bundle_v1, then writes the JSON bundle
atomically. Existing bundles are not overwritten unless overwrite=True, so a
deployment cannot silently replace an approved request without making that
rotation explicit.
Non-local deployment persistence is represented by
build_plugin_execution_request_storage_adapter_manifest(). It validates
backend/URI compatibility for s3_object, gcs_object, azure_blob,
oci_object, and https_api, rejects URI credentials, binds the storage
manifest hash plus bundle hash, and records
adapter_mode=deployment_owned_external_write. SPO emits this handoff manifest
only; deployment-specific writers must consume it and perform object-store or
HTTPS writes under their own credentials and audit controls.
The CLI surface for that adapter is:
spo plugins persist-execution-request REQUEST_JSON OUTPUT_JSON \
--storage-uri file:///var/lib/spo/plugin-requests/request.json \
--created-by deployment_gate
The CLI surface for external handoff manifests is:
spo plugins storage-adapter-manifest REQUEST_JSON \
--storage-uri s3://spo-prod/plugin-requests/request.json \
--storage-backend s3_object \
--created-by deployment_gate
Revocation is represented by
build_plugin_execution_request_revocation() and exposed as:
spo plugins revoke-execution-request REQUEST_JSON \
--revoked-by deployment_gate \
--revocation-reference REV-2026-05-20-01 \
--revocation-reason operator_rotation
The revocation artefact is immutable metadata. It does not delete stored bundles;
the deployment store must feed the emitted request_hash into its revoked-hash
set before persisting or consuming replacement requests.
For stores that maintain multiple lifecycle decisions,
build_plugin_execution_request_revocation_list() aggregates revocation
artefacts into a deterministic list, rejects duplicate request hashes, and
exposes as_revoked_request_hashes() for direct use with request validation.
spo plugins persist-execution-request accepts --revocation-list to bind the
validated aggregate revoked-hash set into the storage manifest. If the request
being persisted is already present in that list, persistence fails before any
bundle is written.
Lifecycle UX is represented by
build_plugin_execution_request_lifecycle_record() and exposed through:
spo plugins lifecycle-status REQUEST_JSON \
--storage-bundle bundle.json \
--revocation-list REVOCATION_LIST_JSON \
--created-by deployment_gate
The lifecycle record is metadata-only. It reports approved, stored, or
revoked by validating the request, optional storage bundle, and optional
revocation list, then hashing the consolidated status record. It does not load
plugin modules, execute targets, delete bundles, or mutate revocation state.
Batch review is represented by
build_plugin_execution_request_lifecycle_summary() and exposed through:
The summary validates every lifecycle record hash, rejects duplicate request
hashes, reports status counts, and emits storage_missing_request_hashes plus
renewal_required_request_hashes so operators can plan persistence completion
and approval renewal without scanning individual JSON files manually.
Operator dashboard policy is represented by
build_plugin_execution_request_lifecycle_policy_report() and exposed through:
spo plugins lifecycle-policy-report SUMMARY_JSON \
--storage-adapter ADAPTER_JSON \
--created-by deployment_gate
The policy report validates the lifecycle summary hash, validates supplied
storage-adapter manifests, rejects duplicate or out-of-summary adapter request
hashes, and emits action counts for persist_request,
register_storage_adapter, renew_approval, and confirm_external_write.
The report is still metadata-only: it does not perform persistence, contact
remote stores, renew approvals, or execute plugin targets.
References¶
Phase extraction contracts are defined in phase_contract.md. Binding spec validation uses binding_spec.schema.json. Custom geometry constraints must preserve the Knm invariants documented in knm_semantics.md.