Five Patrol tests drove the retired Configure Patrol dialog and the
"Automation:" status strip, both removed with the monitor-first workbench,
so they could only time out on the dead affordances. The durable contracts
are re-pinned where the behavior lives now: a rejected Patrol settings save
surfaces the server reason from the settings panel, a save that returns a
not-ready Patrol model surfaces the readiness blocker with provider and
model context, and the Patrol mode group stays clamped to Watch only with
the Pro modes disabled when the runtime lacks autonomy, even when the
server reports a stale full autonomy level. The scoped-trigger strip
wording tests had no surviving surface; workbench presentation is covered
by the monitor-first workbench spec.
The weekly release-dry-run schedule failed at 'Resolve rehearsal
metadata' because GitHub does not apply workflow_dispatch input
defaults to schedule events, so rollback_version arrived empty and
resolve_release_promotion.py rejected the run.
Scheduled runs now pass --derive-rollback-latest-stable, which fills
an empty rollback_version with the latest stable tag preceding the
rehearsal version (currently v6.0.4 for 6.0.5-rc.3). Manual dispatches
keep the explicit rollback_version requirement; the stale prefilled
5.1.29 default is removed so operators state the target themselves.
The deployment-installability contract records the scoped scheduled
exception.
Commit 346ba81ed rewrote the per-platform connection specs against the
consolidated Connected systems workspace but left the old spec paths in
RA20 evidence and two registry.json proof lists, failing release_ready.
Point them at the renamed workspace specs.
The per-platform connection workspaces under
/settings/infrastructure/platforms/* are retired compatibility paths;
connections now live in the consolidated Connected systems workspace, whose
table reads the unified /api/connections endpoint and whose add dialogs
still speak the unchanged per-platform REST APIs. The TrueNAS and VMware
specs are rewritten against that surface: table listing from
/api/connections, the add dialog's draft Test connection payload, the
monitored-system impact preview copy from monitoredSystemPresentation, the
capacity-denial alert (the server explanation now surfaces as a
notification while the dialog stays open), and the structured
unsupported-vCenter draft-test guidance, which kept its testid and copy in
the ConnectionEditor.
The demo-boundary spec's settings heading follows the same rename
(Infrastructure Operations -> Infrastructure); its remaining retired-route
references are tracked for the demo-contract pass.
On mobile viewports the settings navigation sits behind the Settings
drawer trigger rather than a persistent sidebar, and panel descriptions
are desktop-only copy (hidden sm:block). The spec now opens the drawer
before asserting the shared navigation and search on mobile projects and
scopes the description assertion to desktop, instead of failing on a
sidebar mobile intentionally does not show.
Commit 005100432 retired the unified-route e2e specs but left two
status.json evidence entries pointing at deleted files, failing
repo_ready and RA20. Re-point them at the surviving coverage the
retirement commit named: refresh resilience via the ported Proxmox
workloads stability spec, and TrueNAS workload surfaces via the
platform pages shell spec.
The landing page announced v6 GA but the repo README (the highest-traffic
owned surface, 6.1k stars) never did, and its platform list predated
vSphere support. Adds a v6 callout linking the v5 upgrade guide.
The perf spec measured Infrastructure/Workloads tab transitions, both
retired with the platform-first navigation, so it could only ever time out
looking for the removed infrastructure-page hook. It now measures
Proxmox/Docker primary-tab switches with per-platform ready signals and
keeps the same 2200ms default budgets (medians run 300-600ms on the local
stack). Budget env overrides follow the new direction names.
The standalone /infrastructure, /workloads, /storage, and /recovery routes
were deliberately retired with the platform-first navigation (abb6f86ae);
these specs existed to pin those routes' filter, handoff, and scoped-routing
contracts, so there is no platform-first surface for them to assert. The
subjects that do survive the rework are covered elsewhere: workload drawers
and filters by the platform-page and embedded-workloads specs, and refresh
resilience by the workloads stability spec being ported to the Proxmox page.
The settings shell copy moved (General, Billing & Usage, Provider & Models
and their descriptions); spec 15 now mirrors settingsHeaderMeta instead of
pre-rework titles. The first-run agent handoff pre-provisions the scoped
install token, so spec 11 asserts that contract rather than a Generate
token button, exempts the app shell's Patrol open-work polling from the
no-AI-bootstrap guard (it powers the Patrol nav badge on every route),
treats navigation-aborted auth probes as benign console noise, and targets
/settings/support/reporting directly now that the /operations alias is
retired. Spec 12 scopes migration-notice assertions to the settings content
because the global banner legitimately renders the same copy.
Specs 36/54/55 hardcoded the hot-dev URL (127.0.0.1:5173), which does not
exist in the dockerized stack or CI; they now resolve the shared runtime
base URL. Spec 36 also never authenticated (it only ever ran against an
already-authenticated dev session) and pinned fixture timestamps that aged
out of the history view's default period window.
The visual spec injects an inline stylesheet, which the nonce CSP active
outside hot-dev blocks, so it now runs with bypassCSP. The commercial
cancellation specs require a pre-provisioned live-Stripe fixture bundle
(test clocks, customer and subscription ids) that no CI environment
carries; they skip with the missing variables named instead of failing.
The first-session specs depend on /api/security/dev/reset-first-run, which
is gated on development mode; the test stack never set PULSE_DEV, so every
reset attempt answered 403 in both CI and local compose runs. The only other
effects of the flag in this stack are the localhost AllowedOrigins default
and env-gated dev toggles that remain off.
Playwright storage states persist cookies and localStorage only, while the
primary-API-token flow authenticates through sessionStorage. Storage states
captured after token auth were silently unauthenticated, so whole spec files
landed on the login screen whenever the token path happened to win. Storage
states now always come from the cookie-backed password login, cached once
per run and probed before reuse.
Explicit bearer/X-API-Token requests now go through a cookie-less request
context: the backend intentionally CSRF-rejects mutations that carry a
session cookie without a CSRF token even when the Authorization header is
valid, so token-scope coverage could never pass from a logged-in page.
Session-semantics specs use the new ensureSessionAuthenticated instead of
racing the token path.
First-run resets now refuse the implicitly resolved hot-dev runtime (the pid
fallback with no explicit base URL and no harness-written runtime state);
an ad-hoc spec run wiped the live dev backend's auth twice through that
path. Resets can also recover an instance stuck unauthenticated mid-reset by
re-reading the rotated bootstrap token through docker exec, and login()
retries through the backend's 10-per-minute login limiter instead of failing
on burst runs.
Renders UpdateBanner with a Pro runtime identity and asserts the in-app
apply affordance is gone (even when the plan reports canAutoUpdate) while the
Private Release Access portal link and archive/.sshsig steps render; a
community binary still gets the Apply button. Guards the Guard-2 downgrade fix.
The in-app updater and install.sh both fetch the public community build from
github.com/rcourtman/Pulse. On non-Docker Pro installs (systemd, proxmoxve,
source) the "Apply Update" button was live, so applying an update replaced the
separately compiled Pro binary with community and silently stripped Audit,
RBAC, Reporting, and SSO from a paying customer. Docker was already blocked;
nothing else was.
Add a dependency-free pkg/edition marker (defaults to community) that the Pro
binary flips via enterpriseruntime.Initialize, mirroring the existing
coreaudit.SetLogger / server.SetBusinessHooks registration seam. ApplyUpdate
now refuses when the edition is Pro, pointing at the Private Release Access
portal (https://pulserelay.pro/download.html) and the install.sh --archive
path. The gate keys off the compiled binary, not license state: a community
binary with an active license still self-updates as before.
The update banner hides the in-app apply affordance and shows portal
instructions for the Pro runtime, keyed off the existing runtime-identity
signal (runtime.build) rather than a new payload field, so nothing extra is
plumbed through the version API and the frontend reuses the canonical
"which binary am I" contract. The backend gate is the hard guarantee; the
banner is the UX layer.
Guard 2 of the Pro download/update experience spec.
The Kubernetes workloads, services, and configuration tabs kept their
shared-toolbar search and status in component-local signals, so the saved
views shipped with the Docker containers work could never capture a -term
exclusion. The shared toolbar now reads search (q) and status from the URL
with replace-style writes, mirroring the Docker containers table, and the
namespace facet key moves onto new KUBERNETES_QUERY_PARAMS constants. The
shared Workloads surface already URL-backs q/status in
useWorkloadsControlsState; a new test pins that behaviour instead.
Resetting filters now clears every param in one setSearchParams write.
Consecutive writes each merge against the pre-navigation URL because the
router commits inside an async transition, so the Docker containers
clear-all (search, status, host as three writes) resurrected the params the
first writes had just cleared; it now issues a single combined write too.
New tests cover the Kubernetes URL filters end to end and the single-write
reset on both platforms.
Mobile users could not save, apply, or set default views on any
savedViewsKey surface because SavedViewsMenu only rendered in the
desktop controls row. Render it in the mobile chip row, show that row
when a surface has saved views even with zero menu filters, drop the
duplicate Reset the wider chip row introduced, and left-anchor the
dropdown below the sm breakpoint so it stays on screen at 375px.
The public demo wrote ~110K resource_changes rows/day (restart 60K/day,
state_transition 45K/day), making the Changes timeline unreadable and
keeping unified_resources.db churning. Four generator-level engines,
all verified with before/after soaks against scratch mock backends:
- Per-tick flap probabilities ran 43,200x/day on the 2s update loop
(docker restart p=0.01/tick alone is ~430 restarts/day/container).
Churn rates are now expressed as events per day per entity and
converted per tick against the configured interval, tuned to a few
fleet-wide events per day with dwell times long enough to see.
- The pod scheduling reconciler fought the per-tick scenario re-pin
and fabricated a fresh random StartTime on every recovery; derived
uptime moved backwards, which change emission records as a restart.
The reconciler is now idempotent: stable per-pod park/reschedule
choices, StartTime never regenerated, and only pods it parked
itself (NodeNotReady/ClusterOffline/NodeLost) get recovered, so
curated Pending and ImagePullBackOff stories stay put.
- Swarm cluster objects were fabricated per host under one shared
cluster key, so registry dedupe alternated between the divergent
candidates every poll (service renames, status flips, node
re-parenting). One leader manager now reports services, tasks,
secrets, configs and the node inventory, like a real control plane.
- Demo docker host profiles cycled 2 hostnames across 4 online hosts,
collapsing canonical identities, and scripted-offline hosts had
their sighting refreshed right at the 2 minute staleness threshold,
sawtoothing them online/offline. Four distinct host profiles now
exist and offline hosts keep a stably stale sighting.
Before/after soak with a live client: pre-fix ~110-160 rows/min
sustained (restart ~45/min, matching the droplet's 60K/day); post-fix
zero rows/min at steady state with the curated degraded stories
(CrashLoop payments-worker, ImagePullBackOff, offline hosts) intact.
In a multi-manager swarm every manager reports the same cluster-scoped
objects (services, nodes, secrets, configs), and the registry dedupe
picked the candidate whose host had the freshest LastSeen. That ordering
flips between polls, so the winner alternated: swarm node resources
re-parented to whichever manager won (ParentID tracks the reporting
host), and services flipped name/status/updateStatus whenever the
managers' views differed slightly. Every rebuild then wrote phantom
relationship_change / state_transition / config_update rows into
resource_changes, the same class of churn as the registry-rebuild spam
fixed in 74131e56e. Mock mode had the same symptom and was fixed by
pinning a single reporting leader, but real deployments still hit this.
Select winners deterministically instead: richer candidate fields first
(unchanged intent), then managers with an available control plane, and
freshness only when the gap exceeds the docker stale threshold, i.e.
the losing manager has genuinely gone quiet; otherwise the lowest host
ID wins as a stable tiebreak. Equal candidates can no longer alternate.
Regression test rebuilds the registry across polls with two managers,
jittered LastSeen ordering, flipped snapshot host order, and slightly
divergent service views, asserting no change emission; it fails against
the old LastSeen-ordering rule with the exact swarm-node re-parenting
seen in production.
Support requests keep landing for a way to hide noisy containers (one-shot
jobs, helpers) from the Docker dashboard. Free-text search across the
platform tables now understands -term exclusions via a shared
splitSearchExclusions helper in searchQuery.ts, wired into the Docker,
Kubernetes, generic platform, and Workloads search predicates. Workloads
keeps its comma OR semantics; exclusions apply globally.
The Docker containers table now URL-backs its search (q) and status params
alongside the existing host param, so saved views capture the whole filter
state and a default view with exclusions keeps unwanted containers hidden
on every visit. Search tips on the containers toolbar document the syntax.
Proving run 28925348032 showed a shard can still hit the 45-minute job
timeout while the suite carries many real failures: shard 4 held the
visual crawl (5.3 minutes per attempt, currently failing) plus dozens of
14-second failing attempts, and with two retries the failure storm
outran the budget even under the 20-failure cap.
Retry once instead of twice on CI, and run the visual crawl only on the
desktop project. The mobile emulation projects keep their dedicated
mobile specs; a per-project 5-minute crawl was the single most expensive
line in the suite.
Refs #1515
The install command is generated under the Settings infrastructure
installer, not a "Settings > Agents" tab. Correct the agent log, installer
warning, and Machines tooltip to say "the Pulse UI" so the recovery step is
accurate.
Refs #1515
When an upgraded or restored Pulse server no longer recognises an agent's
API token, the report endpoint returns 401. The agent buffered and retried
that report forever with only a generic warning, and the server kept the
node green at its last known agent version because a Proxmox node stays
online via the PVE API poll even after its agent dies.
- Agent: special-case 401 on /api/agents/agent/report. Drop the report
instead of buffering it and log a throttled, actionable error pointing the
operator at the install command to mint a fresh token.
- Installer: verify_agent_server_registration now tells a rejected token
(401/403) apart from "agent has not reported yet" and prints the recovery
steps at install time instead of a vague soft warning.
- Status layer: resourceFromHost flags a stale agent (its host marked
offline by the staleness evaluator) and carries the agent's own last
report time. Coalescing keeps a node online via the PVE source but the
dead agent stays flagged, so its version is no longer presented as current.
The Machines table renders such versions as "(stale)".
Agents push to main every few minutes and a sharded run takes about 30,
so cancel-in-progress meant a busy main could never complete a verdict.
With cancel-in-progress off, the in-flight run finishes and GitHub
collapses queued runs to the newest pending one, so intermediate pushes
still skip without killing the run that is about to report.
contract_patch_has_substantive_change walked git diff --unified=1000
output and tracked the last ## header seen in the patch, so an edit
more than 1000 lines below its section header (e.g. deep inside
unified-resources.md's Current State) lost its section and was wrongly
reported as not substantive, forcing a contract-neutral bypass on
74131e56e.
Replace the patch walk with full-file attribution: diff the HEAD blob
against the staged blob and map every changed line to its section via
a per-line section map built from the complete file. Covers new
contract files (empty HEAD blob) and keeps metadata-only edits
non-substantive. Adds regression tests for edits >1000 lines below
their section header in both directions.
Bump vite ^6.4.2 to ^6.4.3 and pin patched transitives via overrides
(@babel/core ^7.29.6, js-yaml ^4.2.0). Clears 4 Dependabot alerts (2 high,
1 medium, 1 low). npm audit reports 0 vulnerabilities; npm ci, vite build,
vitest (6830 tests), and bundlesize all green.
Also gitignore stray pnpm artifacts. npm is canonical here (CI and the
production Dockerfile both use npm ci from package-lock.json); nothing
consumes pnpm. The unused pnpm-lock.yaml and pnpm-workspace.yaml were
removed separately this cycle and only ever produced Dependabot noise (13
alerts). Ignoring them stops the pair creeping back in, as happened after
the earlier cac5be2ca removal.
The mobile-safari Playwright project (iPhone 12) launches WebKit, but CI
only installed chromium. The sequential run never reached a mobile-safari
test before the 45-minute cancel, so the gap stayed invisible until shard
4 of run 28923995416 hit it: 20 straight browserType.launch failures.
Also add a per-ref concurrency group so rapid successive pushes cancel
superseded runs instead of stacking four shard jobs each.
An OIDC provider upgraded from v5 keeps its v5 redirect URL
(/api/oidc/callback, the DefaultOIDCCallbackPath), so the IdP redirects
the browser back to the 3-segment legacy path carrying only code and
state, with no session cookie and no API token yet. The global auth
middleware only marked the 4-segment per-provider path public, so in
API-token-only mode (AuthUser=="" && AuthPass=="" && HasAPITokens())
the callback was rejected with "API token required via Authorization
header or X-API-Token header", and the route dispatcher 404ed the same
path.
The handler layer (extractOIDCProviderID) already maps the legacy
/api/oidc/login and /api/oidc/callback paths to the migrated legacy
provider, so only the public-path allowlist and the route dispatcher
needed to recognise the 3-segment form. Adds a regression test covering
both legacy paths in API-token-only mode, the exact configuration that
emitted the error.
Refs #1533
Every state update rebuilds the resource registry from scratch, which
reconstructs all relationship edges with fresh ObservedAt/LastSeenAt
stamps and fresh metadata maps. recordRegistryChanges compared the old
and new slices with reflect.DeepEqual, so every relationship-bearing
resource emitted a relationship_change row on every rebuild cycle even
when nothing changed. On the public demo this wrote roughly 450k rows
per day (1.2M of 1.6M rows were literal from==to no-ops), grew
unified_resources.db to 1.6GB in three days, starved the store's single
connection until retention pruning failed with SQLITE_BUSY, and drove
the droplet into the swap-thrash outage on 2026-07-08. Same mechanism
as issue #1496.
Compare relationship sets by edge identity instead: canonical source,
canonical target, type, and active state, order-insensitive. Volatile
provenance fields no longer count as change.
Also cap resource_changes at 200k rows during retention pruning so a
pathological writer can never grow the table unbounded inside the
30-day retention window, and record both invariants in the
unified-resources subsystem contract.
Every main push since the v6 branch flip was cancelled at the 45-minute
job timeout with no verdict. The flip brought the full 94-spec suite onto
main (the last green run, 2026-06-29, ran only 2 specs on the v5 main),
and it runs sequentially against a release-tagged image whose mock-fixture
gate returns 403 without a demo entitlement. Dozens of specs fail, retry
twice each, and burn the budget: of the 31 minutes of suite time in run
28907574469, 18.8 minutes were failing attempts.
- Add GO_BUILD_TAGS build arg (default release) and build the pulse:test
e2e image with it empty, matching the dev harness the suite is green
under. Shipped images keep the release tag; release-gate behavior keeps
its dedicated -tags release Go tests.
- Shard Playwright 4 ways across a CI matrix (214/202/205/203 tests per
shard) with per-shard report artifacts and an aggregate verdict job.
- Cap CI at 20 failures so an env-broken run reports red in minutes
instead of grinding into a no-verdict cancellation.
The MSP report scheduling and alert rollup feature (438291944) grew the
ReportingPanel chunk from 8.35 kB to 12.42 kB gzip, tripping the frontend
bundle size budget and failing Build and Test on main. The growth is the
intended feature payload, so raise the ReportingPanel baseline to the
measured size. All other chunks and totals remain within budget.
tests/integration/README.md and QUICK_START.md referenced the retired
.github/workflows/test-updates.yml; update-flow coverage now runs as
tests/79-update-flow.spec.ts inside the main suite via test-e2e.yml.
The Update Integration Tests workflow lost its Go test
(tests/integration/api) in the v6 release commit and was reduced to a
diagnostic smoke test that duplicated the test-e2e stack boot. Replace
it with tests/79-update-flow.spec.ts in the main suite, which runs via
test-e2e.yml on the same trigger paths:
- stable-channel check returns the mock v99.0.0 release and filters
the v99.1.0-rc.1 prerelease (regression guard for the auto-update
prerelease bug); rc-channel check surfaces the prerelease
- update plan reports honest manual instructions for the docker
deployment with readiness attached
- apply refuses prerelease download URLs on the stable channel (409)
- apply of an unsigned artifact fails closed at SSHSIG verification;
a completed update against the unsigned mock artifact would mean
the pinned-key trust root was bypassed
The old happy-path apply test is intentionally not revived: v6 made
SSHSIG verification against the pinned pulse-installer key mandatory,
so completing an apply would require shipping the real signing key to
the harness or weakening the trust root.
mock-github-server now serves v-prefixed asset names and download
paths like real Pulse releases (pulse-v99.0.0-linux-amd64.tar.gz);
the in-app updater only recognizes v-prefixed versions in download
URLs, so the old unprefixed shape made every apply fail validation
before reaching the paths under test. Unknown non-tarball sidecar
files (e.g. .sshsig) now 404 instead of falling back to tarball bytes.
The spec self-skips when the update check is not served by the mock
server, so managed-local-backend runs are unaffected.
golangci-lint was invoked from the repo root for every staged Go
package, so files in nested modules like
tests/integration/mock-github-server failed typechecking with 'main
module does not contain package' and could never be committed. Group
staged package dirs by their nearest enclosing go.mod and lint nested
modules from their own root.
.dockerignore only matched the root node_modules, so a locally
installed frontend-modern/node_modules entered the build context and
collided with the npm ci layer during COPY frontend-modern/, failing
local image builds. CI checkouts never hit this because they build
from a clean tree.
Settings > Alerts > Systems gains a 'Disk temperature by type' editor
(NVMe/SAS/SATA trigger degC) backing the existing per-type alert
thresholds that were previously hardcoded server-side. Saving alert
settings now round-trips diskTempByType and diskFillByType instead of
silently resetting them to defaults.
Disk temperature colors in the physical disks table, pool detail linked
disks, disk detail cards, and the Machines temperature fallback now
resolve from the live alert config per disk type (warning at trigger
minus the 5 degC hysteresis margin) instead of hardcoded 50/60 cutoffs.
Closes#1540
DiskTempByType unconditionally replaced the resolved host threshold, so a
host-level (or inherited linked-resource) diskTemperature override never
applied to nvme/sas/sata disks. Explicit overrides now win; the per-type
map still beats the global agent default.
Refs #1540
The workflow still invoked TestUpdateFlowIntegration from
tests/integration/api, but that package was removed in the v6 release
commit, and the remaining Playwright diagnostic spec skips itself
unless PULSE_E2E_DIAGNOSTIC is set, so the step ran zero tests and
then failed on the missing Go package. Enable the diagnostic spec so
the step actually exercises the pulse:test stack and drop the dead Go
test invocation.
Hosts (PVE nodes and host agents) were entirely outside the discovery
fingerprint model. Two consequences, one root:
1. collectFingerprints only covered docker/lxc/vm/k8s, so a host never
appeared in GetChangedResources and automatic discovery never ran for
it. Only the per-resource manual trigger worked, which is exactly the
v6.0.5-rc.3 behaviour reported in #1479.
2. cleanupOrphanedData built its keep-set from the same four types, so
CleanupOrphanedDiscoveries swept every agent:* discovery as an orphan
on the next fingerprint cycle (default 5 minutes). Manually discovered
hosts silently reverted to undiscovered.
Fix: add GenerateHostFingerprint (identity, OS, kernel, arch, tags;
status excluded so online/offline flapping cannot trigger rediscovery),
fingerprint snap.Hosts under the canonical agent:<id>:<id> key, and keep
host/node discovery keys (canonical, hostname-alias, and legacy host:
prefix forms) out of orphan cleanup. Also purge the store's in-memory
cache when an orphaned discovery file is removed, so deletions are not
masked for the cache TTL.
The RC3 memory bound for PBS backup polling summarized any group with
more than 8 snapshots into a single synthesized entry built from group
metadata. A synthesized entry has no verification, size, file, or
per-snapshot time data, so most real deployments saw every backup as
Unverified with no size, PBS files not listed, and a backup timeline
collapsed onto the latest backup day.
Keep the issue #1524 memory bounds but derive them from real data:
always fetch snapshots for stale groups, retain the newest bounded set
per group (limit raised from 8 to 100 to cover real keep policies), and
keep the newest-first global live-state cap. Remove the synthesized
group placeholder path entirely and update the monitoring subsystem
contract and tests to pin real-snapshot bounding.
Fixes#1541
Refs #1524
The release-control audits resolve repo identity from the checkout
directory name and expect evidence repos as siblings under one repos
root. The hosted runner checked the repo out at Pulse/Pulse, so
canonical_repo_id returned Pulse instead of pulse and the registry
audit treated every local file reference as untracked (2655 errors).
Check out the main repo at repos/pulse and the evidence repos as
repos/pulse-pro, repos/pulse-enterprise, and repos/pulse-mobile, run
all steps from repos/pulse, and point the PULSE_REPO_ROOT_* env vars
at the new paths.
The dual-key revert (1490a6e6e) removed the docker build line for the
pulse:test image instead of restoring the single-key version, leaving
the step with a bare cd and nothing building the image. Compose then
tried to pull pulse:test from Docker Hub and every run failed before
test execution. Build the runtime target the same way test-e2e.yml
does. The PULSE_LICENSE_PUBLIC_KEY env on the step was dead config:
env vars do not reach docker build and the Dockerfile no longer
declares that ARG.
The Canonical Governance workflow checked out the private pulse-pro,
pulse-enterprise, and pulse-mobile evidence repos with the default
workflow token, which cannot see other private repos, so every run
failed at the pulse-pro checkout. Use the existing WORKFLOW_PAT
secret (already used by create-release.yml to dispatch private Pro
workflows) and avoid persisting the credential in the checkout.
Empty allowedGroups, allowedDomains, allowedEmails, groupsClaim and
groupRoleMappings in a provider PUT were silently restored from the
existing config, so an admin could never clear them. The guards
shielded against lossy round-trips that no longer exist: the detail
GET and the flat list response both carry these fields, so an empty
value is an intentional clear. Nested OIDC/SAML config and the client
secret stay preserved since toggle payloads omit them and secrets are
never echoed in reads.
Also expose groupsClaim and groupRoleMappings in the shared extensions
list response so the enterprise list matches the core one, and make
the Settings enable/disable toggle send only writable fields; the old
list-item spread included computed response fields that the enterprise
strict PUT decoder rejects.
The MSP report scheduling form used raw native <select> elements,
failing the settings architecture guardrail that keeps Settings
selects on the shared labelled primitive. Cadence, Weekday, Format,
and Delivery now render through FormSelect, which also links each
label to its control.
Settings never rendered the oidc.scopes field, so admins could not
request an extra group scope and the authorization request stayed at
the default openid profile email. Adds the Scopes input to the OIDC
create/edit modal, points the Groups Claim help text at it, and covers
the create/edit round-trip with model and panel tests.
References #1535.