Commit Graph

7939 Commits

Author SHA1 Message Date
rcourtman 22534bfe71 Clarify self-hosted product roles 2026-07-10 14:49:20 +01:00
rcourtman 0eaa636eb5 test(frontend): branch-coverage for toolPresentation and resourceDetailDrawerVmwareModel
Add toolPresentation.coverage.test.ts (42 cases) covering chat tool-call
input/output parsing and command-preview formatting, and
resourceDetailDrawerVmwareModel.coverage.test.ts (91 cases) covering the VMware
detail-drawer section builders. Both target previously uncovered pure functions
from the v8 branch-coverage backlog. New test files only; no source changes.
2026-07-10 14:32:58 +01:00
rcourtman 3aca9d8f7a test(frontend): branch-coverage for licensePresentation and patrolSummaryPresentation
Add licensePresentation.coverage.test.ts (75 cases) and
patrolSummaryPresentation.coverage.test.ts (98 cases) covering previously
uncovered pure presentation/plan/verification functions flagged by the v8
branch-coverage backlog. New test files only; no source changes.
2026-07-10 14:22:57 +01:00
rcourtman 0ebdbbe07c feat(frontend): stripe the reclaimable-cache memory segment
At warning level the used segment turns threshold-yellow, which sat
next to the amber cache segment as one indistinguishable yellow mass.
Give the cache segment a vertical-stripe SVG pattern so the used/cache
split stays readable at every severity color. Stripes are vertical
because the bar's preserveAspectRatio=none scaling would shear a
diagonal texture.
2026-07-10 14:09:17 +01:00
rcourtman 3073a50614 Harden the invocation-descriptor contract before the investigation profile
Four classifier ratchets from review of 67c2534c0:

The classification vocabulary is closed: descriptor validation rejects
any class outside the known workflow kinds and mutation targets (an
empty Mutation no longer registers), and InvocationPolicy.Allows
independently denies unknown mutation targets outright, so a class
that somehow bypassed validation still cannot execute. Descriptor
lookups and registration store deep copies, so callers can never
mutate the canonical table through shared case maps or static class
pointers. Registration rejects descriptor overrides for canonical tool
names; overrides exist only for genuinely non-canonical extension and
test names.

Projected governance now derives its action mode from mutation
targets, not workflow kinds, and recomputes it even when no enum value
was filtered; a projection whose remaining invocations mutate nothing
downgrades to scope-only approval metadata (registered scope-only
summaries are preserved). Docker check_updates reclassifies from
{write,none} to {read,none}: it queues a read-only scan, and the write
kind was driving the FSM into verification and making the read-only
Docker projection report mixed. Discovery consequently projects as
mode=read in governance manifests, which is the honest mutation-derived
mode. The pulse_file_edit governance summary no longer claims to read
files. The contract prose also names pulse_read's execution-intent
classifier as mandatory second-stage enforcement for exec, not merely
defense in depth: the static read/none descriptor cannot prove
arbitrary command text safe.

Proofs: open-vocabulary rejection, unknown-target policy denial,
descriptor copy isolation, canonical-override rejection, and the
read-only Docker read/scope-only projection.
2026-07-10 13:53:31 +01:00
rcourtman 1685358872 fix(frontend): memory tooltip Used label tracks bar severity color
The stacked memory bar colors its used segment by threshold (green
below warning, yellow/red above), but the tooltip legend hard-coded
Used as green. At 80% used the bar read as all-yellow while the
tooltip claimed a green Used section. Derive the legend color from
the same severity as the segment.
2026-07-10 13:45:27 +01:00
rcourtman 67c2534c08 Replace the hard-coded tool classifier with registry-owned invocation descriptors
Every registered Pulse tool now carries a canonical invocation
descriptor (internal/agentcapabilities/invocation.go): static or
discriminator-based, classifying each invocation with a workflow kind
plus a mutation target (none / pulse_state / infrastructure). Mixed
descriptors must exactly cover their schema enum and registration
panics otherwise, so an unclassifiable tool cannot exist. Missing,
malformed, unknown, or fabricated discriminator values classify
fail-closed as infrastructure writes.

