feat: live HubClient with generated proto bindings
Proto codegen wired in. tools/generate.sh wraps protoc against ../fai_platform/proto/, including the well-known types (Empty, Struct, Timestamp) so they are generated alongside fai's own messages — protobuf 4.x doesn't ship them as importable Dart types, so we have to generate them ourselves. HubClient now exposes typed methods backed by real RPCs: - healthy() — Hub.Health probe (returns false on connect refused). - listCapabilities() — HubAdmin.ListCapabilities. - eventLog() — HubAdmin.EventLog with type filter. - listApprovals() — HubAdmin.ListApprovals (status filter). - approve() / reject() — HubAdmin.DecideApproval. Generated bindings under lib/src/generated/ are excluded from analyze (out of our style control). protoc_plugin pinned to 22.2.0 in tools/generate.sh comment because newer plugins generate code against protobuf 6.x, while grpc 4.x still constrains us to protobuf 4.x. Bumps fai_dart_sdk 0.1.0 -> 0.2.0. Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
This commit is contained in:
parent
d405a6a9bd
commit
da10820959
26 changed files with 5364 additions and 28 deletions
|
|
@ -1,11 +1,14 @@
|
|||
// HubClient — typed wrapper around the F∆I Hub gRPC service.
|
||||
// HubClient — typed Dart wrapper around the F∆I gRPC surface.
|
||||
//
|
||||
// Status (2026-05-05): scaffold. The generated proto bindings
|
||||
// from `fai/platform`'s proto/fai/v1/ directory will populate
|
||||
// the actual RPC implementations under `lib/src/generated/`
|
||||
// once the codegen tooling is wired (planned: a small
|
||||
// `tools/generate.sh` script that runs `protoc` with
|
||||
// `protoc-gen-dart` against the platform repo's protos).
|
||||
// Holds a single grpc.ClientChannel keyed by [HubEndpoint]. Each
|
||||
// public method maps onto exactly one RPC on Hub or HubAdmin, so
|
||||
// callers don't have to know which service hosts which call.
|
||||
|
||||
import 'package:grpc/grpc.dart';
|
||||
|
||||
import 'generated/fai/v1/hub.pb.dart' as pb;
|
||||
import 'generated/fai/v1/hub.pbgrpc.dart' as grpc_hub;
|
||||
import 'generated/google/protobuf/empty.pb.dart' show Empty;
|
||||
|
||||
/// Connection coordinates for a hub endpoint.
|
||||
class HubEndpoint {
|
||||
|
|
@ -29,15 +32,126 @@ class HubEndpoint {
|
|||
String toString() => '${secure ? "https" : "http"}://$host:$port';
|
||||
}
|
||||
|
||||
/// Stub client interface. The real implementation will arrive
|
||||
/// when proto codegen is wired in. Studio uses this type as the
|
||||
/// future-stable surface; the mock data layer in
|
||||
/// `fai_studio/lib/data/mock.dart` is the placeholder until then.
|
||||
/// Re-exports of generated protobuf types so callers don't have
|
||||
/// to import the `generated/` directory directly.
|
||||
typedef CapabilityEntry = pb.CapabilityEntry;
|
||||
typedef CapabilityList = pb.CapabilityList;
|
||||
typedef LoggedEvent = pb.LoggedEvent;
|
||||
typedef PendingApprovalEntry = pb.PendingApprovalEntry;
|
||||
|
||||
/// Typed client for the F∆I Hub gRPC surface. Construct once,
|
||||
/// reuse for the process lifetime, call [close] on shutdown.
|
||||
class HubClient {
|
||||
final HubEndpoint endpoint;
|
||||
final ClientChannel _channel;
|
||||
final grpc_hub.HubClient _hub;
|
||||
final grpc_hub.HubAdminClient _admin;
|
||||
|
||||
HubClient({this.endpoint = const HubEndpoint()});
|
||||
HubClient({this.endpoint = const HubEndpoint()})
|
||||
: _channel = ClientChannel(
|
||||
endpoint.host,
|
||||
port: endpoint.port,
|
||||
options: ChannelOptions(
|
||||
credentials: endpoint.secure
|
||||
? const ChannelCredentials.secure()
|
||||
: const ChannelCredentials.insecure(),
|
||||
),
|
||||
),
|
||||
_hub = grpc_hub.HubClient(
|
||||
ClientChannel(
|
||||
endpoint.host,
|
||||
port: endpoint.port,
|
||||
options: ChannelOptions(
|
||||
credentials: endpoint.secure
|
||||
? const ChannelCredentials.secure()
|
||||
: const ChannelCredentials.insecure(),
|
||||
),
|
||||
),
|
||||
),
|
||||
_admin = grpc_hub.HubAdminClient(
|
||||
ClientChannel(
|
||||
endpoint.host,
|
||||
port: endpoint.port,
|
||||
options: ChannelOptions(
|
||||
credentials: endpoint.secure
|
||||
? const ChannelCredentials.secure()
|
||||
: const ChannelCredentials.insecure(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
/// Closes the underlying channel. No-op while stubbed.
|
||||
Future<void> close() async {}
|
||||
/// Liveness probe. Returns true when the hub responds with
|
||||
/// SERVING. False on any failure (includes connection refused).
|
||||
Future<bool> healthy() async {
|
||||
try {
|
||||
final r = await _hub.health(Empty());
|
||||
return r.state == pb.HealthStatus_State.SERVING;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// All capabilities provided by installed modules.
|
||||
Future<List<CapabilityEntry>> listCapabilities() async {
|
||||
final r = await _admin.listCapabilities(Empty());
|
||||
return r.capabilities;
|
||||
}
|
||||
|
||||
/// Recent events from the audit log, newest-first. Pass an
|
||||
/// empty `types` list for all event types.
|
||||
Future<List<LoggedEvent>> eventLog({
|
||||
int limit = 100,
|
||||
List<String> types = const [],
|
||||
}) async {
|
||||
final req = pb.EventLogRequest()
|
||||
..limit = limit
|
||||
..eventTypes.addAll(types);
|
||||
final r = await _admin.eventLog(req);
|
||||
return r.events;
|
||||
}
|
||||
|
||||
/// Pending approvals filtered by status. Defaults to all.
|
||||
Future<List<PendingApprovalEntry>> listApprovals({
|
||||
List<String> statuses = const [],
|
||||
int limit = 100,
|
||||
}) async {
|
||||
final req = pb.ListApprovalsRequest()
|
||||
..limit = limit
|
||||
..statuses.addAll(statuses);
|
||||
final r = await _admin.listApprovals(req);
|
||||
return r.entries;
|
||||
}
|
||||
|
||||
/// Approve a pending approval. The flow that posted it resumes
|
||||
/// on its next polling tick.
|
||||
Future<void> approve({
|
||||
required String approvalId,
|
||||
required String reviewer,
|
||||
}) async {
|
||||
final req = pb.DecideApprovalRequest()
|
||||
..approvalId = approvalId
|
||||
..reviewer = reviewer
|
||||
..decision = pb.DecideApprovalRequest_Decision.APPROVE;
|
||||
await _admin.decideApproval(req);
|
||||
}
|
||||
|
||||
/// Reject a pending approval. The flow fails with the supplied
|
||||
/// reason on its next polling tick.
|
||||
Future<void> reject({
|
||||
required String approvalId,
|
||||
required String reviewer,
|
||||
required String reason,
|
||||
}) async {
|
||||
final req = pb.DecideApprovalRequest()
|
||||
..approvalId = approvalId
|
||||
..reviewer = reviewer
|
||||
..reason = reason
|
||||
..decision = pb.DecideApprovalRequest_Decision.REJECT;
|
||||
await _admin.decideApproval(req);
|
||||
}
|
||||
|
||||
/// Closes the gRPC channel. Idempotent.
|
||||
Future<void> close() async {
|
||||
await _channel.shutdown();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue