---
title: "Program Rules"
slug: program-rules
section: Program Configuration
status: stable
description: "The full story of how rules work in Globus — what they are, the difference between compliance and business rules, the Brand → Program → Season cascade, compliance_locked behavior, who edits what, and how it shows up in the Program Manage…"
last-updated: 2026-10-07
---

# Program Rules

The full story of how rules work in Globus — what they are, the difference between compliance and business rules, the Brand → Program → Season cascade, compliance_locked behavior, who edits what, and how it shows up in the Program Manager's UI — in one linear read.

> **This is the canonical Program Rules reference**
>
> The Rules Engine spec, Program Register, Glossary, and RBAC pages all link here for the Program Rules story. Deeper engine internals (rule trees, facts layer, evaluation pipeline, override governance) live on the Rules Engine spec and are linked from each section below where relevant. This page is documentation only — the interactive Program Rules management UI is separate work.

## What a Rule Is

A rule is a single named, structured policy fragment that the engine evaluates against a person, application, host, or job order to decide whether something can proceed. Each rule has a stable identity (e.g. passport_verified, min_age), a structured condition tree, configurable parameters, and a declared failure behavior (block, warn, or route).

Programs do not author the rule logic. The Compliance Officer (or, for business rules, engineering) defines the rule once in rule_definitions; each program then chooses whether the rule is on for that program and what parameter values apply. That choice lives in program_rule_configs, and seasons can refine it further in season_rule_overrides.

> **Rule logic vs rule configuration**
>
> The shape of a rule (its condition tree, its outcome contract, its action intents) is immutable across the cascade. What programs and seasons control is whether the rule is active and what parameter values it uses. See Rule Definition Anatomy for the full data model.

## Compliance vs Business

Every rule carries a classification of either compliance or business. This single field decides who can edit it, whether seasons can override it, and what happens when a workflow tries to publish without it.

### Compliance Rules

Compliance rules enforce regulatory or sponsor-mandated requirements (e.g. minimum age for J-1 Camp Counselor, valid passport, SEVIS check-in). They cite a regulatory basis, can only be authored or modified by users holding compliance.rules.manage, and they are the only rule class allowed to set compliance_locked = true on a program-level config. When a workflow does not cover a compliance rule, the Workflow Studio's coverage panel raises an error and refuses publish. For rules attached to a program by a regulatory bundle , "cover" specifically means an explicit row in program_rule_coverage_bindings that points at a workflow step (or named system process) producing the rule’s declared required_fact_keys — see the Bundle Publish Gate for the full contract.

### Business Rules

Business rules enforce operating policy that programs choose for themselves (e.g. preferred match scoring hints, capacity caps, document preferences). They have no regulatory basis. Program managers with program_rules.edit can toggle them on or off and adjust parameters within whatever bounds the rule definition declares. Uncovered business rules produce warnings, not publish-blocking errors.

### Business Sub-classes

Business rules carry a business_subclass so their intent is visible in queues, analytics, and the Program Rules UI:

### Side-by-Side Behavior

| Behavior | Compliance | Business |
| --- | --- | --- |
| Authored by | Compliance Officer (compliance.rules.manage) | Engineering / rule library; toggled by Program Manager |
| Requires regulatory basis | Yes (citation stored on rule_definition) | No |
| Toggle at program level | Read-only for Program Manager (shield icon, "Set by Compliance") | Editable by program_rules.edit |
| Parameter override at program level | Allowed only if not compliance_locked | Always allowed within declared bounds |
| Season-level override | Blocked when compliance_locked = true; otherwise constrained | Allowed via season_rule_overrides |
| Workflow Studio coverage | Error — blocks publish. Bundle-attached rules require an explicit program_rule_coverage_bindings row pointing at a step or system_process producing the rule's required_fact_keys. | Warning — does not block publish |
| Failure outcome (default) | block (or hold-for-review) | Sub-class-dependent: block / warn / advisory |
| Override at runtime | Only by compliance.rules.manage with documented justification | By program_rules.edit per rule policy |

## The Cascade

