Limitations

What this does not do

Gaps, simulations and known misrepresentations, as recorded. Some are decisions that will not change, some are unbuilt, and some are open questions. Resolved entries stay on the page, collapsed, so the record of what was fixed is here too.

31 current · 12 resolved · 43 total, across 8 areas.

What this tool is (and is not)

ImOnFHIR validates Da Vinci PAS Submit Bundles — and, engine-side since INQ-102, PAS Inquiry request/response Bundles — against FHIR/PAS standards and a set of transaction-scoped semantic rules, and simulates lab-directory lookups. It does not determine medical necessity, issue authorizations, or predict a real payer's decision. Use synthetic or de-identified data only.

Simulated vs authoritative (must not be confused)

  • Lab-directory results are SIMULATED.

    Provider/facility/member "not found," "PAR205," and expected-scenario codes come from an in-process synthetic directory + a payer interpretation config. They are not an actual payer outcome. A ZERO_MATCH means "not in our small synthetic directory," not "the payer rejected this."

  • PAR / operational codes are payer-scoped simulation.

    PAR205 appears when a payer interpretation package (e.g. Onyx) is active — including when it auto-activates from a payload signal such as an ONYX-LAB identifier. It is expected/simulated metadata, not an observed validation finding.

  • Standards Validation is authoritative

    (HL7/Inferno sidecar for base FHIR + PAS profile); semantic rules are the product's own authoritative layer.

Multi-IG scope (2026-07)

  • Payload (sidecar) validation is PAS-only.

    The catalog now lists eight IGs (PAS, US Core, CRD, CDex, DTR, PDex, HRex, CARIN BB), but only PAS has a pas-submit-validation workflow binding and a sidecar hot set. The PAS hot set now spans all four published finals (2.0.1/2.1.0/2.2.0/2.2.1 — previously 2.1.0 + 2.2.1); production is live-verified warm on all four (/api/health?fresh=1, 2026-07-18), while preview still reports only 2.1.0 + 2.2.1origin/preview predates the version-gap train and needs a refresh from main plus warm-validator-igs.sh. Every other IG is package-analysis only: requirement diffs (IG Package Analysis), cross-IG stack divergences (IG Stack Compare), and version-gap evidence — no payload validation. Generalizing sidecar validation beyond PAS requires replacing the pas-ig façade and PasProfileSet role map, de-hardcoding the orchestrator's Claim-Submit compare unit, and adding a package-qualified version scope to the interpretation layer — deliberately out of scope for the version-gap release.

  • The mandate registry presets tools; it never asserts compliance.

    Mandate entries (CMS-0057-F, CMS-0053-F) preselect IG/version choices with an honest basis (named/recommended/supporting/related). There is no computed "complies with mandate X" verdict anywhere.

    References: CMS-0057 · CMS-0053

Current status-model limitations (being addressed — see backlog)

Integrity phase (2026-07): VAL-001 / STD-001 / PAY-001 / ARCH-001 addressed in code and Production-verified at SHA 64aaa75 (closeout decision B). Residual ops/policy: Production L1–L2 DEGRADED Resolved — live-verified 2026-07-16: validator.configured: hosted, reachable: true, PAS 2.1.0 + 2.2.1 loaded; public demo strip blocks AUTHORITATIVE_TEST_SCENARIO on Production APIs (intentional policy — the ARCH-002 manifest server-gate itself shipped in M4; the strip, not the gate, is the remaining behavior); AUTH PASS + synthetic adverse → headline INDETERMINATE remains locked policy.

6 resolved, kept for the record
  • Payload-Analysis can title a resolved entity "requires attention"

    Mitigated (PAY-001)

    demo uses lab-directory framing; PASS rows rendered; labels via labels.ts. Production-verified.

    References: PAY-001

  • Synthetic directory conflicts can drive NOT_READY in demonstration mode

    Mitigated (VAL-001)

    POS/DOB/coverage/delegation gated via syntheticAdverseOutcome(). Production-verified (demo).

    References: VAL-001

  • Readiness badge green when only partially evaluated

    Mitigated (STD-001)

    INDETERMINATE on OverallReadiness + amber badge channel.

    References: STD-001

  • Mid-run sidecar error as payload NOT_READY

    Mitigated (STD-003)

    dependencyFailure → DEGRADED. Production-verified under unconfigured sidecar.

    References: STD-003

  • UNKNOWN package coerced to AVAILABLE

    Mitigated (STD-002)

    UNKNOWN preserved.

    References: STD-002

  • Hosted validator sidecar Auth credentials

    Resolved (2026-07-16, live-verified)

    Production /api/health reports validator.configured: hosted, reachable: true, pas210Loaded/pas221Loaded: true. Credentials were restored after the integrity-closeout note; L1–L2 run live in Production.

