Commit graph

131 commits

Author SHA1 Message Date
rcourtman
d307fe8869 Preserve Patrol investigation evidence continuity 2026-07-15 15:43:18 +01:00
rcourtman
7da4890ce9 Add branch-coverage tests for AI tools command classifiers
New table-driven tests raise branch coverage on the timeout, curl/wget,
env and recovery-point canonicalization helpers in internal/ai/tools,
exercising previously uncovered flag-parsing arms, mutation-method
detection and numeric-detail type cases. Test-only, no source changes.
2026-07-15 15:02:19 +01:00
rcourtman
580093155f Resolve canonical Patrol discovery targets 2026-07-15 12:07:42 +01:00
rcourtman
a95edafcf1 Qualify model-led Patrol investigations 2026-07-15 10:32:16 +01:00
rcourtman
2774744b11 Preserve canonical resource IDs in Patrol queries 2026-07-15 09:16:39 +01:00
rcourtman
f03b0c01e7 Add branch-coverage tests for tools canonical query helpers
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
New white-box table tests over the pure resource-resolution helpers in
internal/ai/tools/current_resource.go, taking canonicalQueryTypeForResolvedResource,
canonicalQueryIDForResolvedResource and resolvedResourceKindMatchesLocation from
partial to full branch coverage. Every kind switch arm, the provider-uid then
resource-id then alias fallback chain, and the nil guards are pinned to exact
output.

One file, tests only, no source changes. Gates green with go test, gofmt and go
vet, plus an adversarial GLM review scoring three KEEP and zero reject.
2026-07-14 23:38:09 +01:00
rcourtman
280a44ef08 Add branch-coverage tests for AI patrol, qualification, tools and agentcapability helpers
New white-box table tests over previously-untested pure functions in
internal/ai (patrol run recency, patrol findings JSON validation),
internal/ai/tools (read-only violation hints, VM config parsing, node
target matching), internal/ai/qualification (compare predicate,
percentile and model summary, runner helpers), internal/ai/chat
(investigation run error, session compaction formatting) and
internal/agentcapabilities (path parameter substitution, markdown
helpers).

Twelve files, tests only, no source changes. All gates green with go
test, gofmt and go vet, plus an adversarial GLM review scoring twelve
KEEP and zero reject.
2026-07-14 22:33:40 +01:00
rcourtman
739b0c92d4 Align Patrol quiet-run tool contracts 2026-07-14 19:30:45 +01:00
rcourtman
9ea8cb2aa1 Clarify Patrol scope identities and finding reads 2026-07-14 19:23:36 +01:00
rcourtman
e008343b7a Align Docker host query schema and executor 2026-07-14 19:13:11 +01:00
rcourtman
f6a289f72d Make Patrol report confirmed health failures 2026-07-14 18:04:09 +01:00
rcourtman
3f45953866 Complete Patrol autonomous qualification loop 2026-07-14 15:35:48 +01:00
rcourtman
71a3b6ebcd Restore release-blocking backend contracts 2026-07-13 21:51:33 +01:00
rcourtman
f510b99095 Enforce the canonical mutation plane 2026-07-11 15:19:41 +01:00
rcourtman
e65bef8e84 Fix atomic action lifecycle replay 2026-07-11 14:02:32 +01:00
rcourtman
ffd1ea8127 Enforce Assistant read-only scope integrity 2026-07-11 12:43:55 +01:00
rcourtman
4edf5f8265 fix: close chat command authority boundary 2026-07-11 11:54:56 +01:00
rcourtman
956f5749d6 A.1: close the proposal-channel contract breaks before Commit B
Privacy: provider-streamed RawInput overrides on tool_progress events
are unredacted model output; for exposure-restricted tools they are now
discarded instead of replacing the projected form (the override was
reintroducing exactly the values the projector removed). Proven with a
progress event carrying a secret in the raw override.

Schema validation: proposal acceptance now validates through the
planner's exported canonical rules - FindCapability's exact-name
matching (the capture previously matched case-insensitively while
planning matched exactly) and ValidateParams for declared, required,
typed, enum, pattern, and malformed-schema cases - so a proposal that
validates is exactly a proposal the planner will accept. The
sensitive-parameter rejection remains a proposal-specific ratchet on
top.