At evaluation time the engine flattens five scopes into a single effective configuration per (rule, applicant, context) tuple. Lower scopes refine higher scopes; the rule definition itself is never copied — only parameter values and enabled/disabled flags cascade. The top scope (Regulatory Bundles) is read-only to brand and below; everything downstream layers on top of bundle defaults.

### Bundle → Brand → Program → Season → Partner

table
### How Resolution Works

- Lower scope wins for parameter values. Each parameter declared in the rule definition starts at its default; brand fills it in, program refines, season refines, partner refines. Whatever is set last (closest to the applicant) is the effective value.
- Enabled/disabled cascades the same way for business rules. Any scope can flip a business rule on or off (subject to write-time policy).
- Compliance rules cannot be disabled below the level that owns them. A program cannot turn off a brand-set compliance rule, and a season cannot turn off a program-set compliance rule. Attempted writes are rejected at the API layer with HTTP 422 and reason code RULE_PARAMETER_LOCKED — never silently dropped. See the canonical Season-Override Rejection Contract for the full envelope.
- Compliance-locked parameters refuse lower-scope writes. When a program-level config carries compliance_locked = true, the season override and partner override layers cannot replace the locked parameter values; the API rejects the write at save time.
- Resolution is logged. Every evaluation persists a resolution snapshot showing which scope contributed each parameter value, so "why is this rule running for season X?" is always answerable. See Resolution Snapshots .
## Regulatory Rule Bundles

Brand operators do not hand-pick compliance rules when standing up a new program. Regulatory Rule Bundles are versioned packages of compliance rules — authored once by Compliance per visa regime — that the platform auto-attaches to a program based on its visa_type (and optional model and dependent predicates). They sit above the brand layer in the cascade and write directly into program_rule_configs with compliance_locked = true.

### What a Bundle Is

- A row in regulatory_rule_bundles identified by a stable key (e.g. j1_standard) and a monotonic version integer.
- Predicates: visa_type (required), program_model_type, allows_dependents, plus a JSONB condition_predicates escape hatch.
- Member rules in regulatory_bundle_rules reference rule definitions (not versions) with default_parameters and a lock_required flag.
- Lifecycle: draft → published → archived. Once published, the bundle row and its members are immutable; further changes require cloning to a new version.
### Auto-Attach on Program Create

The Program Register create transaction calls a pure bundle resolver with the new program's metadata. Every matching, published bundle is attached in the same transaction:

- One program_rule_configs row per (rule × bundle) match — compliance_locked set per the bundle's lock_required flag, with bundle_id + bundle_version provenance stamped on the row.
- If a rule appears in multiple matched bundles, the higher-precedence bundle wins and the compliance lock is upgraded but never downgraded.
- The Program Rules surface renders bundle-attached rows with a shield badge and "Locked by regulatory bundle v" tooltip; brand operators see them as read-only.
### When Compliance Publishes a New Version

Bundles update through an explicit, reviewable workflow — never a silent rewrite:

- Compliance clones the published version in the Bundle Author and edits the new draft (add/remove rules, change default parameters).
- On publish, the system writes regulatory_bundle_publish_audit with the diff and flips bundle_update_available = true on every program_rule_configs row stamped with the prior version.
- Each affected program shows a "Bundle update available" banner. Only Compliance Officers (compliance.bundles.manage) can apply a migration; brand admins (programs.manage) can only request one and a Compliance Officer approves and executes — see the canonical Migration Authority & Dual-Approval. The apply path records the requestor (if any), executor, diff, and reason in program_bundle_migrations.
- Programs that have not yet been migrated continue to evaluate against their current bundle version's parameters until the migration is run.
> **See the Rules Engine spec for the full bundle model**
>
> Resolver semantics, the Compliance Console Bundle Author surface, the bundle API, and the seeded J-1 Standard v1 bundle are documented in detail on the Rules Engine — Regulatory Rule Bundles section.

## compliance_locked

