Skip to content

Conversation

RamilGaripov
Copy link
Contributor

@RamilGaripov RamilGaripov commented Aug 21, 2025

Issues:

Description

Testing

Check each PR.

Release Plan

  • ⚠️ Points to master
  • Merge this

Summary by CodeRabbit

  • Breaking Changes
    • Removed medical Organization API endpoints (create, update, get).
    • CommonWell cert runner env changes: replace CW_MEMBER_OID with ROOT_OID; rename CW_ORG_ID to CW_ORG_OID.
  • New Features
    • Feature flags to enable CommonWell V2 per customer.
    • Cert runner: optional initiator‑only org management flow; ability to reuse an existing consumer Org via env.
    • Retry utility supports an onError callback.
  • Improvements
    • Stronger validation for invalid CommonWell organization OIDs with clearer errors.
    • Cert runner logging standardized; removed artificial waits; refined DocumentReference source handling.
    • SDK organization model now accepts any securityTokenKeyType.
  • Documentation
    • Updated cert runner README for ROOT_OID.

leite08 and others added 10 commits August 15, 2025 15:30
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>
Copy link

coderabbitai bot commented Aug 21, 2025

Walkthrough

Removes 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

Cohort / File(s) Summary of Changes
MAPI E2E Organization tests removed
packages/api/src/__tests__/e2e/mapi/mapi.test.e2e.ts, packages/api/src/__tests__/e2e/mapi/parts/organization.test.part.ts
Removes organization test import/invocation and deletes the organization E2E test part (get/update org with FHIR cross-checks).
Medical Organization routes removed
packages/api/src/routes/medical/index.ts, packages/api/src/routes/medical/organization.ts
Unregisters /organization route and deletes its router (POST/PUT/GET handlers).
CommonWell v2 OID checks and deprecation
packages/api/src/external/commonwell-v2/api.ts, packages/api/src/shared/config.ts
Invalid org OID check now rejects member and system root OIDs; throws MetriportError with orgOID. Adds deprecation JSDoc to getCWMemberOID.
Cert-runner env and ID generation
packages/commonwell-cert-runner/src/env.ts, packages/commonwell-cert-runner/src/payloads.ts, packages/commonwell-cert-runner/README.md, packages/commonwell-cert-runner/src/single-commands/init-contrib-server.ts
Replaces memberOID with rootOID; adds new envs (existingOrgOid, existingInitiatorOnlyOrgOid, contribExistingOrgOid); updates makeOrgId to use rootOID; README envs updated; init-contrib-server uses existingOrgOid and org.organizationId for homeCommunityId.
Contribution server/doc reference updates
packages/commonwell-cert-runner/src/flows/contribution/document-reference.ts, packages/commonwell-cert-runner/src/flows/contribution/contribution-server.ts
Drops memberOID from makeDocumentReference signature and payload; meta.source derived from orgId (urn:oid: prefixing). Server logs standardized; payload omits memberOID.
Cert-runner flow timing/log tweaks
packages/commonwell-cert-runner/src/flows/document-consumption.ts, packages/commonwell-cert-runner/src/flows/link-management.ts
Removes artificial waits (waitSeconds) from flows; proceeds without delays.
Document contribution flow enhancements
packages/commonwell-cert-runner/src/flows/document-contribution.ts
Supports existing consumer org via contribExistingOrgOid; restructures steps: create contributor patient (2.2), create consumer patient (2.3), link (2.4), query documents (2.5); updates logs and IDs.
Org management flows
packages/commonwell-cert-runner/src/flows/org-management.ts, packages/commonwell-cert-runner/src/flows/org-management-initiator-only.ts (new), packages/commonwell-cert-runner/src/index.ts
Switches from existingOrgId to existingOrgOid; removes auto NPI generation; validates orgId right after create; adds initiator-only org management flow file and invokes it from index with isolated error handling.
CommonWell SDK organization model
packages/commonwell-sdk/src/models/organization.ts
Relaxes securityTokenKeyType to string; organizationSchemaWithoutNetworkInfo now includes securityTokenKeyType and gateways (omits only networks and authorizationInformation).
Feature flags (Core)
packages/core/src/command/feature-flags/domain-ffs.ts, packages/core/src/command/feature-flags/types.ts, packages/core/src/command/feature-flags/ffs-on-dynamodb.ts
Adds Quest flag check helper; introduces CommonwellV2 cx-based flag schema, retrieval, and predicates; seeds initial flag entry.
Retry utility onError
packages/shared/src/common/retry.ts, packages/shared/src/common/__tests__/retry.test.ts
Adds optional onError callback to retries; tests validate per-attempt invocation and attempt-number passing.

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
Loading
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
Loading

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 Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch develop

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

feat(core): add FF to enable cxs to CW v2
RamilGaripov and others added 2 commits August 21, 2025 11:50
Part of ENG-000

Signed-off-by: RamilGaripov <ramil@metriport.com>
Backmerge master into develop
@RamilGaripov RamilGaripov changed the title RELEASE ...WIP RELEASE CW cert runner and initial api migration updates Aug 21, 2025
@RamilGaripov
Copy link
Contributor Author

@coderabbitai what are we doing here? how long should i wait??

Copy link

coderabbitai bot commented Aug 21, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the current status of PR #4436
gh pr view 4436 --repo metriport/metriport --json state,mergeable,statusCheckRollup,reviewDecision,isDraft,headRefName,baseRefName

# Check if there are any failing status checks
gh pr checks 4436 --repo metriport/metriport

# Get recent activity on the PR
gh pr view 4436 --repo metriport/metriport --json comments,reviews --jq '.comments[-3:] + .reviews[-3:]'

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!

  • PR is APPROVED and MERGEABLE
  • All CI checks have PASSED (lint, build, test, E2E tests)
  • Staging deployment completed successfully
  • All 60+ status checks are green

