ADR-0008: Spec-Driven Provider Integration
Status
Superseded by ADR-0018
Context
Praetor integrates with external providers (Git forges today; package registries and infrastructure APIs are in scope for the fleet) through internal/forge: a nine-method Forge interface with three hand-written drivers. The GitHub driver (internal/forge/github.go, 429 LOC) hand-codes six REST path families through a single repoPath() template, sends X-GitHub-Api-Version: 2022-11-28, and short-circuits every mutating method to fabricated success when the token carries a test- prefix. The GitLab and Gitea drivers (106 and 114 LOC) contain no HTTP client and return synthetic success from every method; NewForge has no non-test caller because all four production call sites (cmd/standardsctl/{sync,needs,issue}.go) construct the GitHub driver directly. A second, divergent GitHub client lives in internal/milestone/forge.go (unversioned Accept: application/vnd.github.v3+json, unbounded io.ReadAll), the Projects v2 query in internal/forge/project.go is built by string interpolation, and AddItem shells out to the gh CLI and fabricates a local cache record when that fails. Token resolution is implemented in four places. The 2026-09 deep audit confirmed 45 findings inside internal/forge and 6 in internal/milestone/forge.go; roughly 30 of them are transport or request-shape defects that exist only because each endpoint is a hand-written wrapper.
The owner's direction, paraphrased: supporting a provider must mean supporting the provider's whole published API surface, driven by the provider's own machine-readable API description and version-pinned documentation, plus the provider's native MCP server where one exists. Adding a provider should cost a small amount of Go plus declarative configuration, and this must be a general capability of the core program (in the style of internal/flavor), so that one mechanism covers every provider the fleet talks to.
The upstream facts that shape the design were verified on 2026-09-12. GitHub publishes an MIT-licensed OpenAPI 3.0 description (github/rest-api-description) generated from the same source that validates production requests: 815 paths, 1,229 operations, 975 schemas, 272 webhooks, with calendar API versions as literal filenames (api.github.com.2022-11-28.json, api.github.com.2026-03-10.json) across 30 product directories (dotcom, GHEC, GHES 2.18-3.22). It is regenerated several times a day and its GitHub Releases are stale, so the only honest pin is a commit SHA plus a content digest. Every operation carries an operationId, an externalDocs.url into docs.github.com, and x-github metadata (category/subcategory on all 1,229, enabledForGitHubApps, githubCloudOnly, deprecationDate/removalDate, triggersNotification), and 86 x-github-breaking-changes entries carry JSON-Patch deltas keyed by API version. Three things the description does not model must be hand-written by any consumer: authentication (no securitySchemes, no security block), rate limiting (x-rate-limit-* headers are referenced only on GET /rate_limit), and pagination (three styles: page, before/after cursor, since; Link is referenced by 205 operations while per_page/page appear on 252). Projects v2 and Discussions are GraphQL-only; the public SDL is downloadable but unversioned, with breaking changes on quarterly boundaries. GitHub's official MCP server (github/github-mcp-server, v1.12.1) is hand-written over google/go-github and exposes 95 tools across 22 allow-listed toolsets for 1,229 operations; Cloudflare's server exposes 3 tools for roughly 2,500 endpoints by keeping the description server-side. GitLab publishes an autogenerated openapi_v3.yaml with a documented coverage deficit on /projects, /groups and /issues (work item #486493) and a beta MCP server of about 65 hand-curated tools; Gitea serves /openapi.v1.json (OpenAPI 3.0) from 1.27.0 and /swagger.v1.json (Swagger 2.0) everywhere; Forgejo serves Swagger 2.0 only; PyPI publishes no OpenAPI description at all and npm's official description omits the packument read path.
Praetor's constraints decide the shape. The root go.mod has exactly one external dependency (gopkg.in/yaml.v3). HISS-01 prohibits recursion in the shipping call graph, and OpenAPI $ref/oneOf/allOf resolution is inherently recursive (269 oneOf, 68 allOf, 34 anyOf in the GitHub document). HISS-04 and HISS-10 cap complexity and forbid warnings, which rules out committing whole-surface generated clients (ogen's GitHub example emits about 23 MB of Go across 21 files). Several existing mechanisms are directly reusable: the mutex-guarded name-keyed registry in internal/flavor, sha256 lock verification in cmd/standardsctl/lockdigest.go, name@version keying in internal/docdistill, the provider-agnostic tool model in internal/mcp, Ed25519 receipts and NeutralizeInjection in internal/lockdown, LimitTracker in internal/router, and editor config synthesis in internal/editor. Three candidate designs were evaluated by two independent reviewers: build-time code generation from a pruned description, delegation to native MCP servers with a gap executor, and an offline compiler that reduces the description to a flat operation table interpreted at runtime. The third was selected; the strongest elements of the other two are merged below.
Decision
- A provider is a descriptor plus a compiled operation table, not a driver:
- The canonical source for a provider (HISS-16) is
.config/providers/<name>.yaml. Everything else - the compiled table, the HTTP requests, the MCP tools, the CLI subcommands, the editor MCP configs, the distilled documentation and the contract tests - is compiled from it, generalizing the compile-one-source-to-many-targets pattern of ADR-0001 from agent context to provider integrations. - Package layout:
internal/provider(descriptor schema and validator, registry mirroringinternal/flavor/flavor.go:42-72but populated from.config/providers/*.yaml,OpTabletypes and loader, digests);internal/provider/exec(request building, parameter binding, transport, rate-limit governor, three pagination drivers, response classification);internal/provider/auth(one token resolver replacing the four existing paths, plus one module per auth scheme);internal/provider/graphql(persisted-document executor over a digest-pinned SDL);internal/provider/capability(provider-neutral verbs and theforge.Forgeshim);internal/provider/govern(the single policy funnel in front of every call);internal/provider/tools(operation-to-mcp.Toolprojection and toolsets);internal/provider/recordandinternal/provider/providertest(cassettes and the hermetic outbound-request validator);internal/provider/mcpclient(praetor's first MCP client, last phase);tools/specc(the offline compiler, a separate Go module);cmd/standardsctl/provider.go(praetorctl provider <add|pin|compile|lock|doctor|explain|audit|diff|describe|call|record|verify>);cmd/standards-mcp/tools_provider.go. - Committed per provider:
.config/providers/<name>.yaml,.config/providers/<name>/optable.json(compiled table),.config/providers/<name>/spec.pruned.json(the$refclosure of the allow-listed operations, extracted from the upstream document so that recompilation is offline),.config/providers/<name>/overlay.json(optional JSON-Patch),.config/providers/<name>/LICENSE.upstream(the vendor's licence notice for the vendored bytes),.config/providers/<name>/graphql/*.graphql(persisted documents), andtestdata/providers/<name>/*.json(redacted cassettes). - Descriptor schema (top-level YAML keys of
.config/providers/<name>.yaml; the JSON Schema lives at.config/providers/_schema/provider.schema.json): version,name,kind(forge|registry|infra).spec:format(openapi-3.0|openapi-3.1|swagger-2.0),source{repo,commit(40-hex, branch names rejected),path,url} or {instance_url,retrieved_at} for instance-served descriptions,api_version(asserted equal to the calendar version embedded in the pinned filename where the vendor uses one),digest(sha256 of the fetched bytes),license,overlay.product(for exampledotcom|ghec|ghes-3.22; selects the upstream directory on re-pin),base_urlorbase_url_from: instance.auth:scheme(bearer|token_prefix|basic|app|oauth_bearer),env(ordered precedence),fallback(gh-cliroutes throughutil.ResolveAuthTokenContext),headers(static request headers such asAcceptandX-GitHub-Api-Version).ratelimit: header names for limit/remaining/reset/retry-after,points_per_minute,write_cost,max_concurrent,content_creating_per_minute,notification_interval_ms(applied to operations flaggedx-github.triggersNotification).pagination:default(link|page|cursor|since),page_param,per_page_param,per_page_max,max_pages,max_items;timeouts:request_s,walk_s.operations:include_categories(vendor taxonomy, e.g.x-github.category),includeandexclude(operationIds). This allow-list is the compiled surface;provider_callreaches all of it.capabilities: map from a stable praetor verb (issues.create,issues.list,issues.update,labels.upsert,pr.create,status.post,protection.reconcile,milestones.list,milestones.create) to{op, fallback, on, lookup, filter, label_merge, args}.graphql:sdl_url,sdl_digest,documents,capabilities(verb to persisted document).mcp:expose,toolsets,read_only,max_tools,prefer(spec|native|native-then-spec, defaultspec),native_only, andnative{id(MCP-registry name),transport,imageorurl(pinned by OCI or binary digest, neverlatest),env_allow,toolsets,read_only,lockdown}.policy:mutations(allow_list, deny-by-default),allowed_mutations,receipts(required|off),untrusted_content(fence|lockdown|off),budget{calls_per_run,cooldown_s}.docs:distill(bound|none),external_docs.- Offline compiler and the dependency decision:
tools/speccis a separate Go module (tools/specc/go.mod) that is never imported by any shipped binary. It is the only place in the repository that depends ongithub.com/getkin/kin-openapi(v0.149.0), chosen overpb33f/libopenapibecause kin-openapi reads Swagger 2.0 through OpenAPI 3.1 and libopenapi does not read Swagger 2.0, which would exclude Forgejo and Gitea below 1.27. This mirrorsgoogle/go-github'stools/metadata, which materializesopenapi_operations.yamlfrom the same description with the same library.- The root
go.moddoes not change: it keeps exactlygopkg.in/yaml.v3. The single-external-dependency policy is interpreted as applying to the shipped module graph, and a CI gate (go list -deps ./...from the root must not containkin-openapi) enforces that interpretation mechanically. The owner ratifies this interpretation by accepting this ADR. specc pinfetches the pinned document (30 s deadline, 32 MiB bound), applies the overlay, selects the allow-listed operations, writesspec.pruned.json, and compiles.specc compile --offlinerecompiles from the committed pruned subset and must reproduceoptable.jsonbyte-for-byte (sorted iteration, no timestamps, compiler version stamped in the artifact header). Each table entry is flat:{operationId, method, pathTemplate, params[{name, in, required, type, style, explode, multiSegment}], bodyFields, successStatuses, errorStatuses, paginationStyle, minIntervalMs, deprecated, deprecationDate, removalDate, cloudOnly, appEnabled, docsURL, category, subcategory, responseShape}. Flattening uses an explicit worklist stack (maxSchemaDepth32,maxSchemaNodes4096), so even the tool satisfies HISS-01.- The compiler fails when a bound operation requires a parameter style, content type or body shape the executor does not implement (closed-style rule); it also normalizes Swagger 2.0 to OpenAPI 3.0 for Forgejo and Gitea below 1.27, validates persisted GraphQL documents against the pinned SDL, emits contract-test data tables, and replays
x-github-breaking-changesforpraetorctl provider diff. - Runtime code generation is rejected: the runtime emits zero generated Go, so no golangci-lint or
internal/hissexemption is introduced. The escape hatch for a future provider that genuinely needs hundreds of typed operations isoapi-codegenwithinclude-tags/include-operation-ids; that is an amendment to this ADR, not a descriptor flag. - Runtime executor:
internal/provider/execreads only the flat table usinggopkg.in/yaml.v3,encoding/jsonandnet/http. Given(provider, operationId, args)it validates arguments against the entry, binds path (includingx-multi-segmentparameters), query, header and body, injects authentication and the static headers, sends under a per-request context deadline, classifies the status into typed errors (ErrRateLimited,ErrNotFound,ErrConflict,ErrUnprocessable,ErrDeprecated,ErrBoundExceeded), honours rate limits, and drives pagination. Branching lives in data, so cyclomatic complexity is flat in the number of operations.- The three subsystems no description models are hand-written once and shared by every provider: authentication (
internal/provider/auth, foldingutil.ResolveAuthTokenContext, the GitHub driver's bypass,sync.go's rawos.Getenvandneeds.go's fallback into one resolver, with GitHub App installation-token minting as one module per auth scheme rather than per provider); the rate-limit governor (x-ratelimit-*,retry-after, points-per-minute with write cost,triggersNotificationthrottle, jittered bounded retries; an unparseable rate-limit response backs off rather than proceeding); and pagination (link,page,cursor,sincebehind one bounded iterator that returnsErrBoundExceededonmax_pagesrather than a silently short list). - Bounds (HISS-02):
maxOperationsPerTable2048,maxParamsPerOp64,maxBodyFields128,maxPages100,maxItemsPerWalk10000,maxRetries4, response bodies throughio.LimitReaderat 16 MiB, error bodies truncated at 8 KiB, default request deadline 15 s and walk deadline 120 s. The executor takes itshttp.RoundTripperas a required constructor argument; a static test asserts thatinternal/providernever referenceshttp.DefaultClientorhttp.DefaultTransport. - Capability bindings replace the forge drivers; governance code stays:
internal/provider/capabilityresolves a verb to an operation with fallback chains (labels.upsert= update, on 404 create;protection.reconcile= lookup, then update or create).capability/forgeshim.goimplements the existing nine-methodforge.Forgeinterface over capabilities so the four production call sites change one constructor line each andNewForge's switch becomes a registry lookup.internal/forge/github.go,gitlab.go,gitea.go, the remote paths ofproject.go, andinternal/milestone/forge.goare deleted in Phase 2.internal/forgekeeps praetor's own governance semantics (reconciler.go,pr.go,wiki.go,discussions.go,issues.go) and becomes the governance package; the provider boundary is enforced by package structure.- GraphQL is a parallel kind, not an afterthought:
internal/provider/graphqlposts persisted documents with a variables map and no string interpolation; the SDL is digest-pinned and documents are validated at compile time. Projects v2 and Discussions move here. GHES GraphQL is unsupported until an SDL source for GHES is identified. - Versioning and digest pinning:
.standards.lockgains aproviders:list reusinglockEntry{id, version, digest}(cmd/standardsctl/lockdigest.go) withid: "<name>@<api_version>"anddigest= sha256 over the sorted set of descriptor,optable.json,spec.pruned.json, overlay and persisted GraphQL documents; the upstreamspec.digestandsdl_digestare pinned inside the descriptor. A separatemaxProviderArtifactBytes(8 MiB) bound is added next tomaxArchetypeBytes;maxLockEntries(256) is unchanged.praetorctl auditrecomputes every digest and rejects placeholders exactly as it does for archetypes.praetorctl provider doctor <name>fails closed when a bound capability resolves to no operation, when a bound operation isdeprecatedor inside itsremovalDatewindow (warn at 90 days), when the descriptor targets a GHESbase_urlbut a bound operation isgithubCloudOnly, whenauth.scheme: appbinds an operation withenabledForGitHubApps: false, or when a capability's argument map does not satisfy the operation's parameter set.praetorctl provider diff --from <v> --to <v>replays the vendor's breaking-change patches and reports only deltas touching bound operations.- Refresh is a scheduled pull request (the repository already carries
.config/renovate) that bumps the commit SHA, re-pins, and attaches the diff report; an empty report is a rubber stamp, a non-empty one requires review. The GraphQL SDL is re-validated on a quarterly calendar because it carries no version signal. - MCP exposure and consumption:
- Exposure follows the verified vendor precedents (95 tools for 1,229 operations; 3 tools for 2,500 endpoints), never one tool per operation.
cmd/standards-mcpregisters, per provider withmcp.expose: true: one bound tool per capability (github_issues_create; underscore-prefixed so names surviveinternal/mcp/bridge.go's OpenAI conversion) and three meta-tools (<name>_search_operations,<name>_describe_operation,<name>_call) that reach the whole compiled surface at a few hundred tokens of tool definitions. Toolsets partition on the vendor taxonomy withallanddefaultpseudo-sets and a--read-onlyswitch that takes priority over explicit selection;maxToolsPerProvideris 128. - Annotations are computed by praetor from the operation's HTTP method and the capability's declared mutation class (GET/HEAD read-only and idempotent; PUT and DELETE idempotent and destructive; POST and PATCH non-idempotent), overridable per operation in the descriptor; upstream annotations are never relayed.
internal/mcpgainsNewRemoteTool(...)becauseOpenWorldHintis hard-coded false in both existing constructors (internal/mcp/tool.go:86,:112), andbridge.gois extended to carry annotations into the Anthropic shape instead of dropping them. - Consumption defaults to
prefer: spec. Routing through a vendor server that exposes fewer operations than the vendor's own description would be a coverage downgrade (GitLab about 65 beta tools, gitea-mcp about 60).nativeis used only for surfaces OpenAPI cannot express (native_only: [projects, discussions]for GitHub) or where the native server is itself spec-driven and broader than praetor's bound set (Cloudflare qualifies). Native servers are pinned by OCI or binary digest with their MCP-registry identity recorded, spawned with an environment allow-list and no shell, recorded in the SBOM produced byinternal/supplychain, and preferred over HTTP in CI.internal/provider/mcpclientis the last phase and lands only after the current MCP protocol revision and the vendors' support matrix have been verified against the published specification. praetorctl editors syncregenerates.claude/mcp.json,.cursor/mcp.jsonand.gemini/mcp_config.jsonthroughinternal/editor.Synthesize. The default keeps only praetor's own binary registered, so agents receive the governed surface; registering a vendor server directly is an opt-in (editors.expose_upstream: true) and bypasses receipts, budget and sanitization, which the generated config states in a comment.- Security controls (
internal/provider/govern, the single funnel every capability call and every<name>_callpasses through): - Mutation allow-list: non-GET operations are denied unless the capability or operationId appears in
policy.allowed_mutations; undermcp.read_onlyno mutation is reachable regardless of tool selection; a tool result can tighten but never loosen an operator setting. - Receipts: when
policy.receipts: required, every mutating call produces an Ed25519 receipt viainternal/lockdown.CreateReceiptover the canonical request digest, verified withVerifyPinnedReceipt; receipts and an append-only, digest-chained audit log of{capability, provider, operationId, args digest, receipt}are written under.workingdir/. - Budget gating: a per-provider tracker with the
RecordUsage/Record429/IsCoolingDownshape ofinternal/router.LimitTrackerenforcespolicy.budgetand a 60 s quarantine after secondary-limit responses. - Untrusted content: every text field a provider returns (issue bodies, PR descriptions, comments) passes through
internal/lockdown.NeutralizeInjectionbefore it reaches a model, and is fenced and flagged in tool results;lockdownadditionally drops content from authors without push access, mirroringgithub-mcp-server's mode, which is a content filter and not an authorization boundary. - Documentation distillation per pinned version:
internal/docdistillgainsKindProviderSpec(key<name>-api@<api_version>, generated structurally from the table: oneAPISurfaceline per bound operation with method, path, operationId anddocsURL;ConfigRulesfrom the descriptor;Invariantsfrom the vendor extensions),KindProviderOperation(key<name>-api/<operationId>@<api_version>, harvested from each bound operation'sexternalDocs.urlthrough the existing bounded fetcher, bound operations only), andKindProviderSchema(the pinned SDL, reduced to the types the persisted documents select). Provider kinds bypass the Go-syntax regex extractor and reuse only rendering, the 400-token budget and content hashing.AuditDocumentationCoveragerequires a distilled document per bound operation, marks a providerMissingwhen unpinned, and populates the currently unfilledStalefield when the lock digest no longer matches the catalog key. Because the executor sends the descriptor'sapi_versionand the catalog is keyed on the same value, an agent cannot read documentation for a version the binary does not call.compile-contextgains a generated provider section (capability table plus the three meta-tool names) within the ADR-0001 line budget.-
Testing strategy:
specc gen-testsemits contract-test data tables per bound operation, run by one hand-written runner: positive (canonical arguments produce the exact method, path, query and headers on the built*http.Request), negative (each required parameter omitted, unknown parameter, type mismatch, slash in a non-multi-segment path parameter, call to a deprecated operation), boundary (per_pageat 1, at the maximum and above it, empty page, final page withoutrel="next", response at exactly the body bound, 429 withretry-after). HISS-15 coverage becomes a property of binding an operation.internal/provider/providertestis anhttptest.Serverthat validates every outbound request against the pruned spec (path template, required parameters, enum legality, content type, API-version header);internal/provider/recordreplays cassettes through the injectedRoundTripper;TestFixturesAreRedactedfails the build on token-shaped strings intestdata/;TestOptableDigestMatchesLockverifies the lock;make provider-verify(added toverify-all) recompiles offline and byte-diffs the committed table and runstools/specc's own tests.go test -race ./...never opens a socket or spawns a vendor binary. Live conformance (praetorctl provider verify --live --read-only, build tagprovider_live,PRAETOR_PROVIDER_RECORD=1for re-recording) runs on a schedule against a scratch repository with narrowly scoped credentials and files drift as a finding. Thetest-token stub mode and the live-mutatingproject_test.goare deleted rather than repaired.-
Phased migration and the supersession contract:
-
Phase 0: measurement spikes (peak memory and wall time of compiling the 12.96 MB GitHub document; operation-by-operation audit of GitLab's
openapi_v3.yamlagainst the bound set), ledger reconciliation, and notification of the G07a/G07b fix-wave owners before either the waves or Phase 1 merge. - Phase 1 (first milestone, about day 15):
internal/providercore,tools/specc,.config/providers/github.yamlbinding the seven Forge capabilities plus milestones, the govern funnel, cassettes and contract tests, behindPRAETOR_PROVIDER_ENGINE=1. A differential test replays every existing driver request through the new executor and asserts byte-identical requests. Exit criterion: GitHub fully supported from its own description with fewer than 150 lines of provider-specific Go (the expected count is zero for the PAT-authenticated bound set; the GitHub App installation-token module is the only permitted provider-specific code). If the executor cannot reproduce the existing requests from the pinned description, the design is rejected at this point. - Phase 2:
forgeshim, flip the default, delete the three drivers, the remote paths ofproject.goandinternal/milestone/forge.go, migrate the four call sites, collapse token resolution. Phase 3: MCP exposure,NewRemoteTool, editor sync, docdistill kinds,provider doctor/diff, lock extension. Phase 4: GraphQL kind, then Gitea (1.27 or later), Forgejo (via the Swagger 2.0 normalizer) and GitLab (in mixed spec/overlay mode, and only if the Phase 0 audit supports it). Phase 5 (optional):mcpclientand native consumption. - Supersession: the roughly 30 transport-defect findings in
internal/forgeandinternal/milestone/forge.goenumerated in the implementation plan are markedsuperseded-by: ADR-0008 (conditional)in the ledger rather than patched. The supersession is valid only once Phase 2 has merged tomain; if Phase 2 has not merged by 2026-11-30 or this ADR is not Accepted, the entries revert to open and are fixed in place. A superseded finding must never become an unfixed one.
Owner decisions (2026-09-12, recorded in .workingdir/QUESTIONS.md Q-001..Q-003): the single-dependency interpretation is ratified (the OpenAPI parser lives only in the separate tools/specc module); the hand-written forge fixes from the audit are merged now and superseded by this layer later, serving as the differential-test oracle for milestone 1; and v1 covers GitHub, GitLab and Gitea/Forgejo, with milestone 1 (GitHub) remaining the go/no-go gate and the GitLab and Forgejo bindings following within v1 after their Phase 0 description audits. Structural deduplication of the application (ADR-0009, Proposed) precedes the new modules.
Consequences
- Positive: Provider coverage becomes the provider's own published description, pinned by commit SHA and content digest, rather than the subset a maintainer hand-coded; the entire compiled surface is reachable through capabilities, the CLI and the
<name>_callmeta-tool. - Positive: Adding a provider is about 70 lines of YAML, a pin and a compile, with zero Go in the common case; the same pipeline absorbs Swagger 2.0 providers through the offline normalizer, envelope-shaped APIs through one adapter interface, and registries through a praetor-authored spec fragment.
- Positive: The runtime ships no generated Go and no new dependency; recursion is confined to an offline tool that is never linked, so HISS-01, HISS-04 and HISS-10 are satisfied without new exemptions, and the root
go.modstays at one external dependency. - Positive: About 30 confirmed audit findings are dissolved by deleting the code they describe, three fabricated-success paths (the
test-stub, theghCLI fallback, the stub drivers) disappear, andinternal/forgebecomes a governance-only package roughly half its current size. - Positive: Vendor deprecation and breaking-change metadata that praetor currently discards becomes machine-checked gates (
provider doctor,provider diff), and agent-facing documentation is keyed on the identical API version the executor sends. - Positive: One governance funnel gives every provider call an allow-list, an Ed25519 receipt, a budget and injection sanitization, reusing
internal/lockdownandinternal/router. - Negative: The single-dependency policy is reinterpreted as applying to the shipped module graph;
tools/specccarrieskin-openapi, a sub-v1 library with documented breaking changes, and a breakage there blocks re-pinning until fixed (it cannot reach a build, because the compiled table is committed). - Negative: Two artifacts describe the API (the upstream description and the committed table); the byte-diff gate proves reproducibility, not correctness, and only the scheduled live-conformance run proves the table still matches the provider.
- Negative: Authentication, rate limiting and pagination cannot be derived from any description and remain about 600 lines of load-bearing hand-written code that every provider depends on; the per-process rate-limit governor cannot see concurrent agent dispatches, so fan-out relies on 429/
retry-afterconvergence. - Negative: GitLab support is conditional on an unproven coverage audit and may require a large overlay; Forgejo requires the Swagger 2.0 normalizer; PyPI and npm require praetor-authored spec fragments that praetor must then keep honest; GHES GraphQL is unsupported.
- Negative: The
<name>_callmeta-tool is a larger attack surface than thirteen hand-written tools and depends on the allow-list, read-only precedence and content fencing being enforced in code rather than in configuration discipline. - Negative: About 30 in-flight fixes in the G07a/G07b waves become wasted work if they land before Phase 2; the supersession contract requires an owner decision before either merges.
References
- Owner direction, cited verbatim from the design brief supplied to this task (the brief itself paraphrases the owner; no verbatim transcript was available): "when praetor 'supports GitHub' (or any provider), that must mean supporting the provider's whole API surface, driven by the provider's own published API description and versioned documentation, plus its native MCP server where one exists - not a hand-written wrapper per endpoint that has to be prompted into existence one function at a time."
- GitHub REST API description (OpenAPI 3.0 under
descriptions/, 3.1 underdescriptions-next/, MIT): https://github.com/github/rest-api-description ; vendor extensions: https://raw.githubusercontent.com/github/rest-api-description/main/extensions.md - GitHub REST API calendar versioning (
X-GitHub-Api-Version, 2022-11-28 and 2026-03-10): https://docs.github.com/en/rest/about-the-rest-api/api-versions - GitHub REST rate limits: https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api ; pagination: https://docs.github.com/en/rest/using-the-rest-api/using-pagination-in-the-rest-api
- GitHub GraphQL public schema and breaking-change policy: https://docs.github.com/public/fpt/schema.docs.graphql ; https://docs.github.com/en/graphql/overview/breaking-changes
- GitHub official MCP server (v1.12.1, 95 tools, 22 toolsets, lockdown mode,
server.json): https://github.com/github/github-mcp-server google/go-githubmetadata pipeline over the description (tools/metadata/openapi.go,openapi_operations.yaml): https://github.com/google/go-github- Kiota-generated official Go SDK (alpha): https://github.com/octokit/go-sdk ; ogen GitHub example (
examples/ex_github): https://github.com/ogen-go/ogen ; oapi-codegen subsetting options: https://github.com/oapi-codegen/oapi-codegen - kin-openapi v0.149.0: https://pkg.go.dev/github.com/getkin/kin-openapi ; libopenapi v0.38.7: https://pkg.go.dev/github.com/pb33f/libopenapi
- GitLab OpenAPI description and coverage deficit: https://docs.gitlab.com/api/openapi/ ; https://gitlab.com/gitlab-org/gitlab/-/work_items/486493 ; GitLab MCP server: https://docs.gitlab.com/user/model_context_protocol/mcp_server/
- Gitea API usage, 1.27.0 OpenAPI 3.0 endpoint, per-version spec files, official MCP server: https://docs.gitea.com/development/api-usage/ ; https://blog.gitea.com/release-of-1.27.0/ ; https://docs.gitea.com/api/ ; https://gitea.com/gitea/gitea-mcp
- Forgejo API usage and compatibility contract: https://forgejo.org/docs/latest/user/api/usage/
- Cloudflare API schemas and spec-driven MCP server: https://github.com/cloudflare/api-schemas ; https://github.com/cloudflare/mcp
- npm registry OpenAPI: https://api-docs.npmjs.com/ ; PyPI API documentation (no OpenAPI): https://docs.pypi.org/api/
- Internal precedents: ADR-0001 (single canonical source compiled to vendor targets), ADR-0004 (Ed25519 Exit-0 receipts),
cmd/standardsctl/lockdigest.go,internal/flavor/flavor.go,internal/docdistill/cache.go,internal/mcp/tool.go,internal/lockdown/{receipts,sanitize}.go,internal/router/limits.go,internal/editor/editor.go. - Audit ledger: a private audit ledger (not published), findings F157-F220, F669, F677 in
internal/forgeand F205-F216 ininternal/milestone/forge.go; the implementation plan is kept in the same private ledger.