📋 Product Owner view
Feature inventory, spec alignment, behavioural gaps · Product Owner, Product Manager
⚡ Agent trace · 13 steps · $0.0482 · click to expand
search_claims(query='feature capability user facing', category='any') →
search_claims(query='API endpoint route handler', category='any') →
get_dependencies(file_path='.') →
search_claims(query='patient resource FHIR', category='any') →
search_claims(query='pagination search bundle', category='any') →
search_claims(query='client FHIRClient connect', category='any') →
read_source_file(file_path='fhirclient/__init__.py', start_line=1) →
read_source_file(file_path='fhirclient/client.py', start_line=1) →
read_source_file(file_path='fhirclient/server.py', start_line=1) →
read_source_file(file_path='fhirclient/client.py', start_line=62) →
read_source_file(file_path='fhirclient/server.py', start_line=62) →
read_source_file(file_path='fhirclient/auth.py', start_line=1) →
read_source_file(file_path='fhirclient/auth.py', start_line=62) →
read_source_file(file_path='fhirclient/client.py', start_line=123) →
read_source_file(file_path='fhirclient/client.py', start_line=184) →
read_source_file(file_path='fhirclient/auth.py', start_line=123) →
read_source_file(file_path='fhirclient/_utils.py', start_line=1) →
read_source_file(file_path='fhirclient/_utils.py', start_line=62) →
read_source_file(file_path='fhirclient/auth.py', start_line=184) →
read_source_file(file_path='demos/flask/flask_app.py', start_line=1) →
read_source_file(file_path='demos/flask/flask_app.py', start_line=62) →
read_source_file(file_path='fhirclient/auth.py', start_line=245) →
read_source_file(file_path='fhirclient/server.py', start_line=120) →
read_source_file(file_path='fhirclient/server.py', start_line=180) →
read_source_file(file_path='fhirclient/auth.py', start_line=302) →
read_source_file(file_path='fhirclient/auth.py', start_line=362) →
read_source_file(file_path='fhirclient/server.py', start_line=240) →
read_source_file(file_path='fhirclient/server.py', start_line=300) →
read_source_file(file_path='fhirclient/auth.py', start_line=422) →
read_source_file(file_path='tests/client_test.py', start_line=1) →
read_source_file(file_path='tests/server_test.py', start_line=1)
Product View
Feature Inventory
| Feature | Status | Confidence | Notes |
|---|---|---|---|
| SMART on FHIR OAuth2 Authorization | Implemented | High | Full OAuth2 flow with authorization code grant, PKCE support, client credentials grant, and refresh tokens. Supports both public and confidential clients. |
| Patient Context Management | Implemented | High | Patient ID tracking, automatic Patient resource fetching, reauthorization on 401, patient ID clearing on 404. |
| Server Capability Discovery | Implemented | High | Fetches CapabilityStatement from metadata endpoint, extracts OAuth2 URIs from SMART extensions. |
| FHIR Resource CRUD Operations | Implemented | High | GET, PUT, POST, DELETE via request_json, put_json, post_json, delete_json methods. |
| Pagination / Bundle Iteration | Implemented | High | iter_pages() generator, _fetch_next_page(), _get_next_link() with URL sanitization. |
| State Persistence / Session Management | Implemented | High | Full state serialization via state property and from_state(); pluggable save_func callback. |
| Flask Demo App (Patient Prescriptions) | Implemented | High | Working web app showing patient name, prescriptions, OAuth2 login/logout, medication name resolution. |
| Reference Resolution (Contained & Relative) | Implemented | High | Tests confirm contained resource detection, relative reference resolution, type mismatch handling, and endpoint mismatch handling. |
| FHIR Date/Time Parsing | Implemented | High | Tests exist for FHIRDate, FHIRDateTime, FHIRInstant, FHIRTime classes. |
| Client Credentials Grant (Backend Auth) | Implemented | High | authorize() method on FHIROAuth2Auth uses client_credentials grant type with optional JWT assertion. |
| Reauthorization via Refresh Tokens | Implemented | High | reauthorize() method uses refresh_token grant type when refresh token is available. |
| Logout / Patient Reset | Implemented | High | reset_patient() clears launch context, patient ID, and cached patient object. |
| Human Name Formatting | Implemented | High | human_name() method formats HumanName instances into display strings. |
| OAuth Error Handling | Implemented | High | extract_oauth_error() maps OAuth error codes to human-readable messages. |
| HTTP Error Handling (401/403/404) | Implemented | High | Custom exceptions FHIRUnauthorizedException, FHIRPermissionDeniedException, FHIRNotFoundException. |
| Open / No-Auth Server Support | Implemented | High | FHIRAuth base class with auth_type = "none" allows connecting to servers without authentication. |
What the Code Does That the Spec Doesn't Mention
-
PKCE (Proof Key for Code Exchange) is always included — The OAuth2 authorization flow always generates a
code_verifierandcode_challenge(S256) even when the server doesn't require it. This is an undocumented security hardening decision. (fhirclient/auth.py:241-248) -
JWT Bearer Assertion for backend auth — The client supports
client_assertion_type: urn:ietf:params:oauth:client-assertion-type:jwt-bearerfor client credentials grants, allowing backend systems to authenticate with a JWT token instead of a client secret. This is not mentioned in the package docstring or settings documentation. (fhirclient/auth.py:362-366) -
Launch token is automatically appended to scope — When a
launch_tokenis present, the client automatically prependslaunchto the requested scope. When no patient is selected andwants_patientis True, it prependslaunch/patient. This is a behavioral decision that could surprise developers who set their own scope. (fhirclient/client.py:92-101) -
The
patientproperty silently reauthorizes on 401 — If fetching the patient resource returns a 401, the client automatically attempts reauthorization (using refresh token) and retries the read. If reauthorization fails, it returnsNonesilently. (fhirclient/client.py:174-179) -
The
patientproperty clearspatient_idon 404 — If the patient resource is not found (404), the client silently clears the storedpatient_idand returnsNone. This is a destructive operation that could be surprising. (fhirclient/client.py:180-182) -
State serialization includes sensitive tokens — The
statedictionary includesaccess_token,refresh_token,code_verifier, andapp_secretin plaintext. The demo app stores this in Flask's session cookie, which is only signed, not encrypted by default. (fhirclient/auth.py:400-419,demos/flask/flask_app.py:19-20) -
The demo app has a hardcoded
app_idof'my_web_app'— The Flask demo defaults toapp_id: 'my_web_app'and requires the user to manually edit the file to setapi_base. There is no configuration UI or environment variable support. (demos/flask/flask_app.py:11-15) -
Pagination URL sanitization validates scheme and hostname — The
_sanitize_next_linkfunction validates that the next page URL useshttporhttpsand has a non-empty hostname, but it does not validate that the hostname matches the original server. This is a basic safety check, not a security boundary. (fhirclient/_utils.py:48-73) -
The
human_namemethod adds a comma before suffixes — When formatting a human name with suffixes, a comma is inserted before the suffix (e.g., "John Doe, Jr."). This is a specific formatting choice not documented in any spec. (fhirclient/client.py:198-200) -
The demo app resolves medication references by splitting the reference string —
_get_medication_by_refdoesref.split("/")[1]to extract the medication ID, rather than using the FHIR reference resolution infrastructure. This is fragile and assumes a specific reference format. (demos/flask/flask_app.py:44-46)
Spec Gaps
-
No documented support for
app_secretin the settings — The__init__docstring listsapp_id,app_secret,api_base,redirect_uri,patient_id,scope, andlaunch_tokenas supported settings, but the actual code also supportsjwt_token. This is a documentation gap. (fhirclient/client.py:20-28vsfhirclient/client.py:82) -
No documented behavior for what happens when
save_funcis None — The code raises an exception ifsave_funcis None, but the docstring doesn't mention this requirement. (fhirclient/client.py:59-62) -
The
reauthorizemethod's return value is inconsistent — The docstring says it returns a bool, but the underlyingserver.reauthorize()returns the launch context dict or None. The client method converts this to a bool check (self.launch_context is not None), which works but is confusing. (fhirclient/client.py:145-152) -
No documented rate limiting or retry behavior — The code has no retry logic for transient failures. If a request fails (other than 401 which triggers reauthorization), the exception propagates directly to the caller.
-
The
wants_patientflag is undocumented — This flag controls whether the client requestslaunch/patientscope when no patient is selected. It's a significant behavioral toggle with no documentation. (fhirclient/client.py:47-49) -
No documented support for
launch_context— Thelaunch_contextdictionary is stored and serialized but its structure and possible keys (e.g.,patient,encounter,id_token) are not documented anywhere.
Open Questions for Engineering
-
Token storage security — The state serialization stores access tokens, refresh tokens, and client secrets in plaintext. The Flask demo stores this in a session cookie. Is this acceptable for production use, or should we add encryption/warning documentation?
-
Patient ID clearing on 404 — When
patientproperty encounters a 404, it silently clearspatient_id. This means a transient data issue could permanently lose the patient context. Should this be a configurable behavior or at least logged more prominently? -
Scope augmentation behavior — The
desired_scopeproperty automatically addslaunchorlaunch/patientto the scope. Is this always desired? Could it conflict with a developer who explicitly sets a scope that already includes these values? -
Pagination URL validation scope — The
_sanitize_next_linkfunction validates scheme and hostname but doesn't verify the hostname matches the original server. Is this a deliberate design choice (to allow cross-server pagination) or a security gap? -
Demo app configuration — The Flask demo requires editing source code to set
api_base. Should we add environment variable support or a configuration page before considering this demo-ready for customer evaluation? -
Refresh token rotation — The code replaces the refresh token when a new one is issued, but there's no documented behavior for what happens if a refresh token is revoked or expires. Should we add explicit error handling for this case?
-
Client credentials grant vs. authorization code grant — The
authorize()method onFHIROAuth2Authuses client credentials, while the web flow uses authorization code viahandle_callback(). Is the client credentials path intended for backend/service scenarios only? The naming is ambiguous. -
The
_get_medication_by_refstring splitting — The demo app usesref.split("/")[1]to extract medication IDs. This will break if references use full URLs (absolute references). Should we fix this to use the proper FHIR reference resolution?