Commit graph

1095 commits

Author SHA1 Message Date
rcourtman
e1f33c1bad Fix release integration gate target 2026-07-22 18:15:19 +01:00
rcourtman
674364c749 Record v6.1.0 unsigned Windows exception 2026-07-22 15:37:48 +01:00
rcourtman
984ca06918 Prepare v6.1.0 stable release
Some checks are pending
Build and Test / Secret Scan (push) Waiting to run
Build and Test / Frontend & Backend (push) Waiting to run
Canonical Governance / governance (push) Waiting to run
Helm CI / Lint and Render Chart (push) Waiting to run
Patrol Qualification Regression / Catalog, scorer, and replay regression (push) Waiting to run
Core E2E Tests / Playwright Core E2E (shard 1/8) (push) Waiting to run
Core E2E Tests / Playwright Core E2E (shard 2/8) (push) Waiting to run
Core E2E Tests / Playwright Core E2E (shard 3/8) (push) Waiting to run
Core E2E Tests / Playwright Core E2E (shard 4/8) (push) Waiting to run
Core E2E Tests / Playwright Core E2E (shard 5/8) (push) Waiting to run
Core E2E Tests / Playwright Core E2E (shard 6/8) (push) Waiting to run
Core E2E Tests / Playwright Core E2E (shard 7/8) (push) Waiting to run
Core E2E Tests / Playwright Core E2E (shard 8/8) (push) Waiting to run
Core E2E Tests / Agent registration lifecycle (push) Waiting to run
Core E2E Tests / E2E verdict (push) Blocked by required conditions
Unified Agent Native Verification / Linux ARM64 (push) Waiting to run
Unified Agent Native Verification / Linux x64 (push) Waiting to run
Unified Agent Native Verification / Windows x64 (push) Waiting to run
Unified Agent Native Verification / macOS ARM64 (push) Waiting to run
Unified Agent Native Verification / macOS Intel (push) Waiting to run
Unified Agent Native Verification / FreeBSD cross-build contract (push) Waiting to run
2026-07-22 15:05:37 +01:00
rcourtman
e1b38a2fe6 Reframe commercial transition proof as activation gate 2026-07-22 14:35:28 +01:00
rcourtman
9e39ffc920 Enforce Pulse Mobile compatibility from the core contract
Contract-Neutral: mobile compatibility generation does not change agent lifecycle or storage recovery contracts
2026-07-22 13:41:09 +01:00
rcourtman
fb3d60e475 Document What's New dialog in rc.5 2026-07-22 11:53:50 +01:00
rcourtman
67d7a1b454 Delegate candidate artifact read permission 2026-07-22 11:41:10 +01:00
rcourtman
d3070ce1cb Prepare v6.1.0-rc.5 release 2026-07-22 11:28:45 +01:00
rcourtman
f8c5c55d11 Let --report-ip lead host identity and pass it through the installer
An explicit --report-ip is the user naming the primary address on a
multi-NIC host, but identityFromHost appended it after the auto-detected
interface addresses while every consumer of ResourceIdentity.IPAddresses
treats the first entry as primary, so the override never changed what
the Machines table displayed. Prepend it instead.

The install script also rejected --report-ip as an unknown argument even
though the agent supports the flag, forcing hand edits to the service
unit that a later --update run would drop. Accept the flag, render it
into the service ExecStart, persist it in connection state, and
recognise it during saved-state and arg-stream recovery so updates
preserve it.

Refs #829

