The server now echoes its version on unified-agent report acks, and the
agent nudges its auto-updater the moment an ack carries a newer version.
After a server upgrade, agents converge within one report interval
instead of waiting out the hourly update check, so the "older Pulse
agent" notice self-resolves in seconds once upgraded agents report in.
The hourly loop stays as the retry and backstop path. Nudges dedupe per
server version, refuse downgrades, skip disabled and development-mode
updaters, and never fire from observer destination acks — only the
authoritative server may steer an agent's updater, and a nudged check
re-validates against the server and runs the existing checksum and
self-test pipeline before swapping binaries. Agents deployed before
this change still converge on their old hourly cadence once; every
upgrade after that lands within a report cycle.
Contract deltas recorded in agent-lifecycle and api-contracts, with
boundary notes in security-privacy (no update authority in the echo),
performance-and-scalability (no steady-state work), and
storage-recovery (nothing persisted).
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.
The unified agent gains an availability module: probe assignments
arrive through the signed remote-config channel (missing key clears
the schedule), each enabled target runs on its own clamped interval
through the shared probe core, and results queue in a bounded
drop-oldest buffer. A result is offered to the primary server until
one delivery succeeds and never again after - buffered offline
reports are stripped of availability results so the disk buffer
cannot replay observations the queue still holds. ApplyHostReport
feeds accepted reports into the probe ingestion path, where the
ownership check and failure accounting live, and the probe agent id
is projected onto unified availability resources for source
attribution in the UI.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Seventeen files closing the zero-coverage functions the current source drop
left behind. Every named target was measured off 0.0 percent by a per-function
coverage delta, re-measured against current main.
- config: the five new durable Proxmox cluster-node identity helpers
(deterministic id, endpoint equality, alias lookup, id existence, lookup by
id) to 100 percent; VMware and agent-profile persistence round-trips under
t.TempDir including that AppendProfileChangeLog appends rather than
replaces; AI chat session save, load, delete, per-user scoping and age
cleanup, with explicit timestamps rather than time.Now-relative fuzz;
PVEInstance.DeepCopy asserted for nested independence.
- truenas: incidentFromPoolStatus over every pool health string,
RecordsFromSnapshot over nil, empty and populated snapshots, both
TransportStatus accessors, and the RPC handshake and auth typed errors
through errors.Is and errors.As.
- unifiedresources: the maintenance-window operator-state lifecycle on
MemoryStore including the not-found and already-cleared arms, plus the
four remaining View accessors asserted on their exact formatted output.
- api: restoreAgentExecMetadata, buildAlertConnectionSnapshotsWithRuntimeSources
and both mock series generators, asserted on shape, ordering and
determinism rather than non-emptiness.
- cmd/pulse-control-plane: the four remaining MSP and mobile proof report
printers, asserted on the concrete strings in captured stdout.
- ai: cost.EmptySummary, approval.emptyExecutionState, demo.IsDemoRuntimeIntended
and tools.findCanonicalAppContainerResourceByReferences across no-match,
first-match, later-match and ambiguous references.
- monitoring, models, alerts: trueNASAppRunning,
supplementalProviderOwnedSourcesForOrg, IOCounterPresence.Effective,
ValidAlertIntentSignal and intentTimePointer.
No source file is modified. Adversarial review returned no rejects across all
seventeen files and flagged four padding cases plus one dead table field; all
were removed and the per-function coverage re-measured as identical, proving
they carried nothing.
PULSE_ALLOW_CONTRACT_NEUTRAL_COMMIT=test-only branch coverage, no source or contract change
Adds branch-coverage tests for eight packages whose target functions were
measured at 0% before this change. Every named target was verified to move
by running each package's coverage with and without the new file.
- internal/ai/eval: all 36 Scenario constructors and the four PatrolScenario
constructors 0% -> 100%. These are catalog-invariant tests, not literal
echoes: unique names, populated required fields, runnable assertions, tool
references checked against the agentcapabilities registry, and exact
assertion-count deltas across the env-gated conditional appends. A parity
test scans scenarios.go itself, so adding a constructor without registering
it in the table now fails rather than silently going untested.
- cmd/pulse-control-plane: nine MSP and tenant-runtime print helpers
0% -> 100%, covering the nil, empty-slice and optional-field arms.
- internal/ai/memory: RemediationLog GetByID, MarkRolledBack and
GetRollbackable 0% -> 100%, pinning the overwrite-vs-preserve contract on
RollbackInfo and each falsy arm of the rollbackable predicate.
- internal/alerts/config: AlertConfig.UnmarshalJSON 0% -> 90% and
NormalizeAlertConfigAliases 52.9% -> 94.1%.
- internal/config: RunMigrationIfNeeded 0% -> 100%, copyFile 0% -> 88.9%.
- internal/mock: AvailabilityFixtures, FixtureGraph.SupplementalChanges and
generateMockHostRate 0% -> 100%.
- internal/api: testProxmoxPlatformConnection 0% -> 100% through its injected
connect func, so no network is involved.
- internal/servicediscovery: needsDeepScan 0% -> 100% across every return arm
including the confidence boundary.
No source file is modified. Adversarial review found no rejects; two findings
were acted on before committing, replacing a circular catalog-count assertion
with the real source-parity scan and reducing an AllPatrolScenarios test that
compared the function against the same constructors it calls to the ordering
and completeness signal that is actually independent.
PULSE_ALLOW_CONTRACT_NEUTRAL_COMMIT=test-only branch coverage, no source or contract change
Contract-Neutral: test-only branch coverage, no source or contract change
Five new branch-coverage tests over the remaining pure vein, with no source or
existing test touched.
cmd/eval: twelve argument and environment parsers taken to full coverage,
including the two provider-filter variants driven so their differing default
behaviour is proved rather than assumed, the model list and exclude keyword
splitters over empty, whitespace and duplicate input, the selection reason
across every reason it can return, and the provider ordering proved
deterministic across repeated runs over the same map.
internal/actionlifecycle: the plan audit persistence asserted by reading the
record back through the store and by proving a store error is propagated rather
than swallowed, the policy mutation wrapper on both the success and error paths,
and the availability check error unwrapped through errors.Is.
internal/cloudcp: the audit failure accumulator including the monotonic OK flag
and the absence of deduplication, the container health predicate over each state
and health string it recognises with the case sensitivity of the state check
pinned, the tenant display name resolver against a real registry, and the
storage admission guard proved never to reach Docker when disabled.
internal/vmware: the fixture fetcher with independence asserted in both
directions and across consecutive calls, the refresh error wrapping asserted by
sentinel and message, the cached snapshot proved sorted, and the transport close
proved behaviourally by counting accepted connections on a loopback server
rather than by absence of error.
Contract-Neutral: test-only branch coverage, no contract surface touched
Five new branch-coverage tests taking previously unreached functions from zero
to covered, with no source or existing test touched.
cmd/pulse-control-plane: the MSP status failure lookup across exact, absent,
case-differing and substring inputs, and the status state ordering proved
deterministic across repeated runs over the same map so a random map iteration
order cannot pass by luck.
internal/ai/eval: the signal coverage assertion driven through both the lazy
quality evaluation path and the supplied-quality path, at the minimum rate
boundary and either side of it, asserting the formatted message rather than
only the pass or fail outcome, plus the approval write command builder.
internal/api: the setup script server name derivation including the fallback
arm, the patrol autonomy level validator, the approval risk assessment across
every level it can return, and the two typed error messages.
internal/hostagent: the token already exists classifier over each phrase it
recognises, a wrapped error carrying the phrase, and near-miss messages that
must not match.
internal/monitoring: the limited temperature buffer including its truncation
boundary and its aliasing behaviour, and the mock series generators asserted on
determinism under a fixed seed and on their value bounds.
Reported for follow-up, not fixed here: the node escalation arm in the approval
risk assessment is unreachable for the current command set, generatePlateauSeries
panics on a negative point count and leaves its tail unclamped, and the limited
temperature buffer returns a slice that aliases its internal storage.
Contract-Neutral: test-only branch coverage, no contract surface touched
Use the runtime-selected default token path in the token-resolution fixture so the same contract is exercised on Windows and Unix runners.
Contract-Neutral: Platform-aware agent token test fixture changes no runtime contract
Use the installer-owned ProgramData directory consistently for token lookup, enrollment state, agent identity, receipts, and service runtime data. Pass that directory explicitly to the Windows service and retain the existing Linux default elsewhere.
Contract-Neutral: Windows agent state-path alignment preserves the existing installer and agent public contract
Fleets on networks numbered from nominally public IP space (issue
#1522: an AD estate on 192.20.0.0/16) cannot pass the agent's
local-network plaintext heuristic, and the only workaround was pointing
a .internal DNS alias at the server, which bypasses the same control
less visibly than a flag would. --allow-plaintext-http
(PULSE_AGENT_ALLOW_PLAINTEXT_HTTP) records process-wide consent once at
agent startup before any module validates a URL, covers every agent
transport including the websocket command channel, warns at startup
that the API token travels in cleartext, defaults closed, and is never
emitted by generated install commands or settable by the server.
v6.1.0-rc.1 retired the legacy update endpoints before a replacement
existed, so the UI's Update button failed with an internal-jargon 410
(issue #1564). This lands the replacement end to end: update_container
is a typed agentexec operation with its own strict codec, durable
receipts, and a request digest bound to the image digest the plan
observed; the unified agent bridges execution to the Docker module's
existing pull/backup/recreate/verify/rollback implementation (which now
reports rollback attempt and outcome); and the container action
executor plans, dispatches, and reconciles the operation with declared
backup/rollback compensation truth. Containers advertise an
admin-approval update capability while an image update with a stated
current digest is detected. The legacy endpoints stay retired but
return actionable copy.
Proven live against a Colima daemon: single-container update, the
issue-1564 shared-network-namespace update, and the full UI journey
(Update button, governed review, approve, run) all completed with the
namespace preserved and the backup retained.
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.
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.
get_fleet_context returned the whole registry with no way to narrow it,
so a large fleet produced a payload that exceeded the agent harness's
50KB cap and forced agents to receive or page through healthy resources
that are pure noise from a triage standpoint.
Add optional additive query-param filters — hasFindings, severity,
technology, resourceType — that compose by intersection. All optional
(omitting every filter returns the full fleet, backward compatible);
unknown/unmatched values return 200 with resources: [] (a valid triage
answer, not an error). The hasFindings=true filter is the headline
triage case: show me only what needs attention.
The MCP adapter layer had no GET query-param transport — ProjectCapabilityCall
short-circuited GET after path substitution and dropped all other args.
Close that gap generically: ProjectedCall gains a Query url.Values field
populated from leftover non-path public args for GET/DELETE, and
BuildCapabilityHTTPRequest encodes it onto the request URL. Benefits any
future GET capability with filter args, not just fleet-context. The
resources/list adapter calls fleet-context with empty args and continues
to receive the unfiltered fleet.
The manifest now declares the filters via inputSchema so they are
discoverable through the capability surface the same way add_node's
params are.
- ProjectedCall.Query + GET query-param forwarding (projection.go, http.go)
- fleetContextFilter parsing/matching in HandleFleetContext
- fleetContextInputSchema + InputSchema on the capability
- Regenerated cmd/pulse-mcp/README.md via generate-pulse-intelligence-docs
- Contract updates: api-contracts, ai-runtime, agent-lifecycle, storage-recovery
- Tests: adapter query-forwarding unit tests, 7 filter handler tests
(hasFindings/severity/technology/resourceType/no-filter/unknown/compose),
contract pinning inputSchema + end-to-end query forwarding
Agents had no way to discover which governed capabilities a resource
advertises before calling plan_action. The data existed (Resource.Capabilities
with full param schemas) but was only surfaced as count-limited prose
facts inside get_resource_context, which deliberately omits parameter
schemas. This forced agents to guess capabilityName and params.
Add a dedicated structured surface: GET /api/agent/resource-capabilities/{id}
returns the canonical ResourceCapability list (name, type, approval level,
platform, full parameter schemas) for a single resource. A resource with
no advertised capabilities returns 200 with an empty array, the signal
to skip plan_action.
Internal plumbing already existed (registry.Get + Resource.Capabilities,
the same path plan_action validates against); this is the wiring work at
the canonical agent-surface layer. The tools/call dispatch is fully
generic so no mcp.go changes were needed.
Companion to get_resource_context: that tool remains the human-readable
prose summary; this is the structured schema surface for action planning.
- Manifest capability, name/path constants, output schema helper
- Handler mirroring HandleResourceContext error/scope patterns
- Route registration behind monitoring:read
- Activity telemetry mapping to resource_context class
- Regenerated cmd/pulse-mcp/README.md via generate-pulse-intelligence-docs
- Contract updates: api-contracts, ai-runtime, agent-lifecycle, storage-recovery
- Tests: manifest ownership/addressing/table-test, handler happy/empty/404/405,
contract pinning capability presence/scope/wire-shape
1. Regenerate pulse-mcp README from canonical manifest (doc drift)
2. Add 31 missing Pulse Intelligence telemetry fields to both PRIVACY.md
copies to match current Ping struct JSON tags
3. Rebuild portal frontend bundle to update build_manifest.json hash
4. Update action execution contract test to match current code structure
(handler wrapped with withExternalAgentCapabilityActivity, error codes
referenced via agentcapabilities constants instead of literal strings)
Full Go test suite now passes clean: 126 packages, 0 failures.
Add a registry-backed provider model for runtime metadata and chat-compatible transports.
Support Z.ai, Groq, Mistral, Cerebras, Together, and Fireworks through the shared chat-compatible client path while keeping native providers on their existing adapters. Expose provider metadata through settings and render the expanded provider set in the frontend.
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.
The install proof passed green this morning while every provisioned
workspace ran unlicensed; the entitlement gap survived because nothing
asserted lease health. The workspace proof now reads each workspace's
provisioned billing state and verifies the lease exactly the way a
release-build client runtime will: against the hosted entitlement trust
root, through the provider MSP license chain, requiring white_label.
A present license with an unverifiable lease fails the proof with the
specific reason; the environment-fallback plan (dev, no license) reports
entitlement_skipped_reason=no_provider_msp_license instead of asserting.
Proof and install-proof workspace output lines gain
entitlement_lease_checked/verified, entitlement_white_label, and the
skip reason.
Provider-hosted MSP client workspaces previously sat at Community tier
forever: the runtime refreshed leases against the built-in Pulse Cloud URL
(hibernated, 522) instead of the provider control plane, and release-build
images verify leases only against the embedded Pulse key, which an
operator-generated CP_TRIAL_ACTIVATION_PRIVATE_KEY can never satisfy.
- Inject PULSE_PRO_TRIAL_SIGNUP_URL=CP_BASE_URL into client containers so
lease refresh targets the provider control plane.
- Chain trust through the Pulse-signed provider MSP license: the license
binds the provider's lease signing public key
(entitlement_signing_public_key claim); the control plane embeds the
license in every lease (provider_license claim); release-build runtimes
verify embedded Pulse root -> provider license -> lease signature.
- Cap chain-verified leases at ProviderChainedLeaseCapabilities: MSP tier
plus white_label (branded per-client reports), minus Pulse-service-backed
relay/mobile_app/push_notifications, which otherwise loop doomed
registrations against Pulse's relay.
- Fail fast at control-plane startup when the license does not bind the
configured signing key, instead of provisioning silently unlicensed
client workspaces.
Verified live on a Colima harness: release-tagged tenant image with test
embedded root, Traefik TLS, full provider-msp proof, tenant reports
valid=true plan_version=msp_growth with white_label and zero relay
failures.
initDockerWithRetry and initKubernetesWithRetry were the same exponential
backoff loop (5s doubling to a 5m cap, cancel-aware wait, structured
retry logging) duplicated per module; dupl flagged the pair. Extract
initModuleWithRetry[T] and keep the two as thin wrappers so the
test-overridable newDockerAgent/newKubeAgent vars and call sites stay
unchanged. Per-module log strings are preserved verbatim.
Existing cmd/pulse-agent retry tests (success, cancel-before-connect,
cancel-during-wait, both modules) pass unchanged.
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.
Tighten v5-to-v6 upgrade safety, release installability, provider MSP mode handling, AI cost accounting, metrics flushing, and frontend guardrails for the v6.0.0 GA candidate.
Dead-code sweep. Functions flagged unreachable by golang.org/x/tools/cmd/deadcode
and confirmed unused across pulse, pulse-enterprise, pulse-pro and pulse-mobile by
adversarial cross-repo verification. Cross-module reachability was checked
explicitly (only pkg/ exported symbols are importable by other modules; internal/
packages and _test.go files are not). go build, go vet and test-compile all pass.