Commit Graph

8863 Commits

Author SHA1 Message Date
rcourtman 653441b3ac docs: sync the shipped OIDC doc with the Entra manifest note
The docs sync test pins frontend-modern/public/docs to the repo docs
and 85136c582 only updated the repo copy, which broke Frontend CI on
main.

Refs #1635
2026-08-04 11:18:15 +01:00
rcourtman 76d60c7641 Reflect probe health in the workload guest status dot
The row dot resolved through getGuestPowerIndicator, which only knows whether
the hypervisor says the guest is powered on. A guest that is up while the
service behind it stops answering showed a plain green Running dot, so the dot
disagreed with the Attention bucket that already accounts for the same probe.

getGuestHealthIndicator layers the probe result on top of the power state and
the row now uses it, so a running guest whose attached probe cannot reach it
reads as needing attention in both places.

The predicate that decides this moves to availabilityProbePresentation and is
now shared by the indicator and the status bucket, rather than the guard
conditions being written twice. Only attached correlations count, the failure
threshold mirrors the gate the availability poller uses before raising
availability_unreachable, and a guest that is not running keeps its power
state, since a probe says nothing useful about a guest nobody expects to
answer.

The availability check remains the sole owner of the incident, the alert and
the history. Nothing new is minted here.

Verified against the live backend across 34 guests and 36 passing attached
checks with no false positives. The degraded path is covered by unit tests
proven red without the change.

Contract-Neutral: Workloads guest-row presentation only. Reads the availability facet already projected onto the guest and mints no incident, alert, or history, so the source-owned check contract from the 2026-07-23 availability check identity record is untouched. No backend, payload, or subsystem boundary changes.
2026-08-04 11:09:06 +01:00
rcourtman e86ca7da05 chore(templates): make the image reference optional for non-container installs
LXC and bare metal reporters had to type filler into a required
container-image field.

Refs #1675
2026-08-04 10:51:08 +01:00
rcourtman 67cc1aa397 fix(ui): stop Security Overview false negatives for non-admin roles
The security status endpoint truncates by authority and only a
privileged caller receives the settings-derived posture fields. The
Security Overview page scored truncated responses anyway, reading every
absent field as No and showing non-admin RBAC roles a wrongly critical
posture. Gate the posture summary and hardening actions on the
privileged detail level and explain the admin requirement instead.

The banner half of this report was already fixed for #1650.

Refs #1675