Capability limitations (not defects — not built yet)

  • PAS Inquiry shipped as identify + interpret + UI; inquiry↔submit spec-key pairing shipped 2026-07-22 in the Compare court (CMP-207+), not in the single-payload workbench; no DTR Forms Lab / live engine execution

    (no arbitrary CQL/expression evaluation against real patients, no FHIR Path enableWhenExpression, no terminology expansion, no rendering; real SMART launch / live CDS Hooks execution not shipped — both ship as teaching simulators only (static, annotated; no real OAuth, no live hook, not validated) — public copy mentions real execution as a future direction only; DTR shipped comprehension plus H6 scoped declarative execution — evaluated enableWhen gating + retrieve presence, dual-stamped — per boundary (c); education-plane CQL Teaching Playground (DTR-109) additionally executes curated teaching ELM only against synthetic bundles in the browser — zero network, not payer logic on real patients). Since INQ-102 (2026-07-18) the pipeline understands inquiries: runPasInquiryValidation targets the inquiry request/response Bundle + anchor profiles (direction-aware, ClaimResponse anchor for responses), Layer 4 is anchor-aware, semantic rules are transaction-scoped (six PAS-SEM-INQ rules on inquiries; the 36 submit rules and lab-directory resolvers never leak onto them; an UNKNOWN transaction yields PAS-SEM-ENGINE-002 + INDETERMINATE, never a silent PASS), and clinical-routing's missing-procedure/diagnosis errors are gated off for deliberately-sparse inquiries. Since INQ-104 (PR #79, 2026-07-19) the workbench has the workflow selector, and since INQ-106 (PR #128, 2026-07-22) a user-selected workflow is provenance-labeled USER_SELECTED, not DECLARED. Honest boundaries: (a) INQ-103 pairing was dropped from the workbench 2026-07-19 as an architecture change (the workbench holds one payload); its user value shipped 2026-07-22 in the Compare court — the surface that structurally holds two — as spec-key pairing (CMP-207+): a verdict claims two payloads share correlation key K at paths A/B (exact system|value, never fuzzy), refuses non-pairable kind pairs with named reasons, carries per-side kind provenance (detected vs dictated), and treats patient+insurer as corroboration only. This is new lineage, not an INQ-103 reopen (the card stays closed); answerability — whether the paired submit could actually answer the inquiry — stays out of scope. See the phase-2 close note; (b) none of the H2 work is production-verified yet — production predates it (OPS-105); (c) DTR is comprehension + scoped declarative execution (H3 phase 1, 2026-07-22; H6 scoped execution, 2026-07-23): the workbench DTR mode statically analyzes Questionnaire payloads and extracts CQL/ELM data requirements as data, and now additionally EVALUATES two declarative slices: (1) enableWhen gating against a submitted QuestionnaireResponse — operator + typed value only, honoring all/any, three-valued ENABLED/DISABLED/INDETERMINATE, so a required item whose gate is ENABLED and left unanswered is a real gap, a correctly-DISABLED one is not, and an INDETERMINATE gate (unsupported operator/type or a missing controlling answer) keeps refusal semantics; and (2) retrieve presence against an optional data-context Bundle — three-valued PRESENT/ABSENT/INDETERMINATE per Library data requirement. What is STILL never executed: CQL/expression evaluation; the FHIR Path enableWhenExpression (it stays refused — never routed through the declarative comparator); terminology / value-set membership expansion (a value-set-bound retrieve is INDETERMINATE unless the data-context itself carries an inline-expanded ValueSet — no network call, DISABLE_TX + SEC-129 zero-egress); questionnaire rendering; real SMART launch and live CDS Hooks (only the teaching simulators above ship); sidecar/profile validation of DTR resources; EHR prefill; and pairing of DTR resources (the Compare-court pairing in boundary (a) is PAS Claim/ClaimResponse only, and the DTR panel itself stays single-payload). Adaptive questionnaires still refuse static counts rather than reporting numbers. Dual stamp: the static extraction carries not executed — statically extracted; the evaluated enableWhen decisions and retrieve presence carry a distinct evaluated — …; no CQL, FHIR Path expressions, or terminology executed stamp, so "read as data" and "evaluated as declared logic" are distinguishable in the output rather than asserted in prose. The scoped-execution control (validator-dtr-scoped-execution.test.ts) keeps the no-ENGINE boundary falsifiable and couples any future engine execution to same-PR ledger/copy updates. Anything beyond those two declarative slices stays future-tense. Close-out record: H2 close-out / H3 entry note.

    References: CMP-207 · DTR-109 · INQ-102 · SEM-ENGINE-002 · INQ-104 · INQ-106 · INQ-103 · OPS-105 · SEC-129

  • Version comparison is version-distinct via {canonical}|{SD.version} dispatch with user-selected target + up to 2 registry-backed baselines

    (IG-102 + catalog multi-version, 2026-07). Residual: PAS profile canonicals remain identical unversioned URLs across versions, so distinctness depends entirely on the versioned-reference dispatch path — the deploy-time sentinel (scripts/verify-version-distinct-validation.mts) verifies it; comparisons beyond 2 baselines are not offered.

    References: IG-102

  • Human-readable migration guidance is not implemented

    (machine catalogs + JSON export only — and on production the file export itself is off; see the paste-in / copy-out bullet below).

  • Production is paste-in / copy-out only (SEC-129, 2026-07-22).

    File upload (buttons, hidden inputs, drag-drop, CodeMirror file-drop) and file export (every download button plus POST /api/validator/version-aware/export, which 404s) are capability-gated off on imonfhir.com and invisible client-side; compare batch mode (upload-only by construction) hides with them. Copy-to-clipboard is the output path — the version-aware report and the Builder gained Copy JSON buttons so no output is stranded. Preview/local keep full file I/O. Kill switches: NEXT_PUBLIC_LAB_FILE_UPLOAD_ENABLED / NEXT_PUBLIC_LAB_FILE_EXPORT_ENABLED (+ server LAB_* aliases); NEXT_PUBLIC_* values are build-time inlined, so a production flip requires a redeploy.

    References: SEC-129

  • Semantic coverage:

    Diagnosis / Procedure / Bundle-structure / CareTeam / References rule families shipped (SEM-101); FAC-003 / ID-001 remain placeholders that never fail; MEM-002 in-Bundle is a no-op.

    References: SEM-101 · FAC-003 · ID-001 · MEM-002

  • Payload Washer is not HIPAA de-identification

    client-side format-preserving wash for synthetic test data only; never claim compliance de-id. (see /lab/washer, /security)

  • The Washer does not wash provider or facility resources

    DECIDED 2026-07-19 (WASH-107b), permanently. Every shipped policy sets R9 to keep, so Practitioner, PractitionerRole, Organization and Location pass through with only their id re-keyed — names, NPIs, facility addresses, telecom and narratives are preserved verbatim, and they are deliberately excluded from the final leak scrub so nothing catches them downstream either. Rationale: provider and site data is public NPPES reference data, not PHI; users substitute their own NPI when moving a payload into their own system; and provider→site→group rollups must survive intact or the payload stops exercising the relationships it was collected for. There is no opt-in provider-wash mode and none is planned. It remains easy to misread "wash" as covering everything, so the statement stands: washed output can still carry real provider and facility identity. Stated plainly in the Lab help drawer.

    References: WASH-107b

  • Address geography is a three-way choice, and the least protective option keeps a full ZIP.

    R7's geo mode is wash (synthetic triple), keep-region (real state + first three ZIP digits, city dropped), or keep-full (real city/state/ZIP). The street line is replaced in every mode. keep-full retains the most re-identifying geographic field in the payload and exists for operators who need real geography in test data — it is an explicit opt-in, not a default. keep-region is not HIPAA Safe Harbor: the restricted low-population three-digit prefixes are not zeroed (WASH-110).

    References: WASH-110

  • R13 clears note on every resource unconditionally.

    The text narrative strip directly above it is gated on the R13 policy action, but the note clearing is not — so a policy that keeps narratives still destroys clinical notes. Filed as WASH-108.

    References: WASH-108

  • Contained resources are not walked by the per-resource wash rules.

    washResource runs on Bundle entries and the root resource only; identifying values inside contained are caught by the final leak scrub and blanked to [washed] rather than replaced with synthetic values — safe, but it destroys realism and can break parsers the payload was meant to exercise. Filed as WASH-109.

    References: WASH-109

  • NPPES lookup is off in Production

    by default (nppesLookup capability, preview tier); local/preview may enable it behind the kill-switch.

  • Hosted HAPI content writes are refused by default

    (INFRA-101 phase 0): with CF Access creds set and HAPI_HOSTED_WRITES_ENABLED unset, HapiClient.postTransaction/delete throw before any request, projections return an explicit 503 { reason: 'hapi_writes_disabled' }, and the workspace TTL sweep pauses (expired rows are deliberately retained — they are the inventory for the one-time shared-HAPI cleanup). This closes the gap where a Vercel Preview session wrote projection data into the shared production HAPI. Local (no creds) is unaffected; reads are never gated; /api/health reports sidecar.hapiWriteMode. The flag is set at the dedicated preview-stack cutover, never on Production.

    References: INFRA-101

3 resolved, kept for the record
  • mappingConflicts is modeled but not produced at runtime (always null); the CONFLICTED interpretation status is unreachable via mapping conflicts and is not rendered

    Resolved (IG-104, 2026-07-19)

    resolveInterpretationForRequest now sweeps the operational conditions declared by the selected packs, calls mergeOperationalResolutions per condition, and returns real mappingConflicts plus a CONFLICTED status via deriveInterpretationStatus({ mappingConflictCount }). [] now means evaluated, none found; null is reserved for paths that never evaluate (non-Bundle payload, parse failure). The version-aware panel renders a CONFLICTED badge and one row per conflict naming the disagreeing packages and the condition (version-aware-panel.tsx; fixture fixtures/validator/pas-submit/interpretation-mapping-conflict/; tests/unit/interpretation-mapping-conflicts.test.ts, version-aware-conflict-panel.test.tsx, export-parity.test.ts). Residuals: (1) on the current pack inventory a cross-pack disagreement requires two payer packs, which also raises MULTIPLE_PAYER_ROOTS — that selection conflict forces CONFLICTED on its own, so the mapping-conflict count is presently a coincident rather than an independent driver of the status (the new rows are the genuinely new signal). Because every built-in pack carries a distinct payerId, "mapping conflict without selection conflict" is unconstructible through the public API, which means the line plumbing the count into deriveInterpretationStatus is not pinnable by any test driving resolveInterpretationForRequest — a mutation deleting it left the whole suite green. It is now pinned via an @internal test seam (resolveInterpretationForRequestWithCatalog) driving two packs that share one payerId; the public entry point still takes no catalog. If that seam is ever removed, the plumbing becomes unpinned again; (2) targetKey omits workflow, so two conditions differing only by workflow share a key — conflicts are de-duplicated on the full signature instead; (3) mergeOperationalResolutions returns early on an in-pack EQUAL_PRECEDENCE tie, which masks any cross-pack disagreement on that same condition.

    References: IG-104

  • Server-side export route omits interpretation + mappingConflicts fields

    Resolved (IG-103 + EXP-101, PR #24 `8bd47d7`)

    the export route forwards findingInterpretation, interpretationContext, interpretationStatus, mappingConflicts, interpretationProvenance and embeds on-screen counts (src/app/api/validator/version-aware/export/route.ts; tests/unit/export-parity.test.ts).

    References: IG-103 · EXP-101

  • Washer config-as-data and full per-rule fixtures on stacked branches, not main

    Resolved (2026-07-18):

    WASH-103 landed via PR #33 fec5063 (14/14 golden fixtures + sidecar coherence script) and WASH-102 via PR #34 afc6ade (policy/wordlist JSON authoritative; INLINE_DEFAULT_POLICY removed). Residual stacked-not-landed scope resolved (2026-07-18): the #26 version-gap train (gap report, mandate presets, pulse-merged catalog, DTR/PDex/HRex/CARIN package promotion, PULSE-101) cherry-picked onto ops-103/land-version-gap-train (OPS-103; PR #46, merge pending).

    References: WASH-103 · WASH-102 · PULSE-101 · OPS-103

Operational / security limitations

  • No authentication on any API route;

    most lab pages remain publicly reachable by URL. Per-IP rate limiting now guards the four public POST routes (validate, payload-analysis, version-aware export, clinical-context — SEC-102, 2026-07), the NPPES lookup GET (NPPES-101), and — since SEC-108, 2026-07-19 — the IG analysis and stack-compare POSTs, which had been unlimited and uncapped on Production while downloading FHIR packages under maxDuration = 60. All share one bucket per IP, so switching routes earns no fresh quota. It remains per-lambda-instance / best-effort: the effective limit scales with concurrent instance count and resets on cold start (src/lib/ops/rate-limit.ts; Vercel WAF rate rules are the platform-level upgrade path). Public GETs — including /api/health, which uptime monitors poll — remain unlimited by design. Hidden Lab tools differ in how hidden they actually are, and the previous wording had it backwards in both directions:

    • Payload Builder (/lab/builder), Hosted FHIR (/lab/fhir), and Projection Workflows (/lab/projections) have real route gates — all three 307 to /lab on Production, and the Hosted FHIR / projection APIs return 403.
    • Scenario Library is genuinely discovery-only: scenarios is false in the Production matrix, but /lab/validator/test-scenarios and /api/validator/scenarios both still return 200 on Production (live-verified 2026-07-18). Nothing sensitive is exposed — the fixtures are synthetic — but the gate does not match the flag. Tracked as SEC-104 (P3 decision: 307 vs 404, bundled with the 403-vs-404 API inconsistency); deliberately not changed in the tier PR, since removing a route that has served 200s for months is its own decision and shouldn't ride a gate-critical change. (UX-102)

    References: SEC-102 · NPPES-101 · SEC-108 · SEC-104 · UX-102

  • The entire access posture is a function of deployed environment variables, and no test in this repo can observe them

    (2026-07-19 security pass). There is no authentication in production by design — src/middleware.ts:42 short-circuits unless VERCEL_ENV=preview, so the preview lock never arms on Production. What stands between a route and the public is getPublicLabCapabilities(), requireLabMutationsEnabled() (LAB_MUTATIONS_ENABLED), and isPublicValidatorMode() (LAB_RUNTIME_MODE) — all env reads. This is defensible for a synthetic-data lab with nothing worth reaching, and it is disclosed in the external answer pack. State it plainly: the compensating control is that there is no sensitive data behind the gate, not that the gate is strong. A misconfigured deploy is the single largest unverifiable risk, and VERCEL_ENV resolving to anything other than production on a production deployment silently inverts the capability matrix.

  • The Washer ships its own method to every visitor

    (SEC-113, 2026-07-19). washer-client.tsx is 'use client' and imports the whole engine, so ~1,288 lines plus the wordlist/policy JSON are in the client bundle. This is not a defect — it is the direct consequence of the client-side execution that makes "your payload never leaves the browser" true, and that trade was made deliberately in WASH-102 (policies.ts:2-6). It is recorded here because it means the Washer's rule quality cannot be sold as proprietary, and because a runtime-fetched pack does not fix it absent an auth boundary that does not exist. See the SEC-113 card for the full analysis before re-proposing a fix.

    References: SEC-113 · WASH-102

  • sha256 entries in the org-name guard are obfuscation, not a control

    (SEC-111, 2026-07-19). Digests and normalization both live in the repo, so a candidate name list can be hashed and matched by anyone with repo access. There are zero such entries today (all 24 blocked terms are plaintext), which makes switching to keyed HMAC free right now — do it before the first sensitive name is hashed. Relevant to GOV-101 and to any repo transfer.

    References: SEC-111 · GOV-101

  • /api/health is unauthenticated

    and can still take tens of seconds on a cold probe; a 30s in-memory cache reduces repeated poller cost (?fresh=1 bypasses). Top-level status now requires database + HAPI + validator reachable (OPS-101). See fhir-services-monitoring.md.

    References: OPS-101

  • Env vars documented in .env.example

    (including FHIR_PACKAGE_ALLOW_NETWORK). Keep that file current when adding new code-read vars. (SEC-103 residual: confirm all six are listed)

    References: SEC-103

  • Terminology (extended tx) is off by default

    (DISABLE_TX=true on the fhir-validator service); enable it by setting that flag to false and restarting the sidecar (docker compose up -d fhir-validator). Enabling TX is a flag flip on the service — there is no separate compose overlay. (OPS-102)

    References: OPS-102

  • Anonymous usage counters fire from tool UIs

    (2026-07): Vercel Analytics custom events validator_run, washer_run, compare_used, library_read (slug only), security_review_cta — closed union in src/lib/metrics/track.ts. Hard rule: no payload-derived properties, ever; disclosed publicly on /security. Events require a Vercel plan tier that supports custom events to appear in the dashboard.

1 resolved, kept for the record
  • Body-size cap bypassable / no depth guard / unguarded routes

    Resolved (SEC-101, 2026-07):

    the body cap (1 MB, VALIDATOR_MAX_BODY_BYTES) is enforced by streamed byte count — non-bypassable via chunked/absent Content-Length; JSON nesting is capped (64, VALIDATOR_MAX_JSON_DEPTH) including depth bombs inside JSON string payloads; the version-aware export and clinical-context routes are guarded and use the validator error envelope (src/lib/validator/request-guards.ts; tests/unit/request-guards.test.ts, export-clinical-context-guards.test.ts).

    References: SEC-101

Test / evidence limitations

  • UI race scenarios (Group J, stale-result-after-replace) are "safe by inspection" only

    0/10 automated. (TEST-102)

    References: TEST-102

  • Operator UAT for Knowledge Library / Washer is still open

    (KLW-UAT-001) — engineering smoke ≠ cohort interviews. Update 2026-07-18: the Understand track is now published + surfaced on the /library index, so Cohort A's script is executable (evidence scaffold: docs/release/G6_UAT_COHORT_A_2026-07.md). The Lighthouse a11y sub-gate was recorded /library = 100/100 (2026-07-17, G6 note, artifacts archived) but predates the index DOM change — needs a post-surfacing re-record; the cohort/sev-1 gate remains.

    References: KLW-UAT-001

  • The "42 semantic rules" count is the catalog size, not the number that fire on any single payload

    (SEM-105; the figure was 39 until PAY-013 registered PAS-SEM-MEM-003/004 and is pinned by EXPECTED_SEMANTIC_RULE_COUNT in scripts/certify-semantic.mts). Several rules are conditional: under the shipped default rule-set they return NOT_APPLICABLE, and they produce findings only when a rule-set enables the relevant policy — FAC-003 under a facilityAddressPolicies REQUIRED entry, ID-001 under a non-Practitioner REQUIRED identifierPolicies entry, and PROV-003's FAIL/WARNING branches under an explicit providerRoleMappings (the default claimcareteamrole map never yields RENDERING/ORDERING). This is normal conditional-rule behaviour, not dead code — each is credited firing when configured (SEM-108/111 and semantic-conditional-rules.test.ts). MEM-002's in-Bundle path is a no-op by design; its finding comes from the lab-directory resolver.

    References: SEM-105 · PAY-013 · SEM-MEM-003 · FAC-003 · ID-001 · PROV-003 · SEM-108 · MEM-002

2 resolved, kept for the record
  • AUTOMATED in the scenario catalog is a manual claim

    Mitigated (TEST-101)

    catalog verification requires resolvable fixture/named-test refs.

    References: TEST-101

  • version-aware-pas-packages.test.ts silent-pass offline

    Mitigated (TEST-101 / IG-102 path)

    sentinel + fail-closed profile resolution.

    References: TEST-101 · IG-102

Unverified in this audit (open questions)

  • Accessibility

    (keyboard/screen-reader/contrast/focus) was not separately audited — status unknown.

  • Backup / retention / monitoring / alerting / incident response

    were not verifiable from source.

  • LAB_RUNTIME_MODE production value

    (governs validator public-mode redaction) cannot be confirmed from source alone.