Go exports everything with a capital letter, which makes every identifier in
this repository look equally like a promise. Most are not. This document says
which ones are, and internal/apisurface enforces it: the contractual surface
is recorded in docs/api-surface.txt, and a test fails if it drifts without
that file being updated in the same change.
The contractual surface is recorded in docs/api-surface.txt and gated by
internal/apisurface: it cannot drift without a reviewable diff. From v0.1.0
on, changing it requires a minor version bump.
Contractual. Changing these breaks code written against the framework, and a break requires a minor version bump with the reason recorded in the release notes.
| Package | Surface |
|---|---|
dst |
Engine[M], New, Config, Cluster[M], Wire[M], Invariant, InvariantFunc, Violation, Injector, InjectorFunc, Split, Isolate, Link, During |
dst/scenario |
Scenario, Fault, Load, Read, MaxNodes, and the JSON field names |
storage/wal |
Device, Log, Open, Record, Encode, Decode, Recover, ErrChecksum, ErrInvalidTear, MemoryDevice, FileDevice |
storage/storagetest |
RunDeviceSuite, NewDevice |
storage |
Entry |
raft |
StableStore, SnapshotStore, HardState, Entry, Snapshot, ErrStorage |
server |
Spec, SpecNode, LoadSpec, ReadSpec, Spec.ConfigFor, Spec.Validate, MaxNodeID, and the cluster JSON field names |
The raft and server entries are narrow on purpose. In raft only the
persistence boundary is contractual, because that is what an alternative
storage backend implements. In server only the cluster file format is, because
that is what an operator writes; server.Config, server.Server, and the rest
are tier 2 and may change in a patch release.
- Removing or renaming any identifier above.
- Adding a method to
Cluster,Wire,Invariant,Injector, orDevice. Every existing implementation stops compiling. - Changing a method signature, or the meaning of a return value.
- Removing a JSON field from
ScenarioorFault, or changing how one is interpreted. Adding a field is not a break, because parsing rejects unknown fields only in the other direction.
Adding a field to Config or Scenario is not a break for keyed struct
literals, which is the only form this project uses or documents.
Public because Go has no other way to compose packages, but not contractual. These may change in a patch release. Do not build on them without pinning.
raft (beyond the persistence boundary), sim, server, protocol,
statemachine, backup, chaos, storage/raftwal, storage/raftstore,
storage/snapshot, storage/uring, storage/uringwal, storage/faultdisk,
dst/raftcluster.
Two of these deserve a note:
simis retained deliberately. It is the linear-scan reference the equivalence campaigns comparedstagainst, and removing it would remove the gate. It is not a recommended entry point for new work; usedst.dst/raftclusteris an example of implementing the tier 1 interfaces, the same roleexamples/paxosplays for a non-Raft protocol. It is not a supported Raft API.
internal/cli cannot be imported from outside the module, which the compiler
enforces. The command implementations live there precisely so that the CLI can
be restructured without it being an API change.
cmd/promtact and the nine standalone binaries have contractual flags and
exit codes, because scripts/*.sh, .github/workflows/ci.yml, and the
recorded evidence in benchmarks/ invoke them by name and branch on their
status. Output format is not contractual, with one exception: the final status
line of promtact-seeds is quoted in evidence documents.
simulate and verify exist only as subcommands of promtact. They are newer
than the extraction, nothing historical refers to them, and a new command does
not need a standalone binary to keep a promise nobody made.
Removing a flag, changing its default, or changing an exit code is a break at tier 1 severity.
Semantic versioning from v0.1.0:
- patch (
v0.1.0tov0.1.1): tier 2 changes, fixes, new tests, evidence. - minor (
v0.1.xtov0.2.0): any tier 1 change, any CLI flag or exit code change, any new tier 1 identifier. - major (
v1.0.0): a redesign of the framework surface.
A release must carry the gate evidence for the commit it tags, following the
practice in benchmarks/.
Pushing a v* tag runs .github/workflows/release.yml. It refuses to publish
anything until the tagged commit passes go vet, the full race suite, and the
1,000-seed sweep, because a release that skipped its gates would be the kind of
unbacked claim this repository exists to avoid.
It then builds the promtact umbrella binary for linux, darwin, and windows on
amd64 and arm64, writes SHA256SUMS, generates an SPDX bill of materials, and
records a Sigstore build attestation with the workflow's own OIDC identity,
there is no signing key to store or leak.
The commands for checking both live in SECURITY.md, and only there. Repeating them here is how this page came to carry a release that no longer exists and a form of the attestation command that asks for a GitHub account.
Only the umbrella binary ships. It reaches every command, and the standalone binaries exist for the recorded gate scripts, which build from source.
docs/api-surface.txt lists every exported identifier in the tier 1 packages.
internal/apisurface regenerates that list from the source and fails if it
differs. An intentional API change therefore shows up as a diff in a checked-in
file, in the same commit that makes it, where a reviewer can see it. An
unintentional one fails the build.
Regenerate after an intentional change:
go test ./internal/apisurface -update