Official providers
The four official providers and one sample in the public tree, what each does, how credentials and egress work, and exactly what is tested and what is not done.
These providers build and their tests pass against recorded or in-memory fixtures. No live run is claimed: the READMEs list the live end-to-end run as not done. Presence in plugins/official/ is not a readiness claim.
What is in the tree
| Provider | Directory | Port | Plugin id |
|---|---|---|---|
| GitHub source control | plugins/official/scm-github | numinance.ports.scm.v1 | dev.numinance.scm-github |
| GitHub Actions CI | plugins/official/ci-github-actions | numinance.ports.ciexecutor.v1 | dev.numinance.ci-github-actions |
| Repository static verifier | plugins/official/verifier-repo | numinance.ports.verifier.v1 | dev.numinance.verifier-repo |
| Python verifier | plugins/official/verifier-python | numinance.ports.verifier.v1 | dev.numinance.verifier-python |
| Sample knowledge (example) | plugins/examples/sample-knowledge | numinance.ports.knowledge.v1 | dev.numinance.sample-knowledge |
plugins/official/internal/ghprovider is the shared skeleton of the two GitHub providers: the PluginService implementation, the HostService client, a size-limited REST client and the cassette transport. It is internal; providers are expected to use the public SDK directly, as the sample does.
Ports with no provider in the tree: ticketing, authorexecutor, sandboxprovider, evidencestore and transparencylog. A second CI provider is listed as not done in the CI provider's README.
Credentials and egress, the common pattern
Both GitHub providers share one rule: no token is ever in the provider process.
- The provider calls
HostService.RequestCredentialfor an intent its manifest declares (scm.github.apiorci.github.actions). - The answer carries an opaque handle and the address of the egress proxy listener of the invocation's step.
- The provider sends an absolute-form
http://api.github.com:443/...request to that address with the handle in theNuminance-Handleheader. The proxy applies the token it holds and re-originates the request over TLS. - The transport refuses a request with no handle, a handle with no proxy address (or an expired one), and any destination other than
api.github.com:443. It opens no other connection, refuses redirects, and the provider's environment is the three handshake variables with no ambient proxy setting.
The manifest of the live build declares one network destination, api.github.com on port 443, one credential intent, and the environments dev, staging and prod. The replaying build of each provider declares no network destination, no credential intent and the dev environment only, so it cannot be installed where the live provider runs.
Every answer from GitHub is treated as untrusted: at most 4 MiB, decoded without trust, every field checked against the port grammar (commit ids, branch names, URLs, check names, counts), never repaired or truncated. A violation ends the call UPSTREAM_ERROR.
scm-github
A capability provider of numinance.ports.scm.v1 over the GitHub REST API (version 2022-11-28). All twelve core verbs are registered in provider/spec.go; there is no VendorCall. The README text counts eleven; the registered list is authoritative.
| Verb | Declared risk | Notes |
|---|---|---|
Read verbs: GetRepository, GetTree, GetFile, ListBranches, GetBranchProtection, GetPullRequestStatus, SearchCode | READ | SearchCode wraps the query in one quoted phrase so a qualifier like repo:other/x cannot widen the search, and drops hits of other repositories |
CreateBranch, UpdatePullRequest, CommentOnPullRequest | LOW, keyed | CommentOnPullRequest finds its own earlier comment by a marker derived from the host's idempotency key |
OpenPullRequest | MEDIUM, keyed, dry run | Checks the head branch points at head_revision; finds an open pull request for the same head and base before creating one; emits one pr-ref receipt (repository, number, head, base; never title or body) |
MergePullRequest | PRIVILEGED, keyed, dry run | Merges only at expected_head_revision (squash); answers a merge already done as done; refuses a closed or moved pull request |
Behaviors worth knowing:
- Requests are validated against the
scm.protogrammar before anything is sent. A branch or tag never stands for a revision, and a repository with a.or..segment never reaches a request path. GetPullRequestStatusreads every page of reviews and check runs (at most five pages of 100; a full last page isLIMIT_EXCEEDED, never a silent cut) and counts an approval only from a reviewer who holds write access and is not the pull request's author.GetBranchProtectionreads and changes nothing. No verb edits protection, a workflow file or a ruleset.- Who may call which verb is not the provider's decision. The port requires the author-agent identity for opening pull requests and the release-pipeline identity only for merging; the host and policy floors refuse other callers before
Invoke.
ci-github-actions
A capability provider of numinance.ports.ciexecutor.v1. It holds no credential and starts only a workflow the customer already defines. Its contract with the repository is specific:
- It dispatches
workflow_dispatchon the repository's default branch, which is protected, so a candidate cannot change the pipeline definition. Inputs arenuminance_revision(the full commit id),numinance_operation_id(derived from the host's idempotency key) and profile parameters; names starting withnuminance_are refused as parameters. - The workflow must declare both inputs and name its run
numinance ${{ inputs.numinance_operation_id }} ${{ inputs.numinance_revision }}. The provider finds the run by that name before dispatching, so a retry starts no second run, and again after. A run that does not follow this is not an operation of the provider (UPSTREAM_ERROR). - A run is adopted only if it was dispatched on the default branch, its
pathnames the workflow file on that branch, and its name carries the requested revision. - A full page of 100 runs lacking the operation fails closed (
LIMIT_EXCEEDED) and dispatches nothing.
StartOperation is HIGH with a DEPLOY side effect and declares and emits one ci-run-ref receipt. CancelOperation is MEDIUM. A run's state is the CI's claim: an unknown ending is FAILED, never a success. GetLogReference returns the github.com run page, never the log.
verifier-repo
A capability provider of numinance.ports.verifier.v1 that reads a candidate as data and reports what breaks repository conventions: the scope of the change, the Markdown of documents it touches, and the just recipes those documents name. It uses only the standard library plus the public SDK.
It runs no candidate code and no tool. The candidate archive is parsed in memory and never extracted to disk. Its manifest asks for no network, credential or filesystem intent. Rule ids are stable (repo/scope-path, repo/scope-protected, repo/markdown-link, repo/just-recipe, and others); a rule id never changes meaning and is never reused. Findings are normalised, fingerprinted without the line, merged and bounded at 1024 items. Exactly one VerifierReport is emitted through EmitEvidence, with the archive size and sha256 and the finding count, never the findings.
The scope configuration is supplied by the binding loader from the protected base revision; no file of the candidate configures the check. The candidate parser refuses zip-slip names, absolute paths, sparse entries, hard links, devices and mismatched manifests.
verifier-python
A capability provider of the same verifier port that runs ruff, mypy, pytest, Semgrep or import-linter and reports findings. The important boundary is stated in its README: candidate code is not run unnested.
- pytest executes the tests, and import-linter imports the candidate's root package, which runs its
__init__files in the verifier's own process. The port allows that only inside a nested sandbox, which this provider does not build. Both checks are thereforeUNAVAILABLEin the live build, and only the replaying build serves them. - The static checks (ruff, mypy, Semgrep) run from a private working directory outside the candidate, with no
PYTHONPATHandPYTHONSAFEPATH=1, in their own process group that is killed whole at the deadline. - Nothing is hidden from a check: ruff runs with
--ignore-noqa, Semgrep with--disable-nosem, and settings come only from the config. Directives the tool cannot ignore (mypy# type: ignore, pytest skip and xfail marks, ignore files) are reported as findings with anuminance.directive/...rule id. - A tool that exits unexpectedly or prints something not in its own format ends the call
FAILEDwith no findings. Output over 8 MiB ends the callFAILED, never truncated.
This is the same limit the numi init output reports when it notes that unit tests run candidate code and are unavailable in the live verifier provider until its nested sandbox exists.
sample-knowledge
A sample provider of numinance.ports.knowledge.v1 with three read verbs over a seeded in-memory source. It is the target of the knowledge conformance kit and is annotated line by line in Write a plugin in Go.
What is tested
The tests were run in the documentation environment against the pinned spec checkout, with the local Go 1.27.2 toolchain, no network and no GitHub credential.
go test -count=1 -timeout 280s ./plugins/... ./conformance/... ./sdk/go/plugin/...ok github.com/numinance/numinance-spec/plugins/official/ci-github-actions/provider 0.944s
ok github.com/numinance/numinance-spec/plugins/official/internal/ghprovider 0.951s
ok github.com/numinance/numinance-spec/plugins/official/scm-github/provider 0.955s
ok github.com/numinance/numinance-spec/plugins/official/scm-github/replay 0.949s
ok github.com/numinance/numinance-spec/plugins/official/verifier-python/catalog 0.843s
ok github.com/numinance/numinance-spec/plugins/official/verifier-python/provider 4.937s
ok github.com/numinance/numinance-spec/plugins/official/verifier-repo/catalog 0.622s
ok github.com/numinance/numinance-spec/plugins/official/verifier-repo/provider 1.101sThe full command exited 0, and the conformance packages for ciexecutor, knowledge, scm, ticketing and verifier and sdk/go/plugin also passed. Packages without tests (the main packages, cmd/replay, cmd/record, the sample) print [no test files].
Caveats on that result:
- The run happened outside the command sandbox because
sdk/go/plugintests bind a Unix socket, which the sandbox blocks (inside it that test hung until timeout). It was not a failure of the code. go.modasks for toolchain go1.27.3; the run used the local 1.27.2 withGOTOOLCHAIN=local.- Replay fixtures, not live systems. The GitHub providers ran over cassettes that the READMEs say are written from the REST documentation with invented names, not recordings. The Python verifier ran over recorded tool output; the optional real-ruff test (
NUMINANCE_VERIFIER_TOOLS=1) was skipped. - What each test suite covers is as stated in its README: bounds, untrusted answers, no handle means no call, the handle header and no other credential, the manifest, a wrong request failing against the scenario cassette, and the conformance kit. The host-level acceptance (a real plugin host launching the replay build) belongs to the proprietary runtime and was not run.
What is not done
These are taken from the READMEs and are not softened here.
| Provider | Not done |
|---|---|
| scm-github | Committed recordings (the cassette is hand-written). A live test covers only GetRepository and GetBranchProtection; the other verbs are exercised in replay only. GitHub Enterprise URLs and GraphQL. A public catalog entry for the credential intent |
| ci-github-actions | A live run and a recording tool; the host gives the provider no egress-proxy address yet. Jenkins and other CI providers. The wait for a dispatched run is five looks; a run that has not appeared fails the call as unavailable and the host's retry finds it by its key |
| verifier-repo | The "repository CI green" and "pr-ref receipt under the author identity" checks are not this provider's. Exact line counts for modified files (set allow_modify false to bound them). Anchors and Justfile import or mod are not followed |
| verifier-python | The nested sandbox for candidate code. Tools beyond the five. Python tools pinned by digest in an image |
| All | SBOM and provenance references in the manifests are placeholders that install-time checks refuse |
Presence is not a readiness claim
A provider in this tree demonstrates the contract and passes its kit. It does not show that it has been run against a production system, security-reviewed, or distributed through a registry. The distribution mechanism is designed, not shipped.
Troubleshooting
- A test hangs in
sdk/go/plugin. You are in an environment that forbids binding a Unix socket. Run it where local sockets are allowed. unit-testsis unavailable afternumi init. That is the nested-sandbox limit ofverifier-python, by design, not a misconfiguration.- A request with no recording fails
UPSTREAM_UNAVAILABLEin replay. Replay fails closed and never invents an answer.
Next steps
Write a plugin in Go
Build a capability provider with the Go plugin SDK, using the sample knowledge provider as a line-by-line reference, and prove it against the conformance kit.
Knowledge through MCP
How read-only organisational knowledge reaches the Context Resolver through the knowledge port, what the public tree contains, and which parts of MCP-backed retrieval are designed and not shipped.