Fail-closed ratchets: params and evidence identity are deep-cloned on
capture and again on outcome, so caller-side mutation after validation
can never alter the actionable proposal; fingerprint serialization
failures return errors rather than a shared sentinel value; an
investigation run refuses to start without finding and investigation
identity (before any provider call or session exists); and any run
error nils the proposal while preserving simultaneous proposal errors
via errors.Join - a non-nil proposal exists only from a completely
successful run.
2026-07-10 17:08:25 +01:00
rcourtman
9bc51a8a35 Build the narrow proposal channel: patrol_propose_action and its capture sink
Commit A of the coordinated proposal slice (pulse-internal; the
aicontracts interface replacement and enterprise migration follow as
one window because pulse-enterprise builds against this tree via a
replace directive).

patrol_propose_action is a side-effect-free, mutation-none capture
whose schema carries only resource_id, capability_name, params, and
reason. Registry policy rejects it outside the Patrol investigation
profile before the handler runs, and the same check keeps it out of
every other profile's projected manifest. Correlation identity
(proposal, finding, investigation, evidence) is injected from trusted
orchestration context through the request-local ProposalCapture sink,
which executor clones share so one run has exactly one capture.

Tool calls now carry an explicit invocation envelope (tool-use ID,
name, arguments) through ExecuteInvocation; the ID rides the context
because per-turn tool calls execute concurrently. The sink keys on call
identity plus payload fingerprint: idempotent replay re-succeeds, the
same ID with a different payload latches a terminal integrity error,
and a second distinct valid proposal latches terminal ambiguity - both
terminal states invalidate the captured proposal, since concurrency
makes first-wins nondeterministic. Proposals count only after catalog
validation: advertised capability, declared/required/enum parameters,
and sensitive parameters rejected before success with no value echo in
any output.

ExecuteInvestigationStream returns proposal cardinality as a structured
result with typed errors for ambiguity, integrity violations, and the
failed-attempts-only case (never collapsed into the valid zero-proposal
conclusion); ListInvestigationTools projects through the identical
profile path. Proposal parameter values exist only transiently for
provider continuation and validation: the canonical exposure projector
redacts them from the durable transcript and every
tool_start/progress/end stream event, proven end-to-end with a scripted
provider run that also verifies the provider continuation keeps raw
values.

Essential proof included: two concurrent valid proposal calls produce
ErrProposalAmbiguous and a nil proposal regardless of execution order.
2026-07-10 16:47:23 +01:00
rcourtman
d274c93c58 Close the two profile residuals before the proposal slice
The blocked-question branch persisted its refusal only to provider
context, so the durable transcript retained a question call with no
matching result. The refusal is now appended to the transcript as well,
and the previously deferred end-to-end loop proof drives a scripted
provider turn carrying pulse_question plus a sibling pulse_query under
the detection profile, asserting: no waiting-for-answer event, the
persisted call/result pairing, sibling execution, and continued
inference to the model's final answer.

Execution profiles gain the closed-vocabulary ratchet: Valid() pins the
known set, ApplyExecutionProfile and the loop setter reject unknown
values outright, and NonInteractive() fails closed so an unclassifiable
posture can never inherit interactive permissions.
2026-07-10 16:14:38 +01:00
rcourtman
97a30b0600 Introduce the Patrol execution profiles: detection and investigation
Execution posture is now profile-owned through the core-only,
never-serialized tools.ExecutionProfile. Both Patrol profiles are
non-interactive, deny infrastructure mutations, and clear any inherited
autonomous mode. Detection restricts pulse-state mutations to an
explicit allowlist of the finding lifecycle tools - a blanket
pulse-state allowance would also permit alert dismissal and knowledge
writes - while investigation denies all pulse-state mutations, keeping
it structurally read-only. InvocationPolicy gains the allowlist and
Allows() is now tool-name-aware.

Chat turns build ONE effective request executor (control level,
autonomy, profile, resolved context) BEFORE provider projection and
clone that executor per provider attempt, reversing the previous
project-from-base-then-clone order so the offered schema and the
runtime boundary always agree. Scheduled Patrol (ExecutePatrolStream)
now runs under the detection profile instead of autonomous mode -
closing its direct view of Docker/Kubernetes mutation subactions - and
ListAvailableTools projects through the identical path.
toolsForExecutionMode's mode booleans are replaced by the profile on
the executor itself.

