feat(block2): wire the social identity into app_seeds (DI, no UI)

Third slice: bridge Block 1's root seed to the Block 2 transport foundation.

- SocialService: derives the secp256k1 Nostr identity from the SAME root seed
  the recovery QR backs up (no extra backup), exposes npub/pubkey, and opens a
  SocialSession = one shared connection carrying all three transports.
- injector.dart: registers SocialService in get_it. Local-first — derivation is
  cheap + offline; no relay is contacted at startup. Replaces the old
  "nodeId slice of the seed is the social pubkey" placeholder; keeps the CRDT
  author id distinct (unifying it would rewrite authorship — a later decision).
- Test: deterministic identity from the seed hex, matches commons_core, rejects
  malformed hex. 5 tests green; app_seeds analyzes clean (0 errors).
This commit is contained in:
vjrj 2026-07-10 02:44:37 +02:00
parent f50a4737cb
commit 70905a0578
3 changed files with 132 additions and 2 deletions

View file

@ -24,6 +24,7 @@ import '../services/ocr/tesseract_label_extractor.dart';
import '../services/onboarding_store.dart';
import '../services/recovery_sheet_service.dart';
import '../services/share_catalog_service.dart';
import '../services/social_service.dart';
/// The app's service locator. Kept to the composition root — widgets get their
/// repositories from here (or via BlocProvider), never by reaching into it deep
@ -42,10 +43,16 @@ Future<void> configureDependencies() async {
openEncryptedExecutor(await _databaseFile(), dbKeyHex),
);
// Until real key derivation lands, the node/author id is a stable per-install
// slice of the root seed. It becomes the user's public key in the social layer.
// CRDT author id: a stable per-install slice of the root seed. Kept distinct
// from the social-layer public key on purpose unifying the two would rewrite
// authorship of existing rows, a data-model decision for later.
final nodeId = rootSeedHex.substring(0, 16);
// Block 2 social identity: a secp256k1 Nostr key derived (one-way) from the
// SAME root seed, so it needs no extra backup. Cheap + offline; no relay is
// contacted here (local-first the social layer only enriches).
final socialService = await SocialService.fromRootSeedHex(rootSeedHex);
// Seed the bundled species catalog (idempotent) before the UI opens.
final speciesRepository = SpeciesRepository(database, idGen: IdGen());
await speciesRepository.seedBundled(await loadBundledSpecies());
@ -75,6 +82,7 @@ Future<void> configureDependencies() async {
..registerSingleton<FileService>(fileService)
..registerSingleton<LabelTextExtractor>(labelExtractor)
..registerSingleton<OnboardingStore>(OnboardingStore(secretStore))
..registerSingleton<SocialService>(socialService)
..registerSingleton<ExportImportService>(
ExportImportService(
repository: varietyRepository,