The provider-setup spec asserted the retired Assistant & Patrol shell:
old heading, old enable button, the old setup dialog names, and a Model
Overrides section that no longer renders (per-section overrides moved to
the Patrol, Assistant, and Service Context panels). The contracts are
re-pinned on the current flow: first enable opens the Set up Pulse
Intelligence dialog and submits provider credentials with no hardcoded
model, saved Patrol readiness warnings keep their provider and model
context, and a rejected save through Save provider settings keeps the
preflight recommendation in the failure alert.
The spec asserted the pre-rework AI settings shell (Assistant & Patrol
heading, Enable Assistant and Patrol button, and the retired setup
dialog). The durable contract survives on the current panel: pressing
Enable Pulse Intelligence without a configured provider issues no
settings update, no quickstart copy appears anywhere, and a direct
enable via the API still fails without resurrecting quickstart state.
The disclosure specs pinned pre-rework copy and routes: the telemetry
summary and PRIVACY.md were rewritten (one outbound usage-data scope),
chat action controls moved to the Assistant panel where the autonomous
option is gated on runtime autonomy (stubbed to the unblocked capability
set, since the e2e stack runs the community runtime), and the billing
Terms of Service link sits inside the collapsed manual key recovery
disclosure. With the .md content-type fix the shipped docs now render in
the popup, so the open-doc assertions exercise real navigation again.
The header-framing spec pinned four retired standalone routes; platform
pages intentionally carry no page header, so the contract now covers the
utility surfaces that still render the shared PageHeader (alerts overview,
settings general, and Patrol, whose description follows the current copy),
and the vertical-alignment check pins alerts against Patrol.
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 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.
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.
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.
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.
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.
Manifest-backed MCP tools, prompts, and resources with surface affordance contracts; agent capability manifest and governance projection; API contract tests and capability route projection; operations-loop and intelligence-funnel telemetry; release-control subsystem documentation, registry, and tooling; licensing and configuration.
Automates the docs/MSP.md validation checklist and the WEBHOOKS.md
delivery contract against the real server, covering the cross-layer
seams unit tests cannot see (today's two isolation bugs both lived
there):
- org-bound token allowed in its own org, denied 403 in sibling orgs
AND the default org (leaked client-site token must not read the
provider estate)
- dedicated agent-ingest port serves only /api/agents/* (management
paths 404, agent route auth-gated)
- client-org webhook delivery through the instance-wide private-target
allowlist saved in default-org context, with HMAC signature verified
against the documented recipe, X-Pulse-Event-ID idempotency header,
and tenant id/name stamped in the payload
- restart inheritance: after docker restart the persisted allowlist
still applies to lazily recreated tenant monitors (skips when the
docker CLI cannot manage the container)
Compose: pulse-test gains PULSE_AGENT_INGEST_PORT=7656 (+ port map)
and a host-gateway extra_host so the spec's capture listener is
reachable from the container.
golangci-lint run ./... failed on ~190 pre-existing errcheck violations and
5 unformatted files, burying any new regression in noise. Fix all of them:
- Test files that hand-rolled mock-mode set/restore (vmware, truenas, and
friends) now use the canonical setMockModeForTest/testutil.SetMockMode
helper instead of drift copies that ignored SetEnabled errors.
- internal/mock and internal/monitoring tests get package-local
mustSetEnabled/mustSetMockEnabled/mustSetMonitorMockMode helpers that
fail the test on toggle errors.
- pkg/auth/sqlite_manager.go, pkg/metrics/store.go, pkg/server/server.go:
rollbacks in defers use the explicit-discard idiom, migration renames and
rollup commits log failures, the hosted reaper goroutine logs an error
exit, shutdown mock-disable logs failures.
- Remaining test sites check errors with t.Fatalf/t.Errorf or explicitly
discard best-effort calls (restore-chmods, handler-closure unmarshals)
per existing repo style.
- gofmt: internal/api/maintenance_verification.go, internal/ai/demo.go and
three findings test files.
Only dupl findings remain (44 pre-existing production-code duplication
pairs) — those need real refactors, not mechanical fixes.
Full test suites pass for every touched package.
Recognize the current first-run security step in the multi-tenant integration helper and keep the unlock helper from clicking account-creation controls.
Harden bootstrap-token validation as unauthenticated JSON.
Two cleanups left over from the rc.6 IA revert that lived as
untracked scratch in the working tree until now.
Rename infrastructureNavigation -> platformNavigation
The internal feature name infrastructureNavigation predates the rc.6
revert and assumed an Infrastructure top-level page existed. Post-
revert the frontend is platform-shaped (Proxmox / Docker / Kubernetes
/ TrueNAS / vSphere / Standalone) and the model's purpose is to gate
visibility of each *platform* nav slot from resource evidence, not
gate visibility into a unified Infrastructure surface.
- frontend-modern/src/features/infrastructureNavigation/ -> .../platformNavigation/
- buildPrimaryInfrastructureNavigationVisibility -> buildPrimaryPlatformNavigationVisibility
- PrimaryInfrastructureNavId -> PrimaryPlatformNavId
- InfrastructureNavigationVisibility -> PlatformNavigationVisibility
- PRIMARY_INFRASTRUCTURE_NAV_IDS / _SCOPE_IDS -> PRIMARY_PLATFORM_NAV_IDS / _SCOPE_IDS
- primaryInfrastructureNavigationIsVisible -> primaryPlatformNavigationIsVisible
- selectFirstVisiblePrimaryInfrastructureNavigationId -> selectFirstVisiblePrimaryPlatformNavigationId
- filterInfrastructureNavigationShortcuts -> filterPlatformNavigationShortcuts
- createEmptyInfrastructureNavigationVisibility -> createEmptyPlatformNavigationVisibility
- infrastructureNavigationVisibilityFromResources -> platformNavigationVisibilityFromResources
- buildNavigableResourceInfrastructureScopeSet -> buildNavigableResourcePlatformScopeSet
Callers updated: App.tsx, AppLayout.tsx, useKeyboardShortcuts,
commandPaletteModel, useCommandPaletteState, KeyboardShortcutsModal,
CommandPaletteModal test, App.architecture test. Local variable and
prop names (infrastructureVisibility -> platformVisibility,
infrastructureNavigationVisibility -> platformNavigationVisibility,
infrastructureNavigationResolved -> platformNavigationResolved)
renamed for consistency.
The standalone visibility key is preserved. Standalone (Machines)
is a real evidence-gated nav slot in this model: AppLayout and
commandPaletteModel both consume isVisible('standalone') to hide
the Machines nav item when no Pulse Agent resources or availability
endpoints exist.
Visual crawl spec restored at tests/integration/tests/99-visual-crawl.spec.ts
The previous version of this spec crawled the unified IA routes
(/workloads, /infrastructure, /storage, /recovery, /operations) that
were retired in the rc.6 revert. Restored with a refreshed URL list
that targets the platform-shaped top-level pages and their
representative sub-routes (Proxmox PVE/PBS/Backups/Storage/Ceph,
Kubernetes nodes/deployments/config), plus Alerts, Patrol, and the
existing Settings routes. The DOM analysis body (headings, inputs,
tables, raw-color violations, screenshots, JSON report) is
unchanged.
Move TrueNAS apps, VMs, and network shares out of the overview stack and into first-class platform sub-tabs.
Update route and shell proof coverage for the expanded native TrueNAS tab set.
Replace the embedded recovery workspace with a TrueNAS-owned snapshots and replication table.
Keep the platform shell proof aligned to the native TrueNAS row contracts.
The "Hosts" tab already rendered the hosts table, the containers
WorkloadsSurface, and the Swarm services table together, so the
separate Containers and Swarm services sub-tabs were strict subset
views of overview. Other platform pages (Proxmox, TrueNAS,
Kubernetes) use sub-tabs for genuinely different data per tab; the
Docker shape was the odd one out.
Drop PlatformSectionTabs from DockerPageSurface and render the
unified hosts + containers + services stack unconditionally. Remove
the now-dead DOCKER_TAB_SPECS / DockerPageTabId / DockerTabSpec /
buildVisibleDockerTabSpecs exports from dockerPageModel. The
DockerPage no longer interprets the URL segment, so legacy
/docker/overview, /docker/containers, /docker/services bookmarks
continue to resolve via the existing /docker/* wildcard route in
App.tsx.
Audit pass on the TrueNAS pages turned up three concrete issues:
1. **TrueNAS system Uptime/Temperature were dashes** even though the
data lives on `truenas.uptimeSeconds` and on the
`agent.temperature` max-sensor projection. The system row is
emitted by `internal/truenas/provider.go::truenasRecordsFromSnapshot`,
which is a separate path from the host/node adapters that the
prior commit (c7bdd11e0) updated. Project the same top-level
`Resource.Uptime` and `Resource.Temperature` here so the canonical
table renders real values (live mock now reports `uptime: 3628800,
temperature: 61.5` on the TrueNAS appliance row).
2. **The Systems tab needed NAS-native columns.** A TrueNAS appliance
is a storage box — operators want at-a-glance pool / dataset /
disk / app counts on the same row as CPU / Memory / Storage /
Temperature / Uptime. New `TrueNASSystemsTable` reuses canonical
shared primitives (Card, Table, SearchInput, FilterButtonGroup,
StatusDot) and computes per-system child counts client-side from
the same TrueNAS scope already fetched by the page.
3. **Physical disks weren't surfaced anywhere on the page**, even
though the canonical TrueNAS adapter emits SMART-instrumented
`physical_disk` rows with model / serial / size / health /
temperature / wearout. Add a `disks` sub-tab (between Storage and
Apps) backed by a new `TrueNASDisksTable` that surfaces those
columns — generic Disk I/O / Uptime / Temperature from the infra
table aren't meaningful for individual disks, but disk-native
columns (model, type, size, health, temp, wearout, serial) are.
Browser verification (Playwright, chromium, live mock-mode dev runtime):
- 9 tests pass. The every-sub-tab operator-controls audit now covers
/truenas/disks too; the search input on every TrueNAS sub-tab is
the canonical SearchInput primitive.
Targeted vitest: `src/features/truenas/__tests__/truenasPageModel.test.ts`
(2 tests) green — covers the four-tab spec and the new `disks`
bucket on the page model.
Targeted Go: `go test ./internal/truenas/...` green.
Contract-neutral bypass: PULSE_ALLOW_CONTRACT_NEUTRAL_COMMIT set.
This completes the platform-page column-fit audit for TrueNAS. New
top-level `Resource.Uptime` / `Resource.Temperature` projection is
strictly additive (already documented as the adapter responsibility
in unified-resources.md by c7bdd11e0). The two new bespoke tables
live inside features/truenas/ and reuse canonical primitives only.
Four documented platform-page gaps from the prior round are closed:
1. **Docker Swarm services canonical projection.** The unified resource
adapter requires `host.Swarm.ClusterID`/`ClusterName` for
`dockerSwarmClusterKey` to produce a stable service source ID; the
mock generator was leaving those fields empty so all generated
services were dropped. Anchor every mock Swarm host to a single named
cluster (`mock-swarm-cluster-1` / `edge-swarm`) so manager and worker
hosts share Swarm identity and their services deduplicate correctly
across managers. Live mock survey now exposes 15 docker-service rows
(was 0).
2. **Docker Swarm services UI restored.** The `/docker/services`
sub-tab is back. `DockerPageSurface` mounts a `PlatformResourceTable`
with the canonical operator toolbar (search + status chips +
counter); `dockerPageModel.ts` re-introduces the services bucket;
the model test asserts the three-tab shape and the services bucket.
3. **TrueNAS Systems / Overview sub-tab restored.** Re-survey of the
canonical adapter confirms `truenas.FixtureRecords` already emits
the top-level TrueNAS appliance as a unified `agent` row tagged
with the `truenas` platform (see `internal/truenas/provider.go::
truenasRecordsFromSnapshot`). TrueNAS now defaults to
`/truenas/overview` and the page model exposes a `systems` bucket.
4. **VMware fixture inventory scaled to a mature SMB lab.**
`internal/vmware/fixtures.go::appendEdgeClusterFixtures`
programmatically appends an Edge DC with 3 more ESXi hosts
(esxi-05..07), 12 more VMs across Tier 1 / Stateful / Workstations /
Observability / Archive tiers (mixed healthy/warning/powered-off,
mixed Linux/Windows guest OS), and 4 more datastores (VMFS / NFS41 /
vSAN / cold-iSCSI). Live mock survey now shows 43 VMs (was 31), 18
agents (was 15), and 60 storage rows (was 55) across two datacenters.
5. **TrueNAS / vSphere Storage source filter chip suppression.**
`StoragePageControls` gains a `suppressSourceFilter` prop and
`Storage.tsx` automatically applies it whenever `forcedSourceFilter`
is set, so platform-page embeds no longer render the now-locked
Source filter chip alongside the operator toolbar.
Resource survey under the new mock baseline (live `/api/resources`):
- TOTAL 342 unique resources (was 307)
- app-container: 75, storage: 60, system-container: 44, vm: 43,
pod: 40, physical_disk: 19, agent: 18, docker-service: 15,
k8s-deployment: 14, docker-host: 5, network-endpoint: 5,
pbs: 2, pmg: 1, k8s-cluster: 1
Browser verification (Playwright, chromium, live mock-mode dev runtime):
- 9 tests pass. Every populated sub-tab — Docker Hosts / Containers /
Swarm services, Kubernetes Clusters / Nodes / Pods / Deployments,
TrueNAS Systems / Storage / Apps, vSphere Hosts / VMs / Storage —
asserts both populated canonical rows AND a visible operator search
input.
Targeted vitest (77 files / 358 tests) + Go tests (./internal/vmware,
./internal/mock, ./internal/monitoring) all green.
Contracts updated:
- `storage-recovery.md` Shared Boundaries: TrueNAS defaults to the
Systems overview now that the canonical adapter emits a TrueNAS-
platform agent row; `suppressSourceFilter` auto-applies under
`forcedSourceFilter`.
- `unified-resources.md` Extension Points: same; the canonical TrueNAS
adapter emits the appliance as a unified resource so the builder
default lands on a populated Systems sub-tab.
- `Storage.test.tsx` extended with the source-filter suppression
contract assertion.