Skip to main content

Provider Adapter Contract

The provider adapter contract defines the normalized output that every source control and CI provider adapter must produce. The signal inference engine consumes these normalized types and never interacts with raw provider shapes.

This contract is not the semantic core, not the API contract, and not the storage schema. It is the boundary that keeps adapters thin and the core provider-neutral.

Position in the architecture​

Provider API (GitHub / GitLab)
│
▼
Provider Adapter
(normalizes to this contract)
│
▼
Normalized Input Types ← defined here
│
▼
Signal Inference Engine
│
▼
HTTP API / MCP Tools

Every type in this contract maps to one or more concepts from the canonical flow model. No type introduces a concept not needed by at least one of the six MVP signals in the signal catalog.

Normalized input types​

NormalizedRepository​

Maps to: work item scope, ownership context.

FieldRequiredTypeNotes
provideryesenum: github, gitlabIdentifies the provider system
provider_idyesstringProvider-scoped stable identifier
full_nameyesstringe.g. org/repo
default_branchyesstringTrunk branch name
visibilitynoenum: public, private, internal
archivednoboolean
fetched_atyesISO 8601 timestampWhen data was retrieved from the provider API

NormalizedChange​

Maps to: Change (the reviewable modification under proposal).

FieldRequiredTypeNotes
provideryesenum
provider_idyesstring
repository_idyesstringMatches NormalizedRepository.provider_id for the same provider
source_branchyesstring
target_branchyesstring
stateyesenum: open, closed, merged, locked
author_actor_idyesstringMatches NormalizedActor.provider_id
created_atyesISO 8601 timestamp
updated_atyesISO 8601 timestamp
titlenostring
is_draftnobooleanGitHub draft PR or GitLab draft MR
work_item_refsnoNormalizedWorkItemRef[]Empty array when none detected; never null
merged_atnoISO 8601 timestampPresent only when state is merged
closed_atnoISO 8601 timestampPresent only when state is closed
fetched_atyesISO 8601 timestamp

NormalizedActor​

Maps to: Actor (a person or automation accountable in the flow).

FieldRequiredTypeNotes
provideryesenum
provider_idyesstring
display_nameyesstringBest-effort human-readable name
provider_loginnostringUsername as shown in the provider UI
team_membershipsnostring[]Team or group slugs

NormalizedReviewState​

Maps to: Review state (human review status for a change).

FieldRequiredTypeNotes
change_idyesstringMatches NormalizedChange.provider_id
stateyesenum: awaiting_review, under_review, changes_requested, approved, not_required
as_ofyesISO 8601 timestampWhen this state was last computed
reviewer_actor_idsnostring[]Actors assigned or who have responded
reviewer_team_namesnostring[]Requested review teams or groups
last_activity_atnoISO 8601 timestampMost recent review action
approval_countnointeger
required_approval_countnointeger
is_partialnobooleanTrue when review requirements or reviewer identity are incomplete

NormalizedValidationRun​

Maps to: Validation state (automated checks or manual gates).

FieldRequiredTypeNotes
change_idyesstring
scopeyesenum: branch, trunkbranch = pre-merge; trunk = post-merge integration
stateyesenum: pending, running, passed, failed, flaky, skipped
run_atyesISO 8601 timestampWhen the run was recorded or last updated
namenostringCheck name or pipeline name
urlnostringLink to run details
duration_secondsnointeger
failure_summarynostringBrief failure description; no stack traces
is_partialnobooleanTrue when the run record is incomplete or association is ambiguous

NormalizedMergeEvent​

Maps to: Change stage transition (implementation complete; feeds aging and trunk validation signals).

FieldRequiredTypeNotes
change_idyesstring
merged_atyesISO 8601 timestamp
target_branchyesstring
merged_by_actor_idnostringAbsent when the provider does not expose the merge actor
merge_commit_shanostring

NormalizedEvidenceState​

Maps to: Evidence state (required evidence for compliance or risk acceptance).