Provider projection and runtime enforcement consume the same
descriptor under one InvocationPolicy (control level plus the
request-local, non-serializable deny_infrastructure_mutations
restriction, isolated across executor clones): ListTools and
ListToolGovernance remove forbidden enum values, drop empty tools, and
recompute the offered action mode, while ToolRegistry.Execute blocks
forbidden invocations before the handler runs. This closes the mixed
tool control-level bypass, most seriously Docker action:update, which
previously fell through to direct execution at read-only, and fixes
the Kubernetes misclassification: the retired switch read the action
argument while the schema discriminator is type, so type:scale
classified as read.

pulse_file_edit is now write-only (append/write); file inspection
routes through pulse_read action=file, whose exec path keeps its
structural read-only execution-intent enforcement. ClassifyToolCall
consults the descriptor table first and retains only genuinely
non-registry compatibility cases. The deny restriction is deliberately
separate from autonomous mode, which only suppresses interactive
questions and grants no mutation authority.

Proofs: descriptor validation and fail-closed classification unit
tests, plus the invocation-policy regression suite (scale classifies
write and never invokes at read-only or under deny; Docker update
queues nothing at read-only; autonomous plus deny cannot mutate;
fabricated enum values fail at runtime; filtered projection and
runtime enforcement agree; executor clones keep request policies
isolated). Contracts and registry ownership updated for the new
shared invocation descriptor boundary.

