iT邦幫忙

2026 iThome 鐵人賽

DAY 29
0

摘要
Day 29 把 Day 28 的 policy-only sandbox live read/search readiness 往前推進成真正的 allowlisted sandbox read / search-type evidence:只有在 sandbox allowlist、auth mode、synthetic/non-PHI confirmation、CapabilityStatement interaction declaration,以及 OAuth token evidence 條件都通過時,才會呼叫 sandbox Patient / Observation / DiagnosticReport read/search endpoint。

這和「能不能交換」有什麼關係?

Day 28 可以回答:

如果未來要做 live read/search,前置條件和拒絕條件是否已經可稽核?

Day 29 則開始回答更接近 sandbox exchange execution 的問題:

在所有 safety guard 都通過時,quality gate 是否能產生最小、可稽核、request-scoped 的 sandbox read/search execution evidence?

這一步刻意只放寬 README What It Does Not Do 裡的一個邊界:

  • 之前完全不呼叫 Patient / Observation / DiagnosticReport / search endpoints。
  • Day 29 只允許呼叫 allowlisted sandbox 的 PatientObservationDiagnosticReport read / search-type endpoints。
  • production endpoint、Bundle submit、create、update、delete 不做。
  • raw server response、PHI、AuditEvent / Provenance、validation history 不保存、不提交、不顯示。

例如同一份 Bundle 進來時,Day 29 的判斷變成:

情境 結果
validation 尚未執行 sandbox read/search evidence NOT_EVALUATED
sandbox base URL 未 allowlist resource endpoint 不呼叫
sandbox auth mode 是 none resource endpoint 不呼叫
使用者未確認 synthetic/non-PHI resource endpoint 不呼叫
Bundle 含 Patient identifier / name / telecom / address / birthDate non-PHI preflight FAILED,resource endpoint 不呼叫
CapabilityStatement 未宣告 read / search-type 對應 operation 不呼叫
OAuth token evidence 啟用但未通過 resource endpoint 不呼叫
所有前置條件通過,且 Bundle 有對應 resource id 呼叫 allowlisted sandbox read/search endpoint,產生 masked summary evidence
create / update / delete / Bundle submit 永遠 blocked

這讓 report 可以明確說明:

Sandbox live read/search evidence 已能 request-scoped 產生。
但目前只支援 allowlisted sandbox read/search,不做 production exchange execution,也不做 write/submit。

今天的實作範圍

Day 29 的 request / report flow 變成:

使用者上傳、貼上 Bundle,或選擇 built-in synthetic sandbox lab fixture
   │
   ├─ 執行原本 JSON / FHIR / TW Core / contract rules
   ├─ terminology server evidence
   │     ├─ ValueSet/$expand
   │     └─ ValueSet/$validate-code
   │
   ├─ live FHIR metadata evidence
   │     └─ GET {FHIR_BASE_URL}/metadata
   │
   ├─ sandbox readiness evidence
   │     ├─ non-PHI workflow evidence
   │     ├─ sandbox auth boundary
   │     ├─ CapabilityStatement interaction declarations
   │     ├─ SMART/OAuth sandbox token evidence
   │     └─ sandbox live read/search execution evidence
   │           ├─ extract Bundle-local Patient / Observation / DiagnosticReport ids
   │           ├─ build guarded read URL
   │           ├─ build guarded search URL with _id only
   │           ├─ optionally attach request-scoped bearer token internally
   │           ├─ call allowlisted sandbox read/search endpoint
   │           ├─ record HTTP status
   │           ├─ summarize response without raw body
   │           ├─ check expected id match
   │           └─ keep write/submit operations blocked
   │
   ├─ PHI masking / privacy evidence
   │     ├─ masking policy version
   │     ├─ masked field categories
   │     ├─ masking check result
   │     ├─ raw Bundle policy
   │     └─ retention policy = request-scoped only / no persistent history
   │
   └─ AuditEvent / Provenance resource preview
         ├─ input SHA-256
         ├─ selected contract id/version
         ├─ gate outcome
         ├─ rule count
         ├─ terminology status
         ├─ metadata status
         ├─ sandbox status
         ├─ PHI masking status
         └─ generation policy = generated request-scoped only; not persisted; not submitted

最後 Quality Test Report 的 evidence layer 變成:

Quality Gate blocking result
   ├─ FHIR / TW Core / contract local rules
   ├─ Terminology server evidence
   ├─ Live FHIR metadata evidence
   ├─ Sandbox readiness evidence
   │    ├─ Non-PHI workflow evidence
   │    ├─ CapabilityStatement interaction declarations
   │    ├─ SMART/OAuth token flow evidence
   │    └─ Live read/search execution evidence
   │         ├─ endpoint called
   │         ├─ HTTP status
   │         ├─ response summary
   │         ├─ expectedIdMatched
   │         └─ response retention policy
   ├─ PHI masking / privacy evidence
   └─ AuditEvent / Provenance resource preview evidence

Safe resource URL builder 做什麼?

Day 29 新增 sandbox resource URL builder,不再把 /metadata client 當成通用 FHIR client 使用。

支援的 read URL 只有:

{base}/Patient/{id}
{base}/Observation/{id}
{base}/DiagnosticReport/{id}

支援的 search URL 只有:

{base}/Patient?_id={id}
{base}/Observation?_id={id}
{base}/DiagnosticReport?_id={id}

這裡刻意只開 _id search,原因是:

  • _id 來自 request Bundle 內已存在的 synthetic fixture-safe resource id。
  • 不開放使用者自訂 search query。
  • 不用 Patient name、identifier、telecom、birthDate 當 search parameter。
  • 不讓 search evidence 變成 PHI lookup tool。

Endpoint guard 繼續拒絕:

  • non-allowlisted base URL。
  • userinfo。
  • query string。
  • fragment。
  • unsafe scheme。
  • localhost / private network / link-local / metadata network。
  • unsupported resource type。
  • invalid FHIR id。
  • _id 以外的 search parameter。

如果使用者輸入的 sandbox URL 已經是:

https://sandbox.example.test/r4/metadata

resource URL builder 會先回到 FHIR base:

https://sandbox.example.test/r4

再組出:

https://sandbox.example.test/r4/Patient/patient-1

避免錯誤變成:

https://sandbox.example.test/r4/metadata/Patient/patient-1

Live read/search evidence 做什麼?

Day 29 的 execution row 不再只是 policy-only placeholder。

每個 read / search-type row 會記錄:

  • execution status。
  • resource type。
  • operation。
  • policy allowed。
  • endpoint called。
  • HTTP status。
  • response summary。
  • response retention policy。
  • evidence statement。

當所有 guard 通過,而且 Bundle 內有對應 resource id 時,會呼叫:

GET {sandboxBase}/Patient/{patientId}
GET {sandboxBase}/Patient?_id={patientId}
GET {sandboxBase}/Observation/{observationId}
GET {sandboxBase}/Observation?_id={observationId}
GET {sandboxBase}/DiagnosticReport/{diagnosticReportId}
GET {sandboxBase}/DiagnosticReport?_id={diagnosticReportId}

read response summary 只顯示:

resourceType=Patient; expectedIdMatched=yes

search-type response summary 只顯示:

resourceType=Bundle; type=searchset; total=1; entryCount=1; expectedResourceType=Patient; expectedIdMatched=yes

這個 summary 的重點是:

  • 可以知道 server 回的是不是 expected resource type。
  • 可以知道 returned id 是否 match request Bundle 的 expected id。
  • 可以知道 search Bundle 的 type、total、entry count。
  • 不顯示 raw response body。
  • 不顯示 Patient identifier / name / telecom / address / birthDate。

https://ithelp.ithome.com.tw/upload/images/20260830/20177913wdrzqgAAzx.png

https://ithelp.ithome.com.tw/upload/images/20260830/20177913Nih3moSH6q.png

Response retention 的邊界?

Day 29 雖然開始呼叫 allowlisted sandbox read/search endpoint,但不顯示、不保存、不提交 raw server response。
report 使用:

  • resource type。
  • operation。
  • policy allowed / blocked。
  • endpoint called flag。
  • HTTP status。
  • response summary。
  • expected id match result。
  • response retention policy。
  • evidence statement。

它不輸出:

  • raw Patient response。
  • raw Observation response。
  • raw DiagnosticReport response。
  • raw search Bundle response。
  • raw access token。
  • client secret。
  • Patient identifier。
  • Patient name。
  • Patient telecom。
  • Patient address。
  • Patient birthDate。
  • validation history。

Response retention policy 繼續顯示:

request-scoped only; raw server response is not displayed, persisted, or submitted

這表示目前版本仍然不會把 uploaded Bundle、token value、client secret、terminology response、FHIR metadata response、sandbox resource response、AuditEventProvenance 或 PHI 寫進資料庫,也不會送出 Bundle。

OAuth token 的邊界?

Day 27 已經做出 SMART/OAuth sandbox token flow evidence。
Day 29 讓 resource request 可以在同一個 request scope 內使用 raw access token,但這個 token 不會進入 report model。
report 仍然只顯示:

  • token type。
  • expires in。
  • requested scopes。
  • granted scopes。
  • token SHA-256 hash。
  • token retention policy。

raw token 只在 SandboxFhirPreflightService 呼叫 sandbox resource endpoint 時短暫使用,不顯示、不保存、不提交。

今天新增 / 調整的主要類別

新增:

  • SandboxResourceHttpClient
  • SandboxResourceHttpResponse
  • DefaultSandboxResourceHttpClient
  • InternalSandboxOAuthTokenResult

SandboxFhirEndpointGuard 新增:

  • resourceReadUrl
  • resourceSearchUrl
  • supported resource type guard。
  • FHIR id guard。
  • _id-only search parameter guard。
  • /metadata base URL normalization。

SandboxFhirPreflightService 調整:

  • 支援傳入原始 Bundle JSON 以抽取 read/search target id。
  • 在所有 safety gates 通過後執行 allowlisted sandbox read/search。
  • read summary 顯示 expectedIdMatched=yes/no
  • search-type summary 顯示 search Bundle type、total、entry count、expected resource type、expected id match。
  • live response failure 會讓 sandbox preflight status 變成 FAILED
  • create / update / delete / Bundle submit 繼續 blocked。

SandboxLiveInteractionExecutionEvidence 新增:

  • responseSummary

SandboxOAuthTokenService 調整:

  • 保留原本 masked public token evidence。
  • 新增 internal token carrier,讓 raw token 只在同一個 request scope 內提供給 resource HTTP client 使用。

ParseController 調整:

  • 呼叫 sandbox preflight 時傳入 Bundle JSON,讓 service 能抽取 Bundle-local target id。

index.html 調整:

  • Sandbox live read/search execution evidence table 新增 Response summary 欄位。
  • section note 改成 Day 29 allowlisted read/search execution boundary。

README.md 調整:

  • What It Does 從 policy-only readiness 改成 request-scoped live sandbox read/search evidence。
  • What It Does Not Do 明確保留 production endpoint、write endpoint、Bundle submit、persistent history 的邊界。
  • Run Locally 新增 Day 29 sandbox read/search evidence 範例。

Endpoint safety 設定

Day 29 沒有新增新的設定 key。
既有 sandbox endpoint 設定仍控制 /metadata、OAuth token evidence、以及 Day 29 sandbox read/search evidence:

qualitygate.fhir.sandbox.allowed-base-urls=
qualitygate.fhir.sandbox.allow-private-network=false
qualitygate.fhir.sandbox.auth-mode=none
qualitygate.fhir.sandbox.allowed-oauth-token-urls=
qualitygate.fhir.sandbox.oauth-token-url=
qualitygate.fhir.sandbox.oauth-token-request-enabled=false
qualitygate.fhir.sandbox.oauth-client-auth-mode=none
qualitygate.fhir.sandbox.oauth-client-id=
qualitygate.fhir.sandbox.oauth-client-secret=
qualitygate.fhir.sandbox.oauth-scopes=

Day 29 的預設狀態仍是:

Sandbox live read/search execution evidence = NOT_EVALUATED
HTTP Patient / Observation / DiagnosticReport endpoint = not called
HTTP search endpoint = not called
HTTP Bundle submit endpoint = not called

