This page answers one question, asked directly (Owner #268): "can an adapter actually connect to any OSS?" The short answer is no -- the correct question is narrower, and this page states the narrower one, then shows what it costs to answer yes to it.
Everything below is drawn from req/1019_ADAPTER_CONNECTIVITY_AUDIT_2026-09-01.md, a read-only
audit against HEAD at commit 17d87355 (git show HEAD:<path> was used throughout that audit
because crates/ was under concurrent edit by another lane at the time). Figures here are that
audit's figures, not re-derived.
An adapter implements SubstrateAdapter (crates/gx-substrate/src/adapter.rs), a fixed 7-method
trait. It has held at 7 for about 3 months: a test (adapter_spec.rs) watches the method count by
hand, and two separate proposals to add an 8th method were both absorbed into an existing method's
return type instead of landing as a new one.
| method | contract | effect |
|---|---|---|
kind |
which substrate this adapter speaks for | none |
snapshot |
read a locator's current state | read only |
plan |
(intent, snapshot) -> PlannedDelta, deterministic |
pure (reads allowed, writes not) |
precondition |
a fingerprint that must change when the state changes (CAS basis) | read only |
apply |
execute a gate-approved delta; safe to retry the same delta | writes |
invert |
construct the delta that undoes apply, from a prior snapshot |
partial (defined at one point: the prior snapshot) |
commutation |
do two deltas conflict or not | none |
invert is the one worth reading twice. It returns a three-valued Reversibility
(True/False/Unknown), carried inside InvertOutcome, and Ok(None) -- "no inverse" -- is
a valid, structural answer, not a failure the adapter author forgot to handle. The gate reads
that value and escalates to a human rather than refusing the effect outright. An adapter that can
only ever return Ok(None) for a given operation is telling the truth about that operation, not
failing to implement invert.
"Any OSS" is the wrong frame. The frame that holds: any substrate that can answer three questions.
- Readable -- current state expressible as an
ObjectSnapshot, with aFingerprintthat is guaranteed to change when the state changes. - Writable -- an
Intentcan be turned into aPlannedDeltaand executed byapply, andapplyis safe to retry on the same delta. - Inverse or restore, declarable -- either
invertcan construct a true inverse (fs: write the old bytes back; git: point the ref back at what it pointed at before), or, short of that, a deployer can declare per-operation what restoring looks like (the MCP catalogue model, below). If neither is possible,Ok(None)is the honest answer, not a gap to fill.
All three are required. Several classes of operation cannot satisfy condition 3 by construction, and gx does not pretend otherwise:
- send-type effects (
send/post/upload) -- the receiving side's state is outside gx's reach entirely. - append-only creation (
create/add/insert) -- there is no prior value to restore; the escrow would be empty. - pure discard (
delete/remove) -- "write it back with the same call" is definitionally a different call, not a retry of the original one.
Measured against a census of 747 write-type MCP tools (req/1009, classified by the tool name's
leading verb only -- inputSchema contents were not checked): 103 of 747 (13.8%) fall into
these three structurally-irreversible categories. A further 9 of 747 (1.2%) are
escrow-synthesizable in principle but have no implementation yet. The remainder, 532 of 747
(71.2%), is Unknown -- namespace-prefixed verbs (notion_execute), nonstandard verbs (forget,
revoke), or bare write (which could mean create or overwrite and cannot be told apart from the
name alone). Unknown is kept as Unknown and is not folded into either irreversible or reversible;
folding it either way would misreport the number that matters.
If your system can read its own state, apply a change deterministically from an intent, and either construct a true inverse or let a deployer declare a restore, it can be an adapter. If it can only ever accept and never answer what undoing looks like, it cannot -- and that is a property of the operation, not a defect in gx.
Measured, HEAD-time line counts for the 5 adapters that exist (git show HEAD:<path>, counted
per file):
| crate | files | lines | trait methods implemented | where the lines go |
|---|---|---|---|---|
| gx-adapter-fs | 8 | 1,555 | 7/7 | delta.rs (356) + apply.rs (297) are the bulk |
| gx-adapter-git | 9 | 1,806 | 7/7 | + repo.rs, low-level operations via gitoxide |
| gx-adapter-postgres | 11 | 2,530 | 7/7 | sql.rs (469) + db.rs (387) -- SQL generation and connection management |
| gx-adapter-mysql | 11 | 2,964 | 7/7 | sql.rs (606) + db.rs (612) -- heavier than postgres on dialect differences |
| gx-adapter-mcp | 12 | 6,432 | 7/7 | catalogue.rs alone is 3,235 (50% of the crate) |
The trait's 7 methods are not where most of the cost sits. For fs and git it is the
substrate-specific read/write plumbing. For postgres and mysql it is SQL dialect handling and
connection/catalog introspection. For MCP, catalogue.rs is not protocol-connection code -- it is
a declaration DSL (the ArgSource enum: Forward, Const, ConstJson, PriorContentsUtf8,
PriorJson, GitBlobSha1OfForward, DoResult, DoResultNumberFrom) for saying, per tool, what
restoring that tool's effect looks like.
Estimate for a new adapter, on this measured basis:
- Lower bound (fs-class, a plain read/write substrate): 1,500-1,900 lines.
- Mid (SQL substrate): 2,500-3,000 lines, dominated by dialect and connection handling.
- Upper bound (declaration-model substrate, MCP-class): 6,000+ lines, dominated by writing a DSL for per-operation restore declarations, not by speaking the protocol itself.
A Fixture implementation is what an adapter author actually writes; the harness itself
(gx-substrate-conformance) is a fixed, pre-written suite. Fixture (public trait, in
gx-substrate-conformance/src/lib.rs) has 12 methods: 5 required (adapter, locator,
intent, reset, disturb) and 7 with defaults (uninvertible, commuting_pair,
conflicting_pair, promised_target, normalise, equivalent_spellings, resolve) that only
need overriding if the substrate has a real subject for that case.
run_all, run_contracts, and run_laws are public functions, callable from one #[test] in the
adapter's own crate. Each check reports one of three outcomes, not two: Pass, Fail, or
NotSupplied -- "it failed" and "it was not measured" are kept as different answers on purpose, so
an adapter cannot claim conformance by leaving contracts unimplemented and unreported. A verdict
is only is_conformant() (0 failures) and is_complete() (0 unmeasured) when both are true.
Minimal recipe:
- Implement
SubstrateAdapter's 7 methods. - Implement
Fixture's 5 required methods over your adapter. - Call
run_all(&fixture)from one#[test]. - Assert
report.is_conformant()andreport.is_complete()are bothtrue.
Worked example (read the source, not this paraphrase): crates/gx-adapter-fs/tests/conformance.rs.
the_fs_adapter_meets_every_one_of_the_seven_contracts calls run_contracts; a second test,
one_run_reports_sixteen_obligations_and_meets_the_completion_condition, calls run_all and
asserts is_conformant(), is_complete(), and meets_51_7() together (16 = 7 contracts + 9 laws).
The Fixture implementation itself is crates/gx-adapter-fs/tests/support/mod.rs.
publish = falseon every crate'sCargo.toml, confirmed ongx-substrateandgx-adapter-mcpdirectly. There is nocargo addpath to any of this.public/README.mdstates, in its own words, "Not released." Reading the source on GitHub is not the same as installing a package from a registry.gx-adapter-postgresandgx-adapter-mysqlare private. They exist (2,530 and 2,964 lines, 7/7 methods, per the table above) but are not inpublic/Cargo.toml's member list and are not code anyone outside this project can currently read.@mahirhir/tracefoldon npm is a separate thing -- a WASM receipt verifier, not this Rust adapter trait. Do not read the npm package's existence as evidence this trait is packaged anywhere.- MCP's wire layer connects to any MCP server that speaks the protocol; reversibility does not
follow automatically. The wire layer has no bundled client library by design (AC-051: an
external client library would be a second, gate-bypassing path). But which tool undoes which is
not discoverable from the protocol -- it is declared per tool by whoever deploys the server
(the catalogue model). A tool with zero declared restore is treated as irreversible and routed
to human escalation; gx does not invent a restore for it. The one real external target measured
so far is
github-mcp-server-- this isn=1for protocol compatibility, and generalizing to other MCP servers (Notion, Slack, and similar) is not yet demonstrated. - No adapter-writing guide existed before this page.
public/.github/CONTRIBUTING.mdnames no section for adding an adapter, and a grep across the repo fornew adapter|adapter guide|write adapter-shaped phrasing returned 0 hits before this file was written. This page is the direct remedy, not a claim that the gap was already covered elsewhere.
Source: req/1019_ADAPTER_CONNECTIVITY_AUDIT_2026-09-01.md, recommendation 1. That audit's own
unread-range disclosures (its §6) apply here by inheritance: the census this page cites classifies
tool names by leading verb only, and catalogue.rs's 3,235 lines were read in full for structure
but not read in full for content beyond the first ~400 lines and the ArgSource/PriorPointer
sections.
Currency note (2026-09-01, req/1038, req/1032; not re-derived from this page's own audit).
A sixth adapter, crates/gx-adapter-time, landed on main the same day this page's source audit
was filed; §1's table and §3's cost table above are still the 5-adapter figures and have not been
re-measured at 6. It is private (not in public/Cargo.toml's members), same posture as postgres
and mysql in §5. Separately, §5's claim that public/README.md states "Not released" in its own
words no longer matches that file: public/README.md (checked 2026-09-01) carries npm install
instructions and a released-SDK badge instead. A P1 crates.io publish lane ran the same day
(req/1032): zero Rust crates published, zero reached a dry-run, two blockers found and
partially remediated same-day (crate-name collision, private-feature defaults) -- see
docs/LIMITS.md's 2026-09-01 entry for the full, sourced detail. publish = false and "no
cargo add path" both still hold as measured; only the README quote above is stale.
Second currency note (2026-09-02, pub_sync#8 lane, ruling on the item the 2026-09-01 note
left open). gx-adapter-time is now public source (added to tools/pub_sync_dryrun.sh's
SYNC_CRATES this date) -- its "same posture as postgres and mysql in §5" line above no
longer holds and is superseded by this note, not deleted. The ruling: unlike postgres/mysql
(§3's sql.rs+db.rs dominate their line count -- SQL dialect and connection handling) or
the MCP wire layer (§5's AC-051 no-bundled-client design choice) or gx-confine (a capability
boundary crate, a different risk class from a substrate adapter), gx-adapter-time carries no
external-service or credential surface at all (its own Cargo.toml doc comment: "No scheduler
client... [it] starts no process") and is closer in shape to fs/git than to postgres/mysql: 8
source files, 945 lines (git show HEAD:crates/gx-adapter-time/src/*.rs at ruling time), 7/7
SubstrateAdapter methods implemented, all three dependencies (gx-core, gx-canon,
gx-substrate) already public. It was leak-gate-scanned before this ruling: 0 hits across every
category the sync script checks (retired handle, absolute local paths, .env-named files, CJK,
secrets heuristics, private names) -- stronger than "no reason to suspect a problem." Its
Cargo.toml publish = false is left unchanged (matches gx-cli/gx-gate's existing pattern
of public source that is not yet a crates.io publish target; flipping publish is the separate,
already-blocked-once crates.io publish lane's decision, not this one). The prior 5-adapter
figures in §1 and §3's table above are still not re-measured at 6 -- this note corrects the
private/public claim only, not the line-count table, consistent with how the 2026-09-01 note
above handled the same page.
Third currency note (2026-09-02, same lane, found while verifying the above). §5's
"publish = false on every crate's Cargo.toml... no cargo add path to any of this" is now
stale for the 13-crate publish closure: req/1050_CRATES_IO_PUBLISH_LANE_COMPLETE_2026-09-01.md
records 13/13 real publishes to crates.io at 0.1.2 (tracefold, tracefold-core,
tracefold-canon, tracefold-log, tracefold-witness, tracefold-substrate, tracefold-gate,
tracefold-adapter-fs, tracefold-adapter-git, tracefold-engine, tracefold-adapter-mcp,
tracefold-api, tracefold-tui), independently re-verified anonymous (no gh auth) against
crates.io/api/v1/crates/<name> by this lane before writing this note: all 13 return 200 with
default_version: "0.1.2". gx-adapter-time is not among them (its publish = false claim
still holds, per this page's own second note above -- being in SYNC_CRATES makes its source
readable on GitHub, not installable via cargo add, exactly the distinction §5's opening bullet
already draws for gx-cli/gx-gate). docs/LIMITS.md was not re-checked by this note for the
same staleness; that is this note's own unread range.
Fourth currency note (2026-09-02, drift-repair lane). §4 said Fixture "has 11 methods: 5
required ... and 6 with defaults (uninvertible, commuting_pair, conflicting_pair, and 3
more)". Independently recounted against crates/gx-substrate-conformance/src/lib.rs (the trait Fixture block, :310-:379): 5 required methods at :312 (adapter), :316 (locator),
:319 (intent), :327 (reset), :333 (disturb) -- unchanged -- plus 7 methods with
defaults, not 6, at :336 (uninvertible), :341 (commuting_pair), :346
(conflicting_pair), :356 (promised_target), :361 (normalise), :366
(equivalent_spellings), :376 (resolve). Total = 12, corrected in §4 above (this note
does not delete the wrong figure from history -- it names what changed and why, per this page's
own no-delete pattern for the notes above). This was found while auditing this page end to end for
the same class of drift after Owner flagged the 11-vs-12 mismatch; the rest of the page's
numeric/name/signature claims (§1's 7-method SubstrateAdapter table, §2's MCP-census
percentages against req/1009's own table, the ArgSource enum's 8 variants, the run_all /
run_contracts / run_laws / is_conformant / is_complete / meets_51_7 function names, the
two gx-adapter-fs/tests/conformance.rs test names, and the "16 = 7 contracts + 9 laws" identity
against CONTRACT_IDS/LAW_IDS in gx-substrate-conformance) were independently recounted
against the current source in the same pass and matched -- this method-count pair was the only
mismatch found. §3's per-crate line-count table was not recounted (it is already framed above as
a dated HEAD-time audit snapshot, not a live figure, and carries its own currency-note pattern
for exactly this kind of staleness); a spot check found gx-adapter-mcp/src/catalogue.rs now at
3,094 lines against the table's "3,235 alone" -- ordinary drift consistent with that framing, not
a new logical error, so left to the next lane that re-measures §3 rather than patched here as a
one-off. Machine gate: tools/gates/doc_trait_method_count.mjs (new, this lane) parses this
page's Fixture/SubstrateAdapter method-count sentences and the corresponding Rust trait blocks
and fails on any mismatch -- see req/1083_985_FOLDBACK_AND_ADAPTER_GUIDE_DRIFT_REPAIR_2026-09-02.md
§B for the red/green proof.