-
Notifications
You must be signed in to change notification settings - Fork 71
RELEASE CW cert runner and initial api migration updates #4436
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Ref eng-513 Signed-off-by: Rafael Leite <2132564+leite08@users.noreply.github.com>
Ref eng-513 Signed-off-by: Rafael Leite <2132564+leite08@users.noreply.github.com>
Ref eng-200 Signed-off-by: Rafael Leite <2132564+leite08@users.noreply.github.com>
Part of ENG-200 Signed-off-by: RamilGaripov <ramil@metriport.com>
Part of ENG-200 Signed-off-by: RamilGaripov <ramil@metriport.com>
Ref eng-513 Signed-off-by: Rafael Leite <2132564+leite08@users.noreply.github.com>
Ref eng-513 Signed-off-by: Rafael Leite <2132564+leite08@users.noreply.github.com>
Ref eng-513 Signed-off-by: Rafael Leite <2132564+leite08@users.noreply.github.com>
Part of ENG-200 Signed-off-by: RamilGaripov <ramil@metriport.com>
Eng 200 cert runner updates
WalkthroughRemoves Organization E2E tests and the /medical/organization API routes. Updates CommonWell v2 OID validation and deprecates CW member OID access. Migrates cert-runner from memberOID to rootOID, adds env-driven org reuse, introduces an initiator-only org-management flow, adjusts several flows and logs, expands feature flags, relaxes organization SDK schema, and adds retry onError support. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant CR as CertRunner
participant CWc as CommonWell API (Contributor)
participant CWu as CommonWell API (Consumer)
rect rgba(230,245,255,0.5)
note over CR: Initialize
CR->>CR: Load contribExistingOrgOid
alt OID provided
CR->>CWu: Use existing consumer Org (by OID)
CWu-->>CR: Org metadata
else No OID
CR->>CWu: Create consumer Org
CWu-->>CR: organizationId
end
end
rect rgba(235,255,235,0.5)
note over CR: Patients and Linking
CR->>CWc: Create patient on contributor (2.2)
CWc-->>CR: contributorPatientId
CR->>CWu: Create patient on consumer (2.3)
CWu-->>CR: consumerPatientId
CR->>CWu: Link patients (2.4)
CWu-->>CR: Link confirmed
end
rect rgba(255,245,230,0.5)
note over CR: Documents
CR->>CWu: Query documents (2.5)
CWu-->>CR: DocumentReference(s)
end
sequenceDiagram
autonumber
participant CR as CertRunner
participant CW as CommonWell API
rect rgba(235,255,235,0.5)
note over CR: Initiator-only Org Management (8.2.2)
CR->>CW: Create Org (initiator-only payload)
CW-->>CR: organizationId
CR->>CW: Get Org by ID
CW-->>CR: Org details
CR->>CW: Update Org (e.g., city)
CW-->>CR: Updated Org
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
🧪 Generate unit tests
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
feat(core): add FF to enable cxs to CW v2
…-fix ENG-200: cw cert runner fixes
Part of ENG-000 Signed-off-by: RamilGaripov <ramil@metriport.com>
ENG-000 Remove Org e2e tests
Backmerge master into develop
@coderabbitai what are we doing here? how long should i wait?? |
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed:
Length of output: 39835 @RamilGaripov I can see your frustration! Looking at the current status of PR #4436: Good news: ✅ Almost everything is ready to go!
What you're waiting for: 🔄 Only CodeRabbit's review is still pending
Your options:
Since this is a release PR pointing to master and everything else is green, you're technically ready to merge whenever you want! 🐰💭 🧠 Learnings used
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 9
🧹 Nitpick comments (36)
packages/commonwell-sdk/src/models/organization.ts (1)
104-107
: Consider AligningorganizationSchemaWithoutNetworkInfo
with Its Name or Renaming ItIt looks like the current schema still exposes
gateways
andsecurityTokenKeyType
, which may confuse anyone expecting all network-related fields to be omitted. Your ripgrep search didn’t surface any direct uses of theOrganizationWithoutNetworkInfo
type in the codebase, so adjusting the omit or renaming the export should be low-risk.Locations to review:
- packages/commonwell-sdk/src/models/organization.ts
Lines 104–107 define:export const organizationSchemaWithoutNetworkInfo = organizationSchemaWithNetworkInfo.omit({ networks: true, authorizationInformation: true, }); export type OrganizationWithoutNetworkInfo = z.infer<typeof organizationSchemaWithoutNetworkInfo>;Optional refactor suggestions:
• Option A — Omit all network fields to match the name:
export const organizationSchemaWithoutNetworkInfo = organizationSchemaWithNetworkInfo.omit({ networks: true, - authorizationInformation: true, + authorizationInformation: true, + gateways: true, + securityTokenKeyType: true, });• Option B — Keep the current shape but rename for precision:
export const organizationSchemaWithoutNetworksAndAuth = organizationSchemaWithNetworkInfo.omit({ networks: true, authorizationInformation: true, }); export type OrganizationWithoutNetworksAndAuth = z.infer<typeof organizationSchemaWithoutNetworksAndAuth>;packages/shared/src/common/retry.ts (2)
42-47
: Clarify the onError contract in docs (and consider passing attempt for richer context)The doc states onError doesn’t affect shouldRetry/outcome and runs before shouldRetry. Today, if onError itself throws, it will derail the retry flow (see catch path). Either (a) harden the implementation to swallow/log onError errors (recommended — see another comment), or (b) update the docs to explicitly state that onError must not throw.
Doc tweak (if you harden the implementation):
/** - * Function to be called when an error occurs. It doesn't change whether shouldRetry is called - * or not, nor does it change the result of that function. - * It's called before shouldRetry. + * Called once per failed attempt (catch path) before shouldRetry. + * It must not influence shouldRetry or the outcome. + * If it throws, the error is swallowed and logged; the retry flow proceeds. + * Not awaited. */Optionally, consider including the attempt number in the callback for better observability:
- onError?: (error: unknown) => void; + onError?: (error: unknown, attempt: number) => void;If you do this, remember to pass attempt in the catch path and update tests. The existing tests that assert the first arg only will continue to pass.
7-10
: Align defaultShouldRetry signature with declared type (minor consistency)shouldRetry is invoked with (result, error, attempt). Default impl ignores attempt, but adding it to the signature improves consistency/IntelliSense and avoids accidental refactors that rely on parameter counts.
-function defaultShouldRetry<T>(_: T | undefined, error: unknown) { +function defaultShouldRetry<T>(_: T | undefined, error: unknown, _attempt: number) { if (error) return true; return false; }packages/shared/src/common/__tests__/retry.test.ts (1)
118-188
: New onError tests are solid; consider deterministic timing and add two edge casesGreat coverage validating per-attempt invocation, absence on success, and argument identity. Two enhancements:
- Eliminate real sleeps for speed/stability by injecting getTimeToWait: () => 0 in these tests.
- Add a test proving onError is not called when retries are driven by a retryable result (no thrown error).
- If you adopt the onError guard in retry.ts, add a test ensuring an onError throw doesn’t break the retry flow.
Apply zero-wait to the new cases:
await expect(async () => executeWithRetries(fn, { initialDelay: 1, maxAttempts: 3, + getTimeToWait: () => 0, }) ).rejects.toThrow(); ... await expect(async () => executeWithRetries(fn, { initialDelay: 1, maxAttempts: 3, onError, + getTimeToWait: () => 0, }) ).rejects.toThrow(); ... const resp = await executeWithRetries(fn, { initialDelay: 1, maxAttempts: 3, onError, + getTimeToWait: () => 0, }); ... await expect(async () => executeWithRetries(fn, { initialDelay: 1, maxAttempts: 2, onError, + getTimeToWait: () => 0, }) ).rejects.toThrow();Add “result-based retry doesn’t call onError”:
@@ describe("onError", () => { + it("does not call onError when retrying due to a retryable result (no error thrown)", async () => { + const onError = jest.fn(); + const retryableResult = faker.lorem.word(); + const expectedResult = faker.lorem.word(); + fn.mockImplementationOnce(() => retryableResult); + fn.mockImplementationOnce(() => expectedResult); + const resp = await executeWithRetries(fn, { + maxAttempts: 3, + shouldRetry: result => result === retryableResult, + getTimeToWait: () => 0, + onError, + }); + expect(resp).toEqual(expectedResult); + expect(onError).not.toHaveBeenCalled(); + });Add “onError throw is contained” (only if you apply the guard in retry.ts):
@@ describe("onError", () => { + it("swallows onError exceptions and continues retrying", async () => { + const onError = jest.fn(() => { + throw new Error("onError failure"); + }); + fn.mockImplementation(() => { + throw new Error("work error"); + }); + await expect(async () => + executeWithRetries(fn, { + maxAttempts: 2, + getTimeToWait: () => 0, + onError, + }) + ).rejects.toThrow("work error"); + expect(onError).toHaveBeenCalledTimes(2); + });Note: I intentionally kept arrow functions to match the existing test style, per our past learning that you prefer consistency within the file over isolated style changes.
packages/core/src/command/feature-flags/domain-ffs.ts (2)
306-309
: Prefer includes(...) over some(...) for readability and consistencySeveral helpers in this file use
includes(cxId)
. Mirror that here to avoid unnecessary arrow functions and keep the style uniform.export async function isQuestFeatureFlagEnabledForCx(cxId: string): Promise<boolean> { const cxIdsWithQuestEnabled = await getCxsWithQuestFeatureFlag(); - return cxIdsWithQuestEnabled.some(i => i === cxId); + return cxIdsWithQuestEnabled.includes(cxId); }
311-318
: Use includes(...) and consider naming alignment with existing “With...Enabled” getters
- Swap
some(i => i === cxId)
forincludes(cxId)
for consistency/readability.- Optional: for naming consistency with helpers like
getCxsWithEpicEnabled
, considergetCxsWithCommonwellV2Enabled
as the getter name. Not blocking, but it keeps patterns predictable (noting prior team preference to keep file-local patterns consistent).export async function isCommonwellV2EnabledForCx(cxId: string): Promise<boolean> { const cxIdsWithCommonwellV2Enabled = await getCxsEnabledForCommonwellV2(); - return cxIdsWithCommonwellV2Enabled.some(i => i === cxId); + return cxIdsWithCommonwellV2Enabled.includes(cxId); }packages/commonwell-cert-runner/src/flows/document-consumption.ts (4)
11-11
: OK on removing waitSeconds import; consider adding targeted polling to handle CW eventual consistencyDropping the artificial wait can speed up the flow, but CommonWell responses (query/retrieve) are often eventually consistent. Without a brief, bounded poll, you'll see intermittent "no documents yet" or 404s immediately after patient creation. Recommend adding a small polling helper around the first query to reduce flakes.
Apply this diff to add a minimal, dependency-free poll and use it for the first query:
@@ -import { getMetriportPatientIdOrFail, logError } from "../util"; +import { getMetriportPatientIdOrFail, logError } from "../util"; @@ - const documents = await queryDocuments(commonWell, encodedPatientId); + const documents = await poll( + () => queryDocuments(commonWell, encodedPatientId), + ds => ds.length > 0, + { tries: 6, delayMs: 1500 } // ~7.5s max + ); @@ } +/** + * Simple polling helper with linear backoff. + */ +async function poll<T>( + fn: () => Promise<T>, + isDone: (value: T) => boolean, + opts: { tries: number; delayMs: number } +): Promise<T> { + const { tries, delayMs } = opts; + let last: T | undefined; + for (let i = 0; i < tries; i++) { + last = await fn(); + if (isDone(last)) return last; + if (i < tries - 1) await new Promise(r => setTimeout(r, delayMs)); + } + // return last attempt even if not "done" so callers can decide next step + return last as T; +}
13-13
: Use a filesystem-safe timestamp to avoid invalid folder names on Windows/macOStoISOString() contains characters like ":" that break on Windows. Format the timestamp to a safe pattern.
Apply:
-const runTimestamp = buildDayjs().toISOString(); +const runTimestamp = buildDayjs().format("YYYYMMDD-HHmmss");
124-129
: File path safety, cross-platform paths, and variable shadowing
- Potentially unsafe docId (could contain ":"/"/" etc.) is used as filename.
- Paths are manually concatenated; prefer path.join for cross-platform support.
- contentType is shadowed (declared twice), which is easy to misread.
Refactor to sanitize the ID, use path.join, and disambiguate types.
Apply:
+import path from "path"; @@ - const outputDir = `${outputBaseDir}/${runTimestamp}`; + const outputDir = path.join(outputBaseDir, runTimestamp); fs.mkdirSync(outputDir, { recursive: true }); - const fileNamePrefix = `${outputDir}/${docId}`; + const safeDocId = docId.replace(/[^a-zA-Z0-9._-]/g, "_"); + const fileNamePrefix = path.join(outputDir, safeDocId); const docRefFileName = `${fileNamePrefix}.docRef.json`; const contentsFileName = `${fileNamePrefix}.contents${fileExtension}`; @@ - if (contentsFileName.endsWith(".bin")) { - const newFileExtension = getFileExtension(contentType); + if (contentsFileName.endsWith(".bin")) { + const newFileExtension = getFileExtension(contentType); fs.renameSync(contentsFileName, `${contentsFileName.replace(".bin", "")}${newFileExtension}`); }And disambiguate content types:
- const contentType = content?.attachment.contentType ?? "application/octet-stream"; - const fileExtension = getFileExtension(contentType); + const declaredContentType = content?.attachment.contentType ?? "application/octet-stream"; + const fileExtension = getFileExtension(declaredContentType); @@ - const { contentType, size } = await commonWell.retrieveDocument(url, outputStream); + const { contentType: retrievedContentType, size } = await commonWell.retrieveDocument( + url, + outputStream + ); @@ - const newFileExtension = getFileExtension(contentType); + const newFileExtension = getFileExtension(retrievedContentType);Also applies to: 139-142, 150-154
133-146
: Ensure the write stream is closed on failures to avoid FD leaksIf retrieveDocument throws before closing the stream, we may leak a file descriptor. Close the stream in a finally block.
Apply:
- try { + try { const { contentType, size } = await commonWell.retrieveDocument(url, outputStream); console.log( `>>> Downloaded document ${docId}, size: ${size} bytes, contentType: ${contentType}` ); if (contentsFileName.endsWith(".bin")) { const newFileExtension = getFileExtension(contentType); fs.renameSync(contentsFileName, `${contentsFileName.replace(".bin", "")}${newFileExtension}`); } - } catch (error) { + } catch (error) { console.log(`Error retrieving document ${docId}: ${errorToString(error)}`); return false; + } finally { + try { + outputStream.close(); + } catch { + // ignore + } }packages/commonwell-cert-runner/src/flows/link-management.ts (4)
8-8
: OK removing artificial waits; add bounded polling around eventually-consistent stepsLinking/unlinking/reset operations often require a short propagation before reads reflect the new state. Replace fixed sleeps with small, bounded polls around:
- 2.1.2 get links after create
- 2.2.2 get links / 2.2.3 probable links
- 2.3.3 get links after link
- 2.4.2 get links after unlink
- 2.5.2 get links after reset
This preserves speed but avoids flakiness.
Example (apply similarly to other reads):
-const resp_2_3_3 = await commonWell.getPatientLinksByPatientId( - patientWithProbableLinksIdEnconded -); +const resp_2_3_3 = await poll( + () => commonWell.getPatientLinksByPatientId(patientWithProbableLinksIdEncoded), + r => (r.Patients?.length ?? 0) > 0, + { tries: 5, delayMs: 1000 } +);Helper to add at bottom of the file (or import from a util if you already have one):
async function poll<T>( fn: () => Promise<T>, isDone: (value: T) => boolean, opts: { tries: number; delayMs: number } ): Promise<T> { const { tries, delayMs } = opts; let last: T | undefined; for (let i = 0; i < tries; i++) { last = await fn(); if (isDone(last)) return last; if (i < tries - 1) await new Promise(r => setTimeout(r, delayMs)); } return last as T; }
60-67
: Fix typo: Enconded → EncodedMinor naming issue that hurts readability and can lead to future mistakes.
Apply:
-const patientWithProbableLinksIdEnconded = encodeToCwPatientId({ +const patientWithProbableLinksIdEncoded = encodeToCwPatientId({ patientId: patientWithProbableLinksId, assignAuthority: commonWell.oid, }); @@ -const patientWithProbableLinks = await commonWell.getPatient( - patientWithProbableLinksIdEnconded -); +const patientWithProbableLinks = await commonWell.getPatient( + patientWithProbableLinksIdEncoded +); @@ -const resp_2_2_2 = await commonWell.getPatientLinksByPatientId( - patientWithProbableLinksIdEnconded -); +const resp_2_2_2 = await commonWell.getPatientLinksByPatientId( + patientWithProbableLinksIdEncoded +); @@ -const resp_2_2_2x = await commonWell.getPatientLinksByPatientId( - patientWithProbableLinksIdEnconded -); +const resp_2_2_2x = await commonWell.getPatientLinksByPatientId( + patientWithProbableLinksIdEncoded +); @@ -const resp_2_3_3 = await commonWell.getPatientLinksByPatientId( - patientWithProbableLinksIdEnconded -); +const resp_2_3_3 = await commonWell.getPatientLinksByPatientId( + patientWithProbableLinksIdEncoded +); @@ -const resp_2_4_2 = await commonWell.getPatientLinksByPatientId( - patientWithProbableLinksIdEnconded -); +const resp_2_4_2 = await commonWell.getPatientLinksByPatientId( + patientWithProbableLinksIdEncoded +); @@ -const resp_2_5_2 = await commonWell.getPatientLinksByPatientId( - patientWithProbableLinksIdEnconded -); +const resp_2_5_2 = await commonWell.getPatientLinksByPatientId( + patientWithProbableLinksIdEncoded +);Also applies to: 72-80, 83-90, 133-137, 148-153, 167-172
44-45
: PII-heavy logs — consider masking in shared environmentsThe responses include PHI/PII (names, addresses, DOB). If this runner might be used beyond local dev, consider masking or reducing payload logs, or gating them behind an env flag.
Also applies to: 68-71, 124-131, 167-172
178-193
: Optional: delete patients concurrently to reduce teardown timeNon-blocking improvement; cleanup can be parallelized with Promise.allSettled.
Apply:
- const uniquePatientIds = uniq(patientIds); - for (const metriportPatientId of uniquePatientIds) { - try { - const patientId = encodeToCwPatientId({ - patientId: metriportPatientId, - assignAuthority: commonWell.oid, - }); - await commonWell.deletePatient(patientId); - console.log(`>>> Patient deleted: ${metriportPatientId}`); - } catch (err) { - console.log(`>>> Patient NOT deleted: ${metriportPatientId}`); - // intentionally ignore, - } - } + const uniquePatientIds = uniq(patientIds); + await Promise.allSettled( + uniquePatientIds.map(async metriportPatientId => { + try { + const patientId = encodeToCwPatientId({ + patientId: metriportPatientId, + assignAuthority: commonWell.oid, + }); + await commonWell.deletePatient(patientId); + console.log(`>>> Patient deleted: ${metriportPatientId}`); + } catch { + console.log(`>>> Patient NOT deleted: ${metriportPatientId}`); + } + }) + );packages/commonwell-cert-runner/README.md (2)
62-66
: Clarify naming: “Member ID and Root OID”Since the runner now expects ROOT_OID, consider updating the bullet to avoid confusion with CW_MEMBER_OID.
Apply:
-**Member Organization (Service Adopter):** +**Member Organization (Service Adopter):** @@ -- Member ID and OID +- Member ID and Root OID
125-127
: Ensure README’s env var matches the codeThe README in packages/commonwell-cert-runner/README.md still shows
CW_ORG_ID
, but the runner’s code in src/env.ts readsCW_ORG_OID
. Aligning the docs with the implementation will prevent confusion for users.• File: packages/commonwell-cert-runner/README.md
Lines: 125–127Apply:
-# Optional: Use existing organization instead of creating new one -CW_ORG_ID=1.2.3.4.5.678.5.123 +# Optional: Use existing organization instead of creating new one +CW_ORG_OID=1.2.3.4.5.678.5.123packages/commonwell-cert-runner/src/index.ts (2)
51-51
: Track failure label consistently; consider a tiny naming tweakYou push "initiatorOnlyOrgManagement" into failedFlows while the function is named orgManagementInitiatorOnly. Rename for consistency across code/searchability.
Apply within this line:
- await orgManagementInitiatorOnly().catch(() => failedFlows.push("initiatorOnlyOrgManagement")); + await orgManagementInitiatorOnly().catch(() => failedFlows.push("orgManagementInitiatorOnly"));Optional: to reduce repetition of
.catch(() => failedFlows.push(...))
in subsequent lines, consider a small helper:// outside the changed lines, add: function recordFailure<T>(name: string) { return (err: unknown) => { failedFlows.push(name); }; } // then use: // await patientManagement(commonWell).catch(recordFailure("patientManagement"));
57-63
: Use console.log for success path (nit)Using console.error for the “All flows passed!” message is slightly misleading and may trip log parsers. Suggest switching to console.log for the success branch.
Outside selected lines, suggested shape:
if (failedFlows.length < 1) { console.log(`\n>>> >>> All flows passed! <<< <<<\n`); process.exit(0); } else { console.error(`\n>>> >>> Failed flows:\n- ${failedFlows.join("\n- ")}\n`); process.exit(1); }packages/commonwell-cert-runner/src/flows/contribution/document-reference.ts (2)
23-23
: Prefer startsWith and a constant for the OID prefix to avoid false positives and magic stringsChecking for "urn:oid:" with includes() can match unintended positions; startsWith() is safer. Also, avoid magic strings per guidelines—use a constant.
Apply within this line:
- "source": "${orgId.includes("urn:oid:") ? orgId : `urn:oid:${orgId}`}" + "source": "${orgId.startsWith(OID_PREFIX) ? orgId : `${OID_PREFIX}${orgId}`}"And add near the imports (outside the changed lines):
const OID_PREFIX = "urn:oid:";
17-99
: Consider building the DocumentReference as an object instead of JSON string (optional)The JSON-as-string approach makes escaping and type-safety harder. Building an object and returning it avoids parse costs and reduces risk of accidental quoting bugs.
packages/commonwell-cert-runner/src/payloads.ts (3)
24-25
: Root OID import is fine; add guardrail if env is missingIf rootOID is empty/misconfigured, generated IDs will be malformed (e.g., "undefined.5.XXXX"). Consider validating rootOID early and failing fast in this package.
Outside the changed lines, consider:
if (!rootOID) { throw new Error("ROOT_OID is required to build CommonWell IDs"); }
39-41
: makeOrgId now composes from rootOID — add normalization and minimal validation (optional)If callers accidentally pass an orgId that already includes the root (or a prefix), we may double-embed segments. Consider validating the orgId parameter length/format, or at least document that orgId must be a suffix token.
Example minimal guard within these lines:
export function makeOrgId(orgId?: string): string { const org = orgId ?? makeId(); - return `${rootOID}.${ORGANIZATION}.${org}`; + // Ensure no accidental prefixing (e.g., passing a full OID) + const suffix = org.includes(".") ? org.split(".").pop()! : org; + return `${rootOID}.${ORGANIZATION}.${suffix}`; }
188-192
: Use buildDayjs() per codebase guidelines (optional)Guidelines recommend using buildDayjs() rather than direct dayjs(...) to control parsing/timezone behavior. If available in this package, consider switching; otherwise, ignore.
Illustrative change (outside the changed lines, adjust import path as appropriate):
import { buildDayjs } from "@metriport/shared/common/time"; // ... const validFrom = buildDayjs(x509.validFrom).toString(); const validTo = buildDayjs(x509.validTo).toString();packages/commonwell-cert-runner/src/flows/contribution/contribution-server.ts (2)
56-56
: Minor: prefer out().log or a consistent logger (optional)For non-util packages we generally avoid console.log. Given this is a test tool, it may be acceptable—flagging for awareness only.
31-32
: Duplicate express.json middleware (nit)express.json is registered twice (Lines 31-32 and 139). Keep a single registration at the top.
Outside the selected lines, remove the later
app.use(express.json({ limit: "2mb" }));
.Also applies to: 139-139
packages/commonwell-cert-runner/src/flows/org-management-initiator-only.ts (6)
31-33
: Avoid sending empty string for securityTokenKeyType; delete the field insteadEmpty-stringing enum-like fields is brittle and can be rejected by schema validation. Prefer deleting the property so it’s treated as “unset”. Same for updates.
Please confirm the CommonWell API accepts absent securityTokenKeyType but rejects empty strings. If absent is not allowed, consider using undefined rather than "".
- initiatorOnlyOrgCreate.securityTokenKeyType = ""; - delete initiatorOnlyOrgCreate.authorizationInformation; + delete initiatorOnlyOrgCreate.securityTokenKeyType; + delete initiatorOnlyOrgCreate.authorizationInformation; @@ - initiatorOnlyOrg.securityTokenKeyType = ""; - if ("authorizationInformation" in initiatorOnlyOrg) { - delete initiatorOnlyOrg.authorizationInformation; - } + delete initiatorOnlyOrg.securityTokenKeyType; + delete initiatorOnlyOrg.authorizationInformation;Also applies to: 51-54
29-33
: Ensure initiator-only semantics in networks.purposeOfUsemakeOrganization() enables responder by default. For an initiator-only org, flip flags accordingly so behavior matches the label and avoids accidental responder configuration.
Please verify against the CommonWell spec whether initiator-only requires queryResponder=false and queryInitiatorOnly=true.
const initiatorOnlyOrgCreate = makeOrganization(); initiatorOnlyOrgCreate.gateways = []; - initiatorOnlyOrgCreate.securityTokenKeyType = ""; - delete initiatorOnlyOrgCreate.authorizationInformation; + delete initiatorOnlyOrgCreate.authorizationInformation; + // enforce initiator-only behavior + initiatorOnlyOrgCreate.networks?.forEach(n => + n.purposeOfUse?.forEach(p => { + p.queryResponder = false; + p.queryInitiatorOnly = true; + }) + );
35-38
: Gate multi-line JSON logs behind a VERBOSE flagPretty-printed JSON produces multi-line logs, which violate house style and can flood CI. Keep terse logs by default and enable detailed logs only when needed.
- console.log(">>> Response: " + JSON.stringify(respCreateInitiatorOnly, null, 2)); + if (process.env.VERBOSE === "1") { + console.log(">>> Response: " + JSON.stringify(respCreateInitiatorOnly)); + } else { + console.log(">>> Response: created orgId=" + respCreateInitiatorOnly.organizationId); + } @@ - console.log(">>> Response: " + JSON.stringify(respGetInitiatorOnly, null, 2)); + process.env.VERBOSE === "1" && + console.log(">>> Response: " + JSON.stringify(respGetInitiatorOnly)); @@ - console.log(">>> Response: " + JSON.stringify(respUpdateInitiatorOnly, null, 2)); + process.env.VERBOSE === "1" && + console.log(">>> Response: " + JSON.stringify(respUpdateInitiatorOnly));Also applies to: 41-44, 56-58
59-66
: Preserve root cause and avoid “[object Object]” when aggregating errorsPush stringified errors, and include the original errors as
cause
so the stack is retained upstream.- } catch (error) { - console.log(`Error (txId ${commonWellMember.lastTransactionId}): ${errorToString(error)}`); - errors.push(error); - } + } catch (error) { + const errStr = errorToString(error); + console.log(`Error (txId ${commonWellMember.lastTransactionId}): ${errStr}`); + errors.push(errStr); + } @@ - if (errors.length > 0) { - throw new Error(`Failed to run org management initiator only flow: ${errors.join(", ")}`); - } + if (errors.length > 0) { + const msg = `Failed to run org management initiator only flow: ${errors.join(", ")}`; + throw new Error(msg, { cause: new AggregateError(errors) }); + }
48-49
: Guard against missing locationsmakeOrganization() currently sets one location, but a defensive guard prevents runtime errors if payloads change.
- initiatorOnlyOrg.locations[0].city = faker.location.city(); + if (!initiatorOnlyOrg.locations?.[0]) { + throw new Error("Organization payload missing at least one location"); + } + initiatorOnlyOrg.locations[0].city = faker.location.city();
7-9
: Unused export in this moduleOrgManagementResponse is defined but not used here. Consider removing or moving it next to the consumer that returns it.
packages/commonwell-cert-runner/src/flows/org-management.ts (2)
42-42
: Trim env-provided OID to avoid accidental whitespace issuesSmall hardening so an env like " 1.2.3 " doesn't bypass the reuse branch.
- let orgId: string | undefined = existingOrgOid; + let orgId: string | undefined = existingOrgOid?.trim();
48-50
: Prefer concise logs and reuse the same banner format as the initiator-only flowKeep banners consistent and avoid multi-line payload logs in this section. Consider reusing a helper to print TxId + key identifiers.
packages/commonwell-cert-runner/src/env.ts (1)
3-3
: Optional: RenamerootOID
torootOid
incommonwell-cert-runner
for camel-case consistencyTo align with the existing “Oid” pattern (e.g.
existingOrgOid
), update the constant and all its local references in thecommonwell-cert-runner
package:• packages/commonwell-cert-runner/src/env.ts
-export const rootOID = getEnvOrFail("ROOT_OID"); +export const rootOid = getEnvOrFail("ROOT_OID");• packages/commonwell-cert-runner/src/payloads.ts
– Update the import and usages ofrootOID
-import { rootOID } from "./env"; +import { rootOid } from "./env"; // … - payload.root = rootOID; + payload.root = rootOid; - return `${rootOID}.${ORGANIZATION}.${org}`; + return `${rootOid}.${ORGANIZATION}.${org}`;No changes are required to the uppercase environment-variable name (
ROOT_OID
) or to references outside of the cert-runner (e.g.,SYSTEM_ROOT_OID
in other packages).packages/commonwell-cert-runner/src/flows/document-contribution.ts (2)
123-127
: Gate verbose, pretty JSON logsThese logs are multi-line and can be very large. Mirror the VERBOSE gating used elsewhere.
- console.log(">>> 2.2 Response: " + JSON.stringify(resp_2_2, null, 2)); + process.env.VERBOSE === "1" && + console.log(">>> 2.2 Response: " + JSON.stringify(resp_2_2)); @@ - console.log(">>> 2.3 Response: " + JSON.stringify(resp_2_3, null, 2)); + process.env.VERBOSE === "1" && + console.log(">>> 2.3 Response: " + JSON.stringify(resp_2_3));Also applies to: 139-143
149-149
: Guard linking against missing link URLsgetProbableLinksById can return entries without Links.Link. Skip undefined URLs to avoid 4xx from linkPatients.
- await linkPatients(commonWellContributor, firstPatientIdEncoded); + await linkPatients(commonWellContributor, firstPatientIdEncoded); // internally skip empty URLsIf linkPatients doesn’t already guard, add:
if (!urlToLink) { console.log(">>> Skipping link: empty URL"); continue; }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled
- Linear integration is disabled
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (24)
packages/api/src/__tests__/e2e/mapi/mapi.test.e2e.ts
(0 hunks)packages/api/src/__tests__/e2e/mapi/parts/organization.test.part.ts
(0 hunks)packages/api/src/external/commonwell-v2/api.ts
(1 hunks)packages/api/src/routes/medical/index.ts
(0 hunks)packages/api/src/routes/medical/organization.ts
(0 hunks)packages/api/src/shared/config.ts
(1 hunks)packages/commonwell-cert-runner/README.md
(1 hunks)packages/commonwell-cert-runner/src/env.ts
(2 hunks)packages/commonwell-cert-runner/src/flows/contribution/contribution-server.ts
(7 hunks)packages/commonwell-cert-runner/src/flows/contribution/document-reference.ts
(1 hunks)packages/commonwell-cert-runner/src/flows/document-consumption.ts
(1 hunks)packages/commonwell-cert-runner/src/flows/document-contribution.ts
(6 hunks)packages/commonwell-cert-runner/src/flows/link-management.ts
(1 hunks)packages/commonwell-cert-runner/src/flows/org-management-initiator-only.ts
(1 hunks)packages/commonwell-cert-runner/src/flows/org-management.ts
(3 hunks)packages/commonwell-cert-runner/src/index.ts
(2 hunks)packages/commonwell-cert-runner/src/payloads.ts
(3 hunks)packages/commonwell-cert-runner/src/single-commands/init-contrib-server.ts
(2 hunks)packages/commonwell-sdk/src/models/organization.ts
(1 hunks)packages/core/src/command/feature-flags/domain-ffs.ts
(1 hunks)packages/core/src/command/feature-flags/ffs-on-dynamodb.ts
(1 hunks)packages/core/src/command/feature-flags/types.ts
(1 hunks)packages/shared/src/common/__tests__/retry.test.ts
(1 hunks)packages/shared/src/common/retry.ts
(5 hunks)
💤 Files with no reviewable changes (4)
- packages/api/src/routes/medical/index.ts
- packages/api/src/tests/e2e/mapi/parts/organization.test.part.ts
- packages/api/src/tests/e2e/mapi/mapi.test.e2e.ts
- packages/api/src/routes/medical/organization.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursorrules)
**/*.{js,jsx,ts,tsx}
: Don’t use null inside the app, only on code interacting with external interfaces/services, like DB and HTTP; convert to undefined before sending inwards into the code
Useconst
whenever possible
Useasync/await
instead of.then()
Naming: classes, enums: PascalCase
Naming: constants, variables, functions: camelCase
Naming: file names: kebab-case
Naming: Don’t use negative names, likenotEnabled
, preferisDisabled
If possible, use decomposing objects for function parameters
Prefer Nullish Coalesce (??) than the OR operator (||) when you want to provide a default value
Avoid creating arrow functions
Use truthy syntax instead ofin
- i.e.,if (data.link)
notif ('link' in data)
While handling errors, keep the stack trace around: if you create a new Error (e.g., MetriportError), make sure to pass the original error as the new one’s cause so the stack trace is available upstream.
max column length is 100 chars
multi-line comments use/** */
top-level comments go after the import (save pre-import to basic file header, like license)
move literals to constants declared after imports when possible
Files:
packages/commonwell-cert-runner/src/flows/link-management.ts
packages/shared/src/common/__tests__/retry.test.ts
packages/api/src/external/commonwell-v2/api.ts
packages/commonwell-cert-runner/src/index.ts
packages/commonwell-cert-runner/src/flows/document-consumption.ts
packages/core/src/command/feature-flags/domain-ffs.ts
packages/commonwell-cert-runner/src/flows/org-management-initiator-only.ts
packages/commonwell-cert-runner/src/single-commands/init-contrib-server.ts
packages/api/src/shared/config.ts
packages/commonwell-cert-runner/src/flows/contribution/document-reference.ts
packages/core/src/command/feature-flags/ffs-on-dynamodb.ts
packages/commonwell-cert-runner/src/payloads.ts
packages/core/src/command/feature-flags/types.ts
packages/shared/src/common/retry.ts
packages/commonwell-sdk/src/models/organization.ts
packages/commonwell-cert-runner/src/env.ts
packages/commonwell-cert-runner/src/flows/contribution/contribution-server.ts
packages/commonwell-cert-runner/src/flows/org-management.ts
packages/commonwell-cert-runner/src/flows/document-contribution.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursorrules)
Use types whenever possible
Files:
packages/commonwell-cert-runner/src/flows/link-management.ts
packages/shared/src/common/__tests__/retry.test.ts
packages/api/src/external/commonwell-v2/api.ts
packages/commonwell-cert-runner/src/index.ts
packages/commonwell-cert-runner/src/flows/document-consumption.ts
packages/core/src/command/feature-flags/domain-ffs.ts
packages/commonwell-cert-runner/src/flows/org-management-initiator-only.ts
packages/commonwell-cert-runner/src/single-commands/init-contrib-server.ts
packages/api/src/shared/config.ts
packages/commonwell-cert-runner/src/flows/contribution/document-reference.ts
packages/core/src/command/feature-flags/ffs-on-dynamodb.ts
packages/commonwell-cert-runner/src/payloads.ts
packages/core/src/command/feature-flags/types.ts
packages/shared/src/common/retry.ts
packages/commonwell-sdk/src/models/organization.ts
packages/commonwell-cert-runner/src/env.ts
packages/commonwell-cert-runner/src/flows/contribution/contribution-server.ts
packages/commonwell-cert-runner/src/flows/org-management.ts
packages/commonwell-cert-runner/src/flows/document-contribution.ts
**/*.ts
⚙️ CodeRabbit configuration file
**/*.ts
: - Use the Onion Pattern to organize a package's code in layers
- Try to use immutable code and avoid sharing state across different functions, objects, and systems
- Try to build code that's idempotent whenever possible
- Prefer functional programming style functions: small, deterministic, 1 input, 1 output
- Minimize coupling / dependencies
- Avoid modifying objects received as parameter
- Only add comments to code to explain why something was done, not how it works
- Naming
- classes, enums:
PascalCase
- constants, variables, functions:
camelCase
- file names:
kebab-case
- table and column names:
snake_case
- Use meaningful names, so whoever is reading the code understands what it means
- Don’t use negative names, like
notEnabled
, preferisDisabled
- For numeric values, if the type doesn’t convey the unit, add the unit to the name
- Typescript
- Use types
- Prefer
const
instead oflet
- Avoid
any
and casting fromany
to other types- Type predicates: only applicable to narrow down the type, not to force a complete type conversion
- Prefer deconstructing parameters for functions instead of multiple parameters that might be of
the same type- Don’t use
null
inside the app, only on code interacting with external interfaces/services,
like DB and HTTP; convert toundefined
before sending inwards into the code- Use
async/await
instead of.then()
- Use the strict equality operator
===
, don’t use abstract equality operator==
- When calling a Promise-returning function asynchronously (i.e., not awaiting), use
.catch()
to
handle errors (seeprocessAsyncError
andemptyFunction
depending on the case)- Date and Time
- Always use
buildDayjs()
to createdayjs
instances- Prefer
dayjs.duration(...)
to create duration consts and keep them asduration
- Prefer Nullish Coalesce (??) than the OR operator (||) to provide a default value
- Avoid creating arrow functions
- U...
Files:
packages/commonwell-cert-runner/src/flows/link-management.ts
packages/shared/src/common/__tests__/retry.test.ts
packages/api/src/external/commonwell-v2/api.ts
packages/commonwell-cert-runner/src/index.ts
packages/commonwell-cert-runner/src/flows/document-consumption.ts
packages/core/src/command/feature-flags/domain-ffs.ts
packages/commonwell-cert-runner/src/flows/org-management-initiator-only.ts
packages/commonwell-cert-runner/src/single-commands/init-contrib-server.ts
packages/api/src/shared/config.ts
packages/commonwell-cert-runner/src/flows/contribution/document-reference.ts
packages/core/src/command/feature-flags/ffs-on-dynamodb.ts
packages/commonwell-cert-runner/src/payloads.ts
packages/core/src/command/feature-flags/types.ts
packages/shared/src/common/retry.ts
packages/commonwell-sdk/src/models/organization.ts
packages/commonwell-cert-runner/src/env.ts
packages/commonwell-cert-runner/src/flows/contribution/contribution-server.ts
packages/commonwell-cert-runner/src/flows/org-management.ts
packages/commonwell-cert-runner/src/flows/document-contribution.ts
🧠 Learnings (10)
📓 Common learnings
Learnt from: lucasdellabella
PR: metriport/metriport#3866
File: packages/api/src/routes/internal/medical/organization.ts:95-98
Timestamp: 2025-05-27T15:54:47.774Z
Learning: The conversion of the GET `/internal/organization` endpoint from returning a single organization to returning an array of organizations (batch retrieval) in `packages/api/src/routes/internal/medical/organization.ts` was an intentional breaking change planned by the team.
Learnt from: RamilGaripov
PR: metriport/metriport#4176
File: packages/fhir-converter/src/lib/handlebars-converter/handlebars-helpers.js:296-320
Timestamp: 2025-07-17T21:24:37.077Z
Learning: RamilGaripov prefers to maintain consistency with existing patterns in a file rather than making isolated fixes to coding guideline violations like var vs const declarations, when the pattern is already established throughout the codebase.
📚 Learning: 2025-06-03T21:02:23.374Z
Learnt from: leite08
PR: metriport/metriport#3953
File: packages/core/src/command/consolidated/search/fhir-resource/__tests__/search-consolidated-setup.ts:28-30
Timestamp: 2025-06-03T21:02:23.374Z
Learning: The `makePatient()` function returns `PatientWithId` type which requires the `id` field to be present, ensuring `patient.id` is never undefined.
Applied to files:
packages/commonwell-cert-runner/src/flows/link-management.ts
packages/commonwell-cert-runner/src/flows/document-consumption.ts
📚 Learning: 2025-05-20T21:26:26.804Z
Learnt from: leite08
PR: metriport/metriport#3814
File: packages/api/src/routes/internal/medical/patient-consolidated.ts:141-174
Timestamp: 2025-05-20T21:26:26.804Z
Learning: The functionality introduced in packages/api/src/routes/internal/medical/patient-consolidated.ts is planned to be refactored in downstream PR #3857, including improvements to error handling and validation.
Applied to files:
packages/commonwell-cert-runner/src/flows/link-management.ts
📚 Learning: 2025-03-11T20:42:46.516Z
Learnt from: thomasyopes
PR: metriport/metriport#3427
File: packages/core/src/external/ehr/api/sync-patient.ts:16-55
Timestamp: 2025-03-11T20:42:46.516Z
Learning: In the patient synchronization architecture, the flow follows this pattern: (1) `ehr-sync-patient-cloud.ts` sends messages to an SQS queue, (2) the `ehr-sync-patient` Lambda consumes these messages, and (3) the Lambda uses the `syncPatient` function to make the API calls to process the patient data.
Applied to files:
packages/commonwell-cert-runner/src/flows/link-management.ts
📚 Learning: 2025-05-27T15:54:47.774Z
Learnt from: lucasdellabella
PR: metriport/metriport#3866
File: packages/api/src/routes/internal/medical/organization.ts:95-98
Timestamp: 2025-05-27T15:54:47.774Z
Learning: The conversion of the GET `/internal/organization` endpoint from returning a single organization to returning an array of organizations (batch retrieval) in `packages/api/src/routes/internal/medical/organization.ts` was an intentional breaking change planned by the team.
Applied to files:
packages/api/src/external/commonwell-v2/api.ts
packages/commonwell-cert-runner/src/flows/org-management.ts
📚 Learning: 2025-07-16T19:02:26.395Z
Learnt from: leite08
PR: metriport/metriport#4095
File: packages/commonwell-cert-runner/src/flows/org-management.ts:79-85
Timestamp: 2025-07-16T19:02:26.395Z
Learning: In packages/commonwell-cert-runner/src/flows/org-management.ts, the silent error handling in the certificate retrieval catch block is intentional to allow the certification flow to continue processing even when certificate operations fail, as this is a testing tool rather than production code.
Applied to files:
packages/commonwell-cert-runner/src/index.ts
packages/commonwell-cert-runner/src/flows/org-management-initiator-only.ts
packages/commonwell-cert-runner/src/flows/org-management.ts
📚 Learning: 2025-08-21T16:24:56.958Z
Learnt from: RamilGaripov
PR: metriport/metriport#4434
File: packages/core/src/command/feature-flags/domain-ffs.ts:311-318
Timestamp: 2025-08-21T16:24:56.958Z
Learning: In the Metriport codebase, feature flag helper functions in packages/core/src/command/feature-flags/domain-ffs.ts follow a consistent pattern with getCxsWithXXXFeatureFlag() and isXXXEnabledForCx() functions, and individual unit tests are not added for each pair since they follow the same reliable pattern used throughout the codebase.
Applied to files:
packages/core/src/command/feature-flags/domain-ffs.ts
packages/core/src/command/feature-flags/ffs-on-dynamodb.ts
packages/core/src/command/feature-flags/types.ts
📚 Learning: 2025-04-01T20:57:29.282Z
Learnt from: leite08
PR: metriport/metriport#3550
File: packages/api/src/external/commonwell/__tests__/patient.test.ts:5-5
Timestamp: 2025-04-01T20:57:29.282Z
Learning: PR #3574 is the follow-up PR that moves the domain feature flags from packages/api/src/aws/app-config.ts to packages/core/src/command/feature-flags/domain-ffs.ts.
Applied to files:
packages/core/src/command/feature-flags/domain-ffs.ts
packages/core/src/command/feature-flags/ffs-on-dynamodb.ts
packages/core/src/command/feature-flags/types.ts
📚 Learning: 2025-04-01T20:57:29.282Z
Learnt from: leite08
PR: metriport/metriport#3550
File: packages/api/src/external/commonwell/__tests__/patient.test.ts:5-5
Timestamp: 2025-04-01T20:57:29.282Z
Learning: PR #3574 is the follow-up PR that moves the domain feature flags from app-config.ts to the domain folder in packages/core.
Applied to files:
packages/core/src/command/feature-flags/domain-ffs.ts
📚 Learning: 2025-04-01T19:59:22.396Z
Learnt from: leite08
PR: metriport/metriport#3550
File: packages/api/src/external/commonwell/__tests__/patient.test.ts:5-5
Timestamp: 2025-04-01T19:59:22.396Z
Learning: The file at `packages/api/src/aws/app-config.ts` contains domain feature flags (not AWS AppConfig specific functionality) and is planned to be moved to the domain folder in packages/core in a future PR.
Applied to files:
packages/core/src/command/feature-flags/domain-ffs.ts
packages/core/src/command/feature-flags/ffs-on-dynamodb.ts
🧬 Code graph analysis (6)
packages/shared/src/common/__tests__/retry.test.ts (1)
packages/shared/src/common/retry.ts (1)
executeWithRetries
(79-143)
packages/api/src/external/commonwell-v2/api.ts (2)
packages/api/src/shared/config.ts (1)
Config
(4-315)packages/shared/src/index.ts (1)
MetriportError
(43-43)
packages/commonwell-cert-runner/src/index.ts (1)
packages/commonwell-cert-runner/src/flows/org-management-initiator-only.ts (1)
orgManagementInitiatorOnly
(16-67)
packages/commonwell-cert-runner/src/flows/org-management-initiator-only.ts (3)
packages/commonwell-cert-runner/src/flows/org-management.ts (1)
OrgManagementResponse
(21-23)packages/commonwell-cert-runner/src/payloads.ts (1)
makeOrganization
(121-186)packages/shared/src/index.ts (1)
errorToString
(45-45)
packages/commonwell-cert-runner/src/flows/org-management.ts (2)
packages/commonwell-sdk/src/client/commonwell-member.ts (1)
getOneOrg
(199-215)packages/commonwell-cert-runner/src/payloads.ts (1)
makeOrganization
(121-186)
packages/commonwell-cert-runner/src/flows/document-contribution.ts (3)
packages/commonwell-cert-runner/src/payloads.ts (1)
makePatient
(91-117)packages/commonwell-cert-runner/src/util.ts (1)
getMetriportPatientIdOrFail
(45-58)packages/commonwell-sdk/src/common/util.ts (1)
encodeToCwPatientId
(57-67)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: e2e-tests / E2E Tests
🔇 Additional comments (12)
packages/shared/src/common/retry.ts (1)
90-93
: LGTM: onError is correctly threaded through optionsWiring the new onError option alongside the other knobs keeps the API coherent. No concerns here.
packages/core/src/command/feature-flags/ffs-on-dynamodb.ts (1)
68-69
: LGTM: default initialized for new flag
cxsEnabledForCommonwellV2
is correctly added toinitialFeatureFlags
with{ enabled: false, values: [] }
. This aligns with the new schema and prevents undefined access when no record exists.packages/api/src/shared/config.ts (1)
240-243
: Deprecation annotation approved; found 6 call sites—scope removal under ENG-554Deprecating
Config.getCWMemberOID()
is correct. Before we remove it, we need to migrate each use. I’ve located six references:•
packages/api/src/external/commonwell-v2/api.ts
– Line 31:const isMemberAPI = orgOID === Config.getCWMemberOID();
– Line 60:const isMemberAPI = [Config.getCWMemberOID(), Config.getSystemRootOID()].includes(orgOID);
•
packages/api/src/external/commonwell-v1/organization.ts
– Line 98:makeCommonWellAPI(Config.getCWMemberOrgName(), Config.getCWMemberOID());
– Line 124:makeCommonWellAPI(Config.getCWMemberOrgName(), Config.getCWMemberOID());
– Line 160:makeCommonWellAPI(Config.getCWMemberOrgName(), Config.getCWMemberOID());
•
packages/api/src/external/commonwell-v1/api.ts
– Line 56:const isMemberAPI = orgOID === Config.getCWMemberOID();
Next steps:
- Decide how each call should be updated (e.g., inline the env var or introduce a new accessor).
- Schedule and track these migrations under ENG-554.
- Once all usages are replaced, remove
getCWMemberOID()
.No code changes required in this PR—just tracking and planning.
packages/commonwell-cert-runner/README.md (1)
99-108
: ROOT_OID doc update aligns with code migrationThis matches the runner’s shift from memberOID to rootOID. Looks good.
packages/commonwell-cert-runner/src/index.ts (1)
9-9
: Wiring the initiator-only flow is correct and side-effect freeImport looks good and keeps the new flow decoupled from the others. No exported API changes here.
packages/commonwell-cert-runner/src/flows/contribution/document-reference.ts (1)
28-29
: EnsureorgId
is a bare OID and strip anyurn:oid:
prefix for FHIR IDsThe
makeDocumentReference
helper currently usesorgId
verbatim for both the contained Organization’sid
and the author reference:
"id": "${orgId}"
"reference": "#${orgId}"
Because FHIR Resource.id must match
[A-Za-z0-9\-\.]{1,64}
and must not include theurn:oid:
prefix, please:
- Verify at each call site of
makeDocumentReference
thatorgId
is always the bare dotted OID (e.g.2.16.840.1.113883…
) and never already wrapped inurn:oid:
.- To guard against accidental prefixes, normalize at the top of the function:
import { stripUrnPrefix } from "@metriport/core/util/urn"; const pureOrgId = stripUrnPrefix(orgId);- Then update the JSON template to use
pureOrgId
:"contained": [ { "resourceType": "Organization","id": "${orgId}",
],"id": "${pureOrgId}", "name": "${orgName}" }
"author": [
{
"reference": "#${orgId}",
],"reference": "#${pureOrgId}", "type": "Organization" }
This ensures that even if a URN-wrapped OID is passed in, the contained resource IDs and references remain valid.
packages/commonwell-cert-runner/src/single-commands/init-contrib-server.ts (2)
6-6
: Env rename alignment looks correctSwapping to existingOrgOid matches the broader migration away from memberOID; import set is correct.
13-19
: Validate and/or strip theurn:oid:
prefix from CW_ORG_OIDWe confirmed that:
In
packages/commonwell-cert-runner/src/env.ts
(line 22),export const existingOrgOid = getEnv("CW_ORG_OID");is pulled directly from the environment with no validation or mutation.
In
packages/commonwell-cert-runner/src/single-commands/init-contrib-server.ts
(lines 24–27), bothoid: org.organizationId, homeCommunityId: org.organizationId,are set from that same value.
Because there’s currently no logic to strip a leading
urn:oid:
prefix, ifCW_ORG_OID
includes it, bothoid
andhomeCommunityId
will carry the prefix—potentially invalidating downstream identifiers.• Option A: Ensure the
CW_ORG_OID
environment variable is always provided as a bare dotted OID (e.g.,1.2.840.113619.6.3.3.1
), without anyurn:oid:
prefix.
• Option B: Add normalization code immediately after reading the env var, for example:const rawOid = getEnv("CW_ORG_OID"); const organizationId = rawOid.replace(/^urn:oid:/, ""); if (!organizationId) throw new Error("Organization ID is required"); // …packages/commonwell-cert-runner/src/flows/contribution/contribution-server.ts (2)
11-11
: Env import updates align with the new config shapeUsing contribServerPort and contribServerUrl is consistent with the rest of the migration.
158-161
: contribServerPort is already numeric – no change neededWe’ve confirmed that contribServerPort is initialized as
export const contribServerPort = parseInt(getEnvOrFail("CONTRIB_SERVER_PORT"));so its type is
number
(not a string) when passed intoapp.listen
. In our codebase, we rely ongetEnvOrFail
to guarantee required variables and intentionally omit explicit NaN‐checks afterparseInt
per established patterns . No additional coercion or guard is necessary here.packages/commonwell-cert-runner/src/env.ts (1)
22-24
: New optional OID envs look goodThe additions for existingOrgOid, existingInitiatorOnlyOrgOid, and contribExistingOrgOid fit the flows that now support org reuse.
Also applies to: 25-25
packages/commonwell-cert-runner/src/flows/document-contribution.ts (1)
85-101
: Env-driven consumer Org reuse is a solid improvementGood fallback to create-and-wire certs when the OID isn’t provided.
packages/commonwell-cert-runner/src/flows/contribution/contribution-server.ts
Show resolved
Hide resolved
securityTokenKeyType: z | ||
.union([z.literal("JWT"), z.literal("BEARER"), z.literal("HOLDER-OF-KEY")]) | ||
.nullish(), | ||
securityTokenKeyType: z.string().nullish(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Restore strict enum for securityTokenKeyType (avoid widening to arbitrary strings).
Changing from a literal union to z.string()
degrades validation and type-safety, allowing invalid values to slip into requests and removing compile-time exhaustiveness where consumers previously relied on known literals.
Apply this minimal diff to retain strong validation while keeping nullish behavior:
- securityTokenKeyType: z.string().nullish(),
+ securityTokenKeyType: z.enum(["JWT", "BEARER", "HOLDER-OF-KEY"]).nullish(),
Optionally, extract literals to a shared constant (aligns with "move literals to constants"):
// after imports
const securityTokenKeyTypeValues = ["JWT", "BEARER", "HOLDER-OF-KEY"] as const;
export const securityTokenKeyTypeSchema = z.enum(securityTokenKeyTypeValues);
export type SecurityTokenKeyType = typeof securityTokenKeyTypeValues[number];
// usage at line 60
securityTokenKeyType: securityTokenKeyTypeSchema.nullish(),
Run this to check current usages and catch any places relying on widened string
types:
🏁 Script executed:
#!/bin/bash
# Find usages and potential custom values
rg -n -C2 --type=ts 'securityTokenKeyType'
Length of output: 2541
Action Required: Enforce strict enum for securityTokenKeyType
and update all existing assignments
Switching from z.string().nullish()
to a strict z.enum([...]).nullish()
is important to prevent invalid token‐type values, but it will break current code sites that assign ""
or nonstandard literals. Please apply the enum change and update every assignment to one of the allowed values (or null
/undefined
):
• In packages/commonwell-sdk/src/models/organization.ts
, replace line 60 with:
- securityTokenKeyType: z.string().nullish(),
+ securityTokenKeyType: z.enum(["JWT", "BEARER", "HOLDER-OF-KEY"]).nullish(),
• In packages/commonwell-cert-runner/src/flows/org-management-initiator-only.ts
(lines 31 & 51), change:
- initiatorOnlyOrgCreate.securityTokenKeyType = "";
+ // no token type: use undefined or null
+ initiatorOnlyOrgCreate.securityTokenKeyType = undefined;
(and similarly for initiatorOnlyOrg.securityTokenKeyType
)
• In packages/api/src/external/commonwell-v1/organization.ts
(line 71), update the literal:
- securityTokenKeyType: "BearerKey",
+ securityTokenKeyType: "BEARER", // or "HOLDER-OF-KEY" if that better matches intent
• (The assignment in packages/commonwell-cert-runner/src/payloads.ts
using "JWT"
is already valid.)
Optionally, extract the literals into a shared constant to avoid duplication:
const securityTokenKeyTypeValues = ["JWT", "BEARER", "HOLDER-OF-KEY"] as const;
export const securityTokenKeyTypeSchema = z.enum(securityTokenKeyTypeValues);
export type SecurityTokenKeyType = typeof securityTokenKeyTypeValues[number];
And then use:
securityTokenKeyType: securityTokenKeyTypeSchema.nullish(),
🤖 Prompt for AI Agents
In packages/commonwell-sdk/src/models/organization.ts around line 60, replace
the loose z.string().nullish() with a strict enum schema (e.g.,
z.enum(["JWT","BEARER","HOLDER-OF-KEY"]).nullish()) and export the corresponding
type/constant; then update all existing assignments to use one of the enum
values or null/undefined: in
packages/commonwell-cert-runner/src/flows/org-management-initiator-only.ts
(lines ~31 & ~51) change assignments that set "" to undefined (and mirror for
initiatorOnlyOrg.securityTokenKeyType), and in
packages/api/src/external/commonwell-v1/organization.ts (around line 71) change
any non-enum literals to one of the allowed enum strings; optionally centralize
the enum values into a shared constant and import that schema/type where used.
@@ -1,6 +1,7 @@ | |||
import { random } from "lodash"; | |||
import { MetriportError } from "../error/metriport-error"; | |||
import { errorToString } from "../error/shared"; | |||
import { emptyFunction } from "./general"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Ensure emptyFunction’s type is compatible with onError’s signature
onError is typed as (error: unknown) => void via defaultOptions. Make sure emptyFunction’s declaration is variadic (e.g., (..._args: unknown[]) => void) so it’s assignable under strictFunctionTypes. If emptyFunction is typed as () => void, TypeScript may complain when assigning it to onError. If it’s already variadic, we’re good.
Run this to confirm emptyFunction’s type:
If you want to decouple from emptyFunction and make the intent explicit, you can inline a local noop with the exact type:
-import { emptyFunction } from "./general";
+// Local noop avoids cross-file typing drift and ensures the right signature.
+const noopOnError: (error: unknown) => void = () => {};
...
- onError: emptyFunction,
+ onError: noopOnError,
Also applies to: 18-18
🏁 Script executed:
#!/bin/bash
# Locate and print the emptyFunction declaration with context
fd -a --full-path 'general.(ts|tsx|js)' packages | xargs -I{} rg -nC3 'emptyFunction' {}
Length of output: 394
Update emptyFunction signature for strictFunctionTypes compatibility
The imported emptyFunction
is currently declared as
export function emptyFunction() {}
which under strictFunctionTypes
is not assignable to the onError
signature (error: unknown) => void
. You must either:
- Make
emptyFunction
variadic so it accepts any arguments, or - Inline a local noop in
retry.ts
with the exact(error: unknown) => void
type.
Required changes:
• packages/shared/src/common/general.ts
-export function emptyFunction() {}
+export function emptyFunction(..._args: unknown[]): void {}
• packages/shared/src/common/retry.ts (or wherever onError
is set)
Option A: continue using emptyFunction
-onError: emptyFunction,
+onError: emptyFunction, // now variadic, compatible with (error: unknown) => void
Option B: inline a typed noop
-import { emptyFunction } from "./general";
+// Local noop ensures exact onError signature
+const noopOnError: (error: unknown) => void = () => {};
...
- onError: emptyFunction,
+ onError: noopOnError,
These changes are necessary to prevent TypeScript errors when assigning a zero-arg function to a one-arg callback under strictFunctionTypes
.
Issues:
Description
Testing
Check each PR.
Release Plan
master
Summary by CodeRabbit