FieldRequiredTypeNotes
change_idyesstringMatches NormalizedChange.provider_id
stateyesenum: not_required, required, pending, recorded, stale
as_ofyesISO 8601 timestampWhen this evidence state was last computed
required_typesnostring[]Names of required evidence types, e.g. security attestation, deployment trace
owner_actor_idnostringActor responsible for supplying the evidence; matches NormalizedActor.provider_id
freshness_atnoISO 8601 timestampWhen evidence was last confirmed fresh; absent when state is pending, required, or not_required
is_partialnobooleanTrue when evidence requirements could not be fully determined

NormalizedOwnershipHint​

Maps to: Ownership state (clarity of accountability for a scope).

At least one of owner_actor_ids or owner_team_names must be present when the hint represents a known owner. An empty-owner hint with confidence: inferred signals that ownership data could not be found.

FieldRequiredTypeNotes
repository_idyesstring
owner_actor_idsnostring[]
owner_team_namesnostring[]Team or group slugs
path_patternnostringGlob pattern from CODEOWNERS; absent means repository-level hint
sourcenoenum: codeowners, group_membership, branch_protection, manualHow the hint was derived
confidencenoenum: declared, inferreddeclared when from an explicit file or rule
is_partialnobooleanTrue when owner identities could not be fully resolved

NormalizedWorkItemRef​

Maps to: Work item (reference only; not a full work item record).

FieldRequiredTypeNotes
external_idyesstringAs seen in the provider (issue number, ticket key)
titlenostringBest-effort from PR/MR body or linked issue
providernostringSource system if known
urlnostring
statenostringCoarse state if fetchable: open or closed

Normalization rules​

Review state​

Provider signalNormalized state
GitHub: no reviewer assignedawaiting_review
GitHub: reviewer assigned, no response yetawaiting_review
GitHub: reviewer has commented but not approved or requested changesunder_review
GitHub: CHANGES_REQUESTED from any reviewerchanges_requested
GitHub: required approvals metapproved
GitHub: review bypass rule active or review not requirednot_required
GitLab: approvals required > 0, not metawaiting_review
GitLab: at least one reviewer has responded without approvingunder_review
GitLab: blocking note without approvalchanges_requested
GitLab: required approvals metapproved
GitLab: approval rules disabled or MR lockednot_required

Validation state​

Scope assignment: a validation run is branch scope if it ran before merge (triggered on the PR or MR). It is trunk scope if it ran after merge on the default branch or trunk integration line.

Provider signalNormalized state
GitHub Checks API: successpassed
GitHub Checks API: failure or errorfailed
GitHub Checks API: in_progressrunning
GitHub Checks API: queued or waitingpending
GitHub Checks API: neutral or skippedskipped
GitHub legacy commit status: successpassed
GitHub legacy commit status: failure or errorfailed
GitHub legacy commit status: pendingrunning
GitLab Pipeline: successpassed
GitLab Pipeline: failedfailed
GitLab Pipeline: runningrunning
GitLab Pipeline: pending or createdpending
GitLab Pipeline: canceled, skipped, or manualskipped
Same scope alternates passed/failed across recent runsflaky

Ownership hints​

Provider signalsourceconfidence
GitHub CODEOWNERS file entrycodeownersdeclared
GitLab Code Owners file entrycodeownersdeclared
GitLab group owner membershipgroup_membershipdeclared
GitHub branch protection required reviewersbranch_protectiondeclared
Derived from past PR/MR author historyomitinferred
No ownership data foundemit hint with empty owner listsinferred

Work item references​

Adapters must extract work item references from PR/MR title and description using provider-standard patterns, for example closes #123 or fixes #123 for GitHub and Closes #123 or Related to #456 for GitLab. If no references are detected, work_item_refs must be an empty array. Adapters must not infer work items from branch name patterns alone.

Provenance​

Every normalized type carries provider and fetched_at. These fields:

  • Identify which provider system the data came from.
  • Enable staleness detection in the inference engine.
  • Support audit and debugging without requiring full raw event storage.

Adapters must record fetched_at as the time data was retrieved from the provider API, not the time of the underlying provider event.

The optional is_partial flag (boolean) must be set to true on any type where the adapter could not retrieve all required fields due to a provider API gap or authorization constraint. Signal inference must treat partial inputs as reduced-confidence.

Deferred GitHub semantics in the MVP slice​

