Skip to main content

Review and publish rule changes

Outcome

A rule version moves through DRAFT → IN_REVIEW → PUBLISHED via the Rules Engine Editor with a complete audit trail. The change resolves correctly in production for the intended scope.

Prerequisites

  • rules.read to browse, rules.update to author and transition.
  • A rule kind in mind: INGEST, CHARGE_VALIDATE, CHARGE_DERIVE, CLAIM_BUILD_GROUPING, PRE_SUBMIT_VALIDATE, COMPANION_GUIDE_VALIDATE, POSTING, EVV_CONFIG.

Lifecycle

There is no separate APPROVED state on the server; approval is the verb, PUBLISHED is the terminal pre-archive status.

Steps — primary path (UI)

  1. Open the editor: Configuration → Rules → /admin/rules. Pick a kind from the sidebar.

  2. Open the rule set's version list by clicking a row. Hit New draft to open the Monaco YAML editor seeded with a schema-valid template.

  3. Edit the YAML. Live lint runs against the kind's Zod schema; the header shows ✓ schema valid or an error count. Save as draft is disabled until the schema is green.

  4. Optionally bind the new version to a narrower scope via the Scope picker (facility × site × billing entity × payer × program × service-line × effective date). Click Bind scope before Save as draft so the scope threads through.

  5. Use the Dry-run panel to paste a sample entity and confirm precedence

    • rule resolution before promoting the draft.
  6. Submit for review to move DRAFT → IN_REVIEW. An approver (anyone with rules.update) clicks Approve & publish (IN_REVIEW → PUBLISHED) or Reject (requires a reason; sends it back to DRAFT).

  7. Once PUBLISHED, the Version diff view shows side-by-side changes vs. the prior PUBLISHED version, and the Approval history drawer captures the SUBMIT / APPROVE / REJECT / ARCHIVE trail.

  8. Archive the old version when superseding: open the old row, click Archive (PUBLISHED → ARCHIVED). New evaluations stop resolving to it immediately.

E2E coverage: tests/e2e/specs/rules-engine.spec.ts.

Validation

CheckExpected
Editor schema badge✓ schema valid before save
Version rowDRAFT → IN_REVIEW → PUBLISHED transitions visible
Approval history drawerAll transitions captured with actor + reason
Resolver/resolve returns the new version for the bound scope

Troubleshooting

SymptomCauseFix
Save as draft disabledSchema validation failingRead the editor's error count + Monaco markers.
Reject button missing in IN_REVIEWReviewer's role lacks rules.updateEscalate scope.
Diff view shows nothingPrior PUBLISHED version doesn't exist (this is the first published)Expected.
Resolver still returns the old versionCache or scope mismatchConfirm config_scope matches; some resolvers cache for ~30s.
Break-glass: API / SQL

Use only when the UI is unavailable (e.g. rcm-app down, rcm-core up).

# Create artifact
curl -X POST http://localhost:3008/artifacts \
-H "Content-Type: application/json" \
-d '{"content": "<yaml rule content>", "contentType": "application/yaml"}'

# Create version pointing at the artifact (DRAFT)
curl -X POST http://localhost:3008/versions \
-H "Content-Type: application/json" \
-d '{"ruleSetId":"<id>","artifactHash":"<hash>",
"effectiveFrom":"2026-01-01","lifecycleStatus":"DRAFT"}'

Submit for review:

UPDATE config.rule_set_version
SET lifecycle_status = 'IN_REVIEW'
WHERE version_id = '<id>' AND lifecycle_status = 'DRAFT';
INSERT INTO config.config_version_approval (version_id, action,
performed_by, comments)
VALUES ('<id>', 'SUBMIT', 'user@example.com', 'Ready for review');

Approve and publish:

INSERT INTO config.config_version_approval (version_id, action,
performed_by, comments)
VALUES ('<id>', 'APPROVE', 'reviewer@example.com', 'Approved');
UPDATE config.rule_set_version
SET lifecycle_status = 'PUBLISHED',
published_by = 'reviewer@example.com', published_at = NOW()
WHERE version_id = '<id>' AND lifecycle_status = 'IN_REVIEW';

Cross-references

Next

4.2 — Commercial billing-NPI & filing windows