Skip to content

feat(web): partner-wide ("all orgs") owner option on config policy create - #2064

Merged
ToddHebebrand merged 2 commits into
mainfrom
Issue-with-partner-level-config-policy-creation
Jun 30, 2026
Merged

feat(web): partner-wide ("all orgs") owner option on config policy create#2064
ToddHebebrand merged 2 commits into
mainfrom
Issue-with-partner-level-config-policy-creation

Conversation

@ToddHebebrand

Copy link
Copy Markdown
Collaborator

Problem

Two reported symptoms, one root cause:

  1. Can't configure a config policy with "All Org" selected. In the All-orgs scope (scope pill → currentOrgId null), the create page sent { orgId: null } and the API rejected it ("orgId is required when partner has multiple organizations"). There was no UI to make a partner-wide policy at all.
  2. Assigning an org-level policy to the Partner level doesn't work. It appears to succeed, but resolution filters org-owned policies to org_id = device.orgId, so the assignment only ever reaches that one org — never the other orgs under the partner.

Partner-owned ("all orgs") policies were shipped backend-only in #1724/#1751 — the schema (org_id XOR partner_id), service (createConfigPolicy accepts ownerScope: 'partner'), routes, validators, and the resolution join all support it. The create page was never wired up, hardcoding orgId: currentOrgId.

Fix (frontend only — no backend/schema/migration)