Non-interactive profiles independently hide pulse_question from the
manifest AND runtime-block it before the interactive-call-set special
case: a fabricated question call returns a non-interactive error
without emitting a waiting event, and sibling tool calls from the same
provider turn keep processing. Approval waits never block for
non-interactive profiles (they queue), and the tool-only-turn wrap-up
guardrail is interactive-profile-owned instead of keyed on autonomy.
The system prompt describes detection and investigation modes directly
rather than claiming controlled or autonomous execution; the
investigation prompt directs the model toward typed action proposals.

Proofs: detection allowlist enforcement (alerts resolve and knowledge
remember blocked, finding tools allowed, projection agrees),
investigation structural read-only (patrol mutation tools dropped from
the manifest and blocked at runtime), profile clone isolation, profile
prompt modes, and question-tool hiding across profiles.
2026-07-10 15:53:38 +01:00
rcourtman
8c8affc041 Split tool registration authority and make the registry append-only
Review of 3073a5061 found the remaining registration hole: Register
rejected canonical descriptor overrides but still accepted a canonical
NAME with a nil override, inheriting the canonical descriptor while
replacing the governed handler in the map - an extension could re-register
pulse_read and bypass its execution-intent enforcement.

Registration authority is now split. registerBuiltin is the unexported
construction-time path for canonical Pulse tools: shared descriptor
mandatory, overrides rejected. RegisterExtension - the only path exposed
through PulseToolExecutor.RegisterTool - rejects every canonical tool
name outright and requires the extension to declare its own descriptor.
Both paths are append-only: a name registers exactly once, so no later
registration can swap out an already-governed handler.

Proofs cover the exact bypass (canonical name, nil override), extension
and builtin duplicate rejection, builtin override rejection, and
descriptor-less extension rejection. Tests that previously swapped
handlers by re-registering now use fresh executors per scenario.
Contract prose and source pins updated.
2026-07-10 15:16:06 +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
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
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
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
48cf663237 Strengthen evidence and impact guidance in patrol finding tool
The patrol_report_finding tool descriptions for evidence, impact, and
recommendation were too permissive, causing the LLM to frequently omit
them. Future patrol runs will produce better-scaffolded findings.

1. Rewrite tool descriptions to emphasize evidence as a trust anchor
   that should always be included, and impact as expected whenever the
   data supports it.
2. Add 'Authoring Evidence' section to the patrol system prompt with
   concrete examples, matching the existing 'Authoring Impact' section.
3. Add test verifying trust scaffolding guidance is in the system prompt.

Addresses checklist L48-50 (trust scaffolding).
2026-06-27 14:53:48 +01:00
rcourtman
ee8a24e14a backend and governance: MCP contract, agent capabilities, API, and release-control
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.
2026-06-23 17:26:15 +01:00
rcourtman
6725d6a784 Add governed Docker and Podman lifecycle actions
Refs #1034
2026-06-12 21:17:58 +01:00
rcourtman
8439ce6e6b Dedupe AI runtime guest-family, tool-pipeline, and provider clones
Clears the ten dupl pairs across internal/ai:

- patrol_intelligence.go, tools_query.go, tools_storage.go: VM and LXC
  system-container paths collapse into generics over read-state view
  method subsets (gatherGuestIntelligenceFromViews, canonicalGuestGetResult
  + guestViewGetResult, addCanonicalGuestSearchMatches +
  addGuestViewSearchMatches, appendGuestDiskSummaries).
- tools_file.go: append/write share executeFileMutation driven by
  fileMutationSpec (approval-command text, shell redirect, verification
  strategy stay per-action and verbatim).
- tools_kubernetes.go: deployment restart / pod delete share
  executeKubernetesResourceAction driven by kubernetesResourceAction.
