13 KiB
Development Plan — service-satusehat
| Field | Value |
|---|---|
| Document Version | 1.0 |
| Status | Draft for review |
| Owner | Backend Engineering — Meninjar |
| Last Updated | 2026-05-13 |
| Horizon | ~2 quarters (M1 → M5) |
Read this together with
PRD.md(the what / why),analysis.qmd(the as-is), andDEVLOG.md(the what happened).
1. Guiding Principles
- Stabilise before extending. The 19 FHIR resources work end-to-end but rest on near-zero tests and an empty gRPC surface. Hardening comes before new features.
- Pure mappers. No I/O, no env reads inside
Map*ToFHIR. Inject from the service layer. This is theEpisodeOfCarepattern applied broadly. - One change, one PR, one CI run. No more multi-resource omnibus
commits like
af32d9c. - Audit everything that talks to SATUSEHAT. Outbound calls are regulator-visible artefacts.
- Production readiness is a checklist, not a feeling. See §6.
2. Milestones
M1 — Hardening (Weeks 1–3)
Goal: make the codebase safe to refactor. After M1, every PR runs through CI with linting + tests, the obvious bugs are fixed, and the debug-log noise is gone.
| ID | Task | Effort | Owner | Acceptance |
|---|---|---|---|---|
| T-01 | Set up CI (GitHub Actions): go vet, golangci-lint, go test, make audit, make security-check, Docker build |
1 d | Backend | PR triggers green pipeline; status checks required for main |
| T-02 | Add golangci-lint.yml config (errcheck, gocritic, gosec, revive, govet) |
0.5 d | Backend | make lint returns clean baseline (existing offenders allowlisted, not silenced) |
| T-03 | Unit tests for every Map*ToFHIR (19 resources) |
4 d | Backend | Golden-file tests; make test ≥ 60 % coverage on internal/satusehat/usecase/** |
| T-04 | Apply the EpisodeOfCare pattern to the remaining 18 usecases (drop env reads, accept orgID via constructor) |
3 d | Backend | No usecase mapper imports os; main.go wires cfg.SatuSehat.OrgID for all |
| T-05 | Replace fmt.Printf debug in pkg/utils/query/** with logger.Debug() |
0.5 d | Backend | grep -R "fmt.Printf" pkg/utils/query returns 0 |
| T-06 | Make NoOp cache goroutine-safe with sync.RWMutex |
0.5 d | Backend | Race-detector tests pass under load |
| T-07 | Delete commented-out Kafka producer + ImagingStudy worker from cmd/api/main.go, or move behind a KAFKA_ENABLED feature flag |
0.5 d | Backend | main.go has no commented blocks > 3 lines |
| T-08 | Rename internal/master/role/accses → access |
0.5 d | Backend | One renaming commit; all imports updated |
Exit criteria: CI required on main, lint clean baseline, FHIR mapper
unit-test coverage ≥ 60 %, no debug Printf in pkg/.
M2 — Auditability (Weeks 4–5)
Goal: every outbound SATUSEHAT call leaves a row we can defend in a Kemenkes audit.
| ID | Task | Effort | Owner | Acceptance |
|---|---|---|---|---|
| T-10 | Design table satusehat_submission |
0.5 d | Backend | Columns: id, resource, method, endpoint, request_id, request_body_hash, response_status, response_body_hash, satusehat_resource_id, error_code, created_at, latency_ms, org_id, user_id |
| T-11 | Migration + GORM model + CommandRepository / QueryRepository | 1 d | Backend | Goose migration runs; CQRS pair compiles |
| T-12 | Wrap internal/interfaces/satusehat.Client.DoRequest with an audit interceptor (decorator) |
1 d | Backend | Every outbound call writes a row; failures still write |
| T-13 | Add request-ID middleware (X-Request-ID propagation, ULID generation if absent) | 0.5 d | Backend | All logs + audit rows carry the same request_id |
| T-14 | Retention policy doc — keep submissions for ≥ 25 years per Permenkes 24/2022 | 0.5 d | Backend / Compliance | docs/RETENTION.md exists and references the regulation |
| T-15 | Health probe /health/satusehat — last successful call within 5 min |
0.5 d | Backend | Returns 200 when at least one submission within the window |
Exit criteria: Audit rows for every outbound call across all 19
resources, joinable on request_id, retained per policy.
M3 — gRPC Catch-up (Weeks 6–8)
Goal: bring the gRPC transport from empty to parity with REST for the four hottest resources.
| ID | Task | Effort | Owner | Acceptance |
|---|---|---|---|---|
| T-20 | Author proto/v1/satusehat/{encounter,episodeofcare,condition,observation}.proto |
2 d | Backend | make proto generates clean Go code |
| T-21 | Implement gRPC handlers for the four resources reusing the existing services | 2 d | Backend | gRPC + REST share the same service layer |
| T-22 | Register handlers in cmd/api/main.go gRPC server |
0.5 d | Backend | grpcurl … list shows the four services |
| T-23 | Add buf lint + breaking-change check to CI | 0.5 d | Backend | PRs that break protobuf surface fail CI |
| T-24 | Postman → grpcurl examples in docs/api/ |
0.5 d | Backend | One example call per service in markdown |
Exit criteria: Four resources callable via gRPC, behind the same auth, with parity behaviour to REST.
M4 — Resilience (Weeks 9–10)
Goal: survive a flaky SATUSEHAT without dropping submissions.
| ID | Task | Effort | Owner | Acceptance |
|---|---|---|---|---|
| T-30 | Retry middleware on Client.DoRequest: exponential backoff (250 ms · 2^n, cap 8 s), max 5 attempts, retry on 5xx + network errors only |
1 d | Backend | Unit tests with fake transport |
| T-31 | Circuit breaker (sony/gobreaker) per upstream (FHIR, KFA, KYC, Consent, DICOM) |
1 d | Backend | Opens after 5 consecutive failures, half-opens after 30 s |
| T-32 | Idempotency key support — caller supplies Idempotency-Key, service dedupes within 24 h via Redis |
1 d | Backend | Replays return the original response, not a duplicate submission |
| T-33 | Rate-limit middleware on inbound (Redis token bucket, key by user_id then ip) |
1 d | Backend | cfg.Security.RateLimit.RequestsPerMinute enforced |
| T-34 | Refresh-token revocation on logout — persist to revoked_tokens table; check on every refresh (closes TODO at internal/auth/service.go:233) |
1 d | Backend | Logout test passes; refresh returns 401 after logout |
| T-35 | Persist KYC verification result locally (closes TODO in kyc/service.go) |
0.5 d | Backend | New table kyc_verification; result row on every success |
| T-36 | Standardise cache invalidation in role/master, role/pages, role/permission, role/access — invalidate on every command op |
1 d | Backend | TODOs removed; integration test covers stale-cache scenario |
Exit criteria: Service tolerates 30 s of SATUSEHAT 5xx without losing a submission; logout truly invalidates; cache is never stale after a write.
M5 — Production Rollout (Weeks 11–12)
Goal: from "running on a dev box" to "running in the hospital DC, one instance, watched."
| ID | Task | Effort | Owner | Acceptance |
|---|---|---|---|---|
| T-40 | Production docker-compose.prod.yml review with ops; document the external network requirement |
0.5 d | Backend / Ops | docs/deployment.md updated; one-shot setup script |
| T-41 | Grafana dashboard JSON (request volume, p50/p95/p99 latency, error rate by code, DB pool, cache hit ratio) | 1 d | Backend | Dashboard imports cleanly; screenshot in docs/ |
| T-42 | Prometheus alert rules (SATUSEHAT 5xx > 5/min, p95 > 5 s for 5 min, DB pool exhaustion) | 0.5 d | Backend / Ops | Rules under deploy/prom/alerts.yaml |
| T-43 | Backup strategy for satusehat_submission and master DB (daily pg_dump, 30-day retention onsite, 1-year offsite encrypted) |
0.5 d | Ops | docs/BACKUP.md exists |
| T-44 | DR runbook — what to do when SATUSEHAT is down, when Redis is down, when the DB primary fails | 1 d | Backend / Ops | docs/RUNBOOK.md exists |
| T-45 | Soft launch — 1 hospital, 1 day of shadow traffic | 1 d | All | No P0 incidents; audit table populated |
| T-46 | GA — promote to all sites that depend on SATUSEHAT submission | 0.5 d | All | Stakeholder sign-off |
Exit criteria: Service running in production with full observability, alerts, backups, and a runbook.
3. Backlog (post-M5, not yet scheduled)
- B-01 Multi-tenant support: one binary, multiple
org_ids per request based on JWT claim (depends on T-04 pattern across all 19). - B-02 Event-driven mode: re-enable the commented-out Kafka producer
(
cmd/api/main.go:137–141) so that submissions are emitted as events for downstream consumers (data warehouse, BI). - B-03 Background worker for ImagingStudy DICOM uploads (re-enable
cmd/api/main.go:286–300behind a worker process flag). - B-04 OpenTelemetry tracing (replace ad-hoc request-id with
traceparent). - B-05 Bulk submission endpoints (
POST /v1/Encounter/bundle) for back-fill scenarios. - B-06 Mobile-friendly DTOs (slimmer JSON than the full FHIR shape).
- B-07 Finish or remove the
tools/generate.gocode generator. - B-08 Postman → automated contract tests in CI.
4. Risk Register
| ID | Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|---|
| RK-1 | Refactor breaks an in-production FHIR resource | Med | High | T-03 unit tests must land before T-04 refactor cascade |
| RK-2 | SATUSEHAT contract changes mid-quarter | Low | High | Contract tests behind a build tag, run nightly against staging |
| RK-3 | Hospital DC ops cannot operate Prometheus stack | Med | Med | Provide a one-shot Compose + Grafana with default dashboards |
| RK-4 | Single instance is a SPOF | High | Med | M5+ plan to run two instances behind nginx; cache is shared so it's safe |
| RK-5 | Audit table grows unbounded | High | Low | Partition by month + offsite archive (T-14) |
| RK-6 | Refresh-token revocation table grows unbounded | Med | Low | TTL cleanup job; keep only until exp + 7 d |
| RK-7 | gRPC contract evolves and breaks mobile clients | Med | Med | T-23 buf breaking-change check |
5. Dependencies & Coordination
| Dependency | Needed by | Status |
|---|---|---|
| SATUSEHAT staging credentials | M1+ | ✓ in .env |
| Keycloak realm config | M1+ | depends on hospital IAM team |
External Docker network service-general_default |
M5 | ✓ created by service-general compose |
| Prometheus + Grafana in hospital DC | M5 | ⚠ to confirm with ops |
| Offsite backup target | M5 | ⚠ to confirm with ops |
6. Definition of Done (per task)
A task is Done only when:
- Code merged to
mainvia PR. - CI green (lint + test + audit + security-check + Docker build).
- Tests cover the happy path and at least one error path.
docs/DEVLOG.mdhas a new entry referencing the task ID.- If user-visible:
docs/PRD.mdupdated; if architectural:docs/analysis.qmdupdated. - If touching ops:
docs/deployment.md/RUNBOOK.mdupdated. - No new TODO without a backlog ID.
7. Cadence
- Daily: 15-min stand-up over chat; update task status in the issue tracker.
- Weekly: 30-min review — what shipped, what's blocked, what's next.
- Per milestone: written retrospective appended to
DEVLOG.mdwith scopedecision.