GitHub API Emulation — Fetcher Demo Emulation¶
Visual reference only. Authoritative behaviour stays in DEMO_DRIVER_SPECIFICATION.md §5, GITHUB_EMULATOR_SPECIFICATION.md, and FETCHER_SPECIFICATION.md §5. Wire shapes are in openapi.yaml (dashboard API) and the fetcher spec's endpoint tables (GitHub surface).
Diagram A — Demo-mode topology¶
Three services; two parallel write paths into Dashboard.Api.
flowchart LR
subgraph Panel["Operator (browser panel)"]
P1["Write-path controls<br/>/demo/ingest · /demo/emit"]
P2["GitHub-source controls<br/>/demo/github/* (proxy)"]
end
subgraph Driver["Demo Driver :3001<br/>write-path + /demo/github/* proxy"]
WP["Write-path module"]
Proxy["/demo/github/*<br/>proxy controller"]
end
subgraph Emulator["github-emulator :3100<br/>GitHub REST @ root + /_github/* control"]
GHREST["GET /repos/{o}/{r}/…<br/>GET /rate_limit<br/>X-RateLimit-* · Link headers"]
GHCtl["/_github/seed · /_github/clear<br/>/_github/emit · /_github/status"]
Store["In-memory<br/>GitHub store"]
end
subgraph Fetcher["Dashboard.Fetcher<br/>GITHUB_BASE_URL=http://github-emulator:3100<br/>(code unchanged — F9)"]
FA["GithubActionsAdapter"]
end
subgraph API["Dashboard.Api"]
Ingest["POST /api/deployments"]
end
DB[("PostgreSQL")]
SPA["Angular SPA"]
P1 --> WP
WP -->|"POST /api/deployments"| Ingest
P2 --> Proxy
Proxy -->|"/_github/*"| GHCtl
GHCtl --> Store
Store --> GHREST
FA -->|"GET /repos/… · /rate_limit"| GHREST
FA -->|"POST /api/deployments<br/>(mapped events)"| Ingest
Ingest --> DB
DB --> SPA
Key constraint. The fetcher code is unchanged; GITHUB_BASE_URL=http://github-emulator:3100 points its root-relative paths at the emulator. The driver proxy (/demo/github/*) exists solely for browser same-origin reachability — not an auth boundary.
Diagram B — Seed → backfill → poll sequence¶
sequenceDiagram
autonumber
actor Op as Operator (panel)
participant D as Demo Driver
participant E as github-emulator (/_github/*)
participant GH as Emulated GitHub REST (/repos/…)
participant F as Fetcher
participant API as Dashboard.Api
Op->>D: POST /demo/github/seed {dataset:"demo"}
D->>E: POST /_github/seed {dataset:"demo"} (proxy)
E->>E: populate in-memory store<br/>(repos · deployments · statuses<br/>runs · workflow YAML · environments · artifacts)
E-->>D: GithubStoreStatus
D-->>Op: GithubStoreStatus
Note over F: cursor null → backfill (F14, §5.8)
F->>GH: GET /repos/{o}/{r}/actions/workflows?per_page=100
GH-->>F: {workflows:[…]} + X-RateLimit-* headers
F->>GH: GET /repos/{o}/{r}/environments
GH-->>F: {environments:[…]}
loop per environment
F->>GH: GET /repos/{o}/{r}/deployments?environment={E}&per_page=100
GH-->>F: [{deployment…}] + Link rel=next (when more pages)
F->>GH: GET /repos/{o}/{r}/deployments/{id}/statuses
GH-->>F: [{status…}]
F->>GH: GET /repos/{o}/{r}/actions/runs/{run_id}
GH-->>F: {id, name, path, head_sha}
F->>GH: GET /repos/{o}/{r}/contents/{path}?ref={sha}
GH-->>F: {content: "<base64 workflow YAML>", encoding:"base64"}
end
Note over F: map events · resolve parent_deployments (§5.6)<br/>· resolve version (§5.7) · POST batch oldest-first
F->>API: POST /api/deployments (batch — incl. parent_deployments + version)
API-->>F: 201
Note over F: cursor advanced to max(status.created_at)
Op->>D: POST /demo/github/emit {enabled:true}
D->>E: POST /_github/emit {enabled:true} (proxy)
E-->>D: {emitting:true}
D-->>Op: {emitting:true}
loop every EMIT_INTERVAL_MS
E->>E: append new deployment + status lifecycle<br/>(created_at=now)
Note over F: next poll — cursor present → incremental
F->>GH: GET /repos/{o}/{r}/deployments?environment={E}
GH-->>F: new deployment(s) since cursor
F->>API: POST /api/deployments (new events)
API-->>F: 201
end
Decisions (locked)¶
| # | Decision |
|---|---|
| G1 | GitHub emulation lives in a separate github-emulator service (demo/github-emulator/) — not in the driver. Per-adapter isolation: future ADO/Jenkins emulators are sibling services, each at their own root, with no path-prefix or port collision and no fetcher change required. |
| G2 | No fetcher code change — fetcher is config-driven (FETCHER_SPEC F9); demo mode sets GITHUB_BASE_URL=http://github-emulator:3100. The fetcher-host is wired into the demo compose profile (currently absent from all compose files). |
| G3 | Demo set = curated demo/data/github/ fixture (workflow YAML + dev→staging→prod needs chain + artifact-sourced version), coherent with existing demo service/env names. Proves parent_deployments (F10) + artifact version (F15). |
| G4 | Random set / periodic emit = GitHub-shaped generator in the emulator, parallel to the write-path random-event-generator.ts. |
| G5 | Emulated REST emits X-RateLimit-* headers + Link pagination + serves /rate_limit; ETag/304 deferred. Exercises the fetcher's F8/F16 paths. |
| G6 | The emulator IS the test mock for fetcher integration coverage — tests seed it via POST /_github/seed then assert the fetcher's output (realizes FETCHER_SPEC §7.2). |