ConfigPolicyCreatePage.tsx now shows an "Apply to" picker for partner-scope creators (gated on JWT scope, mirroring AlertTemplateEditor's availability picker):

  • All organizations (partner-wide) → POSTs { ownerScope: 'partner' }; the server derives the partner from the caller's token (never a client value) and enforces orgAccess: 'all'.
  • A specific organization → classic { orgId } payload, with an org select.

Defaults to partner-wide when the user is viewing All-orgs scope, else the focused org. Org-scope creators are unaffected — picker hidden, behavior identical to before. The backup-not-supported-on-partner-wide caveat is surfaced inline.

For true cross-org propagation, the policy must be partner-owned (this PR), not an org-owned policy assigned at the partner level — the AssignmentsTab partner level was already correct for partner-owned policies.

Tests

New ConfigPolicyCreatePage.test.tsx (5 cases): picker visible + partner-wide default in All-orgs scope; partner-wide POST omits orgId; switching to a specific org sends orgId without ownerScope; org-default when a concrete org is focused; picker hidden + classic payload for org-scope creators. All config-policy web tests (64), the no-silent-mutations guard, tsc, and eslint pass.

🤖 Generated with Claude Code

…eate

Partner-owned configuration policies (org_id NULL, partner_id set — the
"all orgs" shape) were shipped backend-only in #1724/#1751: the service,
routes, validators, and resolution all support `ownerScope: 'partner'`,
but the create page was never wired to use it. It hardcoded
`orgId: currentOrgId`, so in All-orgs scope (currentOrgId null) creation
400'd, and there was no way to make a partner-wide policy at all.

Assigning an *org-owned* policy at the Partner level appears to work but
doesn't: resolution filters org-owned policies to org_id = device.orgId,
so it only ever reaches that one org. True all-orgs propagation requires
a partner-owned policy — which you now can create.

Adds an "Apply to" picker on the create page for partner-scope creators
(gated on JWT scope, mirroring AlertTemplateEditor's availability picker):
All organizations (partner-wide) vs a specific organization. Defaults to
partner-wide when viewing All-orgs scope. Org-scope creators are
unaffected (picker hidden, classic orgId payload). Backup-not-supported
caveat surfaced inline.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 30, 2026

Copy link
Copy Markdown

Deploying breeze with  Cloudflare Pages  Cloudflare Pages

Latest commit: 5f27a97
Status:🚫  Build failed.

View logs

…iew)

Review of #2064 (silent-failure-hunter + code-reviewer) found the
org-scoped submit value diverged from the dropdown. `effectiveOrgId =
ownerOrgId || currentOrgId || ''` meant a partner creator focused on an
org who cleared the select to the placeholder would silently POST the
focused org while the UI showed nothing chosen. The org dropdown is now
authoritative for partner-scope creators (no currentOrgId fallback);
org-scope creators (no picker) still use their own org.

Also guard the empty-org case inside onSubmit, not just on the disabled
button — the Enter key bypassed the button and POSTed `{ orgId: '' }`,
surfacing only an opaque server 400 instead of an actionable message.

Plus a comment-accuracy tweak: the picker follows AlertTemplateEditor's
JWT-scope detection but, unlike it, shows for any partner-scope creator
(not only >1 org). Adds 3 guard tests + asserts the focused-org POST body.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ToddHebebrand
ToddHebebrand merged commit 2fe27e2 into main Jun 30, 2026
38 of 40 checks passed
@ToddHebebrand
ToddHebebrand deleted the Issue-with-partner-level-config-policy-creation branch June 30, 2026 15:48
ToddHebebrand added a commit that referenced this pull request Jul 1, 2026
…ab (#2107)

## Problem

The create-page **"Apply to" owner picker** (#2064) collided with the
**Assignments tab**. Both surfaced a "partner-wide / all orgs" choice,
but they control two independent axes:

- **Ownership** — `configuration_policies.org_id` XOR `partner_id` (DB
CHECK), set once at create. The eligibility universe.
- **Assignment** — `config_policy_assignments` rows, what
`resolveEffectiveConfig` actually reads.

Three concrete breakages:
1. The create picker **applied nothing** — a partner-owned policy was
created with zero assignments, so it resolved to **no devices** until
the user separately found the Assignments tab.
2. The Assignments tab was **blind to ownership** — got `orgId=null` for
partner-owned policies (breaking target lookups) and offered all 5
levels regardless.
3. **Org-owned + partner-level** validated and saved but silently
applied only to the one owning org (footgun).

## Fix — ownership is the single source of truth; the tab is
ownership-aware

- **`crud.ts`** — creating a partner-owned policy **auto-seeds the
matching partner-level assignment**, so "All organizations" applies
immediately. The two inserts aren't transactional, so a non-unique seed
failure **rolls back the orphan** and surfaces the error (logged with
the policy id) rather than leaving a committed-but-unassigned policy;
unique violations are tolerated.
- **`configurationPolicy.ts`** — `validateAssignmentTarget` now
**rejects org-owned policies at the Partner level** with an actionable
message.
- **`AssignmentsTab.tsx`** — partner-owned shows a banner + a re-assign
card only when unassigned; org-owned **drops the Partner-Wide level
option**. Guards against a null `orgId` in the site/group target fetch.
- **`ConfigPolicyDetailPage.tsx`** — `PolicyDetail` carries `partnerId`
(`orgId` now nullable), passed to the tab.
- **`ConfigPolicyCreatePage.tsx`** — relabeled **"Apply to" → "Scope"**;
clarified helper text.
- **`aiToolsConfigPolicy.ts`** — documented the partner-level constraint
in the tool description.

## Tests

- New `configurationPolicy.validateAssignment.test.ts` — ownership
gating (all four combinations), asserting the illegal combos
short-circuit before any DB query.
- `crud.test.ts` — partner auto-assign, org-owned does-not-auto-assign,
unique-swallow (still 201), non-unique-rollback (500 +
`deleteConfigPolicy` called).
- `AssignmentsTab.test.tsx` — partner banner/no-picker, re-assign POST
body (level + priority + role/OS filters, no `targetId`), card hiding,
org-owned level list excludes `partner`, org-owned assign POST includes
`targetId`.

## Verification

`astro check` 0 errors · API `tsc` clean · eslint clean (both apps) ·
API 94 config-policy tests + web 14 component tests green.

This is a follow-up to #2064. Reviewed via the pr-review-toolkit (code /
tests / silent-failure / comments / types); all surfaced findings were
addressed in this branch.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Todd Hebebrand <todd@lanternops.io>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ToddHebebrand added a commit that referenced this pull request Jul 1, 2026
Documentation sweep for the **v0.88.0** release (range
`v0.87.0..v0.88.0`). Updates 15 existing docs pages to reflect shipped
features and fixes — all `Update`s, no new pages.

## Pages updated
- **Networking** — `features/integrations.mdx` (self-hosted UniFi
controller: agent-mediated, no cloud key, one controller → many orgs; +
cloud Site Manager accuracy, #2097/#2103), `features/discovery.mdx`
(native network-device detail page + `GET /discovery/assets/:id`,
#1998), `features/dns-security.mdx` (Pi-hole v6: `piholeVersion`, app
password, 429 `no_seats`, #2069)
- **Security & incidents** — `features/incident-response.mdx` +
`reference/api.mdx` (EDR-aware Incidents feed, `GET /incidents/feed`,
#2095), `features/pam.mdx` + `security/pam.mdx` + `reference/api.mdx`
(SHA-256 signer **thumbprint pin** — API-only, fail-closed, XOR
signer-group, #2080), `features/security.mdx` (Elastic Defend provider
row, #2068)
- **Patching** — `features/configuration-policies.mdx` +
`features/patch-management.mdx` ("Manage Windows Update exclusively
through Breeze" toggle, default OFF, `NoAutoUpdate=1`, #2079;
`releaseDate` + firmware/drivers-only reject, #2116; partner-wide owner
accuracy + priority-direction fix, #2064), `features/deployments.mdx`
(`.exe`/`.msi` detection rules + `forceReinstall`, #2088)
- **Ops workflows** — `features/maintenance-windows.mdx`
(reboot-if-pending sweep, #2096), `features/ticketing.mdx`
(`unknownSenderMode` + `dropUnverifiedSenders`, #2105),
`features/alerts.mdx` (suppress: no-suppress-when-resolved, "Forever",
expiry reaper, bulk suppress, #2110), `features/reports.mdx` (posture
report now UI-selectable, #2087)

## Also
- `scripts/docs-review/mapping.json` — +4 code→doc mappings
(incidents→api, softwareDeployment→deployments, new
`maintenanceRebootWorker.ts`→maintenance-windows, `devices/network`
page→discovery)
- `scripts/docs-review/last-reviewed.json` — tracker bumped to `v0.88.0`
- **`AssignmentsTab.tsx`** — one-line fix: the policy **Priority**
tooltip said "higher values override lower ones," but the resolver sorts
`priority ASC` with first-wins (`featureConfigResolver.ts:207`,
`configurationPolicy.ts:1328`) — i.e. **lower-number-wins**. Tooltip
corrected to match. (Small code fix bundled here because it's the same
priority-direction accuracy issue the docs sweep surfaced.)

`pnpm --filter docs build` passes — **130 pages**, no MDX/frontmatter
errors.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Todd Hebebrand <todd@lanternops.io>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

1 participant