mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 10:35:51 +00:00
67c2534c08
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.