Skip to content

Provider Architecture

Lex edited this page Aug 21, 2026 · 3 revisions

Provider Architecture

SpecGit derives acceptance from two TypeScript seams, behind one product contract: forge evidence flows exclusively through authenticated CLIs (gh for GitHub, glab for declared GitLab), and git facts come exclusively from local git. No direct REST client, no stored or logged tokens.

The two ports

GitPortsrc/gitfacts/port.ts

Local git facts plus the delivery-bootstrap write operations, implemented for production by LocalGitAdapter (src/gitfacts/local.ts).

Member Role
facts Repo, toplevel, branch, HEAD sha, dirty state, worktree layout, origin URL, upstream drift. Feeds the context and drift gates.
headContains Ancestor-or-equal containment of a full hex object id in local HEAD history; proves merged-delivery lineage.
checkoutOrCreateBranch Bootstrap write: check out the delivery branch, creating it from HEAD when absent.
commitFile Bootstrap write: pathspec-limited commit of one state file; unchanged file is a successful no-op.
pushBranch Bootstrap write: push the delivery branch with upstream (git push -u).
remoteDefaultBranch origin/HEAD for the PR base; falls back to main.
hooksPath The hooks directory git will actually use (linked-worktree and core.hooksPath aware).

ForgeProvidersrc/github/port.ts

Platform-neutral forge evidence and mutations, composed of two surfaces:

  • ForgeReadPort — evidence collection and delivery-lifecycle operations: preflight, getIssue, getOpenIssueNumbers, getOpenIssues, getPr, getCheckRuns, createIssue, createDraftPr, listOpenPrsByHead, addIssueComment.
  • ForgeAdminPort — branch-protection and auto-merge administration: getBranchProtection, enableBranchProtection, getRepoAutomerge, enableRepoAutomerge.

ForgeProvider = ForgeReadPort & ForgeAdminPort. There are no optional port members: each method feeds a gate or bootstrap decision, and each returns an Evidence envelope so a runtime failure is classified evidence that fails closed — never a silent skip. The pre-#169 name GitHubProvider stays exported as a deprecated compatibility alias.

The adapters

Implementation File Platform
GhCliGitHubProvider src/providers/github/gh-cli.ts GitHub — evidence through the authenticated gh CLI
GlabProvider src/providers/gitlab/glab-cli.ts Self-managed GitLab — evidence through the authenticated glab CLI
PlatformRoutingProvider src/providers/routing.ts The production composition — dispatches per call
LocalGitAdapter src/gitfacts/local.ts Local git (implements GitPort)

Both forge adapters share the CLI spawn transport at src/providers/cli-spawn.ts and mirror the same failure taxonomy per platform: gh_missing / gh_unauthenticated / gh_transport, and glab_missing / glab_unauthenticated / glab_transport (plus gitlab_version_unsupported for the self-managed version window). All are exit-3, fail-closed.

Routing

The production composition wires a single PlatformRoutingProvider. Every provider call dispatches on the ref's platform marker:

parseRepoRef(origin, declaration) → { owner, repo, platform: "github" | "gitlab" }
                                            │
                ┌───────────────────────────┴───────────────────────────┐
                ▼                                                       ▼
        GhCliGitHubProvider (gh)                                GlabProvider (glab)
  • A ref is marked platform: gitlab only when the origin matches a spec_git/providers.yaml declaration (host and port). The platform is declared, never guessed.
  • A GitLab-declared origin's issues, MRs, and pipeline jobs flow through glab; everything GitHub flows through gh. No gh call ever sees a group/subgroup ref.
  • classifyPlatform(originUrl) (the github | gitlab | unknown heuristic) is used only for diagnostics and interactive questions — it never resolves a ref and never grants provider capability.
  • An undeclared GitLab-looking origin fails closed as gitlab_unsupported; specgit doctor reports it on the origin probe.

Contract discipline

  • Both ports carry compile-checked member inventories (GIT_PORT_MEMBERS, FORGE_PROVIDER_MEMBERS, FORGE_READ_PORT_MEMBERS, FORGE_ADMIN_PORT_MEMBERS) beside their interfaces; a contract test pins the docs' tables to those lists member-for-member.
  • Adding a required member is compile-breaking by design: one delivery updates the port, every implementation, the inventories, and the docs together.
  • Optional members exist only on evidence facts (e.g. IssueFact.title), never on port methods, each with a written fallback.
  • Deprecation: mark @deprecated, one release of overlap, then remove port + implementations + inventories + docs in one delivery.

Chinese version: Provider-Architecture-zh

Clone this wiki locally