Slice 3a of the typed-lifecycle ratchet; the patrol_investigation
execution profile and patrol_propose_action tool build on this
substrate next.
2026-07-10 13:31:11 +01:00
rcourtman 99dad2b511 Add a guided Ollama quickstart blessing qwen3:8b for Patrol
Ollama is the zero-cost AI path but the setup row offered only a Server
URL, and Patrol then failed on models that cannot emit tool_calls
(#1463, #847, #1425, #1152, #880). Bless qwen3:8b, the model family
Ollama's own tool-calling docs are written against, verified locally
against Patrol's real preflight: qwen3:8b emitted the tool call on
every run; qwen3:4b never did (0/4), so no low-RAM tag is suggested.

- Registry: SuggestedModel/Note/Equivalents on AIProviderDefinition,
  projected on /api/settings/ai providers; Ollama default model goes
  llama3.2 -> qwen3:8b.
- Provider row: copyable 'ollama pull qwen3:8b' block with hardware
  note, and a next-step hint when a successful test resolves a model
  outside the blessing set.
- Model resolution: exact-ID blessed preference, so pulling qwen3:8b
  makes it the auto-resolved Patrol model with no manual selection.
- Readiness copy names the blessed model (its contract pins landed
  with 94ccc7a0c's staging; this commit restores green).
- manual_ollama_preflight_test.go is the env-gated re-blessing
  harness; contracts updated for ai-runtime, api-contracts,
  frontend-primitives, and the agent-lifecycle/storage-recovery
  dependent boundaries; subsystem_lookup_test line pins follow the
  api-contracts.md insertion.
2026-07-10 13:02:26 +01:00
rcourtman a97774466e Update stale test-connection assertion to the Provider & Models rename 2026-07-10 12:45:27 +01:00
rcourtman 94ccc7a0c2 Close the 2b residuals on the typed Patrol proposal seam
Proposals now require their full correlation identity before anything
persists: a Submit without a finding ID or investigation ID is refused,
so a planned action can never lose the deterministic link back to its
Patrol finding. Pinned in the plan-only broker contract test.

The persisted-state transition callback becomes org-scoped
(OnActionTransition func(orgID, record)) and is wired through
ResourceHandlers.SetActionTransitionPublisher into the shared lifecycle
service, so a multi-tenant Patrol reconciler can key per-tenant stores
and can never apply a transition to the wrong tenant. Publication still
strictly follows persistence; code-standards pins guard the org-scoped
signature and wiring.

pkg/aicontracts/action_broker.go gains machine ownership: a shared
ai-runtime/api-contracts registry boundary (owned_files plus a sorted
shared_ownerships entry) with path policies proving through
pkg/aicontracts/contracts_test.go, and Shared Boundaries entries
inserted in canonical sorted order in both contracts.

Slice 2b-1 of the typed-lifecycle ratchet; the Patrol reconciler itself
lands with the orchestrator wiring now that ai_handlers.go is free.
2026-07-10 12:43:17 +01:00
rcourtman 8d07089eb3 Finish the Assistant & Patrol settings rename to Pulse Intelligence
Commit cc948b022 fixed the Patrol preflight strings that gated keyless
onboarding; this sweeps the rest. Error messages, readiness checks, and
guidance copy that still pointed at the retired Assistant & Patrol
settings page now name the real surfaces: Pulse Intelligence settings
for the area as a whole, and the Provider & Models settings page for
provider-credential guidance, matching the phrasing cc948b022
established. Pinning tests updated in step, including three
ai_handlers_test.go assertions cc948b022 had already left stale.
2026-07-10 12:04:03 +01:00
rcourtman 0db74c7eab Retheme the provider portal onto the product design tokens with dark mode
The portal shipped with a GitHub-Primer palette, light-only, while every
"Open client" hands the operator into the slate/blue Pulse product UI
(which most monitoring users run dark). The portal now uses the product's
Tailwind slate/blue semantic tokens so both halves of the MSP workflow
read as one product:

- Light theme mirrors the product exactly: slate-100 page, white surfaces,
  slate borders/inks, blue-600 solid buttons.
- Full dark theme (slate-900/800 surfaces, blue-400 links, product-style
  translucent status chip backgrounds with readable 300-series text),
  applied via prefers-color-scheme by default with a persisted header
  toggle (data-theme override in localStorage, nonce'd no-flash bootstrap
  in the page template).
- Hardcoded badge/status border literals, the toast, the danger-button
  hover, and border-only panels that relied on the old white page are all
  tokenized so every state renders in both themes.
- Dropped two dead Roboto @font-face blocks and their data-URI payloads
  (nothing referenced the family): portal_app.css shrinks 135KB -> 43KB.

Verified live in both themes: workspace roster, setup queue, client
onboarding panel chips, Access invite panel, signed-out instruction card,
and the error toast; theme choice survives reload and sign-out. Portal
vitest suite 103/103 incl. new theme tests; frontend/dist sync test green.
2026-07-10 11:59:06 +01:00
rcourtman 26718e70b6 Add the typed Patrol action-proposal seam over the shared lifecycle
pkg/aicontracts gains the plan-only OrchestratorActionBroker contract:
ActionProposal (typed capability reference, no command, host, risk, or
approval fields), a read-only ActionCapabilityCatalog with parameter
sensitivity, ActionDisposition over the existing safe ActionPlanInfo
projection, and an additive Action *ActionReference on
InvestigationSession and InvestigationRecord. ProposedFix/ApprovalID are
documented as migration-only; OrchestratorDeps gains the ActionBroker
seam while CmdExecutor/ApprovalStore are marked legacy pending removal.

internal/api/patrol_action_broker.go implements the seam tenant-bound
over ResourceHandlers.ActionLifecycle(): fixed pulse_patrol actor,
broker-owned ActionOrigin stamped through the service's internal
PlanWithOptions (the public plan endpoint cannot claim an origin),
plan-only submission even for ApprovalNone capabilities, and refusal of
proposals that populate IsSensitive parameters before any persistence.

The lifecycle service adds Capabilities (same registry resolution and
typed errors as planning) and an OnActionTransition persisted-state
callback covering plan, decision, and terminal execution transitions,
published only after the store write succeeds, so Patrol can reconcile
decisions and outcomes deterministically. ActionAuditRecord carries the
new broker-owned Origin, persisted in action_audits.origin_json with a
schema migration and round-trip normalization.

Contracts updated across api-contracts, ai-runtime, unified-resources,
agent-lifecycle, and storage-recovery; proofs added in
pkg/aicontracts/contracts_test.go (propose-only method set, command-free
wire shape, additive reference), internal/api/contract_test.go
(plan-only broker pins), broker behavior tests, lifecycle origin and
transition tests, and a SQLite origin round-trip test.

Slice 2a of the typed-lifecycle enforcement ratchet: additive core
fabric only; enterprise migration and side-door deletion follow.
2026-07-10 11:56:54 +01:00
rcourtman cc948b022c Route keyless Patrol setup to Provider & Models instead of a dead-end model check
A fresh install with no AI provider looped: the Patrol zero state sent
users to the Patrol model check, the check told them to enable Pulse
Assistant in a settings page that does not exist by that name, and the
Patrol model field silently degraded to a bare text input with no hint
that a provider key or Ollama server was the missing step (#1463, #847).

- Preflight and readiness copy now names the real surfaces (Provider &
  Models, Patrol settings) and the real first step: add an API key or an
  Ollama server.
- The Patrol zero-state CTA, header Fix setup link, and readiness
  banners route config-level causes (assistant_disabled,
  provider_not_configured) to Provider & Models; model-level causes
  keep the Check Patrol model action.
- The Patrol model field shows a linked zero-provider notice instead of
  a bare text input when no provider is configured.
- The preflight result box no longer renders the same failure message
  twice.
2026-07-10 11:38:23 +01:00
rcourtman 0e40ec07cb Make the provider MSP portal honest and self-sufficient without an email provider
Exercised the full provider portal as a pilot MSP would and fixed what
made it feel broken:

- The signed-out portal promised "a sign-in link is on the way" even when
  the control plane has no email provider (the bundle default), and team
  invitations silently sent nothing. The portal bootstrap now carries
  email_sign_in_available and provider_hosted_mode; the sign-in page shows
  the host command that actually prints a link, and the invite panel says
  invitation emails are not sent and how to hand over a link instead.
- New "provider-msp portal-link --email" CLI mints a one-time portal link
  for an account member or pending invitee, so teammates can sign in at
  all on email-less installs (bootstrap only covers the owner).
- Portal sessions were fixed at 12h; CP_SESSION_TTL now configures them
  and provider-hosted MSP mode defaults to 7 days.
- Creating a client past the license cap showed a generic "Failed to
  create workspace." toast: the limit error is now a JSON payload with
  current/limit, the API client no longer drops non-JSON error bodies
  (double body read), and the toast explains the license limit.
- Copy polish: provider-mode sign-in intro (no refunds/privacy register),
  least-privilege default invite role, queue tile label matches "Client
  onboarding", softer Support tab with a docs/MSP.md pointer, setup.sh
  summary now prints the bootstrap next step and day-2 sign-in commands,
  .env.example and docs/MSP.md document portal sign-in and sessions.

Contracts updated (cloud-paid, api-contracts, deployment-installability,
security-privacy) with verification pins in tenant_handlers_test,
config_test, magiclink_test, and provider_msp_deploy_test.

Verified live against a dockerless control plane: portal-link for an
invitee redeems, promotes the invitation, and sets a 7-day session;
the at-cap toast shows the license copy; portal vitest suite and
cloudcp/auth/account/installtests Go suites pass.
2026-07-10 11:22:57 +01:00
rcourtman ef19e64b28 Stop dev-server full reloads when test files are saved
Once anything requests a test module through the Vite dev server it joins
the client module graph, and every later save of that file dead-ends HMR
into a full page reload broadcast to all connected clients. With parallel
agents saving test files continuously, the dev UI reloaded out from under
interactive sessions every few seconds during bursts (31k+ reloads in the
hot-dev log), which read as "tabs sometimes don't load when clicked".

Ignore test files in the dev watcher, scoped off vitest's test mode so
watch-mode re-runs still see changes. Verified live: test-file touch no
longer reloads a connected page, source HMR unaffected, vitest run green.
2026-07-10 11:09:40 +01:00
rcourtman f356994869 Extract the transport-independent action lifecycle service
Planning, approval decisions, and execution for typed resource actions
move out of the HTTP handlers in internal/api/actions.go into a new
internal/actionlifecycle.Service owned by api-contracts. The REST
handlers become thin decode/actor/error-mapping adapters over the one
shared service, and ResourceHandlers.ActionLifecycle() exposes the same
service for in-process consumers, so a future Patrol action broker
inherits identical resource lookup, availability checks, plan hashing,
audit persistence, remediation locks, plan-drift revalidation,
execution, and terminal publication instead of loopback HTTP or a
parallel lifecycle.

Behavior is preserved: same status codes, error codes, and audit/
lifecycle persistence ordering, backed by the existing api contract
tests plus new fail-closed proofs for the service itself (unknown
resource/capability, availability refusal, unapproved execution,
remediation lock, plan drift, missing executor, missing store).

Contract text in api-contracts, agent-lifecycle, and storage-recovery
now names the service alongside actions.go and planner.go; the
subsystem registry owns internal/actionlifecycle/ under api-contracts
with a dedicated path policy; the code-standards and contract source
pins follow the moved invariants; and the subsystem_lookup line-number
pin shifts with the api-contracts canonical-files list insertion.

This is the first slice of making the typed action lifecycle the only
autonomous execution route for Patrol, Assistant, and MCP.
2026-07-10 11:06:55 +01:00
rcourtman 3c252918b3 Add branch-coverage tests for eight more frontend models
Covers previously-uncovered branches in truenas page model, disk/storage
presentation, agent machine table, alert overview/config models, connections
table fleet-governance signals, and the TrueNAS detail drawer - 295 assertions
through public entry points, no source changes.
2026-07-10 10:39:47 +01:00
rcourtman 1f139771f7 governance: assign subsystem ownership for internal/agentexec and internal/actionplanner
The typed action planner and the agent command-execution server were
unowned in the subsystem registry even though the contracts already
claim them in prose: api-contracts declares the action plan contract
API-owned through internal/actionplanner/planner.go, and
agent-lifecycle documents internal/agentexec request normalization and
semaphore semantics as one shared protocol contract.

Encode that ownership: internal/agentexec/ joins agent-lifecycle with
a command-execution path policy pinned to the package's test files,
and internal/actionplanner/ joins api-contracts pinned to
planner_test.go. registry_audit and contract_audit pass; both test
packages are green.
2026-07-10 10:12:44 +01:00
rcourtman 67fa4c89c7 Recover the initiating provider on the legacy OIDC callback path
The legacy v5 callback path /api/oidc/callback carries no provider ID,
so extractOIDCProviderID hard-maps it to the legacy-oidc sentinel.
Providers created in the v6 UI get a UUID ID, and when their IdP is
still registered with the legacy redirect URI the callback missed the
provider lookup and returned provider_not_found with no log, breaking
SSO login (authentik and similar).

Peek the pending authorization state across providers to recover the
provider that actually initiated the flow, re-validate it, and proceed.
State stays single-use (peek never consumes), unknown/disabled/expired/
replayed all still fail closed, and the 4-part provider_mismatch guard
is unchanged. The fail-closed path now logs instead of failing silently.

Refs #1533, #1535
2026-07-10 09:53:09 +01:00
rcourtman 7da1f0ae99 Add branch-coverage tests for eight already-tested frontend models
Covers previously-uncovered branches in proxmox backup recovery, AI finding
presentation, chat tool/transcript formatting, docker page model, alert
history/overrides models, and storage page state - 275 assertions through
public entry points, no source changes.
2026-07-10 09:45:11 +01:00
rcourtman f2b327b42d Cover PBS/PMG resource adapters and vSphere page-model sort ranks 2026-07-10 08:51:21 +01:00
rcourtman 0abeb1a041 Update stale Patrol readiness copy assertions to match shipped strings
Commit 20b310201 renamed the Patrol provider-setup action copy
('Check model' -> 'Fix setup', 'Open Provider & Models' ->
'Check Patrol model' with href /settings/pulse-intelligence/patrol)
and updated the sibling patrol tests, but missed these four, leaving
build-and-test.yml red on main. Point them at the shipped copy.
2026-07-10 08:50:44 +01:00
rcourtman 0c7796c0bf Record native lifecycle verification contracts 2026-07-10 01:57:09 +01:00
rcourtman 11bf0c9e74 Fix native agent release lifecycle verification 2026-07-10 01:49:38 +01:00
rcourtman a0170d3252 Block silent downgrades on apply and give the updater a sanctioned rollback path
The in-app updater validated download URL and channel but never compared
the target against the running version, so any valid older release asset
URL installed silently while the UI presented it as an update. ApplyUpdate
now rejects targets at or below the running version on both the community
and Pro broker paths before any history entry or download, with an explicit
allowDowngrade opt-in on POST /api/updates/apply for sanctioned cases.

The rollback half already existed but nothing reached it: createBackup
retains three backups, restoreBackup works, and history records BackupPath,
yet no endpoint or UI called restoreBackup. RollbackToBackup restores the
retained backup recorded on a history entry after re-validating the path
against the managed backup roots, shares the update-in-flight slot with
ApplyUpdate, records an Action rollback history entry linked to the source
update, marks that update rolled_back, streams a restoring stage through
the existing status/SSE machinery, and restarts via the exit-for-systemd
path. POST /api/updates/rollback carries it with the same RequireAdmin plus
settings:write gating as apply. Rollback is purely local, so the Pro
edition gate never applies to it.

Settings now has the update history surface that was missing entirely:
nothing called /api/updates/history before. The Updates panel lists recent
updates with a Roll back action on successful entries whose backup is still
retained, behind a confirmation dialog naming the restore version, and the
rollback rides updateStore's shared pending-apply marker for the
post-restart toast. restoreBackup also honors PULSE_INSTALL_DIR now instead
of hardcoding /opt/pulse, matching createBackup.

Contract deltas ride along: api-contracts picks up the rollback transport
and downgrade-conflict semantics, agent-lifecycle and storage-recovery pin
rollback as server self-update plumbing, ai-runtime and cloud-paid pin the
update watcher stage vocabulary as non-assistant non-paid shell chrome, and
frontend-primitives adds UpdateHistorySection as the history/rollback
presentation owner with matching architecture proofs.
2026-07-10 01:46:08 +01:00
rcourtman eb63de0b75 Harden first-run security boundaries 2026-07-10 01:28:33 +01:00
rcourtman c3f7b4d5c9 Keep action completion contract aligned with remediation locks 2026-07-10 01:22:05 +01:00
rcourtman 33d318a29e Delete the unreachable adapter Execute/Rollback update path and the unused UpdateQueue
The only production consumer of the UpdaterRegistry is the plan endpoint
(GET /api/updates/plan); every real apply runs through the in-Go pipeline
in manager.go ApplyUpdate, with concurrency held by updateMu/updateInFlight.
The InstallShAdapter Execute/Rollback path (install.sh piping, rollback
binary download, health wait) and the UpdateQueue were never called outside
tests, so fixes made there silently did nothing. Adapters are now plan
providers only: the Updater interface keeps SupportsApply, PrepareUpdate,
and GetDeploymentType, pinned by the new registry proof in
internal/api/updates_test.go.

The orphaned fetchAndVerifyReleaseSignature helper goes with it; its
fail-closed sidecar tests now exercise the manager's
downloadAndVerifyReleaseSignature directly. The deployment-installability
sshsig invariant now names the manager pipeline as the only surface that
fetches release artifacts, and api-contracts, storage-recovery, and
agent-lifecycle pin the same plan-provider-only shape at their update
transport boundaries. Stale adapter references in build-release.sh,
validate-release.sh, and the installtests comments are updated to match.
2026-07-10 01:14:40 +01:00
rcourtman 26a4a062f6 Unify the update apply flow behind one store action with a post-update toast
Both the update banner and the Settings update panel duplicated the
confirm/apply POST with divergent error handling; the banner's failure
path was a native alert(). updateStore.applyUpdate() now owns the POST,
the shared error toast, and a persisted pending-apply marker recording
the pre-update version. On the next version fetch after the post-update
reload, the store confirms the running version moved and shows a
one-time 'Updated to vX.Y.Z' toast, clearing the marker either way.
2026-07-10 01:12:54 +01:00
rcourtman 0c91799e0f Self-test the new binary before the in-app update swaps it in
Checksum and SSHSIG verification prove the downloaded artifact matches
what was published, not that it can run on this host or is the version
the user approved. The apply pipeline previously swapped the binary and
exited, relying on systemd to restart into an unproven executable; a
wrong-arch fallback asset or unstamped build would take Pulse down with
the backup left unused.

ApplyUpdate now locates the extracted binary and probes it with
--version before the backup and swap stages, failing the update with
zero changes applied when the probe fails or reports a version other
than the apply target. Same pattern the agent updater already uses.
Pinned in the deployment-installability contract with
internal/updates/selftest_test.go as the owned proof surface.
2026-07-10 00:44:01 +01:00
rcourtman 4c37a8c332 reporting: fit and wrap operator-controlled cover title and brand lines
Long custom titles on generate-multi covers overflowed the page edges;
the cover now shrinks to fit, wraps at the minimum size, and flows the
subtitle and scope blocks below a wrapped title. Also drop the stale
frontend-primitives contract and registry references to the
Settings/reportingResourceTypes.ts re-export removed in c862fb0ca; that
frontend-only commit skipped the governance audit, leaving the stale
references blocking every Go commit.
2026-07-10 00:42:06 +01:00
rcourtman f6d9468a84 Lead README with the watching-not-showing story and demote platform-page navigation 2026-07-10 00:38:42 +01:00
rcourtman c862fb0ca0 Remove dead frontend type module and re-export barrel
Dead-code audit batch 5: types/responsive.ts (all 8 exports had zero
non-definition references) and the one-line
components/Settings/reportingResourceTypes.ts barrel (all consumers
import @/utils/reportingResourceTypes directly). Both were referenced
only via ?raw text imports in guardrail tests; those imports and their
assertions are dropped here in the same commit. The TYPE_COLUMN_*
contract remains pinned against the live sources
(utils/typeColumnContract.ts, utils/typeColumnDefinition.ts).
2026-07-10 00:36:30 +01:00
rcourtman 51155a42c9 Remove uncalled test helpers
Dead-code audit batch 3: hasPrefix in internal/ai/service_test.go and
the staticExternalAgentActivityProvider test double in
internal/api/agent_resource_context_test.go have no callers in any
test or production code.

Skipped from the audit list after re-verification:
createTestEncryptionKey in cmd/pulse/test_helpers_test.go (called nine
times by commands_integration_test.go under the integration build tag).
2026-07-10 00:29:10 +01:00
rcourtman f7aa9ee922 Remove unreachable exported functions from pkg/
Dead-code audit batch 1: five functions with zero references from main,
tests, or pulse-enterprise (deadcode -test, grep-verified per symbol,
enterprise compile-checked). Also renames the tlsutil test that
exercised the deleted compat alias's target to name the Unverified
function it actually calls.

Skipped from the audit list after re-verification: IsNilAlertPayload
(live pulse-enterprise consumer in internal/aialertanalysis).
2026-07-10 00:27:35 +01:00
rcourtman 3f4bc295d2 Record general release workflow proof 2026-07-10 00:23:28 +01:00
rcourtman e5faca5035 Fix Docker update copy to recreate the container instead of docker restart
docker restart reuses the image the container was created with, so the
pulled update never takes effect. Point users at docker compose pull &&
docker compose up -d, with pull/stop/rm and re-run for non-compose
installs, matching docs/DOCKER.md.
2026-07-10 00:22:39 +01:00
rcourtman 4ac518a020 Replace reverted unified-nav copy in README with the shipped platform-first navigation 2026-07-10 00:19:48 +01:00
rcourtman ef895abbd5 portal: replace raw white_label token in onboarding template copy 2026-07-10 00:16:40 +01:00
rcourtman f4c2fd0c38 Fail closed on unknown remediation lock state for autonomous dispatches
The AI action broker treated an unreadable operator lock as unlocked:
isResourceRemediationLocked returned (false, nil) with no audit store
wired, and the caller logged store errors then dispatched anyway. An
operator's NeverAutoRemediate=true could be silently ignored whenever
the policy store was missing or erroring, which is unacceptable while
Patrol and Assistant run at assisted or full autonomy.

Posture change at the dispatch decision point:
- isResourceRemediationLocked now reports unknown state (nil store or
  lookup failure) as an ErrRemediationLockStateUnknown-wrapped error
  instead of silently defaulting to unlocked.
- New checkRemediationLockForDispatch gate: dispatches without an
  approved human decision fail CLOSED on unknown lock state and
  surface "remediation lock state unknown; operator approval
  required". Human-approved dispatches keep the historical fail-open
  behavior with a warning log. A confirmed lock still refuses even
  approved dispatches, as before.
- executeNativeActionWithAudit (TrueNAS app start/stop/restart) now
  enforces the lock too; it previously skipped the check entirely.
- Refusals persist Failed audit records with stable
  remediation_lock_state_unknown: / resource_remediation_locked:
  ErrorMessage prefixes.
- ai-runtime subsystem contract updated to pin the new posture.

Tests cover store-error and nil-store at both autonomy postures on
both dispatch paths; routing/control tests now wire an in-memory
audit store since autonomous dispatch without one is refused.
2026-07-10 00:14:01 +01:00
rcourtman dc6b5c3197 Verify AI Kubernetes scale and Docker update actions after execution
Two AI write paths asserted unverified success while the Proxmox guest
and Docker start/stop/restart handlers already do read-after-write
checks. Bring both in line with that idiom:

- pulse_kubernetes scale: re-read the deployment's spec/ready replicas
  via kubectl through the same agent (bounded settle-and-retry window)
  and return a JSON response with a verification block instead of
  "Action complete - no verification needed".
- pulse_docker update: the docker agent already recreates the container,
  health-checks it, rolls back on failure, and acks a terminal command
  status; expose that status through a new Monitor lookup
  (GetDockerCommandStatus) plus UpdatesProvider.GetCommandStatus, and
  poll it within a bounded window. Responses now report verified
  success, verified failure (is_error), or an explicit inconclusive,
  never unverified success.

kubernetes_control_test.go also carries a small in-flight fix from the
parallel remediation-lock work (in-memory ActionAuditStore in the test
helper) that these tests require to run on this tree.
2026-07-10 00:05:22 +01:00
rcourtman 85b2bc4008 Improve platform attention workflows 2026-07-10 00:00:07 +01:00
rcourtman 44da863e1a Remove dead internal/license/subscription alias package
Pure re-export shim over pkg/licensing with zero importers in pulse or
pulse-enterprise (internal/ is unreachable from external modules anyway).
Canonical implementations stay in pkg/licensing.
2026-07-09 23:56:33 +01:00
rcourtman 1c11d0945d Skip deleted directories when deriving golangci-lint targets in pre-commit 2026-07-09 23:52:22 +01:00
rcourtman 1894d2e665 Make native agent tests Windows-safe 2026-07-09 23:51:50 +01:00
rcourtman a3ef1226b7 Fail fast on missing native signing configuration 2026-07-09 23:44:22 +01:00
rcourtman 211b80717d Partition host agent tests by platform 2026-07-09 23:40:34 +01:00
rcourtman 51c60df3a4 Exercise native signing in release rehearsals 2026-07-09 23:36:06 +01:00
rcourtman 8a186e2c58 Reconcile concurrent archive validation commits 2026-07-09 23:34:14 +01:00