From aca36102e42c067263971219d4551d3abdd27997 Mon Sep 17 00:00:00 2001 From: flemming-it Date: Fri, 17 Jul 2026 00:59:01 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20planSetup=20=E2=80=94=20guided-setup=20?= =?UTF-8?q?plan=20preview=20over=20the=20hub=20connection?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wraps the new HubAdmin.PlanSetup RPC (pure plan assembly, no state change). Regenerated stubs from the updated proto. Signed-off-by: flemming-it --- lib/src/generated/chain/v1/hub.pb.dart | 247 +++++++++++++++++++++ lib/src/generated/chain/v1/hub.pbgrpc.dart | 35 +++ lib/src/generated/chain/v1/hub.pbjson.dart | 63 ++++++ lib/src/hub_client.dart | 26 +++ 4 files changed, 371 insertions(+) diff --git a/lib/src/generated/chain/v1/hub.pb.dart b/lib/src/generated/chain/v1/hub.pb.dart index cb75f02..265eff5 100644 --- a/lib/src/generated/chain/v1/hub.pb.dart +++ b/lib/src/generated/chain/v1/hub.pb.dart @@ -7408,6 +7408,253 @@ class ReloadAuthResponse extends $pb.GeneratedMessage { void clearTokenCount() => $_clearField(1); } +/// Guided-setup answers, mirroring chain_core::guided_setup:: +/// SetupAnswers (kebab-case wire values, e.g. "trying-out"). +/// Unknown values are an INVALID_ARGUMENT — the enums live in Rust. +class PlanSetupRequest extends $pb.GeneratedMessage { + factory PlanSetupRequest({ + $core.String? scenario, + $core.String? intent, + $core.String? target, + $core.bool? requireApproval, + $core.bool? dataMustStayLocal, + $core.bool? allowUnsignedModules, + }) { + final result = create(); + if (scenario != null) result.scenario = scenario; + if (intent != null) result.intent = intent; + if (target != null) result.target = target; + if (requireApproval != null) result.requireApproval = requireApproval; + if (dataMustStayLocal != null) result.dataMustStayLocal = dataMustStayLocal; + if (allowUnsignedModules != null) + result.allowUnsignedModules = allowUnsignedModules; + return result; + } + + PlanSetupRequest._(); + + factory PlanSetupRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory PlanSetupRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'PlanSetupRequest', + package: const $pb.PackageName(_omitMessageNames ? '' : 'chain.v1'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'scenario') + ..aOS(2, _omitFieldNames ? '' : 'intent') + ..aOS(3, _omitFieldNames ? '' : 'target') + ..aOB(4, _omitFieldNames ? '' : 'requireApproval') + ..aOB(5, _omitFieldNames ? '' : 'dataMustStayLocal') + ..aOB(6, _omitFieldNames ? '' : 'allowUnsignedModules') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + PlanSetupRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + PlanSetupRequest copyWith(void Function(PlanSetupRequest) updates) => + super.copyWith((message) => updates(message as PlanSetupRequest)) + as PlanSetupRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static PlanSetupRequest create() => PlanSetupRequest._(); + @$core.override + PlanSetupRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static PlanSetupRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static PlanSetupRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get scenario => $_getSZ(0); + @$pb.TagNumber(1) + set scenario($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasScenario() => $_has(0); + @$pb.TagNumber(1) + void clearScenario() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get intent => $_getSZ(1); + @$pb.TagNumber(2) + set intent($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasIntent() => $_has(1); + @$pb.TagNumber(2) + void clearIntent() => $_clearField(2); + + @$pb.TagNumber(3) + $core.String get target => $_getSZ(2); + @$pb.TagNumber(3) + set target($core.String value) => $_setString(2, value); + @$pb.TagNumber(3) + $core.bool hasTarget() => $_has(2); + @$pb.TagNumber(3) + void clearTarget() => $_clearField(3); + + @$pb.TagNumber(4) + $core.bool get requireApproval => $_getBF(3); + @$pb.TagNumber(4) + set requireApproval($core.bool value) => $_setBool(3, value); + @$pb.TagNumber(4) + $core.bool hasRequireApproval() => $_has(3); + @$pb.TagNumber(4) + void clearRequireApproval() => $_clearField(4); + + @$pb.TagNumber(5) + $core.bool get dataMustStayLocal => $_getBF(4); + @$pb.TagNumber(5) + set dataMustStayLocal($core.bool value) => $_setBool(4, value); + @$pb.TagNumber(5) + $core.bool hasDataMustStayLocal() => $_has(4); + @$pb.TagNumber(5) + void clearDataMustStayLocal() => $_clearField(5); + + @$pb.TagNumber(6) + $core.bool get allowUnsignedModules => $_getBF(5); + @$pb.TagNumber(6) + set allowUnsignedModules($core.bool value) => $_setBool(5, value); + @$pb.TagNumber(6) + $core.bool hasAllowUnsignedModules() => $_has(5); + @$pb.TagNumber(6) + void clearAllowUnsignedModules() => $_clearField(6); +} + +/// The assembled SetupPlan — field-for-field what `chain init +/// --answers … --plan-json` prints, so a GUI can render the same +/// preview from either source. +class PlanSetupResponse extends $pb.GeneratedMessage { + factory PlanSetupResponse({ + $core.String? profile, + $core.Iterable<$core.String>? modules, + $core.String? starterFlow, + $core.String? runbook, + $core.Iterable<$core.String>? curatedDocs, + $core.bool? requireSignatures, + $core.bool? wormAudit, + $core.bool? approvalStep, + }) { + final result = create(); + if (profile != null) result.profile = profile; + if (modules != null) result.modules.addAll(modules); + if (starterFlow != null) result.starterFlow = starterFlow; + if (runbook != null) result.runbook = runbook; + if (curatedDocs != null) result.curatedDocs.addAll(curatedDocs); + if (requireSignatures != null) result.requireSignatures = requireSignatures; + if (wormAudit != null) result.wormAudit = wormAudit; + if (approvalStep != null) result.approvalStep = approvalStep; + return result; + } + + PlanSetupResponse._(); + + factory PlanSetupResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory PlanSetupResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'PlanSetupResponse', + package: const $pb.PackageName(_omitMessageNames ? '' : 'chain.v1'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'profile') + ..pPS(2, _omitFieldNames ? '' : 'modules') + ..aOS(3, _omitFieldNames ? '' : 'starterFlow') + ..aOS(4, _omitFieldNames ? '' : 'runbook') + ..pPS(5, _omitFieldNames ? '' : 'curatedDocs') + ..aOB(6, _omitFieldNames ? '' : 'requireSignatures') + ..aOB(7, _omitFieldNames ? '' : 'wormAudit') + ..aOB(8, _omitFieldNames ? '' : 'approvalStep') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + PlanSetupResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + PlanSetupResponse copyWith(void Function(PlanSetupResponse) updates) => + super.copyWith((message) => updates(message as PlanSetupResponse)) + as PlanSetupResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static PlanSetupResponse create() => PlanSetupResponse._(); + @$core.override + PlanSetupResponse createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static PlanSetupResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static PlanSetupResponse? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get profile => $_getSZ(0); + @$pb.TagNumber(1) + set profile($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasProfile() => $_has(0); + @$pb.TagNumber(1) + void clearProfile() => $_clearField(1); + + @$pb.TagNumber(2) + $pb.PbList<$core.String> get modules => $_getList(1); + + @$pb.TagNumber(3) + $core.String get starterFlow => $_getSZ(2); + @$pb.TagNumber(3) + set starterFlow($core.String value) => $_setString(2, value); + @$pb.TagNumber(3) + $core.bool hasStarterFlow() => $_has(2); + @$pb.TagNumber(3) + void clearStarterFlow() => $_clearField(3); + + @$pb.TagNumber(4) + $core.String get runbook => $_getSZ(3); + @$pb.TagNumber(4) + set runbook($core.String value) => $_setString(3, value); + @$pb.TagNumber(4) + $core.bool hasRunbook() => $_has(3); + @$pb.TagNumber(4) + void clearRunbook() => $_clearField(4); + + @$pb.TagNumber(5) + $pb.PbList<$core.String> get curatedDocs => $_getList(4); + + @$pb.TagNumber(6) + $core.bool get requireSignatures => $_getBF(5); + @$pb.TagNumber(6) + set requireSignatures($core.bool value) => $_setBool(5, value); + @$pb.TagNumber(6) + $core.bool hasRequireSignatures() => $_has(5); + @$pb.TagNumber(6) + void clearRequireSignatures() => $_clearField(6); + + @$pb.TagNumber(7) + $core.bool get wormAudit => $_getBF(6); + @$pb.TagNumber(7) + set wormAudit($core.bool value) => $_setBool(6, value); + @$pb.TagNumber(7) + $core.bool hasWormAudit() => $_has(6); + @$pb.TagNumber(7) + void clearWormAudit() => $_clearField(7); + + @$pb.TagNumber(8) + $core.bool get approvalStep => $_getBF(7); + @$pb.TagNumber(8) + set approvalStep($core.bool value) => $_setBool(7, value); + @$pb.TagNumber(8) + $core.bool hasApprovalStep() => $_has(7); + @$pb.TagNumber(8) + void clearApprovalStep() => $_clearField(8); +} + class AuthStatusResponse extends $pb.GeneratedMessage { factory AuthStatusResponse({ $core.String? validator, diff --git a/lib/src/generated/chain/v1/hub.pbgrpc.dart b/lib/src/generated/chain/v1/hub.pbgrpc.dart index 7b3d2a5..6fdc916 100644 --- a/lib/src/generated/chain/v1/hub.pbgrpc.dart +++ b/lib/src/generated/chain/v1/hub.pbgrpc.dart @@ -597,6 +597,21 @@ class HubAdminClient extends $grpc.Client { return $createUnaryCall(_$authStatus, request, options: options); } + /// Assemble the guided-setup plan for the given answers — a pure + /// computation (chain_core::guided_setup::assemble), no state + /// change, nothing written. Lets a GUI render the setup preview + /// over its existing hub connection instead of spawning the CLI: + /// on macOS that subprocess can be the app's first file-system + /// touch and trigger a permission prompt BEFORE the operator has + /// seen what would be set up — the wrong order for trust. Apply + /// remains a deliberate separate step. + $grpc.ResponseFuture<$0.PlanSetupResponse> planSetup( + $0.PlanSetupRequest request, { + $grpc.CallOptions? options, + }) { + return $createUnaryCall(_$planSetup, request, options: options); + } + /// List host services declared in the operator config. Each /// entry carries name + endpoint; reachability is operator-side /// (on-demand `chain service status` or Studio probe). @@ -997,6 +1012,11 @@ class HubAdminClient extends $grpc.Client { '/chain.v1.HubAdmin/AuthStatus', ($1.Empty value) => value.writeToBuffer(), $0.AuthStatusResponse.fromBuffer); + static final _$planSetup = + $grpc.ClientMethod<$0.PlanSetupRequest, $0.PlanSetupResponse>( + '/chain.v1.HubAdmin/PlanSetup', + ($0.PlanSetupRequest value) => value.writeToBuffer(), + $0.PlanSetupResponse.fromBuffer); static final _$listServices = $grpc.ClientMethod<$1.Empty, $0.ServiceList>( '/chain.v1.HubAdmin/ListServices', ($1.Empty value) => value.writeToBuffer(), @@ -1359,6 +1379,13 @@ abstract class HubAdminServiceBase extends $grpc.Service { false, ($core.List<$core.int> value) => $1.Empty.fromBuffer(value), ($0.AuthStatusResponse value) => value.writeToBuffer())); + $addMethod($grpc.ServiceMethod<$0.PlanSetupRequest, $0.PlanSetupResponse>( + 'PlanSetup', + planSetup_Pre, + false, + false, + ($core.List<$core.int> value) => $0.PlanSetupRequest.fromBuffer(value), + ($0.PlanSetupResponse value) => value.writeToBuffer())); $addMethod($grpc.ServiceMethod<$1.Empty, $0.ServiceList>( 'ListServices', listServices_Pre, @@ -1799,6 +1826,14 @@ abstract class HubAdminServiceBase extends $grpc.Service { $async.Future<$0.AuthStatusResponse> authStatus( $grpc.ServiceCall call, $1.Empty request); + $async.Future<$0.PlanSetupResponse> planSetup_Pre($grpc.ServiceCall $call, + $async.Future<$0.PlanSetupRequest> $request) async { + return planSetup($call, await $request); + } + + $async.Future<$0.PlanSetupResponse> planSetup( + $grpc.ServiceCall call, $0.PlanSetupRequest request); + $async.Future<$0.ServiceList> listServices_Pre( $grpc.ServiceCall $call, $async.Future<$1.Empty> $request) async { return listServices($call, await $request); diff --git a/lib/src/generated/chain/v1/hub.pbjson.dart b/lib/src/generated/chain/v1/hub.pbjson.dart index 9d26ba2..ecdfa75 100644 --- a/lib/src/generated/chain/v1/hub.pbjson.dart +++ b/lib/src/generated/chain/v1/hub.pbjson.dart @@ -1791,6 +1791,69 @@ const ReloadAuthResponse$json = { final $typed_data.Uint8List reloadAuthResponseDescriptor = $convert.base64Decode( 'ChJSZWxvYWRBdXRoUmVzcG9uc2USHwoLdG9rZW5fY291bnQYASABKA1SCnRva2VuQ291bnQ='); +@$core.Deprecated('Use planSetupRequestDescriptor instead') +const PlanSetupRequest$json = { + '1': 'PlanSetupRequest', + '2': [ + {'1': 'scenario', '3': 1, '4': 1, '5': 9, '10': 'scenario'}, + {'1': 'intent', '3': 2, '4': 1, '5': 9, '10': 'intent'}, + {'1': 'target', '3': 3, '4': 1, '5': 9, '10': 'target'}, + {'1': 'require_approval', '3': 4, '4': 1, '5': 8, '10': 'requireApproval'}, + { + '1': 'data_must_stay_local', + '3': 5, + '4': 1, + '5': 8, + '10': 'dataMustStayLocal' + }, + { + '1': 'allow_unsigned_modules', + '3': 6, + '4': 1, + '5': 8, + '10': 'allowUnsignedModules' + }, + ], +}; + +/// Descriptor for `PlanSetupRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List planSetupRequestDescriptor = $convert.base64Decode( + 'ChBQbGFuU2V0dXBSZXF1ZXN0EhoKCHNjZW5hcmlvGAEgASgJUghzY2VuYXJpbxIWCgZpbnRlbn' + 'QYAiABKAlSBmludGVudBIWCgZ0YXJnZXQYAyABKAlSBnRhcmdldBIpChByZXF1aXJlX2FwcHJv' + 'dmFsGAQgASgIUg9yZXF1aXJlQXBwcm92YWwSLwoUZGF0YV9tdXN0X3N0YXlfbG9jYWwYBSABKA' + 'hSEWRhdGFNdXN0U3RheUxvY2FsEjQKFmFsbG93X3Vuc2lnbmVkX21vZHVsZXMYBiABKAhSFGFs' + 'bG93VW5zaWduZWRNb2R1bGVz'); + +@$core.Deprecated('Use planSetupResponseDescriptor instead') +const PlanSetupResponse$json = { + '1': 'PlanSetupResponse', + '2': [ + {'1': 'profile', '3': 1, '4': 1, '5': 9, '10': 'profile'}, + {'1': 'modules', '3': 2, '4': 3, '5': 9, '10': 'modules'}, + {'1': 'starter_flow', '3': 3, '4': 1, '5': 9, '10': 'starterFlow'}, + {'1': 'runbook', '3': 4, '4': 1, '5': 9, '10': 'runbook'}, + {'1': 'curated_docs', '3': 5, '4': 3, '5': 9, '10': 'curatedDocs'}, + { + '1': 'require_signatures', + '3': 6, + '4': 1, + '5': 8, + '10': 'requireSignatures' + }, + {'1': 'worm_audit', '3': 7, '4': 1, '5': 8, '10': 'wormAudit'}, + {'1': 'approval_step', '3': 8, '4': 1, '5': 8, '10': 'approvalStep'}, + ], +}; + +/// Descriptor for `PlanSetupResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List planSetupResponseDescriptor = $convert.base64Decode( + 'ChFQbGFuU2V0dXBSZXNwb25zZRIYCgdwcm9maWxlGAEgASgJUgdwcm9maWxlEhgKB21vZHVsZX' + 'MYAiADKAlSB21vZHVsZXMSIQoMc3RhcnRlcl9mbG93GAMgASgJUgtzdGFydGVyRmxvdxIYCgdy' + 'dW5ib29rGAQgASgJUgdydW5ib29rEiEKDGN1cmF0ZWRfZG9jcxgFIAMoCVILY3VyYXRlZERvY3' + 'MSLQoScmVxdWlyZV9zaWduYXR1cmVzGAYgASgIUhFyZXF1aXJlU2lnbmF0dXJlcxIdCgp3b3Jt' + 'X2F1ZGl0GAcgASgIUgl3b3JtQXVkaXQSIwoNYXBwcm92YWxfc3RlcBgIIAEoCFIMYXBwcm92YW' + 'xTdGVw'); + @$core.Deprecated('Use authStatusResponseDescriptor instead') const AuthStatusResponse$json = { '1': 'AuthStatusResponse', diff --git a/lib/src/hub_client.dart b/lib/src/hub_client.dart index cc33ce1..1154e08 100644 --- a/lib/src/hub_client.dart +++ b/lib/src/hub_client.dart @@ -89,6 +89,7 @@ typedef CuratedModel = pb.CuratedModel; typedef ClearEventLogResponse = pb.ClearEventLogResponse; typedef DaemonPathsResponse = pb.DaemonPathsResponse; typedef AuthStatusResponse = pb.AuthStatusResponse; +typedef PlanSetupResponse = pb.PlanSetupResponse; typedef AuthTokenInfo = pb.AuthTokenInfo; typedef JwtValidatorInfo = pb.JwtValidatorInfo; typedef UninstallModuleResponse = pb.UninstallModuleResponse; @@ -303,6 +304,31 @@ class HubClient { return _admin.authStatus(Empty()); } + /// Assemble the guided-setup plan for [scenario] / [intent] / + /// [target] (kebab-case wire values) — a pure computation on the + /// hub, no state change. Field-for-field what `chain init + /// --plan-json` prints, so GUIs can render the setup preview over + /// the existing connection instead of spawning the CLI. + Future planSetup({ + required String scenario, + required String intent, + required String target, + bool requireApproval = false, + bool dataMustStayLocal = false, + bool allowUnsignedModules = false, + }) { + return _admin.planSetup( + pb.PlanSetupRequest( + scenario: scenario, + intent: intent, + target: target, + requireApproval: requireApproval, + dataMustStayLocal: dataMustStayLocal, + allowUnsignedModules: allowUnsignedModules, + ), + ); + } + /// Server-streaming audit-log subscription. The hub first /// replays up to [backfill] historical events (oldest-last so /// the receiver sees them in chronological order), then keeps