What you're waiting for: 🔄 Only CodeRabbit's review is still pending

  • Status shows "Review in progress"
  • This is a large release PR with 24 files being processed
  • CodeRabbit reviews can take 5-15 minutes for complex PRs like this

Your options:

  1. Wait a few more minutes - The review should complete soon
  2. Proceed without waiting - Since all critical checks passed and the PR is approved, you can merge now if needed
  3. Pause CodeRabbit - You can comment @coderabbitai pause to stop the review and merge immediately

Since this is a release PR pointing to master and everything else is green, you're technically ready to merge whenever you want!

🐰💭
( The rabbit is working hard on this big PR... )
‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾


🧠 Learnings used
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.

Copy link

@coderabbitai coderabbitai bot left a 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 Aligning organizationSchemaWithoutNetworkInfo with Its Name or Renaming It

It looks like the current schema still exposes gateways and securityTokenKeyType, which may confuse anyone expecting all network-related fields to be omitted. Your ripgrep search didn’t surface any direct uses of the OrganizationWithoutNetworkInfo 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 cases

Great coverage validating per-attempt invocation, absence on success, and argument identity. Two enhancements:

  1. Eliminate real sleeps for speed/stability by injecting getTimeToWait: () => 0 in these tests.
  2. Add a test proving onError is not called when retries are driven by a retryable result (no thrown error).
  3. 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 consistency

Several 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) for includes(cxId) for consistency/readability.
  • Optional: for naming consistency with helpers like getCxsWithEpicEnabled, consider getCxsWithCommonwellV2Enabled 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 consistency

Dropping 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/macOS

toISOString() 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 leaks

If 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 steps

Linking/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 → Encoded

Minor 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 environments

The 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 time

Non-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 code

The README in packages/commonwell-cert-runner/README.md still shows CW_ORG_ID, but the runner’s code in src/env.ts reads CW_ORG_OID. Aligning the docs with the implementation will prevent confusion for users.

• File: packages/commonwell-cert-runner/README.md
Lines: 125–127

Apply:

-# 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.123
packages/commonwell-cert-runner/src/index.ts (2)

51-51: Track failure label consistently; consider a tiny naming tweak

You 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 strings

Checking 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 missing

If 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 instead

Empty-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.purposeOfUse

makeOrganization() 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 flag

Pretty-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 errors

Push 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 locations

makeOrganization() 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 module

OrgManagementResponse 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 issues

Small 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 flow

Keep 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: Rename rootOID to rootOid in commonwell-cert-runner for camel-case consistency

To align with the existing “Oid” pattern (e.g. existingOrgOid), update the constant and all its local references in the commonwell-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 of rootOID

-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 logs

These 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 URLs

getProbableLinksById 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 URLs

If 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 8d6badf and 57cf8ce.

📒 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
Use const whenever possible
Use async/await instead of .then()
Naming: classes, enums: PascalCase
Naming: constants, variables, functions: camelCase
Naming: file names: kebab-case
Naming: Don’t use negative names, like notEnabled, prefer isDisabled
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 of in - i.e., if (data.link) not if ('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, prefer isDisabled
    • For numeric values, if the type doesn’t convey the unit, add the unit to the name
  • Typescript
    • Use types
    • Prefer const instead of let
    • Avoid any and casting from any 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 to undefined 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 (see processAsyncError and emptyFunction depending on the case)
    • Date and Time
      • Always use buildDayjs() to create dayjs instances
      • Prefer dayjs.duration(...) to create duration consts and keep them as duration
  • 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 options

Wiring 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 to initialFeatureFlags 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-554

Deprecating 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:

  1. Decide how each call should be updated (e.g., inline the env var or introduce a new accessor).
  2. Schedule and track these migrations under ENG-554.
  3. 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 migration

This 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 free

Import 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: Ensure orgId is a bare OID and strip any urn:oid: prefix for FHIR IDs

The makeDocumentReference helper currently uses orgId verbatim for both the contained Organization’s id and the author reference:

  • "id": "${orgId}"
  • "reference": "#${orgId}"

Because FHIR Resource.id must match [A-Za-z0-9\-\.]{1,64} and must not include the urn:oid: prefix, please:

  • Verify at each call site of makeDocumentReference that orgId is always the bare dotted OID (e.g. 2.16.840.1.113883…) and never already wrapped in urn: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 correct

Swapping to existingOrgOid matches the broader migration away from memberOID; import set is correct.


13-19: Validate and/or strip the urn:oid: prefix from CW_ORG_OID

We 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), both

    oid: org.organizationId,
    homeCommunityId: org.organizationId,

    are set from that same value.

Because there’s currently no logic to strip a leading urn:oid: prefix, if CW_ORG_OID includes it, both oid and homeCommunityId 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 any urn: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 shape

Using contribServerPort and contribServerUrl is consistent with the rest of the migration.


158-161: contribServerPort is already numeric – no change needed

We’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 into app.listen . In our codebase, we rely on getEnvOrFail to guarantee required variables and intentionally omit explicit NaN‐checks after parseInt 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 good

The 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 improvement

Good fallback to create-and-wire certs when the OID isn’t provided.

securityTokenKeyType: z
.union([z.literal("JWT"), z.literal("BEARER"), z.literal("HOLDER-OF-KEY")])
.nullish(),
securityTokenKeyType: z.string().nullish(),
Copy link

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";
Copy link

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.

@RamilGaripov RamilGaripov added this pull request to the merge queue Aug 21, 2025
Merged via the queue into master with commit a6e58e2 Aug 21, 2025
77 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants