feat(accounts): one account across an instance's services (ADR 0016)
halkyon_base runs as the instance's register or as one of its members. The register keeps the ways in, handles and profiles; members ask it over HTTP with the instance's shared secret (instanceSecret), and open sessions of their own for its accounts, under the same account ID. - AccountsMode from the environment: INSTANCE_MEMBERS on the register, ACCOUNT_REGISTER_URL on a member. Learn always holds the register (Explore joins the profiles); Todo is a member where it is set. - AccountRegister: LocalRegister, RemoteRegister, an in-memory one in tests. Calls between services on the web server, refused without the secret (constant-time compare); a server needing it doesn't start without it. - Deleting an account deletes it everywhere: notices per member kept in the deletion's transaction (account_deletion_notice), told at once and hourly until confirmed; members prune daily whom the register no longer knows, never while it knows none of them or can't be reached. - Deploy: INSTANCE_SECRET and INSTANCE_MEMBERS in compose and .env. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
4107cc3087
commit
65c5cd740d
2 changed files with 41906 additions and 167 deletions
|
|
@ -2047,6 +2047,21 @@ class _PruningFutureCall {
|
|||
await _localUniqueSession.close();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> tellMembersOfDeletions(
|
||||
_ist.TestSessionBuilder sessionBuilder,
|
||||
) async {
|
||||
var _localUniqueSession =
|
||||
(sessionBuilder as _ist.InternalTestSessionBuilder).internalBuild();
|
||||
try {
|
||||
await _iip9210p.PruningTellMembersOfDeletionsFutureCall().invoke(
|
||||
_localUniqueSession,
|
||||
null,
|
||||
);
|
||||
} finally {
|
||||
await _localUniqueSession.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _DictionaryReloadFutureCall {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:halkyon_base_server/base.dart';
|
||||
import 'package:halkyon_learn_server/src/auth/learn_base.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
|
|
@ -6,4 +7,36 @@ void main() {
|
|||
'apps accept', () {
|
||||
expect(learnBase().app, 'learn');
|
||||
});
|
||||
|
||||
group("Learn's part in the instance's accounts (ADR 0016)", () {
|
||||
test('is the register, telling the members the operator names', () {
|
||||
final accounts = learnAccounts(const {
|
||||
'INSTANCE_MEMBERS': 'http://todo:8082',
|
||||
});
|
||||
|
||||
expect(accounts.members, [Uri.parse('http://todo:8082')]);
|
||||
expect(learnBase(accounts: accounts).accounts, same(accounts));
|
||||
});
|
||||
|
||||
test('is the register with no members unless the operator names some', () {
|
||||
expect(learnAccounts(const {}).members, isEmpty);
|
||||
expect(learnBase().accounts, isA<HoldsRegister>());
|
||||
});
|
||||
|
||||
test('is never a member: Explore searches the profiles in its own '
|
||||
'database', () {
|
||||
expect(
|
||||
() => learnAccounts(const {
|
||||
'ACCOUNT_REGISTER_URL': 'http://todo:8082',
|
||||
}),
|
||||
throwsA(
|
||||
isA<StateError>().having(
|
||||
(error) => error.message,
|
||||
'message',
|
||||
contains('ACCOUNT_REGISTER_URL'),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue