fix(mcs): send credentials to the measure engine and target the job's MCS - #393
Open
blakenan-bellese wants to merge 2 commits into
Open
fix(mcs): send credentials to the measure engine and target the job's MCS#393blakenan-bellese wants to merge 2 commits into
blakenan-bellese wants to merge 2 commits into
Conversation
… MCS
Jobs against a remote MCS failed every patient with HTTP 401
"Authorization header missing Bearer token". Two independent breaks:
1. evaluate_measure() had no auth parameter at all — a bare client.get()
with no headers. The orchestrator resolved the job's MCS *url* via
_get_mcs_url() but there was no equivalent for credentials.
2. push_resources(), wipe_patient_data(), and resolve_evaluated_resource()
all defaulted to settings.MEASURE_ENGINE_URL with no auth, so patient
data went to the local engine while evaluation ran against the remote
one. Fixing only the 401 would have produced clean 200s with every
population at zero — a silent wrong answer.
Adds _get_mcs_auth_headers(), mirroring _get_cdr_auth_headers(): reads
credentials from the live MCSConfig via Job.mcs_id rather than duplicating
secrets onto the job row, returns {} when no MCS is linked, and raises when
the linked config was deleted instead of silently evaluating unauthenticated.
No migration — Job.mcs_id already existed.
MCS resolution moves above the wipe in run_job so the wipe targets the same
engine the job pushes to and evaluates against; wiping a different server
would leave the real target's prior-run data in place and contaminate
populations.
Verified against the CMS connectathon server: 401s 122 -> 0, all responses
200, and patient data now reaches the remote MCS (0 -> 56 Patients, 131
Encounters, 131 Conditions). Remaining failures there are server-side
(HSEARCH800001, Hibernate Search not initialized), not client-side.
Note: the wipe is now destructive against a shared remote MCS. Tracked
separately in #392.
Refs #391 (token refresh)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
HTTP 401 "Authorization header missing Bearer token".evaluate_measure()had no auth parameter at all — a bareclient.get(url)with no headers. MCS connections wired the URL through to jobs (Job.mcs_url,_get_mcs_url()) but never the credentials.push_resources(),wipe_patient_data(), andresolve_evaluated_resource()all defaulted tosettings.MEASURE_ENGINE_URLwith no auth. So patient data went to the local engine while evaluation ran against the remote one. Fixing only the 401 would have produced clean200s with every population at zero — a silent wrong answer, worse than the visible failure._get_mcs_auth_headers()mirroring_get_cdr_auth_headers(), and routes all four interactions at the job's MCS with its credentials. No migration —Job.mcs_idalready existed.Related issue
Refs #391 (token refresh — the stored bearer token is still static and expires)
Refs #392 (the wipe is now destructive against a shared remote MCS)
Type of change
Checklist
docs/decisions.md— ADR-011 added (changed auth behavior)Design notes
Credentials are read live, not snapshotted onto the job.
Jobsnapshotsmcs_url/mcs_name/mcs_idso job rendering never depends on current config state, but credentials are deliberately excluded and read from the liveMCSConfig— same as the CDR path. Copying secrets onto every job row would multiply the blast radius of a DB disclosure and leave stale tokens across history. Trade-off: deleting an MCS config makes its jobs unrunnable, handled by raising a clear error rather than silently evaluating unauthenticated.MCS resolution moved above the wipe in
run_job. The wipe must clear the same server the job pushes to and evaluates against. Wiping a different server leaves the real target's prior-run patients in place, and those inflate the next evaluation's populations.No
mcs_auth_typecolumn needed.mcs_idalone distinguishes "no MCS linked" (→{}) from "config deleted" (→ raise), so unlike the CDR path this needed no migration.Security implications
_validate_ssrf_url()already gates which hosts a connection may point at.Authorization/Bearerin logged errors — confirmed in the captured 401s.DELETE {Type}?_lastUpdated=gt1900-01-01across 23 resource types against the job's MCS. On a dedicated local engine that is correct and unchanged in effect. On a shared remote MCS it deletes other users' patient data. Filed as bug: job start wipes ALL patient data on the target MCS — must be optional/scoped for shared remote servers #392 with a proposed scoped-wipe fix. Flagging explicitly because this PR is what makes that reachable.Test plan
Automated
test_full_workflow.pywas deliberately kept in (not ignored) since this touches the measure pipeline.New tests, all written failing-first and confirmed RED before implementation:
test_run_job_targets_job_mcs_with_credentialstest_get_mcs_auth_headers_builds_from_linked_configAuthorizationheadertest_get_mcs_auth_headers_empty_when_no_mcs_linkedtest_get_mcs_auth_headers_raises_when_config_deletedtest_evaluate_measure_sends_auth_headerstest_evaluate_measure_without_auth_sends_no_authorizationtest_resolve_evaluated_resource_uses_given_base_and_authFour pre-existing tests needed updating — all stale mock signatures against the intentionally-changed call shape (
fake_resolve,mock_evaluate,mock_wipe.assert_awaited_once_with). Assertions were updated, not loosened.Manual — against the real CMS connectathon server
Remaining failures on that server are server-side —
HSEARCH800001: Hibernate Search was not initialized, returned as200with anOperationOutcomebody, whichevaluate_measurealready classifies as a failure. Nothing client-side can fix that.Local regression: a job against the seeded local measure engine completed 56/56, 0 failed with valid MeasureReports. Populations came back zero for the period I chose; direct
$evaluate-measureagainst the same engine with Lenny out of the loop returns the same zeros, so that is a data/period/cohort property and not a change in behavior.🤖 Generated with Claude Code