若要讓 Day 29 的 read/search rows 真正呼叫 sandbox endpoint,至少需要:

  • sandbox base URL allowlist。
  • sandbox auth mode 不是 none
  • 使用者確認 synthetic / non-PHI test data。
  • direct Patient identifier scan 通過。
  • CapabilityStatement 宣告對應 resource interaction。
  • Bundle 內有對應 Patient / Observation / DiagnosticReport id。
  • 如果 OAuth token request enabled,OAuth token evidence 必須通過。

本機執行範例:

QUALITYGATE_FHIR_SANDBOX_ALLOWED_BASE_URLS="http://localhost:19090/fhir" \
QUALITYGATE_FHIR_SANDBOX_ALLOW_PRIVATE_NETWORK="true" \
QUALITYGATE_FHIR_SANDBOX_AUTH_MODE="bearer_token_configured" \
QUALITYGATE_FHIR_SANDBOX_OAUTH_TOKEN_REQUEST_ENABLED="false" \
./mvnw spring-boot:run

UI 操作至少需要:

Use built-in synthetic sandbox lab fixture = checked
Confirm this Bundle is synthetic/non-PHI test data = checked
Sandbox FHIR base URL = https://sandbox.example.test/r4
Sandbox auth mode = bearer_token_configured

測試重點

今天新增 / 更新測試確認:

  • resource read URL 只允許 allowlisted sandbox base URL。
  • resource search URL 只允許 _id parameter。
  • unsupported resource type 會被拒絕。
  • invalid FHIR id 會被拒絕。
  • private network blocking 繼續有效。
  • base URL 如果以 /metadata 結尾,resource URL 不會錯誤 append 到 /metadata 後面。
  • sandbox base URL 未 allowlist 時,resource endpoint 不被呼叫。
  • sandbox auth mode 是 none 時,resource endpoint 不被呼叫。
  • non-PHI preflight 未通過時,resource endpoint 不被呼叫。
  • CapabilityStatement 未宣告 interaction 時,對應 read/search 不被呼叫。
  • OAuth token evidence 啟用但未通過時,resource endpoint 不被呼叫。
  • 所有 guard 通過時,會呼叫 Patient / Observation / DiagnosticReport read/search。
  • search response summary 會顯示 expectedIdMatched=yes
  • raw Patient identifier / name / telecom / address / birthDate 不會出現在 response summary。
  • raw access token 不會出現在 public token evidence 或 live interaction evidence。
  • create / update / delete / Bundle submit 永遠 policy blocked。

目前測試結果:

Tests run: 124, Failures: 0, Errors: 0, Skipped: 0

https://ithelp.ithome.com.tw/upload/images/20260830/20177913GUmY8bw58t.png

Day 29 完成後的邊界

完成後,專案的定位變成:

pre-exchange quality gate
   ├─ FHIR / TW Core validation
   ├─ partner contract rules
   ├─ contract lifecycle / compatibility
   ├─ unit normalization evidence
   ├─ terminology server evidence
   ├─ metadata preflight
   ├─ sandbox readiness evidence
   │    ├─ non-PHI workflow evidence
   │    ├─ CapabilityStatement interaction declarations
   │    ├─ SMART/OAuth token flow evidence
   │    └─ allowlisted live read/search execution evidence
   ├─ formal PHI masking / privacy evidence
   └─ request-scoped AuditEvent / Provenance preview

但它仍不是:

  • production exchange platform。
  • full SMART/OAuth client。
  • SMART launch app。
  • authorization-code redirect UI。
  • refresh token manager。
  • production live resource executor。
  • Bundle submitter。
  • create / update / delete client。
  • persistent audit log。
  • external FHIR AuditEvent / Provenance writer。
  • database-backed validation history。
  • PHI repository。
  • production de-identification / pseudonymization engine。
  • retention deletion workflow。

Repository:twcore-data-quality-gate


上一篇
Day28 - Safe Live Read/Search Policy Readiness
下一篇
Day30 - TW Core Lab Quality Gate 專案總回顧
系列文
醫療資料通過標準驗證,就真的能交換嗎?——30 天打造 TW Core 資料品質閘門30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言