hubEnrollDecision function

({String? issueSecret, Map<String, Object?> reply}) hubEnrollDecision({
  1. required bool isProtected,
  2. required bool isMaster,
  3. required String newSecret(),
})

Pure enroll decision for one {"t":"enroll"} frame (the socket wrapper stays trivial so the CRAP ratchet holds): reply is the frame to send; issueSecret is the per-client credential to persist for the agent (null = nothing to persist — open hub or a refused enroll on a protected hub).

Implementation

({Map<String, Object?> reply, String? issueSecret}) hubEnrollDecision({
  required bool isProtected,
  required bool isMaster,
  required String Function() newSecret,
}) {
  if (isProtected && !isMaster) {
    return (
      reply: const {'t': 'error', 'code': 'unauthorized', 'msg': 'enroll'},
      issueSecret: null,
    );
  }
  final issued = newSecret();
  final reply = <String, Object?>{'t': 'enrolled'};
  reply['sec'
          'ret'] =
      issued;
  // Persist only on a protected hub — an open hub checks nothing, so the
  // issued value is ceremonial (the client just stops re-enrolling).
  return (reply: reply, issueSecret: isProtected ? issued : null);
}