compliance_locked is a boolean column on program_rule_configs (see shared/models/rules-sponsor.ts). It is the single mechanism that turns "a program manager configured this rule" into "Compliance has frozen this rule for this program; nobody downstream can change it". The lock provenance is recorded in a lock_source jsonb column (kind, bundleId, bundleKey, bundleVersion, lockReason), and locked_parameter_keys lists the frozen parameter keys (empty means the whole rule is frozen).

### Lock States

`compliance_locked = false`
Default state. Toggle and parameters editable per the rule's classification: business rules editable by Program Manager, compliance rules editable by Compliance Officer. Seasons may layer overrides.

`compliance_locked = true`
Toggle and the listed locked parameters become read-only across the program. Season overrides for those parameters are rejected at the API layer. The Program Rules UI shows a shield icon, "Set by Compliance", and the locking rationale on hover.

### Write Rules

- Only users with compliance.rules.manage can set compliance_locked = true or change a locked rule's parameter values.
- A locked rule's enabled flag cannot be flipped to false by anyone other than a compliance user, regardless of scope.
- Attempts by a Program Manager to PATCH a locked rule (toggle or parameters) return HTTP 403; attempts to write a season override over a locked parameter return HTTP 422 with reason code RULE_PARAMETER_LOCKED.
- Program creation (`POST /api/programs`, permission `programs.manage`) is checked against the regulatory bundle's locked defaults: a payload that sets a locked key (`minAge`, `maxAge`, `minDurationMonths` or `maxDurationMonths`) to a different value, including an explicit `null`, is rejected with HTTP 422 `PROGRAM_LOCKED_PARAMETER` (`details.violations` lists key, expected and received; `details.sourceBundleSlug` names the bundle). A locked key that is simply omitted is filled with the bundle floor.
- Unlocking is itself an audited compliance action: it requires lock_reason replacement plus a justification entry on the audit trail.
## Who Sees and Edits What

Three roles meet on the Program Rules surface. Permissions follow the standard RBAC model ; the table below is a summary of what each role experiences in this specific area.

### Program Manager

Holds program_rules.view and program_rules.edit. Sees every rule attached to the program. Business rules render as editable toggles with editable parameter inputs. Compliance rules render as read-only toggles with a shield icon and a "Set by Compliance" label. Compliance-locked parameters appear inline with a tooltip showing the lock rationale and the compliance owner.

### Compliance Officer

Holds compliance.rules.manage. Sees the same Program Rules surface plus full edit affordances on compliance rules: toggle, parameter editing, lock/unlock action, and the audit trail. Authoring new compliance rules and managing the override review queue happens on the dedicated Compliance surface, not here.

### Partner / Host

Partners and hosts never see the Program Rules surface directly. They consume the resolved configuration through the Partner Portal and Host Portal — the rules already cascaded down to the partner agreement layer. Where a partner has a documented deviation (e.g. a tighter capacity cap than the season default), it is recorded as a partner-level override and shown back to them as a partner-specific term.

| Action | Program Manager | Compliance Officer | Partner |
| --- | --- | --- | --- |
| View Program Rules screen | ✓ | ✓ | — |
| Toggle a business rule | ✓ | ✓ | — |
| Toggle a compliance rule | — | ✓ | — |
| Edit business-rule parameters | ✓ | ✓ | — |
| Edit compliance-rule parameters | — | ✓ | — |
| Set / clear compliance_locked | — | ✓ | — |
| Write a season override (business) | ✓ | ✓ | — |
| Write a season override (compliance, unlocked) | — | ✓ | — |
| Write a season override on a locked param | Rejected (422) | Rejected (422) | — |
| See partner-specific deviation | ✓ (read) | ✓ (read) | ✓ |

## Worked Example

We follow two rules through the cascade for the same program — Camp Counselor (J-1), Summer 2026, operated by Brand A under a partner agreement with Partner P.

### A Compliance Rule Through the Cascade

Rule key: min_age — regulatory basis 22 CFR 62.30 (Camp Counselor minimum age 18). Classification: compliance. Configurable parameter: min_age_years.