The current GitHub adapter intentionally defers some provider nuances:

  • Stale approvals after new commits and dismissed reviews are not expanded into reviewer identity changes.
  • Requested review teams are surfaced as team names; individual team members are not expanded.
  • Branch protection rules that require specific code owners are represented as partial ownership when identities cannot be resolved.
  • Post-merge validation is attached only when a run matches the merge commit SHA; missing evidence is marked partial instead of over-associating.

Provider capability notes​

CapabilityGitHub SaaSGitLab SaaSGitLab self-managed
CODEOWNERS fileyesyesyes
Checks API (check suites and runs)yesno — use Pipelinesno — use Pipelines
Legacy commit statusesyes (deprecated path)yesyes
Pipeline per MRno — PR checks onlyyesyes
Merge actor identityyesyesyes (≥ 14.x)
Approval rule countvia branch protectionvia approval rulesvia approval rules
Work item cross-referencesvia linked issuesvia related issuesvia related issues
Group-level ownershipnoyesyes
Draft or WIP stateyes — draft PRyes — draft MRyes

Adapters for providers with gaps must map to the closest normalized concept and set is_partial: true when the gap affects a required field.

Missing-data and partial-data rules​

Absent or ambiguous fieldRequired behavior
reviewer_actor_ids absent on an open changeSet state to awaiting_review
work_item_refs is emptyDo not infer work item context; signals that depend on work item scope must degrade gracefully
No NormalizedOwnershipHint for the repositoryDownstream ownership state resolves to missing; signals must handle missing explicitly
validation_runs absent for an open changeTreat as pending; do not treat as passed
merged_by_actor_id absent on a merge eventRecord the merge event without the actor; do not block inference
Any timestamp absentSignals relying on elapsed time must reduce confidence or skip time-based inference
is_partial: true on any typeDownstream inference reduces confidence for signals that depend on that partial type
Provider API error retrieving an optional fieldSet is_partial: true; do not fail the entire normalized output
approval_count absent when required_approval_count is presentTreat approval count as zero
required_approval_count absentDo not infer an approval threshold; use not_required only when the provider explicitly signals no review is needed
No pipeline or check data after a merge eventEmit a trunk-scoped NormalizedValidationRun with state: pending
Conflicting ownership hints for the same pathEmit all hints and set confidence: inferred on each; the inference engine resolves conflicts

Fixture shape​

Fixtures used in scenario tests follow this canonical YAML shape. Each fixture file provides all normalized types needed by the targeted flow insight signals. Fixtures must be self-contained and must not reference external resources or depend on a live provider API.

fixture_id: "string — unique identifier"
scenario: "string — flow insight signal this fixture targets"
provider: "github | gitlab"
description: "string — what this fixture represents"

repository:
provider: "github | gitlab"
provider_id: "string"
full_name: "string"
default_branch: "string"
fetched_at: "ISO 8601"

changes:
- provider: "github | gitlab"
provider_id: "string"
repository_id: "string"
source_branch: "string"
target_branch: "string"
state: "open | closed | merged | locked"
author_actor_id: "string"
created_at: "ISO 8601"
updated_at: "ISO 8601"
work_item_refs: []
fetched_at: "ISO 8601"

actors:
- provider: "github | gitlab"
provider_id: "string"
display_name: "string"

review_states:
- change_id: "string"
state: "awaiting_review | under_review | changes_requested | approved | not_required"
as_of: "ISO 8601"

validation_runs:
- change_id: "string"
scope: "branch | trunk"
state: "pending | running | passed | failed | flaky | skipped"
run_at: "ISO 8601"

merge_events:
- change_id: "string"
merged_at: "ISO 8601"
target_branch: "string"

evidence_states:
- change_id: "string"
state: "not_required | required | pending | recorded | stale"
as_of: "ISO 8601"
required_types: []
owner_actor_id: "string"
freshness_at: "ISO 8601 — optional; absent when state is pending, required, or not_required"
is_partial: "boolean — optional; true when evidence requirements could not be fully determined"

ownership_hints:
- repository_id: "string"
owner_team_names: []
source: "codeowners | group_membership | branch_protection | manual"
confidence: "declared | inferred"

See schema/fixtures/provider-adapter-input/blocked-on-review-github.yaml for a worked example aligned with the "blocked on review" signal scenario.