摘要
Day 29 把 Day 28 的 policy-only sandbox live read/search readiness 往前推進成真正的 allowlisted sandboxread/search-typeevidence:只有在 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 read / search-type endpoints。例如同一份 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
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。Endpoint guard 繼續拒絕:
_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
Day 29 的 execution row 不再只是 policy-only placeholder。
每個 read / search-type row 會記錄:
當所有 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 的重點是:


Day 29 雖然開始呼叫 allowlisted sandbox read/search endpoint,但不顯示、不保存、不提交 raw server response。
report 使用:
它不輸出:
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、AuditEvent、Provenance 或 PHI 寫進資料庫,也不會送出 Bundle。
Day 27 已經做出 SMART/OAuth sandbox token flow evidence。
Day 29 讓 resource request 可以在同一個 request scope 內使用 raw access token,但這個 token 不會進入 report model。
report 仍然只顯示:
raw token 只在 SandboxFhirPreflightService 呼叫 sandbox resource endpoint 時短暫使用,不顯示、不保存、不提交。
新增:
SandboxResourceHttpClient
SandboxResourceHttpResponse
DefaultSandboxResourceHttpClient
InternalSandboxOAuthTokenResult
SandboxFhirEndpointGuard 新增:
resourceReadUrl
resourceSearchUrl
_id-only search parameter guard。/metadata base URL normalization。SandboxFhirPreflightService 調整:
read summary 顯示 expectedIdMatched=yes/no。search-type summary 顯示 search Bundle type、total、entry count、expected resource type、expected id match。FAILED。SandboxLiveInteractionExecutionEvidence 新增:
responseSummary
SandboxOAuthTokenService 調整:
ParseController 調整:
index.html 調整:
Sandbox live read/search execution evidence table 新增 Response summary 欄位。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 範例。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,至少需要:
none。本機執行範例:
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
今天新增 / 更新測試確認:
_id parameter。/metadata 結尾,resource URL 不會錯誤 append 到 /metadata 後面。none 時,resource endpoint 不被呼叫。expectedIdMatched=yes。目前測試結果:
Tests run: 124, Failures: 0, Errors: 0, Skipped: 0

完成後,專案的定位變成:
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
但它仍不是:
Repository:twcore-data-quality-gate