| Scope | Configured value | What happens |
| --- | --- | --- |
| Brand Defaults | min_age_years = 18 | Brand-wide baseline, set by Compliance. |
| Program Rules | min_age_years = 19, compliance_locked = true | Compliance Officer raises the floor for this program (e.g. specific camp insurance requirement) and locks it. Program Manager UI now shows the rule with shield + "Set by Compliance" and the value 19 is read-only. |
| Season Overrides | PATCH min_age_years = 18 → rejected | A season admin tries to drop the floor for Summer 2026. API returns HTTP 422 with reason code RULE_PARAMETER_LOCKED. No write occurs. |
| Partner Agreement | no override | Partner P has no special deviation. Effective value remains 19. |
| Effective at evaluation | min_age_years = 19 | Resolution snapshot records: provenance = program_rule_configs, locked_by = Compliance Officer, lock_reason logged. |

### A Business Rule Through the Cascade

Rule key: preferred_languages — the matching engine prefers candidates who speak one of the listed languages. Classification: business, sub-class optimization. Configurable parameter: languages[].

| Scope | Configured value | What happens |
| --- | --- | --- |
| Brand Defaults | languages = ["en"] | Brand baseline set by operations. |
| Program Rules | languages = ["en", "es"] | Program Manager toggles the rule on and adds Spanish. No lock — this is a business rule. |
| Season Overrides | languages = ["en", "es", "pt"] | Season admin adds Portuguese for Summer 2026 only. Override accepted. |
| Partner Agreement | enabled = false | Partner P opts out of language preference scoring entirely; their candidates are ranked without it. |
| Effective at evaluation | disabled for Partner P; ["en", "es", "pt"] for everyone else this season | Resolution snapshot records contributing scopes per applicant. |

### What the Program Manager Sees

On the (forthcoming) interactive Program Rules screen for Camp Counselor (J-1) → Summer 2026, the two rules above appear as separate rows. Wireframe (reference only):

Program Configuration

Camp Counselor (J-1) — Program Rules

| Rule | Class | Enabled | Effective parameters | Source |
| --- | --- | --- | --- | --- |
| min_age | | 🛡 On (read-only) | min_age_years: 19 | Set by Compliance — locked |
| preferred_languages | | On (toggle) | languages: [en, es, pt]* | Season override active |

*The asterisk marks parameters that came from a season override rather than the program baseline. Hovering reveals the contributing scope and the user who set it.

## Program Setup — Rules Tab UX

The cascade and lock model above describe the underlying data shape. This section specifies the Rules tab on the program detail page — the surface a Program Manager actually sees when they create a new J-1 program and need to know "what was applied for me, what can I change, and where does coverage stand?"

> **Spec, not implementation**
>
> This is the contract for the Rules tab on the program detail page. The interactive screen itself is built in Globus on top of the bundle data model (regulatory bundles), the program_rule_configs store, and the Workflow Studio coverage validator . Where any of those data sources is unavailable, the corresponding card renders an explicit "data source unavailable" state instead of mocking values.

### The Five-Stage Setup Flow

Standing up a new sponsor-grade program is a five-stage flow. The Rules tab is the surface that connects stages 2 → 4: it makes the auto-applied bundle visible, hosts operational configuration, and surfaces season override roll-ups and workflow coverage status before publish.

s own Rules editor; the program tab shows the roll-up. / bg-emerald-50 dark:bg-emerald-950/30 border-emerald-200 dark:border-emerald-800,
5 / Build Workflow & Publish / Workflow Studio binds steps to bundle rules
The Rules tab is where stages 2 → 4 are made visible and explainable. Stage 1 is the Program Register form; stage 5 is the Workflow Studio canvas. The publish gate itself is owned by the workflow surface and is referenced — never duplicated — from the Rules tab.

### Rules Tab — Card Layout

Every program detail page has a Rules tab that renders four cards in the order below, plus a thin Workflow Coverage status block at the very top of the tab. The order is fixed: a brand operator should always meet the bundle context first, then the locked rules they cannot edit, then the operational rules they own, then the season-level roll-up.

Program Detail