- providers/anthropic.go + anthropic_oauth.go: message conversion shared
  via convertMessagesToAnthropic (the OAuth copy was annotated 'same as
  regular client').
- memory/changes.go + memory/remediation.go: history loading shared via
  the generic loadMemoryHistory in memory/paths.go (10 MiB cap, sort,
  missing-file semantics preserved via a found flag).
- findings.go and unified/alerts.go: Finding/findingJSON and
  UnifiedFinding/unifiedFindingJSON are deliberate marshal-mirror twins
  (AlertIdentifier json:"-" vs alert_identifier round-trip); merging
  would break every public literal. Suppressed with nolint:dupl and
  enforced instead by new reflect-based mirror-sync tests.

Contract Extension Points name the shared helpers and the mirror
invariant. Full ./internal/ai/... test tree passes.
2026-06-10 09:38:52 +01:00
rcourtman
ab9552716c Auto-clear event/persistent Patrol findings on affirmative deterministic verification
The finding lifecycle was asymmetric around the deterministic-resolve
gate: the gate correctly blocks LLM resolves of event/persistent
findings (backup, reliability, security, general) when the verifier
still detects the signal, but nothing ever cleared such a finding when
the underlying issue WAS fixed — absence-based stale auto-resolve only
covers performance/capacity, so a fixed backup stayed an active finding
indefinitely unless the LLM happened to call patrol_resolve_finding.

reconcileStaleFindings now runs the deterministic verifier for seeded,
unreported event/persistent findings whose key has one, and resolves
ONLY on an affirmative "signal gone" verification. Still-present or
inconclusive results fail closed (same standard as the resolve gate);
verifications are capped per run (3) with deferred candidates logged
and retried next run. Test seam: PatrolService.verifyFixResolvedFn.

Two enabling repairs found during verification:
- hasDeterministicVerifierForKey listed 2 of the 7 keys the
  verifyFixDeterministically dispatch handles, so the LLM-resolve gate
  silently skipped existing verifiers for backup-stale and
  guest-unreachable findings. Now aligned; documented as the single
  source of truth for both consumers.
- The finding-key vocabulary was forked: the patrol_report_finding tool
  suggested keys (high-cpu, ...) the verifier switch never matched, so
  verification rarely engaged for new findings. normalizeFindingKey now
  aliases unambiguous directional synonyms onto the canonical verifier
  vocabulary (high-cpu -> cpu-high etc.; pbs-job-failed and node-offline
  deliberately NOT aliased — different resource models), and the tool
  description teaches the canonical keys.

NOT changed: the synthetic ai-patrol-error finding — verification showed
the reported "accumulation" is a non-problem (deterministic ID, store
merges repeats as heartbeats, successful runs auto-resolve it).

Teeth: 8 new tests in patrol_reconcile_test.go including the
idempotence invariant (repeat reconcile over unchanged state produces
zero resolves and zero lifecycle growth) and the cap-defers case.
Contract: verified stale-resolve clause added beside the
deterministic-resolve-gate in ai-runtime.md. Full internal/ai tree green.
2026-06-09 21:47:14 +01:00
rcourtman
a24e944f1b Carry per-fact discovery provenance through to the model
buildDiscoveryToolResponse rebuilt each fact as {category,key,value} and dropped
the Source (the command/origin that produced it) and Confidence the DiscoveryFact
already carries — so the model could state facts it couldn't attribute or weight.
Serialize both per fact (omitted when empty) so the Assistant can report "Debian
12, per /etc/os-release" instead of a bare, untraceable claim.

This is the first piece of the provenance/trust groundwork: make the model
*receive* the source metadata it needs to cite. Surfacing it to the user (UI) and
push-path discovery freshness are follow-ups.

- Test: TestBuildDiscoveryToolResponse_IncludesFactProvenance (source+confidence
  present when set, omitted when empty).
- Contract: ai-runtime section 8 documents the pulse_discovery fact provenance.
2026-06-09 10:35:29 +01:00
rcourtman
accebfb305 Infer resource_type from a canonical handle in pulse_query get
In the trace for 'hows esphome', the model called get with the canonical handle
'system-container-599a2e3...' as resource_id and no resource_type, and it failed
twice with 'resource_type is required' before recovering with 'get 102'. The user
sees those failed tool calls in the chat.

A canonical handle already encodes the type (unifiedresources/ids.go builds ids
as '<type>-<hash>'), so executeGetResource now infers resource_type from the
handle when it's omitted, via resourceTypeFromCanonicalID (the trailing hex hash
segment is unambiguous since no type word is all-hex). A bare numeric VMID still
requires an explicit type. Test TestResourceTypeFromCanonicalID covers it; full
internal/ai/tools green.
2026-06-08 15:49:09 +01:00
rcourtman
ddc480ff3c Recalibrate resource sensitivity: ordinary workloads aren't secret
User on a cloud model saw 'redacted by policy' everywhere. Root cause: the
default classification (classifyResourceSensitivity) treated every VM, container,
pod, k8s workload, and docker service as 'Sensitive', which redacts their
hostname/IP/alias/path for cloud models. For Pulse's homelab/SMB audience that
crippled the cloud Assistant — a workload named 'grafana' isn't a secret, and its
private LAN IP isn't either.