Contract-Neutral: behavioral fix: gate Security Overview posture on privileged detail level so truncated responses stop rendering false negatives, no contract delta (#1675)
2026-08-04 10:47:50 +01:00
rcourtman 1807ab294c fix(ui): make Proxmox storage tables container-aware
Contract-Neutral: Responsive Proxmox Storage table presentation and verification only; storage ownership, data contracts, and extension boundaries are unchanged
2026-08-04 10:44:15 +01:00
rcourtman e4bc063eb0 fix(monitoring): stop disk alert flap after settings-save reloads
A full monitor reload (triggered by every node settings save) starts
from empty host state, and the physical disk poll runs on the first
cycle. Evaluating disk alerts before a node's agent has re-reported
treats the agent's --disk-exclude patterns as absent, so excluded disks
fired wearout/health alerts that the next cycle resolved. Defer disk
alert evaluation for unlinked nodes during a two minute settle window
after monitor start.

Refs #1674

Contract-Neutral: behavioral fix: defer disk alert evaluation until host-agent linkage settles after reload, no contract delta (#1674)
2026-08-04 10:35:09 +01:00
rcourtman 85136c582a docs: cover the Entra groupMembershipClaims manifest override
A reporter on #1635 found that Assignment required alone does not stop
Entra emitting every security group when the app manifest still lists
SecurityGroup in groupMembershipClaims. Add the manifest check to the
group-overage warning.

Refs #1635
2026-08-04 10:23:11 +01:00
rcourtman 4fbca57dd4 Bucket a guest with a failed attached probe as needing attention
A Proxmox guest whose availability probe cannot reach it kept a running
bucket, so it stayed out of the Attention filter and read as healthy while
the service behind it was down. The hypervisor only knows the guest is
powered on, and resolveWorkloadStatusBucket read only resourceStatus, which
never carries the probe result.

The availability check remains the sole owner of the incident, the alert and
the history, per the availability check identity record of 2026-07-23. This
mints nothing new. It reads the facet already projected onto the guest and
stops that guest from being counted as healthy.

Only attached correlations count, since a standalone, ambiguous or unresolved
probe has not been proven to describe the guest, and the failure threshold
mirrors the gate the availability poller uses before raising
availability_unreachable, so the bucket and the alert agree. A stopped guest
keeps its own bucket because a probe says nothing useful about a guest nobody
expects to answer.

Verified against the live backend across 36 attached checks with none
failing, confirming no false positives. The failing path is covered by unit
tests proven red without the change.

Contract-Neutral: Workloads status-bucket presentation only. Reads the availability facet the guest already carries and mints no incident, alert, or history, so the source-owned check contract from the 2026-07-23 availability check identity record is untouched. No backend, payload, or subsystem boundary changes.
2026-08-04 10:22:28 +01:00
rcourtman 2bfab3610e fix(ui): make Proxmox overview tables container-aware
Contract-Neutral: Responsive Proxmox Overview behavior and verification only; canonical subsystem ownership and public contracts are unchanged
2026-08-04 01:14:22 +01:00
rcourtman 46af83d5fd Pin the workload availability cell rendering
The cell had no test coverage. Locks the shipped behaviour so the response
time cannot be dropped by accident and the failure reason keeps replacing it
when a probe stops answering.
2026-08-04 00:51:02 +01:00
rcourtman 8b30ef2fa6 Reduce the Workloads Avail and Backup columns to their signal
Both columns spent permanent width rendering the healthy state, so a fleet with
nothing wrong read as two columns of identical repeated values.

Avail printed a freshness suffix on every probed row. Freshness is Pulse
vouching for its own probe pipeline rather than a fact about the guest, and it
reads fresh on almost every row, so it now renders only when the probe data is
stale or of unknown age. Avail also becomes toggleable. It was the only
permanently pinned column in the table, which made operators who run no
availability probes pay 56px for mostly empty cells with no way to hide them.

Backup drew a bordered, filled pill with a compact age on every guest. The
shield colour already carries the verdict and the configured threshold already
decides what counts as acceptable, so the age was decoration on the rows nobody
has to act on. A fresh backup is now a bare green shield, and the age, the pill
chrome and the alerting colours are reserved for stale, critical and never. The
exact timestamp stays one hover away in the tooltip and the age stays in the
aria-label, so nothing is lost for assistive tech.

Verified against the live backend at 1440x900 and 390x844 across 34 guests.

Contract-Neutral: Workloads guest-row presentation only. GuestRowCells.tsx and guestRowModel.tsx are the contracted owners for cell presentation and column metadata in performance-and-scalability.md and this change extends them in place, keeps the shared workloadGuestPresentation helpers required by frontend-primitives.md, and moves no contract section, width rule, or payload field.
2026-08-04 00:33:11 +01:00
rcourtman 3a24d9694f Route mock toggle npm scripts through toggle-mock.sh
The mock:on and mock:off scripts used GNU 'sed -i' syntax, which fails on
the default macOS BSD sed. The '||' fallback then appended a fresh
PULSE_MOCK_MODE line on every invocation, so a local .env accumulated three
contradictory entries.

They also targeted the repo-root .env, which hot-dev.sh does not consult
when choosing the data directory. hot-dev.sh reads the canonical flag from
tmp/dev-config/.env, written authoritatively by toggle-mock.sh. The npm
wrappers were therefore a no-op that corrupted .env as a side effect, while
leaving the operator believing mock mode had switched.

Point them at toggle-mock.sh, which handles BSD sed, writes the canonical
file, syncs the runtime env and restarts the managed runtime. Add
mock:status and mock:edit; the hot-dev startup banner already advertised
'npm run mock:edit', which did not exist.

Extend the deployment-installability contract to cover the mock wrappers
alongside the existing repo-root dev entry rules, and add a regression test
asserting the wrappers delegate rather than rewriting the flag inline, and
that every mock command named in the hot-dev banner exists.
2026-08-04 00:30:28 +01:00
rcourtman 113d8b03a5 Clarify Docker host inventory navigation 2026-08-03 23:41:59 +01:00
rcourtman de5a1b20df Fix RC7 documentation contract checks 2026-08-03 23:18:54 +01:00
rcourtman 76ee3c1f07 Fail the build when the AI architecture docs drift from the code
The three deep-dive documents describe enumerations and tuned constants that
live in code. Prose cannot notice when a fifth state or an eighth signal type
is added next to it, so the documents would have rotted silently.

These guards derive the truth from the source rather than asserting literal
strings. A test that only checks the document contains RESOLVING keeps
passing when a new state appears, which is the drift actually worth catching.

Covered, in both directions where the set is enumerable. Session states and
tool kinds against internal/ai/chat/fsm.go and the ToolCallKind String
method. Patrol signal types and the five tuned signal thresholds against
internal/ai/patrol_signals.go. The investigation attempt limit and cooldown
against internal/ai/findings.go. The look-before-asking bound and the tool
concurrency cap against internal/ai/chat/agentic.go. Identifiers named in the
structured errors section against the declared error codes and tool names.

Error codes are checked document to code only. The document names a
representative sample rather than all 45 declared codes, so requiring the
reverse would force a table that churns on every addition while catching
nothing a reader cares about. A rename or removal still fails.

Writing the guard immediately found a real mistake in the document it
guards. ErrCodeFSMBlocked is FSM_BLOCKED, not fsm_blocked as the prose
claimed, corrected here.

Each guard was verified to fail by adding a fifth session state, adding a
signal type, and changing a threshold, then confirming the failure named the
document and the value.

Contract-Neutral: test and documentation only
2026-08-03 23:10:54 +01:00
rcourtman cf4bed184b Pin the Alerts read-only proof file added with the tab fix
d00249fd7 added frontend-modern/src/pages/__tests__/Alerts.readOnly.test.tsx
without adding it to the pinned proof list in the canonical completion
guard, so the guard's expected list no longer matched what the registry
derives. canonical-governance has been red on main since that commit, and
the pre-commit hook has been refusing every commit locally for the same
reason. Six commits landed in that window, including the v6.2.0-rc.7 release
preparation.

The delta is one file added and none removed, so this restores the snapshot
rather than lowering the bar. A removal would have meant proof coverage was
dropped, which is a regression to investigate rather than a pin to update.

Contract-Neutral: governance pin restored to match the registry
2026-08-03 22:56:29 +01:00
rcourtman 63fc89f701 Write the deep-dive docs the AI pages promised
AI.md, AI_AUTONOMY.md and PULSE_PRO.md linked nine times into
docs/architecture/, which .gitignore marks as not for public release. The
targets were never missing, they were deliberately unpublished, so every one
of those links was dead for anybody but the maintainer.

Write the three promised documents against the code and publish them under
docs/ where the rest of the shipped set lives.

PATROL_ARCHITECTURE.md covers a run end to end. The interesting part is that
deterministic signal detection runs after the model, not before, so unmatched
signals catch what the model failed to file. Signal types, the thresholds
those signals derive from your own alert settings, and every condition in
Finding.ShouldInvestigate are documented from internal/ai/patrol_signals.go
and internal/ai/findings.go.

ASSISTANT_SAFETY.md documents the session state machine in
internal/ai/chat/fsm.go, its four states, the tool classification it runs on,
and its invariants. No write without a validated target, no second write
before the first is verified, no final answer about an unverified change, and
no attempt count that wears the gate down.

ASSISTANT_ARCHITECTURE.md covers the agentic loop around that machine, the
three-phase pipeline, why only execution parallelises and at what cap, the
read-before-write batch that must stay ordered, the look-before-asking gate
and its bound, and the stable error codes.

Also removed two older pointers into the same private directory, in API.md
and UPGRADE_v6.md, and the two references to ENTITLEMENT_MATRIX.md in
PULSE_PRO.md. That file exists locally and is a monetization document, so
publishing it is not a documentation decision.

Unresolvable intra-doc links are now 2 of 218, both internal release-control
documents deliberately withheld from the shipped set.

Contract-Neutral: documentation only
2026-08-03 22:20:57 +01:00
rcourtman d4609665dc Fix release workflow JSON input transport 2026-08-03 22:19:32 +01:00
rcourtman 27948c6de4 Prepare v6.2.0-rc.7 release 2026-08-03 22:17:03 +01:00
rcourtman d1d0281bdd Contain table scrolling on narrow screens 2026-08-03 22:07:32 +01:00
rcourtman 32d373395c Make Patrol attention filters responsive 2026-08-03 19:49:50 +01:00
rcourtman d00249fd7d Keep active alert tabs visible 2026-08-03 19:35:36 +01:00
rcourtman caaf5ed232 Rewrite the agent integrations doc for the operator reading it
This document is linked from Settings, API Access, Agent integrations, so
its reader is somebody trying to connect an MCP client to their own
instance. It was written as a note to the maintainer, opening by describing
what landed on a branch and offering itself as a reminder in three weeks.

Rewritten for that reader. The endpoint reference, error codes and adapter
details are unchanged in substance, since an integrator needs all of them.
What changed is who the prose is addressed to.

Three things were cut rather than reworded because they were about building
Pulse rather than using it.

The Provable claims section listed test function names, referenced numbered
work slices, and recounted that discovery used to return 401 before a fix.
The guarantees underneath were worth keeping, so they are stated as
guarantees now, that the manifest matches the implementation, that
discovery needs no token, that error codes come in two layers, and that the
surfaces are exercised together. The test names that enforce them are not
the reader's concern.

Where to read more pointed at internal subsystem contracts under
docs/release-control, which are deliberately not shipped, and at Go and TSX
source paths. It now points at Configuration, API and AI, which are all
shipped and all resolve in the viewer.

The roadmap sentence about the next work item being whatever friction first
usage surfaces became an invitation to open an issue. The Gatekeeper
warning stayed, because that one is something a reader actually hits.

Title is now Pulse agent integrations, matching the panel the reader
arrives from, rather than substrate, which is not a word they saw.

Contract-Neutral: documentation rewrite, no contract delta
2026-08-03 19:27:14 +01:00
rcourtman 84687ee352 Move Storage status into filters 2026-08-03 19:21:57 +01:00
rcourtman 1501c1c239 Hide GPU column without machine telemetry 2026-08-03 19:10:11 +01:00
rcourtman c37a62b3ad Ship the architecture and contributing docs the index links to
The documentation index links both, and the viewer resolved them to routes
with no asset behind them, so following either reached the not-shipped
state. Both are public repository documents, so ship them alongside the
rest and let the links work.

They live at the repository root rather than under docs/, so the sync test
maps them the way it already maps SECURITY.md and TERMS.md.

CONTRIBUTING.md links as docs/AI_TRANSPARENCY.md, which is the path from
the repository root. Shipped flat into the docs root that prefix has to
collapse rather than nest into /docs/docs/, which the resolver already did
via normalizeDocPath. Pinned with a test now that it is a shipped case
rather than an incidental one.

Both were read in full before shipping. Neither carries internal governance
content or credentials beyond the documented local dev defaults that are
already public in the repository.

Unresolvable intra-doc links are now 11 of 202, down from 23. Nine are
references to docs/architecture/ files that do not exist in the repository
at all, and two are the internal release-control documents deliberately
withheld from the shipped set.

Contract-Neutral: ships existing public documentation as static assets
2026-08-03 19:08:21 +01:00
rcourtman 8ceff6b29f Hide empty Docker View menu 2026-08-03 19:04:17 +01:00
rcourtman 29b9b324d9 Render shipped documentation in-app instead of serving raw markdown
Documentation links opened the raw .md asset, which browsers display as
plain text, so operators got markdown source complete with ## markers,
asterisks and unclickable [label](TARGET.md) syntax. Shipping the full set
made the content right without making it readable.

Add a viewer at /docs/<path> that renders the markdown. The raw asset stays
at /docs/<path>.md, which is what the viewer fetches, so nothing that wants
the source loses it, and the extensionless route cannot collide because
every shipped file ends in .md.

Notes on the pieces that are not obvious:

- The renderer is separate from the AI chat's. That one sets breaks: true
  globally, which would break every hard-wrapped paragraph in the shipped
  set, and pins hrefs to http/https/mailto, which would strip the relative
  links holding the documentation together. Options are passed per parse
  call so neither renderer disturbs the other. DOMPurify still gates output.
- Intra-doc links are rewritten to viewer routes and navigate in-app. Links
  climbing above the docs root, such as ../SECURITY.md, are clamped to it,
  because those files ship flat and the link only made sense on GitHub.
- A document that is not shipped does not 404. The static handler falls back
  to index.html for any unmatched non-API path including one ending in .md,
  so the viewer treats an HTML content type as missing rather than rendering
  the application shell as markdown.
- The route is public because the underlying markdown is already served
  unauthenticated. Gating the readable form would make it less reachable
  than its own source.
- Tables get their own scroll container and inline code is allowed to break,
  otherwise a wide table or a long URL pushes the whole page into horizontal
  scrolling on a phone. Verified by measuring scroll width at 390 pixels.

The doc URL constants now resolve to the viewer route, so the assertions
pinning the old .md paths were updated to match. That is the change being
made rather than a baseline being quietly moved.

Contract-Neutral: frontend rendering of existing static assets
2026-08-03 18:58:30 +01:00
rcourtman aac7acc6ba Move v6 execution governance out of the operator docs index
The second thing a user read in the documentation portal was the v6
Execution Canonical Source section, telling them to consult
release-control internal files, run status_audit.py, registry_audit.py and
contract_audit.py, and observe pre-commit partial-staging rules for
.husky/pre-commit and canonical-governance.yml. That is agent and
maintainer execution guidance, and it means nothing to somebody who has
just installed Pulse. The docs are shipped and served raw under /docs, so
it reached self-hosted operators as written.

Move it into SOURCE_OF_TRUTH.md as an Execution Entry Point subsection,
which is where the section already pointed and which is excluded from the
shipped docs set. Nothing is lost: the three facts that lived only in the
index, contract_audit.py, the hook-sensitive partial-staging list, and the
substantive-contract-section rule, are all carried across, and every other
reference already existed under Canonical Control Files.

The operator index now runs from its welcome line straight into Getting
Started.
2026-08-03 18:31:00 +01:00
rcourtman 164a9f92c6 Ship the documentation set the in-app docs index references
The in-app docs index listed 41 documents while only 9 were shipped, so
anything a self-hosted operator reached under /docs/ that was not one of
those 9 returned 404. Copy the 39 reachable documents from docs/ into
frontend-modern/public/docs so the set the index describes is actually
present offline, which is the point of shipping docs with a self-hosted
product rather than linking GitHub.

Deliberately not shipped: docs/release-control/v6/internal/
RELEASE_PROMOTION_POLICY.md and docs/releases/V6_PRERELEASE_RUNBOOK.md,
which are internal release governance rather than operator documentation.
Every shipped file was checked for GitHub blob/main links, which the sync
test forbids, and for internal-only content.

The sync test now derives its pairs from what is actually in public/docs
instead of a hand-maintained list, so a copied doc cannot silently drift
from its source and a new one cannot be added without one.

Known remaining gaps, all pre-existing: nine references to
docs/architecture/ files that do not exist in the repository, and parent
relative links such as ../SECURITY.md that resolve on GitHub but not under
the shipped /docs/ root.

Contract-Neutral: ships existing documentation as static assets
2026-08-03 18:24:53 +01:00
rcourtman b9d0f3336f Simplify Kubernetes workload attention 2026-08-03 18:23:44 +01:00
rcourtman 6bd382173b Keep active platform tabs visible 2026-08-03 18:19:35 +01:00
rcourtman 9d9f13e7b0 Self-issue the MSP evaluation licence, and fix two things it exposed
The evaluation mode added earlier today was hollow. An unlicensed control
plane starts, but release-build client runtimes only trust entitlement
leases chained to a Pulse-signed licence, so its client workspaces ran
without the capabilities the provider was evaluating. Standing the stack
up proved the portal and the isolation boundary and nothing else.

setup.sh now requests a capped evaluation licence from the licence server
when no licence path is set, sending only the public half of the key it
generated locally. It degrades rather than blocks: a missing key, an
unreachable server, or a licence-free response leaves the install
unlicensed with an explicit warning, an existing licence on disk is
reused, and PULSE_PROVIDER_MSP_SKIP_EVAL_LICENSE skips it for air-gapped
hosts. Guarded with an if-test rather than a trailing true inside the
command substitution, because the derive helper calls die and exit in a
subshell is not a status that can be caught, so setup.sh aborted under
set -e.

Second fix. The lease capability ceiling was selected by licence
presence, so an unlicensed provider control plane fell through to the
Pulse-hosted branch and minted leases claiming relay, mobile and push,
which a provider deployment cannot serve and which previously caused
repeating relay registration failures in client runtimes. The ceiling now
follows hosting via SetProviderHosted. providerChained keeps its narrower
meaning of having a licence available to embed.

Third. Corrects a sentence I wrote into cloud-paid.md this afternoon
claiming msp_eval carries the same MSP capabilities. A plan version
selects the workspace cap; it does not entitle a workspace.

The regression test was negative-tested by reverting the ceiling selector
and confirming it catches relay. All three setup.sh degradation paths
were exercised directly. Licence server side is pulse-pro 7f6a319 and is
not live until the next deploy-license-server run.
2026-08-03 18:14:05 +01:00
rcourtman 9992d98172 Narrow two over-broad triage changes back to scope
Two follow-ups from auditing this morning's triage commits.

2a1bf2839 placed the config-directory guard after safe_systemctl enable, so
a failure to create the directory would have left the update timer enabled
while the installer reported automatic updates as disabled and never wrote
system.json. Move the guard above both the asset install and the timer
enable so a failure leaves nothing half-configured, and pin the ordering
with a test.

419385ccf regenerated the whole bundle-size baseline when only the FilterBar
chunk was over budget. That raised the recorded baseline for 22 other chunks
that were still passing, absorbing about 15 kB of accumulated drift and
resetting their headroom. Restore every other chunk and both totals to their
previous values and bump FilterBar alone, so the drift those chunks have
already accrued stays visible to the next change.

Contract-Neutral: scope corrections to earlier triage commits

Refs #1663
2026-08-03 18:00:04 +01:00
rcourtman b96640ccdc preserve readable mobile platform tables 2026-08-03 17:45:55 +01:00
rcourtman 4e67a93d49 Keep virtio and Xen disks in host disk I/O collection
99ad8c2c4 routed collectDiskIO through fsfilters.IsVirtualBlockDevice to
stop ZFS zvols being collected. That helper answers "can this device report
SMART", which is the wrong question for I/O accounting: it also matches
vd* and xvd*, so every agent running inside a KVM, Proxmox or Xen VM lost
host disk I/O entirely, since vda is the real disk on those machines. It
also dropped md, nbd, rbd, drbd and pmem, none of which were part of the
report.

Add IsNonPhysicalDiskIODevice for the I/O question and use it here. It
covers only loop, ram, zram, dm- and zd, so the zvol fix for #1671 stands
while everything that was collected before 99ad8c2c4 is collected again.
Tests assert vda and xvda survive the I/O filter while still being excluded
from SMART, so the two predicates cannot be conflated again.

Contract-Neutral: behavioural regression fix, no contract delta

Refs #1671
2026-08-03 17:39:55 +01:00
rcourtman 6be2af1c19 Let providers evaluate MSP without asking permission first
Two mandatory round-trips stood between an interested MSP and their first
screen, and neither was technical.

setup.sh required four image digests shipped as literal <pin>
placeholders, so the only way to get them was to ask. All four images are
publicly readable, so there was never anything to hand out. setup.sh now
resolves each blank pin to an immutable digest from its published tag via
buildx imagetools and writes it back to .env; hand-set values are left
alone.

setup.sh then died outright without a licence file, so nobody could start
the stack, create a workspace, or see the portal until a human minted a
licence for them. The control plane already ran unlicensed via
ProviderMSPPlanSourceEnvFallback; only the installer refused. A licence
path that is set but missing is still a hard failure, since that is a
misconfiguration rather than a choice.

Unlicensed now means evaluation rather than the cheapest paid tier. The
env fallback defaulted to msp_starter, handing every unlicensed
deployment the full 5-client Starter allowance and leaving no boundary
between evaluating and buying. Adds msp_eval at 2 workspaces: same
capabilities, smaller cap, not purchasable, not on the public ladder.

An isolation guarantee is the one claim a provider cannot evaluate from a
screenshot, and both MSP leads this year went quiet at exactly this step.

Contracts: cloud-paid records the unlicensed plan rule and the
strictly-below-paid invariant; deployment-installability records
credential-free, correspondence-free installability.

Verification: TestMSPEvalCapStaysBelowCheapestPaidTier,
TestCanonicalizePlanVersion_MSPEval,
TestProviderMSPSetupScriptSupportsUnlicensedEvaluation. The last was
negative-tested by reintroducing a <pin> placeholder and confirming it
fails. ensure_image_pins exercised against the live registries.
licensing, cloudcp, control-plane and installtests all green.
2026-08-03 16:31:24 +01:00
rcourtman f668614afd Add a portal preview scenario fit to be seen in public
The existing preview scenarios exercise edge cases and are named for it
(MSP Test Workspace A/B/C), and none of them set the alert counts, so
the Alerts column renders 'unknown' and the fleet rollup is invisible.
That is fine for a dev harness and useless for showing anyone what the
provider portal does.

Adds a 'demo' scenario: six settled clients with invented names on
reserved example domains, one critical and one warning, so the portal
surfaces a real 'Next: Review Hilltop Care' and the fleet summary reads
the way it does on a normal morning. This is what the pulserelay.pro MSP
walkthrough is recorded against.

Preview harness only, no production path touched. Portal suite green,
103 tests.
2026-08-03 15:34:20 +01:00
rcourtman 8365780282 tighten mobile filter action layout 2026-08-03 15:33:07 +01:00
rcourtman 85b0530439 standardize filter bar label presentation 2026-08-03 15:09:09 +01:00
rcourtman 3afc8bda15 align Add filter controls 2026-08-03 14:50:42 +01:00
rcourtman 37dfdf95ad fix platform table filter reset 2026-08-03 14:18:44 +01:00
rcourtman e3fdfc98b4 fix alert history filter reset 2026-08-03 12:22:19 +01:00
rcourtman 2402858e3c fix Proxmox backup saved views 2026-08-03 12:03:34 +01:00
rcourtman 59eb6dd68f fix machines saved view consistency 2026-08-03 11:34:13 +01:00
rcourtman d755c61f6a Keep all-scope mobile filter labels meaningful 2026-08-03 10:21:03 +01:00
rcourtman 419385ccfa Refresh the frontend bundle-size baseline
The FilterBar chunk grew past its budget as the saved-views and View
controls work landed, so the bundle-size gate held Build and Test red once
the lint and docs-sync failures ahead of it were cleared. The growth is
accounted for by that merged work rather than accidental bloat, and total
JS and CSS both stayed within budget throughout.

Regenerated with vite build plus check-bundle-size.mjs --update-baseline.

Contract-Neutral: build budget baseline, no contract delta
2026-08-03 10:09:55 +01:00
rcourtman 7f74e04be6 Ship the agent substrate doc the Settings panel links to
AGENT_SUBSTRATE_DOC_URL resolves to /docs/AGENT_SUBSTRATE.md and is
rendered as a link in AgentIntegrationsPanel, but the file was never
copied into frontend-modern/public/docs, so following it returned a 404.

Ship the doc and cover it in the sync test alongside the others.
2026-08-03 09:55:16 +01:00
rcourtman daecc6d456 Sync shipped docs with the AI transparency link
17f113708 added the AI-Assisted Development entry to docs/README.md but
left the shipped copy under frontend-modern/public/docs untouched, so the
docs-sync test failed and Build and Test stayed red once the lint error in
front of it was cleared.

Copy the README across and ship AI_TRANSPARENCY.md too, otherwise the new
entry resolves to a missing /docs/AI_TRANSPARENCY.md in the in-app viewer.
Add the pair to the sync test so the two cannot drift again.

Contract-Neutral: shipped documentation sync, no contract delta
2026-08-03 09:54:28 +01:00
rcourtman 2a1bf28394 Create the config directory before enabling auto-updates
Reinstalling after removing /etc/pulse reaches setup_auto_updates before
setup_directories has recreated the config directory. The system.json
write then failed with "No such file or directory" while the run still
printed that automatic updates were enabled, so the installer reported a
state it had not reached.

mkdir -p the config directory first, and fall back to disabling
auto-updates when it cannot be created.

Contract-Neutral: installer behavioural fix, no contract delta

Refs #1663
2026-08-03 09:47:03 +01:00