Contract-Neutral: behavioral fix: user-specified report-ip leads host identity addresses and the installer passes --report-ip through; no public contract change (#829)
2026-07-22 00:49:43 +01:00
rcourtman
43bb95a406 Integrate SignPath Windows release signing 2026-07-21 22:34:00 +01:00
rcourtman
ea664843ba Align prerelease dry-run signing requirements
Keep macOS notarization mandatory for every release candidate while requiring Windows Authenticode only for stable promotion, matching the publish workflow and RC4 release packet.
2026-07-21 09:17:39 +01:00
rcourtman
ed84c92da3 Add Patrol model readiness advisor 2026-07-20 20:45:39 +01:00
rcourtman
057cf74629 Add alert intent policies and delivery receipts 2026-07-20 20:27:39 +01:00
rcourtman
e23f19459d Add Agent Doctor fleet diagnostics workflow 2026-07-20 19:53:34 +01:00
rcourtman
cee4f64614 Complete trust-gate governance contracts 2026-07-20 17:43:01 +01:00
rcourtman
b6a74576bc Integrate trust-gate reliability fixes 2026-07-20 16:03:29 +01:00
rcourtman
d89e3e3163 Add staged-file prettier formatting to pre-commit
New scripts/release_control/format_staged_frontend.py mirrors the staged
Go formatter: formats staged frontend-modern/src {ts,tsx,css,json} blobs
through prettier --stdin-filepath, writes results back to the index
directly (no broad restaging), syncs the worktree only when it matches
the previously staged content, and iterates to a fixed point to absorb
prettier's occasional non-idempotence. Skips gracefully when prettier is
not installed (fresh clones, linked worktrees without node_modules).
Wired into .husky/pre-commit after the Go formatter, with unit tests in
the governance battery, a README note, and a .gitignore allowlist entry.
With the one-time sweep in the previous commits, prettier drift can no
longer re-accumulate and make format stays clean on a clean tree.
2026-07-20 10:44:36 +01:00
rcourtman
3f405af0c6 Align Windows agent state with the installer
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
2026-07-19 19:41:57 +01:00
rcourtman
7b65e9a7b1 Keep Windows installer compatible with TLS 1.2 runtimes
Contract-Neutral: Legacy Windows PowerShell TLS enum compatibility preserves the existing installer transport contract without changing its public interface
2026-07-19 19:36:15 +01:00
rcourtman
52ed884640 Prepare v6.1.0-rc.4 release 2026-07-19 15:52:42 +01:00
rcourtman
4b066a0f1e Add report-only Unified Agent observer destinations 2026-07-19 15:30:34 +01:00
rcourtman
83672a00a8 Harden Operational Trust rollout contracts 2026-07-19 15:11:02 +01:00
rcourtman
69533c7afb Build canonical availability resource facets 2026-07-19 15:11:01 +01:00
rcourtman
07a4d05ca3 Build canonical Patrol attention workbench 2026-07-19 15:11:01 +01:00
rcourtman
93dbdd91cf Build canonical protection posture 2026-07-19 15:10:38 +01:00
rcourtman
cf0486492e Build operational trust lifecycle foundation 2026-07-19 15:10:38 +01:00
rcourtman
b9eb7c3241 Prepare v6.1.0-rc.3 release 2026-07-18 16:56:00 +01:00
rcourtman
a3f8b18bfb feat(ai): make pulse_summarize self-target and forbid identifier elicitation
A fresh install's natural first question ("how is my machine doing?",
observed live with Ollama qwen3:8b) made the model call pulse_summarize
action=fleet without resource_ids; the tool errored and the agentic loop
surfaced a structured question asking a first-run user for "the
comma-separated list of resource IDs" — pure jargon, funnel-killing
(GitHub discussion #1042). Two root fixes:

pulse_summarize self-targets. action=fleet with resource_ids omitted now
enumerates the known fleet from the executor's unified resource provider
(infrastructure parents first, then guests, then storage; deduped;
bounded at the existing 50-resource cap with a truncation note). Both
modes resolve what models actually pass — canonical unified IDs and
unambiguous names — onto the reporting request shape the way the API
path's resolveReportSubject does: the canonical ID stays ResourceID
(findings/recovery keying) and the resolved metrics target rides
MetricsResourceID, so store queries find data instead of silently
returning zero points. Reporting types are classified from the unified
resource (agent-backed hosts "agent", pure Proxmox nodes "node" — the
documented target-type exception — Docker hosts "docker-host");
resource_type becomes an optional filter/default. Remaining error paths
instruct the model to enumerate or retry and forbid asking the operator
for resource IDs.

Ask-user policy covers tool-argument recovery. The system prompt's
resolve-before-asking section (945e2198's target policy) now extends to
failed tool calls: missing/invalid arguments are self-recovered
(enumerate, retry), and internal identifiers are never valid questions.
The pulse_question description carries the same prohibition so the
structured clarification surface cannot become an identifier elicitation
channel on small local models.

ai-runtime and api-contracts contracts updated in-commit (the
subsystem_lookup line pin follows the api-contracts insertion); full
./internal/ai/... and ./internal/agentcapabilities/... suites green.
2026-07-17 23:55:38 +01:00
rcourtman
e4e43c3f11 feat(telemetry): attribute approved-action failures to cause buckets
The 30d window said 18 of 26 approved action attempts failed but
carried zero cause information. Every approved attempt that is not a
verified success now lands in exactly one content-free bucket:

- pre_dispatch: terminally refused before dispatch (plan drift, expiry,
  emergency stop, policy authorization)
- execution: dispatched execution failed or ended inconclusive
- unverified: execution succeeded but outcome verification was not
  confirmed (reads as success in the UI, counted as failure here)
- stuck_executing: still executing over an hour after dispatch

plus one sanitized machine reason code for the most recent failure.
Successes, failure buckets, and recent in-flight attempts partition the
attempt count, so the next window explains its own gap.

RefuseActionExecution now persists the specific refusal code
(plan_drift, action_plan_expired, ...) as the canonical execution
reason code instead of the generic pre_dispatch_refused, so audit truth
and telemetry distinguish refusal causes without message parsing.

Privacy disclosures updated in both PRIVACY.md copies; the adoption
report script surfaces the new counters.

Contract-Neutral: additive content-free telemetry counters + specific pre-dispatch refusal reason codes; privacy disclosures updated in-commit per security-privacy extension point
2026-07-17 23:39:34 +01:00
rcourtman
1e267566af fix(telemetry-report): stream remote rows as JSON-lines to avoid droplet OOM
fetch_rows_remote loaded every row in the window into a list and
json.dumps'd the whole payload on the remote side; on the 1GB
pulse-license droplet a 30-day window over a 104k-row DB got the
process OOM-killed. The remote helper now prints a db_stats header
line and then one JSON line per row while iterating the cursor, so
it never holds the full result set. Local parsing updated to match
and covered by unit tests; fetch_rows_local is unchanged.

Verified end to end against the droplet with --since-days 30.

Contract-Neutral: fetch_rows_remote OOM fix: stream JSON-lines instead of one giant payload; no contract delta, report output unchanged
2026-07-17 23:31:47 +01:00
rcourtman
6351fd6526 fix(updates): select releases by highest version, not GitHub list order
Production telemetry (7d, 2026-07-17) shows a persistent tail of live
installs on 6.0.0-rc.x and 6.0.3/6.0.4 while 6.0.5 has been stable since
2026-07-09. Root cause for the reachable stranding class: release
selection trusted GitHub's created_at ordering, and this repo interleaves
v5-line maintenance releases with v6 releases (v5.1.36 was created the
day before v6.0.5). Whenever a v5.1.x release is the most recently
created stable:

- stable channel (getLatestReleaseForChannel) returned the first
  non-prerelease in list order -> "no update" for every v6 install
  until the next v6 release ships
- the RSS rate-limit fallback returned the first feed entry matching the
  channel, same failure, both channels
- the unattended updater trusted /releases/latest, which GitHub defines
  as most-recently-created stable -> timer no-ops in the same window

All three now select the highest eligible version: the API path tracks
newest stable + newest prerelease in one pass (prerelease = GitHub flag
OR tag shape), the feed fallback picks the max matching the channel, and
pulse-auto-update.sh scans /releases pairing each tag with its own
draft/prerelease flags before the fail-closed shape filter, keeping
/releases/latest as fallback. helm-chart-* tags fail semver parsing and
are dropped everywhere.

Verified not broken (pinned by new tests): rc-channel installs are
offered the newer stable (6.0.0-rc.6 -> 6.0.5) and move onto the next rc
line when one opens (-> 6.1.0-rc.2); the auto-update prerelease filter
(fail-closed is_prerelease_tag, metadata-flag refusal) still refuses
prerelease targets on stable. Go tests fail on the pre-fix code
(returned 5.1.37 / "no update").

Contract: deployment-installability now pins version-max release
selection with proofs in manager_stranded_upgrade_test.go and
pulse_auto_update_test.go.
2026-07-17 23:21:49 +01:00
rcourtman
0f15de6a4a fix(telemetry): suppress outbound pings while mock mode is enabled
Some checks are pending
Build and Test / Secret Scan (push) Waiting to run
Build and Test / Frontend & Backend (push) Waiting to run
Canonical Governance / governance (push) Waiting to run
Patrol Qualification Regression / Catalog, scorer, and replay regression (push) Waiting to run
Core E2E Tests / Playwright Core E2E (shard 1/4) (push) Waiting to run
Core E2E Tests / Playwright Core E2E (shard 2/4) (push) Waiting to run
Core E2E Tests / Playwright Core E2E (shard 3/4) (push) Waiting to run
Core E2E Tests / Playwright Core E2E (shard 4/4) (push) Waiting to run
Core E2E Tests / E2E verdict (push) Blocked by required conditions
Unified Agent Native Verification / Linux ARM64 (push) Waiting to run
Unified Agent Native Verification / Linux x64 (push) Waiting to run
Unified Agent Native Verification / Windows x64 (push) Waiting to run
Unified Agent Native Verification / macOS ARM64 (push) Waiting to run
Unified Agent Native Verification / macOS Intel (push) Waiting to run
Unified Agent Native Verification / FreeBSD cross-build contract (push) Waiting to run
Every mock-mode boot (e2e/CI managed backends, qual runs, demo containers)
was sending startup/heartbeat pings describing the synthetic fixture fleet,
registering as real installs in the license-server telemetry DB and
dominating the VMware/Kubernetes/TrueNAS adoption aggregates (346 of 6,225
weekly-active installs matched the mock signature, incl. 120 of 227 on
6.1.0-rc.1).

- internal/telemetry: route startup and heartbeat sends through sendEvent,
  which drops the ping while mock.IsMockEnabled() is true; checked per event
  so runtime mock toggles take effect immediately. Disclosed in the package
  doc and both PRIVACY.md copies.
- scripts/telemetry_adoption_report.py: exclude historical mock-fleet rows
  (kubernetes_pods = 120×N with vmware_hosts = 7×N, the internal/mock
  fixture template) by default, report the excluded row/install counts, and
  add --include-mock-fleet to audit them. Versions through 6.1.0-rc.2 keep
  pinging the mock fleet until upgraded, so report-side filtering stays
  necessary.
- security-privacy contract: record the mock-mode suppression boundary and
  the report-side mock-fleet exclusion as subsystem invariants.
- Tests: Go sendEvent suppression/sending pair against an httptest endpoint;
  Python signature-matcher and summarize_rows exclusion coverage.
2026-07-17 18:19:18 +01:00
rcourtman
971520a8e5 fix(release-control): scrub hook git env in remaining scratch-repo git users
The core.bare=true corruption of the shared repository recurred on
2026-07-17: a script that runs scratch git commands while the pre-commit
environment from a linked worktree (absolute GIT_DIR) is still exported
re-initializes the REAL repository as bare. a0fda6b26 fixed four helper
test files but missed two spots in scripts/release_control/internal:

- verify_commit_slice_test.py's git() helper only popped GIT_INDEX_FILE,
  so its scratch 'git init' calls re-init the real repo when GIT_DIR is
  inherited. It now scrubs via the shared repo_file_io.strip_local_git_env.
- verify_commit_slice.py's production git_env() kept the inherited hook
  env even when unit tests patch REPO_ROOT to a temporary repository,
  pointing git plumbing (including index writes) at the wrong repo. It
  now scrubs in the test-patched branch only, matching format_staged_go.

Regression teeth:
- verify_commit_slice_test.py gains a canary test that exports the real
  hook env shape (absolute GIT_DIR + GIT_INDEX_FILE, no GIT_WORK_TREE —
  with GIT_WORK_TREE set the corruption does not reproduce) against a
  scratch repo + linked worktree and asserts core.bare stays false.
- repo_file_io_test.py (runs in the pre-commit battery) gains a static
  guard failing any release-control *_test.py that runs scratch
  'git init' without referencing strip_local_git_env.
- The six hand-rolled 4-var pop loops from a0fda6b26 migrate to the
  shared strip_local_git_env helper so the guard enforces one pattern.

Verified: full release-control battery green; every touched test file
also green with GIT_DIR/GIT_INDEX_FILE pointed at a canary repo's linked
worktree, canary config and status intact afterward.
2026-07-17 16:39:17 +01:00
rcourtman
ef50c10a9a fix(release-control): scrub inherited git env in hook scratch-repo helpers
Pre-commit runs from a linked git worktree export an absolute GIT_DIR.
The release-control test helpers and the test-patched branch of
git_env() only removed GIT_INDEX_FILE, so their scratch-repo commands
targeted the REAL repository: 'git init' in a tempdir re-initialized it
with core.bare=true (breaking git status/commit for every checkout and
worktree) and the subsequent scratch 'git add' failed the hook.

Scrub GIT_DIR, GIT_WORK_TREE, GIT_INDEX_FILE, and GIT_COMMON_DIR in:
- git_env() of format_staged_go.py, governance_stage_guard.py, and
  subsystem_contracts.py (test-patched branch only; production hook
  behavior unchanged)
- the scratch-repo git() helpers in format_staged_go_test.py,
  governance_stage_guard_test.py, readiness_assertion_guard_test.py,
  subsystem_contracts_test.py

Matches the pattern contract_audit_test.py and status_audit_test.py
already used. Verified the full hook test battery passes with
GIT_DIR/GIT_WORK_TREE/GIT_INDEX_FILE pointed at a canary repo, which
stays un-corrupted.
2026-07-17 15:52:11 +01:00
rcourtman
eb5a477dc9 fix(governance): make commit-time guard verdicts survive to CI
Canonical Governance failed on nearly every push today (6 of the last 8
commits) because the local pre-commit hook and the CI workflow disagreed
about the canonical completion guard in two ways.

Frontend-only commits skipped the guard entirely: the hook gated ALL
governance checks behind governance-path detection as a perf
optimization, but subsystem contracts name canonical frontend files, so
those commits landed locally and failed the same guard in CI. The hook
now always runs the (cheap) completion guard; only the multi-minute Go
test and audit battery stays path-gated.

Contract-neutral bypasses did not travel: PULSE_ALLOW_CONTRACT_NEUTRAL_COMMIT
authorized the commit in the local shell and vanished, so CI re-ran the
guard without it and went red. A new prepare-commit-msg hook records the
reason as a Contract-Neutral trailer in the commit message, and the
workflow now evaluates the guard per commit (each commit's file list
against its own parent), honoring the trailer exactly as the commit-time
hook honored the env var. Per-commit evaluation also stops a compliant
commit from being blamed for range-mates.

The new hook is registered as a worktree-sensitive governance file in
governance_stage_guard.py with a matching unit test assert.
2026-07-17 14:35:34 +01:00
rcourtman
7ab605452d fix(release): fail closed on malformed release notes 2026-07-17 11:12:22 +01:00
rcourtman
a857c7e783 Prepare v6.1.0-rc.2 release 2026-07-16 23:45:31 +01:00
rcourtman
afbcd3023c Bound automatic Patrol inference allowances 2026-07-16 19:55:44 +01:00
rcourtman
676117bf27 Separate Patrol evidence and model-turn budgets 2026-07-16 17:50:08 +01:00
rcourtman
b91ba759c9 Route Claude native tool intent through Pulse 2026-07-16 16:09:48 +01:00
rcourtman
4421945e9b Normalize v prefix in install.ps1 agent version check
The Windows installer compared the downloaded agent's --version output
(v6.0.5) against the server's /api/version value (6.0.5) literally, so
every matching install still warned about a version mismatch (#1527).
Strip the leading v from both sides before comparing, matching what
install.sh already does.
2026-07-16 09:16:51 +01:00
rcourtman
a8e42fc7fe Preserve coding-plan provenance in qualification 2026-07-15 00:06:56 +01:00
rcourtman
89a7b88093 Distinguish subscription allowances from API spend 2026-07-14 23:12:37 +01:00
rcourtman
917a9e5421 Add local subscription model routes 2026-07-14 22:57:38 +01:00
rcourtman
538c1baaef Add community Patrol qualification exports 2026-07-14 22:08:32 +01:00
rcourtman
ae4162f8f2 Enforce installation-scoped license invalidation 2026-07-14 16:18:21 +01:00
rcourtman
3f45953866 Complete Patrol autonomous qualification loop 2026-07-14 15:35:48 +01:00
rcourtman
f5aeac590b Survive NAS installer environments without od and with Synology systemd
Two agent-install failures from stock NAS shells: QNAP ships no od, so
the ELF header sniff read empty and rejected a valid download (issue
#1572); Synology DSM 7's patched systemd cannot apply the unit's
sandbox directives and killed the service with
status=227/NO_NEW_PRIVILEGES before exec (issue #1578). The header
sniff now falls back od -> hexdump -> xxd and skips with a warning when
none exist (checksum verification still guards integrity), and the
systemd unit omits the sandbox hardening block on DSM.
2026-07-14 14:47:45 +01:00
rcourtman
f50bcce2dc Govern Relay commercial invalidation 2026-07-14 14:38:58 +01:00
rcourtman
848b4d5038 Preserve customer data across plan downgrades 2026-07-14 11:47:25 +01:00