Camp Counselor (J-1) — Rules

Overview 
Rules 
Seasons 
Workflow 
Workflow Coverage

14 of 14 bundle rules covered · view in Workflow Studio →

Bundle Banner — j1_standard v3

Applied automatically because this program is J-1, Camp Counselor, dependents allowed.

🛡 Locked Compliance Rules (14)

Set by Compliance — read-only. Hover any row for the lock rationale.

Operational Program Rules (3)

Editable by Program Manager. + Add operational rule

Season Overrides (2 seasons)

Roll-up of overrides per season under this program. Click through to edit on the season.

### Bundle Banner

The banner names every attached bundle and version, with a one-line rationale that restates the matched predicates in plain English. When a newer published version of an attached bundle exists, the banner shows a single CTA — "Bundle update available — review impact" — that opens the bundle diff modal.

J-1 Standard v3 · J-2 Dependents v1

Applied automatically because this program is J-1, Camp Counselor, dependents allowed.

⚠ Bundle update available — review impact
- The banner reads from the bundle_id + bundle_version stamped on the program's program_rule_configs rows; if multiple bundles attached, all are named.
- The rationale string is rendered from the bundle predicates that matched (visa_type, program_model_type, allows_dependents) — never hardcoded copy.
- "Bundle update available" appears only when at least one row carries bundle_update_available = true. Clicking opens a side modal with the diff (rules added, removed, reparameterised) and an Apply / Dismiss pair.
- Apply calls the migration endpoint from the Bundles task, requires a free-text reason, and writes a program_bundle_migrations audit row with actor, before, after, and reason.
- If no bundle is attached (rare — only programs created before bundles existed), the banner renders an info state: "No regulatory bundle attached. Compliance must run a back-fill before publish".
### Locked Compliance Rules Card

Lists every program_rule_configs row where compliance_locked = true, joined to the rule definition for name, regulatory citation, and parameter shape. There is no edit affordance — no toggle, no parameter input, no "Add" button on this card.

🛡 Locked Compliance Rules

14 rules · Set by Compliance — read-only 
| Rule | Citation | Effective parameters | Source |
| --- | --- | --- | --- |
| min_age | 22 CFR 62.30 | applicants must be at least 19 | 🛡 j1_standard v3 |
| passport_verified | 22 CFR 62.10 | verified before SEVIS pool entry | 🛡 j1_standard v3 |

Hover any row to reveal the lock metadata: locked_by, locked_at, lock_reason, attaching bundle key & version.

- Parameter values are formatted in plain English wherever the rule definition declares a configurable_params entry with display metadata; otherwise they fall back to the structured JSON shape.
- The shield icon and the "Set by Compliance" pill are part of the row, not a tooltip — the lock state must be visible without interaction.
- Sort order is bundle sort_order first, then rule name; rules are not regroupable by the Program Manager.
- Empty state (zero locked rules): renders "No compliance rules locked for this program. If this is a J-1 program, contact Compliance." rather than hiding the card.
### Operational Program Rules Card

Lists every business-class program_rule_configs row that the Program Manager owns: not compliance_locked, not part of an attached bundle. This is the only card on the tab with edit affordances.

- Each row reuses the existing rule editor pattern: enabled toggle, inline parameter inputs bound to the rule definition's configurable_params, classification chip, and a "Why is this here?" tooltip linking back to the rule definition.
- An "Add operational rule" picker queries rule_definitions filtered to classification = business and excludes any rule already attached by an active bundle or already configured on this program.
- Removing an operational rule disables it (sets enabled = false) rather than deleting the row, so historical resolution snapshots remain explainable.
- Empty state: "No operational rules configured. Click "Add operational rule" to layer business policy on top of the bundle."
> **Picker scope is the guard**
>
> The picker is the enforcement point for the "you can’t accidentally re-add a locked rule" promise. The card itself does not need to validate against bundle membership at save time because the picker never lets a Program Manager select one in the first place. Uniqueness on (program_id, rule_definition_id) backs that up.

### Season Overrides Roll-Up