Recalibrate: compute workloads classify as 'Internal' (cloud-summary, no
redaction) so cloud models can see their names/IPs. Escalation to
Sensitive/Restricted is by tag (database, backup, customer-data, secret, ...) or
by genuinely sensitive TYPE: storage/data-at-rest (storage, PBS, Ceph,
physical-disk, network-share, network, k8s PV/PVC/StorageClass), configuration
(docker-config, k8s-configmap), and security (k8s RBAC, secrets, PMG). Secrets
and PMG stay Restricted; the tag-based escalation is unchanged.

Tests: new TestRefreshPolicyMetadata_PlainComputeWorkloadsAreInternalNotRedacted
+ TestComputeWorkloadPolicyIsInternalUnlessEscalated lock it in. ~13 AI-subsystem
redaction tests that assumed plain compute = Sensitive updated to tag their
fixtures so they still exercise redaction on a genuinely-sensitive resource (no
assertions weakened). Contract: unified-resources Extension Points documents the
recalibrated classification. internal/ai/... + internal/unifiedresources/... green.
2026-06-08 15:21:51 +01:00
rcourtman
d7f0de0ba2 Add data directories to remediation context
Some checks failed
Build and Test / Secret Scan (push) Has been cancelled
Build and Test / Frontend & Backend (push) Has been cancelled
FormatForRemediation surfaced config and log paths but not data paths,
while FormatForAIContext (chat) does. For remediation those matter —
backup targets, disk-full triage, restore points (e.g. a database data
dir or HA's /config/.storage). Add a Data Directories section, matching
the chat pack. Extends the remediation test to assert a data path
reaches it; teeth-checked. Full servicediscovery package green.
2026-06-07 13:36:09 +01:00
rcourtman
7c583c0547 Answer Assistant inventory counts locally 2026-06-06 01:47:05 +01:00
rcourtman
774d49e2a4 Fix Assistant current-resource tool routing 2026-06-05 19:10:46 +01:00
rcourtman
05d00a65a6 Hide raw Assistant tool-call output 2026-06-05 14:05:38 +01:00
rcourtman
29ac5945bc Fix resource-context Assistant tool targeting 2026-06-04 19:18:08 +01:00
rcourtman
3f8525a7c2 Implement resource-aware Assistant context 2026-06-04 16:36:04 +01:00
rcourtman
faefe6edc8 Remove 198 unreachable Go functions
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.
2026-06-03 12:29:37 +01:00
rcourtman
d3934e19e3 Harden repository advisory boundaries
Some checks are pending
Build and Test / Secret Scan (push) Waiting to run
Build and Test / Frontend & Backend (push) Waiting to run
2026-05-24 08:15:29 +01:00
rcourtman
bc2adac8b5 Stabilize dev runtime and complete PMG read-state migration
Refs architecture post-RC canonicalization follow-up.
2026-05-21 15:15:23 +01:00
rcourtman
9c55c341e2 Implement Discovery observed context UX 2026-05-20 13:21:55 +01:00
rcourtman
6aad1118cd Align discovery with tool-led AI runtime
- add a forced run action to pulse_discovery for known resources

- make discovery progress describe model-backed evidence analysis rather than a live Assistant chat

- keep shared select hydration stable for persisted discovery intervals
2026-05-15 23:05:36 +01:00
rcourtman
0561a1b5fa Remove Assistant and Patrol tool-routing heuristics 2026-05-15 14:51:15 +01:00
rcourtman
895b78bd89 Make Assistant tool choice model-owned 2026-05-15 11:27:15 +01:00
rcourtman
f024d3b560 Align action audit verification projection 2026-05-13 18:36:00 +01:00
rcourtman
fbbf1daf4c Harden action start refusal audit 2026-05-13 14:25:04 +01:00