Overview
Every seam of Better Compute is a typed contract you register through options. What each extension point is for, where it runs and how stable it is.
Better Compute has no plugin loader. Every place you can change its behaviour is a typed contract in
@better-compute/core, and you plug an implementation in by passing it to betterCompute(). Two rules from PLAN.md
§5.1 shape all of them:
- Code registers kinds; data configures instances. A driver, a provider or an attestor is code you register once. A BMC binding, a provider account or a join token is a row, validated by the manifest that code declares.
- Registration is explicit. Nothing is discovered from the file system or
node_modules. Everything arrives in an options array, and the registry refuses what it cannot check: a duplicatekind:id@version, a method whose action is missing from the IAM catalog, or a migration named for another module are allINVALID_CONFIGwhenbetterCompute()orinitialize()runs.
The extension points
| Extension point | Declared with | Import from | Registered through | Runs in |
|---|---|---|---|---|
| Module | defineModule | better-compute | modules | control plane |
| Plugin (a module with an id) | definePlugin | better-compute | plugins | control plane |
| API method | defineMethod | better-compute/core | a module's api() | control plane |
| Driver | defineDriver | better-compute | drivers, or a module's extensions | control plane, site agent, node, ramdisk |
| Out-of-process driver (bcx) | serveDriver, externalDriver | @better-compute/driver-sdk | drivers | its own process |
| Attestor | defineAttestor | better-compute | attestors | JoinService, in the control plane |
| Provider | defineProvider | better-compute/vps | providers | control plane |
| Scheduler plugin | defineSchedulerPlugin | better-compute | scheduler.plugins, or a module's extensions | the placer, in api and worker processes |
| Event sink | defineSink({ kind: 'events' }) | better-compute | sinks.events, or a module's extensions | the sinks worker role |
| Audit sink | defineSink({ kind: 'audit' }) | better-compute | sinks.audit, or a module's extensions | the audit-sinks reconciler, controllers role |
| Step and profile | defineStep, defineProfile | better-compute/provision | a module's extensions and profiles | control plane or one agent command |
The root entry better-compute exports defineModule, definePlugin, defineDriver, defineSchedulerPlugin,
defineAttestor and defineSink. defineMethod, defineStep, defineProfile and defineProvider come from the
better-compute/core, /provision and /vps subpaths.
Other contracts in core are ports rather than plug-ins: SqlExecutor (the store), IamPort, Clock,
Notifier, LeaseProvider, SecretStore, BlobStore, CertificateAuthority, ImageSource and NodeAdapter. You
replace them through their own options (database, iam, clock, notifier, secrets, pki), not through
extensions; the configuration reference lists them.
Where each one plugs in
A module is the unit everything else hangs off. It can carry API groups, reconcilers, migrations, command kinds,
NodeSpec sections, IAM catalog fragments, CLI commands, profiles and extensions (drivers, steps, scheduler
plugins, attestors, sinks). The top-level options (drivers, providers, attestors, scheduler.plugins,
sinks) register extensions that do not need a module of their own. Both paths end in the same registry and the
same kind:id@version uniqueness check, and every consumer reads both: scheduler plugins a module lists join
scheduler.plugins in the placer, and its event and audit sinks join sinks.events and sinks.audit.
import { betterCompute, defineSchedulerPlugin, defineSink } from 'better-compute';
import { acmeWidgets } from '@acme/bc-widgets'; // definePlugin({ id: 'acme-widgets', … })
export const compute = betterCompute({
// …database, secret, baseURL, iam
plugins: [acmeWidgets()],
scheduler: { plugins: [defineSchedulerPlugin({ id: 'rack-spread', scorers: [/* … */] })] },
sinks: {
events: [defineSink({ kind: 'events', id: 'warehouse', async deliver(events) { /* … */ } })],
},
});What is stable
Better Compute is pre-1.0. The contracts in @better-compute/core froze at the end of phase P0 and change only by
additive edits: new optional members in minor versions, and a new interface selected by version for anything
breaking. PLAN.md §5.1 keeps every define* helper experimental until v1.0, so pin your versions.
| Extension point | State today |
|---|---|
| Modules, plugins, methods | Built. Every built-in feature (capacity, events, fleet, rollout, audit, iam, vps, metal, provision) is a module on this contract. |
| Reconcilers, command kinds, NodeSpec sections, migrations | Built, with the v0.1 worker runtime. |
| Scheduler plugins | Built: filters, scorers, atom pickers and admission hooks run in both placers. |
| Sinks | Built: event sinks on OutboxDispatch with durable cursors and dead letters; audit sinks on the built-in audit-sinks module. Usage sinks are refused with INVALID_CONFIG (usage goes to IAM billing through the bridge); see sinks. Sinks and scheduler plugins a module lists in extensions run like those in the options. |
| Attestors | Built: join-token, x509pop, provisioned, discovery, cloud-iid and tpm are all defineAttestor values. bcd produces join-token joins, provisioned joins for an OS a deploy installed (bcd join --node-id), discovery joins from bcd ramdisk with its site agent's signed assertion, and tpm joins with the host's TPM (bcd join --attestor tpm, 8545688); its producers for x509pop and cloud-iid are not written. |
| Drivers, steps, profiles | Built in TypeScript (metal, provision). bcd runs the metal command kinds that steps send (Redfish BMC operations, boot entries and site.httpProxy on bcd site; wipes, image writes, kexec and DCGM on bcd ramdisk), and driver.invoke in the bcx plugins it hosts; not released and tested by Go unit tests only. |
| Providers | Built: hetzner and proxmox provider packages, checked by providerConformanceCases(). |
| bcx out-of-process drivers | A v0.5 draft. @better-compute/driver-sdk works for TypeScript plugins, and bcd hosts sha256-pinned plugins (see drivers); the proto is unstable until v0.5 ships. |
Rules every extension follows
These hold for anything you write against the contracts, and the built-in modules follow them too:
- Modules never import each other's internals. They meet through the ports on
ModuleContext: thevpsmodule books host capacity throughctx.capacity.reserve, not by importing@better-compute/capacity. The repository enforces the direction withscripts/check-deps.mjs. - IAM is never awaited inside a compute transaction. The envelope makes the one decision before your handler's
transaction opens. Composite methods decide their sub-calls with
ctx.checkbefore they open a transaction. - Write state, events and work in one transaction.
ctx.events.publish,ctx.work.enqueue,ctx.commands.enqueueandctx.authz.upsertall take the caller'stx, so a rollback leaves nothing behind. - Business time comes from the database. SQL uses
compute_now(), nevernow(); TypeScript usesctx.clock.scripts/check-sql-now.mjsfails CI on a barenow()inpackages/*/src. - Everything is retried. Reconcilers are level-triggered, sinks are at-least-once, commands carry idempotency keys. Write each piece so that running it twice changes nothing.
Conformance
A contract comes with a conformance suite: plain { name, run } cases you run against your implementation. The
runner lives on its own subpath of @better-compute/core, so the root entry carries no test code:
import { runConformance } from '@better-compute/core/conformance';
import { providerConformanceCases } from 'better-compute/vps';
import { acmeCloudSubject } from './acme-cloud-subject'; // your provider, against recorded responses
const report = await runConformance(providerConformanceCases(), () => acmeCloudSubject());
// report: { passed: string[], failed: { name, error }[] }; a factory gives each case a fresh subjectstoreConformanceCases() from @better-compute/postgres/conformance does the same for a SqlExecutor. The acceptance tests in
tests/extensions/ register third-party-style plugins with zero edits to @better-compute/*; see
walkthroughs.
Better Compute is created by Sean Filimon
Last updated