syntax = "proto3";

package heddle.v1;

import "google/protobuf/timestamp.proto";
import "heddle/v1/common.proto";

// Programmable hooks. The service manages hook registrations and
// brokers live event delivery: clients subscribe to the
// out-of-process event stream, the server emits events from the
// capture/merge code paths (correlated by `hook_event_id`), and hook
// implementations call back via `RespondToHook` to deliver the hook's
// JSON-encoded reply or veto.
service HookService {
  rpc RegisterHook(RegisterHookRequest) returns (Hook);
  rpc DeregisterHook(DeregisterHookRequest) returns (DeleteResponse);
  rpc ListHooks(ListHooksRequest) returns (ListHooksResponse);
  rpc GetHookEventSchema(GetHookEventSchemaRequest) returns (GetHookEventSchemaResponse);
  // Server-streamed live event delivery. Each event carries an
  // opaque `hook_event_id` correlator that the subscriber echoes
  // back to `RespondToHook`. Read-shaped — no idempotency.
  rpc SubscribeHookEvents(SubscribeHookEventsRequest) returns (stream HookEvent);
  // Deliver the hook's reply (or veto via `abort`) for a previously
  // received event. State-changing — caller supplies
  // `client_operation_id` for retry safety, though the broker will
  // also dedup on `hook_event_id` since each id is single-shot.
  rpc RespondToHook(RespondToHookRequest) returns (RespondToHookResponse);
}

message RegisterHookRequest {
  string repo_path = 1;
  string name = 2;
  string command = 3;
  // Subscribed event names. See `GetHookEventSchema` for the catalog.
  repeated string events = 4;
  uint32 timeout_ms = 5;
  string client_operation_id = 15;
}

message Hook {
  string name = 1;
  string command = 2;
  repeated string events = 3;
  uint32 timeout_ms = 4;
}

message DeregisterHookRequest {
  string repo_path = 1;
  string name = 2;
  string client_operation_id = 15;
}

message ListHooksRequest {
  string repo_path = 1;
}

message ListHooksResponse {
  repeated Hook hooks = 1;
}

message GetHookEventSchemaRequest {
  // Empty returns every registered event's schema.
  string event_name = 1;
}

message GetHookEventSchemaResponse {
  repeated HookEventSchema schemas = 1;
}

message HookEventSchema {
  string event_name = 1;
  uint32 schema_version = 2;
  // JSON-encoded JSON Schema describing the payload.
  string payload_schema_json = 3;
  // JSON-encoded JSON Schema describing the response.
  string response_schema_json = 4;
}

// Live hook event delivery messages. The subscribe RPC
// streams these to every interested hook process; each carries an
// opaque `hook_event_id` correlator so `RespondToHook` can route a
// reply back to the in-flight emitter. Payloads are JSON-encoded
// against the schemas catalogued by `GetHookEventSchema`.

message SubscribeHookEventsRequest {
  string repo_path = 1;
  // Optional event-name filter. Empty = subscribe to every event in
  // the catalog. Same wire shape as `RegisterHookRequest.events`.
  repeated string events = 2;
}

// One emitted hook event:
//   - `hook_event_id` correlates the emit-side waiter with the
//     `RespondToHook` reply.
//   - `event_name` is one of the catalog entries (`pre_capture` etc.).
//   - `payload_json` is the JSON-encoded event payload, validated by
//     the catalog's `payload_schema_json`.
message HookEvent {
  string hook_event_id = 1;
  string event_name = 2;
  string payload_json = 3;
  // Wall-clock emit time. Useful for debugging slow hooks; the
  // emit-side timeout is enforced server-side.
  google.protobuf.Timestamp emitted_at = 4;
}

message RespondToHookRequest {
  string repo_path = 1;
  // Echo from the matching `HookEvent`. Single-shot: re-using an id
  // returns `failed_precondition`.
  string hook_event_id = 2;
  // When non-empty, the operation is aborted with this string as the
  // reason. Universal veto channel — see `HookResponse.abort`.
  string abort = 3;
  // Free-form JSON-encoded extension payload. Decoded into
  // `HookResponse.extra` on the emit-side.
  string extra_signals_json = 4;
  string client_operation_id = 15;
}

message RespondToHookResponse {
  // True when the broker accepted the reply and dispatched it to the
  // waiting emitter. False when the emitter has already timed out or
  // an unrelated reply was already recorded for this id.
  bool accepted = 1;
}