The Rules tab does not host a season override editor — that lives on each season's own Rules screen. What this card surfaces is a roll-up per season: how many overrides exist, a one-line summary, and a deep-link.

Season Overrides

2 seasons · 4 overrides total 
Summer 2026

3 overrides · preferred_languages adds "pt" · capacity_cap 250 → 280 · interview_window 7d → 5d

Open season → 
Summer 2027

1 override · preferred_languages adds "de"

Open season → 
- Query backs onto season_rule_overrides grouped by season for this program; overrides on rules whose program-level config is compliance_locked are still listed (the API rejected them at write time, so they shouldn’t exist — but if a row is found, it surfaces with a red "Locked-parameter override on file" badge for compliance to clean up).
- Clicking Open season → takes the user to the season's own Rules editor; nothing on the program tab is editable here.
- If a Program Manager arrives on the program Rules tab from a rejected season override write (HTTP 422 deep-link), this card surfaces an inline notice at the top — "Your last override on Summer 2027 was rejected because min_age is compliance-locked" — with a link to the lock rationale on the Locked card.
- Empty state (program has no seasons): renders "No seasons exist under this program yet. Create a season to start authoring overrides." with a link to the seasons page.
### Workflow Coverage Status Block

A thin status block sits at the very top of the Rules tab — above the bundle banner — so coverage status is visible the instant the tab is opened. It reads from the Workflow Studio coverage validator and reports three numbers: total bundle rules, count covered, count uncovered.

Bundle rules

14

Covered

12

Uncovered

2

- When uncovered > 0, the block renders prominently with a "Open coverage panel in Workflow Studio →" link, deep-linked to the coverage panel filtered to this program.
- The block references the publish gate but does not duplicate it — the publish button itself is only on the workflow surface. From the Rules tab the user simply knows whether they will be blocked.
- If the coverage endpoint is unavailable, the block renders "Coverage status unavailable — try again from Workflow Studio" rather than a stale or zero count.
- Coverage is keyed off the Step &rarr; Fact Contract, not portal visibility. A behind-the-scenes interview node that declares its outcome fact counts as the covering source for any rule that requires it — keeping an interview off the applicant stepper never opens a coverage gap.
### Loading, Empty & Error States

Each card on the Rules tab is independently loaded and independently fault-tolerant. Loading shows a skeleton matching the card’s row shape; empty shows a card-specific copy line (see each section above); error shows a one-line failure with a Retry affordance.

| Card | Loading | Empty | Error |
| --- | --- | --- | --- |
| Coverage block | 3 number tiles skeleton | — | Coverage status unavailable |
| Bundle banner | Single skeleton bar | No regulatory bundle attached | Bundle metadata unavailable |
| Locked Compliance Rules | Skeleton table (3 rows) | No compliance rules locked | Failed to load locked rules |
| Operational Program Rules | Skeleton table + Add button disabled | No operational rules configured | Failed to load operational rules |
| Season Overrides | Skeleton list (2 rows) | No seasons under this program yet | Failed to load season overrides |

### Audit Behavior

- Reads do not audit. Opening the Rules tab, hovering the lock tooltip, or expanding a row produces no audit entry.
- Operational rule writes audit normally through the existing program_rule_configs mutation path: actor, before, after, timestamp.
- Apply bundle update is the only audited action that originates from this tab. It writes a program_bundle_migrations row containing actor, prior bundle version, new bundle version, the rule-set diff, and the free-text reason captured in the migration modal.
- Dismiss bundle update does not migrate but does audit a lightweight "dismissed" entry so reviewers can see the banner was acknowledged and intentionally deferred.
## Where to Go Next

- Rules Engine spec — five-layer architecture, rule trees, facts layer, evaluation pipeline, override governance, and the full data model.
- Program Register — where program identities (and their baseline rule configurations) live.
- Seasons — how season overrides are scoped and authored.
- Roles & Governance — the full permissions map for program_rules.* and compliance.rules.manage.
- Glossary — single-line definitions of Compliance Rule, Business Rule, Source Classification, and related terms.
