From 297e1931a7b5913275e8e9b0a46494d95e7e5e7c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Michael=20Mur=C3=A9?= <batolettre@gmail.com>
Date: Mon, 14 Aug 2023 16:58:06 +0200
Subject: [PATCH] WIP 2

---
 api/graphql/models/lazy_identity.go           |  4 +-
 cache/bug_cache.go                            | 20 ++---
 cache/cached.go                               |  8 +-
 cache/subcache.go                             | 13 ++--
 cache/with_snapshot.go                        | 13 ++--
 entities/bug/bug_actions.go                   |  5 +-
 entities/bug/comment.go                       |  3 +-
 entities/bug/op_add_comment.go                |  5 +-
 entities/bug/op_add_comment_test.go           |  5 +-
 entities/bug/op_create.go                     |  5 +-
 entities/bug/op_create_test.go                |  4 +-
 entities/bug/op_edit_comment.go               |  7 +-
 entities/bug/op_edit_comment_test.go          |  4 +-
 entities/bug/op_label_change.go               |  9 +--
 entities/bug/op_label_change_test.go          |  7 +-
 entities/bug/op_set_metadata.go               |  5 +-
 entities/bug/op_set_status.go                 |  9 +--
 entities/bug/op_set_status_test.go            |  3 +-
 entities/bug/op_set_title.go                  |  7 +-
 entities/bug/op_set_title_test.go             |  3 +-
 entities/bug/snapshot.go                      | 11 ++-
 entities/bug/timeline.go                      |  5 +-
 entities/identity/common.go                   |  4 +-
 entities/identity/identity.go                 | 21 ++---
 entities/identity/identity_stub.go            |  8 +-
 entities/identity/identity_test.go            | 36 ++++-----
 entities/identity/key.go                      |  7 +-
 entities/identity/key_test.go                 |  2 +-
 entities/identity/version.go                  |  6 +-
 entities/identity/version_test.go             |  2 +-
 .../boostrap/identity.go                      | 32 ++++++--
 entity/dag/common_test.go                     | 10 +--
 entity/dag/entity.go                          |  3 +-
 entity/dag/entity_actions.go                  |  7 +-
 entity/dag/example_test.go                    | 20 ++---
 entity/dag/op_noop.go                         |  3 +-
 entity/dag/op_noop_test.go                    |  3 +-
 entity/dag/op_set_metadata.go                 | 18 +++--
 entity/dag/op_set_metadata_test.go            |  2 +-
 entity/dag/operation.go                       | 11 ++-
 entity/dag/operation_pack.go                  | 15 ++--
 entity/dag/operation_testing.go               |  2 +-
 entity/identity.go                            | 78 ++-----------------
 entity/operations.go                          |  3 +-
 misc/random_bugs/create_random_bugs.go        | 15 ++--
 45 files changed, 203 insertions(+), 260 deletions(-)
 rename entities/identity/interface.go => entity/boostrap/identity.go (73%)

diff --git a/api/graphql/models/lazy_identity.go b/api/graphql/models/lazy_identity.go
index c19d077b..efc102a8 100644
--- a/api/graphql/models/lazy_identity.go
+++ b/api/graphql/models/lazy_identity.go
@@ -111,10 +111,10 @@ func (li *lazyIdentity) IsProtected() (bool, error) {
 var _ IdentityWrapper = &loadedIdentity{}
 
 type loadedIdentity struct {
-	identity.Interface
+	entity.Identity
 }
 
-func NewLoadedIdentity(id identity.Interface) *loadedIdentity {
+func NewLoadedIdentity(id entity.Identity) *loadedIdentity {
 	return &loadedIdentity{Interface: id}
 }
 
diff --git a/cache/bug_cache.go b/cache/bug_cache.go
index 8f4bca27..5356fa1a 100644
--- a/cache/bug_cache.go
+++ b/cache/bug_cache.go
@@ -29,7 +29,7 @@ func NewBugCache(b *bug.Bug, repo repository.ClockedRepo, getUserIdentity getUse
 			repo:            repo,
 			entityUpdated:   entityUpdated,
 			getUserIdentity: getUserIdentity,
-			entity:          &withSnapshot[*bug.Snapshot, bug.Operation]{Interface: b},
+			entity:          &withSnapshot[*bug.Snapshot, bug.Operation]{WithCommit: b},
 		},
 	}
 }
@@ -47,7 +47,7 @@ func (c *BugCache) AddCommentWithFiles(message string, files []repository.Hash)
 	return c.AddCommentRaw(author, time.Now().Unix(), message, files, nil)
 }
 
-func (c *BugCache) AddCommentRaw(author entity.Interface, unixTime int64, message string, files []repository.Hash, metadata map[string]string) (entity.CombinedId, *bug.AddCommentOperation, error) {
+func (c *BugCache) AddCommentRaw(author entity.Identity, unixTime int64, message string, files []repository.Hash, metadata map[string]string) (entity.CombinedId, *bug.AddCommentOperation, error) {
 	c.mu.Lock()
 	commentId, op, err := bug.AddComment(c.entity, author, unixTime, message, files, metadata)
 	c.mu.Unlock()
@@ -66,7 +66,7 @@ func (c *BugCache) ChangeLabels(added []string, removed []string) ([]bug.LabelCh
 	return c.ChangeLabelsRaw(author, time.Now().Unix(), added, removed, nil)
 }
 
-func (c *BugCache) ChangeLabelsRaw(author entity.Interface, unixTime int64, added []string, removed []string, metadata map[string]string) ([]bug.LabelChangeResult, *bug.LabelChangeOperation, error) {
+func (c *BugCache) ChangeLabelsRaw(author entity.Identity, unixTime int64, added []string, removed []string, metadata map[string]string) ([]bug.LabelChangeResult, *bug.LabelChangeOperation, error) {
 	c.mu.Lock()
 	changes, op, err := bug.ChangeLabels(c.entity, author, unixTime, added, removed, metadata)
 	c.mu.Unlock()
@@ -85,7 +85,7 @@ func (c *BugCache) ForceChangeLabels(added []string, removed []string) (*bug.Lab
 	return c.ForceChangeLabelsRaw(author, time.Now().Unix(), added, removed, nil)
 }
 
-func (c *BugCache) ForceChangeLabelsRaw(author entity.Interface, unixTime int64, added []string, removed []string, metadata map[string]string) (*bug.LabelChangeOperation, error) {
+func (c *BugCache) ForceChangeLabelsRaw(author entity.Identity, unixTime int64, added []string, removed []string, metadata map[string]string) (*bug.LabelChangeOperation, error) {
 	c.mu.Lock()
 	op, err := bug.ForceChangeLabels(c.entity, author, unixTime, added, removed, metadata)
 	c.mu.Unlock()
@@ -104,7 +104,7 @@ func (c *BugCache) Open() (*bug.SetStatusOperation, error) {
 	return c.OpenRaw(author, time.Now().Unix(), nil)
 }
 
-func (c *BugCache) OpenRaw(author entity.Interface, unixTime int64, metadata map[string]string) (*bug.SetStatusOperation, error) {
+func (c *BugCache) OpenRaw(author entity.Identity, unixTime int64, metadata map[string]string) (*bug.SetStatusOperation, error) {
 	c.mu.Lock()
 	op, err := bug.Open(c.entity, author, unixTime, metadata)
 	c.mu.Unlock()
@@ -123,7 +123,7 @@ func (c *BugCache) Close() (*bug.SetStatusOperation, error) {
 	return c.CloseRaw(author, time.Now().Unix(), nil)
 }
 
-func (c *BugCache) CloseRaw(author entity.Interface, unixTime int64, metadata map[string]string) (*bug.SetStatusOperation, error) {
+func (c *BugCache) CloseRaw(author entity.Identity, unixTime int64, metadata map[string]string) (*bug.SetStatusOperation, error) {
 	c.mu.Lock()
 	op, err := bug.Close(c.entity, author, unixTime, metadata)
 	c.mu.Unlock()
@@ -142,7 +142,7 @@ func (c *BugCache) SetTitle(title string) (*bug.SetTitleOperation, error) {
 	return c.SetTitleRaw(author, time.Now().Unix(), title, nil)
 }
 
-func (c *BugCache) SetTitleRaw(author entity.Interface, unixTime int64, title string, metadata map[string]string) (*bug.SetTitleOperation, error) {
+func (c *BugCache) SetTitleRaw(author entity.Identity, unixTime int64, title string, metadata map[string]string) (*bug.SetTitleOperation, error) {
 	c.mu.Lock()
 	op, err := bug.SetTitle(c.entity, author, unixTime, title, metadata)
 	c.mu.Unlock()
@@ -163,7 +163,7 @@ func (c *BugCache) EditCreateComment(body string) (entity.CombinedId, *bug.EditC
 }
 
 // EditCreateCommentRaw is a convenience function to edit the body of a bug (the first comment)
-func (c *BugCache) EditCreateCommentRaw(author entity.Interface, unixTime int64, body string, metadata map[string]string) (entity.CombinedId, *bug.EditCommentOperation, error) {
+func (c *BugCache) EditCreateCommentRaw(author entity.Identity, unixTime int64, body string, metadata map[string]string) (entity.CombinedId, *bug.EditCommentOperation, error) {
 	c.mu.Lock()
 	commentId, op, err := bug.EditCreateComment(c.entity, author, unixTime, body, nil, metadata)
 	c.mu.Unlock()
@@ -182,7 +182,7 @@ func (c *BugCache) EditComment(target entity.CombinedId, message string) (*bug.E
 	return c.EditCommentRaw(author, time.Now().Unix(), target, message, nil)
 }
 
-func (c *BugCache) EditCommentRaw(author entity.Interface, unixTime int64, target entity.CombinedId, message string, metadata map[string]string) (*bug.EditCommentOperation, error) {
+func (c *BugCache) EditCommentRaw(author entity.Identity, unixTime int64, target entity.CombinedId, message string, metadata map[string]string) (*bug.EditCommentOperation, error) {
 	comment, err := c.Compile().SearchComment(target)
 	if err != nil {
 		return nil, err
@@ -209,7 +209,7 @@ func (c *BugCache) SetMetadata(target entity.Id, newMetadata map[string]string)
 	return c.SetMetadataRaw(author, time.Now().Unix(), target, newMetadata)
 }
 
-func (c *BugCache) SetMetadataRaw(author entity.Interface, unixTime int64, target entity.Id, newMetadata map[string]string) (*dag.SetMetadataOperation[*bug.Snapshot], error) {
+func (c *BugCache) SetMetadataRaw(author entity.Identity, unixTime int64, target entity.Id, newMetadata map[string]string) (*dag.SetMetadataOperation[*bug.Snapshot], error) {
 	c.mu.Lock()
 	op, err := bug.SetMetadata(c.entity, author, unixTime, target, newMetadata)
 	c.mu.Unlock()
diff --git a/cache/cached.go b/cache/cached.go
index 99f38487..d2531f0f 100644
--- a/cache/cached.go
+++ b/cache/cached.go
@@ -9,17 +9,17 @@ import (
 	"github.com/MichaelMure/git-bug/util/lamport"
 )
 
-var _ dag.Interface[dag.Snapshot, dag.Operation] = &CachedEntityBase[dag.Snapshot, dag.Operation]{}
-var _ CacheEntity = &CachedEntityBase[dag.Snapshot, dag.Operation]{}
+var _ entity.Interface[entity.Snapshot, dag.Operation] = &CachedEntityBase[entity.Snapshot, dag.Operation]{}
+var _ CacheEntity = &CachedEntityBase[entity.Snapshot, dag.Operation]{}
 
 // CachedEntityBase provide the base function of an entity managed by the cache.
-type CachedEntityBase[SnapT dag.Snapshot, OpT dag.Operation] struct {
+type CachedEntityBase[SnapT entity.Snapshot, OpT dag.Operation] struct {
 	repo            repository.ClockedRepo
 	entityUpdated   func(id entity.Id) error
 	getUserIdentity getUserIdentityFunc
 
 	mu     sync.RWMutex
-	entity dag.Interface[SnapT, OpT]
+	entity entity.WithCommit[SnapT, OpT]
 }
 
 func (e *CachedEntityBase[SnapT, OpT]) Id() entity.Id {
diff --git a/cache/subcache.go b/cache/subcache.go
index c1a550c3..2b0da1a5 100644
--- a/cache/subcache.go
+++ b/cache/subcache.go
@@ -10,6 +10,7 @@ import (
 	"github.com/pkg/errors"
 
 	"github.com/MichaelMure/git-bug/entity"
+	bootstrap "github.com/MichaelMure/git-bug/entity/boostrap"
 	"github.com/MichaelMure/git-bug/repository"
 )
 
@@ -29,17 +30,17 @@ type getUserIdentityFunc func() (*IdentityCache, error)
 // Actions expose a number of action functions on Entities, to give upper layers (cache) a way to normalize interactions.
 // Note: ideally this wouldn't exist, the cache layer would assume that everything is an entity/dag, and directly use the
 // functions from this package, but right now identities are not using that framework.
-type Actions[EntityT entity.Interface] struct {
+type Actions[EntityT entity.Bare] struct {
 	ReadWithResolver    func(repo repository.ClockedRepo, resolvers entity.Resolvers, id entity.Id) (EntityT, error)
-	ReadAllWithResolver func(repo repository.ClockedRepo, resolvers entity.Resolvers) <-chan entity.StreamedEntity[EntityT]
+	ReadAllWithResolver func(repo repository.ClockedRepo, resolvers entity.Resolvers) <-chan bootstrap.StreamedEntity[EntityT]
 	Remove              func(repo repository.ClockedRepo, id entity.Id) error
 	RemoveAll           func(repo repository.ClockedRepo) error
-	MergeAll            func(repo repository.ClockedRepo, resolvers entity.Resolvers, remote string, mergeAuthor entity.Interface) <-chan entity.MergeResult
+	MergeAll            func(repo repository.ClockedRepo, resolvers entity.Resolvers, remote string, mergeAuthor entity.Identity) <-chan entity.MergeResult
 }
 
-var _ cacheMgmt = &SubCache[entity.Interface, Excerpt, CacheEntity]{}
+var _ cacheMgmt = &SubCache[entity.Bare, Excerpt, CacheEntity]{}
 
-type SubCache[EntityT entity.Interface, ExcerptT Excerpt, CacheT CacheEntity] struct {
+type SubCache[EntityT entity.Bare, ExcerptT Excerpt, CacheT CacheEntity] struct {
 	repo      repository.ClockedRepo
 	resolvers func() entity.Resolvers
 
@@ -60,7 +61,7 @@ type SubCache[EntityT entity.Interface, ExcerptT Excerpt, CacheT CacheEntity] st
 	lru      *lruIdCache
 }
 
-func NewSubCache[EntityT entity.Interface, ExcerptT Excerpt, CacheT CacheEntity](
+func NewSubCache[EntityT entity.Bare, ExcerptT Excerpt, CacheT CacheEntity](
 	repo repository.ClockedRepo,
 	resolvers func() entity.Resolvers, getUserIdentity getUserIdentityFunc,
 	makeCached func(entity EntityT, entityUpdated func(id entity.Id) error) CacheT,
diff --git a/cache/with_snapshot.go b/cache/with_snapshot.go
index 674b6923..47bad74b 100644
--- a/cache/with_snapshot.go
+++ b/cache/with_snapshot.go
@@ -3,15 +3,16 @@ package cache
 import (
 	"sync"
 
+	"github.com/MichaelMure/git-bug/entity"
 	"github.com/MichaelMure/git-bug/entity/dag"
 	"github.com/MichaelMure/git-bug/repository"
 )
 
-var _ dag.Interface[dag.Snapshot, dag.OperationWithApply[dag.Snapshot]] = &withSnapshot[dag.Snapshot, dag.OperationWithApply[dag.Snapshot]]{}
+var _ entity.Interface[entity.Snapshot, dag.OperationWithApply[entity.Snapshot]] = &withSnapshot[entity.Snapshot, dag.OperationWithApply[entity.Snapshot]]{}
 
 // withSnapshot encapsulate an entity and maintain a snapshot efficiently.
-type withSnapshot[SnapT dag.Snapshot, OpT dag.OperationWithApply[SnapT]] struct {
-	dag.Interface[SnapT, OpT]
+type withSnapshot[SnapT entity.Snapshot, OpT dag.OperationWithApply[SnapT]] struct {
+	entity.WithCommit[SnapT, OpT]
 	mu   sync.Mutex
 	snap *SnapT
 }
@@ -20,7 +21,7 @@ func (ws *withSnapshot[SnapT, OpT]) Compile() SnapT {
 	ws.mu.Lock()
 	defer ws.mu.Unlock()
 	if ws.snap == nil {
-		snap := ws.Interface.Compile()
+		snap := ws.WithCommit.Compile()
 		ws.snap = &snap
 	}
 	return *ws.snap
@@ -31,7 +32,7 @@ func (ws *withSnapshot[SnapT, OpT]) Append(op OpT) {
 	ws.mu.Lock()
 	defer ws.mu.Unlock()
 
-	ws.Interface.Append(op)
+	ws.WithCommit.Append(op)
 
 	if ws.snap == nil {
 		return
@@ -46,7 +47,7 @@ func (ws *withSnapshot[SnapT, OpT]) Commit(repo repository.ClockedRepo) error {
 	ws.mu.Lock()
 	defer ws.mu.Unlock()
 
-	err := ws.Interface.Commit(repo)
+	err := ws.WithCommit.Commit(repo)
 	if err != nil {
 		ws.snap = nil
 		return err
diff --git a/entities/bug/bug_actions.go b/entities/bug/bug_actions.go
index 651d24dc..1d1bda78 100644
--- a/entities/bug/bug_actions.go
+++ b/entities/bug/bug_actions.go
@@ -1,7 +1,6 @@
 package bug
 
 import (
-	"github.com/MichaelMure/git-bug/entities/identity"
 	"github.com/MichaelMure/git-bug/entity"
 	"github.com/MichaelMure/git-bug/entity/dag"
 	"github.com/MichaelMure/git-bug/repository"
@@ -22,14 +21,14 @@ func Push(repo repository.Repo, remote string) (string, error) {
 // This function will return an error if a merge fail
 // Note: an author is necessary for the case where a merge commit is created, as this commit will
 // have an author and may be signed if a signing key is available.
-func Pull(repo repository.ClockedRepo, resolvers entity.Resolvers, remote string, mergeAuthor identity.Interface) error {
+func Pull(repo repository.ClockedRepo, resolvers entity.Resolvers, remote string, mergeAuthor entity.Identity) error {
 	return dag.Pull(def, wrapper, repo, resolvers, remote, mergeAuthor)
 }
 
 // MergeAll will merge all the available remote bug
 // Note: an author is necessary for the case where a merge commit is created, as this commit will
 // have an author and may be signed if a signing key is available.
-func MergeAll(repo repository.ClockedRepo, resolvers entity.Resolvers, remote string, mergeAuthor identity.Interface) <-chan entity.MergeResult {
+func MergeAll(repo repository.ClockedRepo, resolvers entity.Resolvers, remote string, mergeAuthor entity.Identity) <-chan entity.MergeResult {
 	return dag.MergeAll(def, wrapper, repo, resolvers, remote, mergeAuthor)
 }
 
diff --git a/entities/bug/comment.go b/entities/bug/comment.go
index 7835c5a8..15fb6a5d 100644
--- a/entities/bug/comment.go
+++ b/entities/bug/comment.go
@@ -3,7 +3,6 @@ package bug
 import (
 	"github.com/dustin/go-humanize"
 
-	"github.com/MichaelMure/git-bug/entities/identity"
 	"github.com/MichaelMure/git-bug/entity"
 	"github.com/MichaelMure/git-bug/repository"
 	"github.com/MichaelMure/git-bug/util/timestamp"
@@ -18,7 +17,7 @@ type Comment struct {
 	// targetId is the Id of the Operation that originally created that Comment
 	targetId entity.Id
 
-	Author  identity.Interface
+	Author  entity.Identity
 	Message string
 	Files   []repository.Hash
 
diff --git a/entities/bug/op_add_comment.go b/entities/bug/op_add_comment.go
index 17cc5dd0..833aa894 100644
--- a/entities/bug/op_add_comment.go
+++ b/entities/bug/op_add_comment.go
@@ -3,7 +3,6 @@ package bug
 import (
 	"fmt"
 
-	"github.com/MichaelMure/git-bug/entities/identity"
 	"github.com/MichaelMure/git-bug/entity"
 	"github.com/MichaelMure/git-bug/entity/dag"
 	"github.com/MichaelMure/git-bug/repository"
@@ -72,7 +71,7 @@ func (op *AddCommentOperation) Validate() error {
 	return nil
 }
 
-func NewAddCommentOp(author identity.Interface, unixTime int64, message string, files []repository.Hash) *AddCommentOperation {
+func NewAddCommentOp(author entity.Identity, unixTime int64, message string, files []repository.Hash) *AddCommentOperation {
 	return &AddCommentOperation{
 		OpBase:  dag.NewOpBase(AddCommentOp, author, unixTime),
 		Message: message,
@@ -89,7 +88,7 @@ type AddCommentTimelineItem struct {
 func (a *AddCommentTimelineItem) IsAuthored() {}
 
 // AddComment is a convenience function to add a comment to a bug
-func AddComment(b Interface, author identity.Interface, unixTime int64, message string, files []repository.Hash, metadata map[string]string) (entity.CombinedId, *AddCommentOperation, error) {
+func AddComment(b Interface, author entity.Identity, unixTime int64, message string, files []repository.Hash, metadata map[string]string) (entity.CombinedId, *AddCommentOperation, error) {
 	op := NewAddCommentOp(author, unixTime, message, files)
 	for key, val := range metadata {
 		op.SetMetadata(key, val)
diff --git a/entities/bug/op_add_comment_test.go b/entities/bug/op_add_comment_test.go
index fee9e785..7727c45b 100644
--- a/entities/bug/op_add_comment_test.go
+++ b/entities/bug/op_add_comment_test.go
@@ -3,17 +3,16 @@ package bug
 import (
 	"testing"
 
-	"github.com/MichaelMure/git-bug/entities/identity"
 	"github.com/MichaelMure/git-bug/entity"
 	"github.com/MichaelMure/git-bug/entity/dag"
 	"github.com/MichaelMure/git-bug/repository"
 )
 
 func TestAddCommentSerialize(t *testing.T) {
-	dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author identity.Interface, unixTime int64) (*AddCommentOperation, entity.Resolvers) {
+	dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author entity.Identity, unixTime int64) (*AddCommentOperation, entity.Resolvers) {
 		return NewAddCommentOp(author, unixTime, "message", nil), nil
 	})
-	dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author identity.Interface, unixTime int64) (*AddCommentOperation, entity.Resolvers) {
+	dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author entity.Identity, unixTime int64) (*AddCommentOperation, entity.Resolvers) {
 		return NewAddCommentOp(author, unixTime, "message", []repository.Hash{"hash1", "hash2"}), nil
 	})
 }
diff --git a/entities/bug/op_create.go b/entities/bug/op_create.go
index 63eb438d..33504e3d 100644
--- a/entities/bug/op_create.go
+++ b/entities/bug/op_create.go
@@ -3,7 +3,6 @@ package bug
 import (
 	"fmt"
 
-	"github.com/MichaelMure/git-bug/entities/identity"
 	"github.com/MichaelMure/git-bug/entity"
 	"github.com/MichaelMure/git-bug/entity/dag"
 	"github.com/MichaelMure/git-bug/repository"
@@ -89,7 +88,7 @@ func (op *CreateOperation) Validate() error {
 	return nil
 }
 
-func NewCreateOp(author identity.Interface, unixTime int64, title, message string, files []repository.Hash) *CreateOperation {
+func NewCreateOp(author entity.Identity, unixTime int64, title, message string, files []repository.Hash) *CreateOperation {
 	return &CreateOperation{
 		OpBase:  dag.NewOpBase(CreateOp, author, unixTime),
 		Title:   title,
@@ -107,7 +106,7 @@ type CreateTimelineItem struct {
 func (c *CreateTimelineItem) IsAuthored() {}
 
 // Create is a convenience function to create a bug
-func Create(author identity.Interface, unixTime int64, title, message string, files []repository.Hash, metadata map[string]string) (*Bug, *CreateOperation, error) {
+func Create(author entity.Identity, unixTime int64, title, message string, files []repository.Hash, metadata map[string]string) (*Bug, *CreateOperation, error) {
 	b := NewBug()
 	op := NewCreateOp(author, unixTime, title, message, files)
 	for key, val := range metadata {
diff --git a/entities/bug/op_create_test.go b/entities/bug/op_create_test.go
index d8bde46f..f340876f 100644
--- a/entities/bug/op_create_test.go
+++ b/entities/bug/op_create_test.go
@@ -40,10 +40,10 @@ func TestCreate(t *testing.T) {
 }
 
 func TestCreateSerialize(t *testing.T) {
-	dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author identity.Interface, unixTime int64) (*CreateOperation, entity.Resolvers) {
+	dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author entity.Identity, unixTime int64) (*CreateOperation, entity.Resolvers) {
 		return NewCreateOp(author, unixTime, "title", "message", nil), nil
 	})
-	dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author identity.Interface, unixTime int64) (*CreateOperation, entity.Resolvers) {
+	dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author entity.Identity, unixTime int64) (*CreateOperation, entity.Resolvers) {
 		return NewCreateOp(author, unixTime, "title", "message", []repository.Hash{"hash1", "hash2"}), nil
 	})
 }
diff --git a/entities/bug/op_edit_comment.go b/entities/bug/op_edit_comment.go
index 788d16d9..d2f47db9 100644
--- a/entities/bug/op_edit_comment.go
+++ b/entities/bug/op_edit_comment.go
@@ -5,7 +5,6 @@ import (
 
 	"github.com/pkg/errors"
 
-	"github.com/MichaelMure/git-bug/entities/identity"
 	"github.com/MichaelMure/git-bug/entity"
 	"github.com/MichaelMure/git-bug/entity/dag"
 	"github.com/MichaelMure/git-bug/repository"
@@ -107,7 +106,7 @@ func (op *EditCommentOperation) Validate() error {
 	return nil
 }
 
-func NewEditCommentOp(author identity.Interface, unixTime int64, target entity.Id, message string, files []repository.Hash) *EditCommentOperation {
+func NewEditCommentOp(author entity.Identity, unixTime int64, target entity.Id, message string, files []repository.Hash) *EditCommentOperation {
 	return &EditCommentOperation{
 		OpBase:  dag.NewOpBase(EditCommentOp, author, unixTime),
 		Target:  target,
@@ -117,7 +116,7 @@ func NewEditCommentOp(author identity.Interface, unixTime int64, target entity.I
 }
 
 // EditComment is a convenience function to apply the operation
-func EditComment(b Interface, author identity.Interface, unixTime int64, target entity.Id, message string, files []repository.Hash, metadata map[string]string) (entity.CombinedId, *EditCommentOperation, error) {
+func EditComment(b Interface, author entity.Identity, unixTime int64, target entity.Id, message string, files []repository.Hash, metadata map[string]string) (entity.CombinedId, *EditCommentOperation, error) {
 	op := NewEditCommentOp(author, unixTime, target, message, files)
 	for key, val := range metadata {
 		op.SetMetadata(key, val)
@@ -130,7 +129,7 @@ func EditComment(b Interface, author identity.Interface, unixTime int64, target
 }
 
 // EditCreateComment is a convenience function to edit the body of a bug (the first comment)
-func EditCreateComment(b Interface, author identity.Interface, unixTime int64, message string, files []repository.Hash, metadata map[string]string) (entity.CombinedId, *EditCommentOperation, error) {
+func EditCreateComment(b Interface, author entity.Identity, unixTime int64, message string, files []repository.Hash, metadata map[string]string) (entity.CombinedId, *EditCommentOperation, error) {
 	createOp := b.FirstOp().(*CreateOperation)
 	return EditComment(b, author, unixTime, createOp.Id(), message, files, metadata)
 }
diff --git a/entities/bug/op_edit_comment_test.go b/entities/bug/op_edit_comment_test.go
index 2ca1345e..76a68f30 100644
--- a/entities/bug/op_edit_comment_test.go
+++ b/entities/bug/op_edit_comment_test.go
@@ -76,10 +76,10 @@ func TestEdit(t *testing.T) {
 }
 
 func TestEditCommentSerialize(t *testing.T) {
-	dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author identity.Interface, unixTime int64) (*EditCommentOperation, entity.Resolvers) {
+	dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author entity.Identity, unixTime int64) (*EditCommentOperation, entity.Resolvers) {
 		return NewEditCommentOp(author, unixTime, "target", "message", nil), nil
 	})
-	dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author identity.Interface, unixTime int64) (*EditCommentOperation, entity.Resolvers) {
+	dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author entity.Identity, unixTime int64) (*EditCommentOperation, entity.Resolvers) {
 		return NewEditCommentOp(author, unixTime, "target", "message", []repository.Hash{"hash1", "hash2"}), nil
 	})
 }
diff --git a/entities/bug/op_label_change.go b/entities/bug/op_label_change.go
index 0d13fe9e..816effc8 100644
--- a/entities/bug/op_label_change.go
+++ b/entities/bug/op_label_change.go
@@ -8,7 +8,6 @@ import (
 
 	"github.com/pkg/errors"
 
-	"github.com/MichaelMure/git-bug/entities/identity"
 	"github.com/MichaelMure/git-bug/entity"
 	"github.com/MichaelMure/git-bug/entity/dag"
 	"github.com/MichaelMure/git-bug/util/timestamp"
@@ -96,7 +95,7 @@ func (op *LabelChangeOperation) Validate() error {
 	return nil
 }
 
-func NewLabelChangeOperation(author identity.Interface, unixTime int64, added, removed []Label) *LabelChangeOperation {
+func NewLabelChangeOperation(author entity.Identity, unixTime int64, added, removed []Label) *LabelChangeOperation {
 	return &LabelChangeOperation{
 		OpBase:  dag.NewOpBase(LabelChangeOp, author, unixTime),
 		Added:   added,
@@ -106,7 +105,7 @@ func NewLabelChangeOperation(author identity.Interface, unixTime int64, added, r
 
 type LabelChangeTimelineItem struct {
 	combinedId entity.CombinedId
-	Author     identity.Interface
+	Author     entity.Identity
 	UnixTime   timestamp.Timestamp
 	Added      []Label
 	Removed    []Label
@@ -120,7 +119,7 @@ func (l LabelChangeTimelineItem) CombinedId() entity.CombinedId {
 func (l *LabelChangeTimelineItem) IsAuthored() {}
 
 // ChangeLabels is a convenience function to change labels on a bug
-func ChangeLabels(b Interface, author identity.Interface, unixTime int64, add, remove []string, metadata map[string]string) ([]LabelChangeResult, *LabelChangeOperation, error) {
+func ChangeLabels(b Interface, author entity.Identity, unixTime int64, add, remove []string, metadata map[string]string) ([]LabelChangeResult, *LabelChangeOperation, error) {
 	var added, removed []Label
 	var results []LabelChangeResult
 
@@ -186,7 +185,7 @@ func ChangeLabels(b Interface, author identity.Interface, unixTime int64, add, r
 // responsible for what you are doing. In the general case, you want to use ChangeLabels instead.
 // The intended use of this function is to allow importers to create legal but unexpected label changes,
 // like removing a label with no information of when it was added before.
-func ForceChangeLabels(b Interface, author identity.Interface, unixTime int64, add, remove []string, metadata map[string]string) (*LabelChangeOperation, error) {
+func ForceChangeLabels(b Interface, author entity.Identity, unixTime int64, add, remove []string, metadata map[string]string) (*LabelChangeOperation, error) {
 	added := make([]Label, len(add))
 	for i, str := range add {
 		added[i] = Label(str)
diff --git a/entities/bug/op_label_change_test.go b/entities/bug/op_label_change_test.go
index e6dc8803..c27e906d 100644
--- a/entities/bug/op_label_change_test.go
+++ b/entities/bug/op_label_change_test.go
@@ -3,19 +3,18 @@ package bug
 import (
 	"testing"
 
-	"github.com/MichaelMure/git-bug/entities/identity"
 	"github.com/MichaelMure/git-bug/entity"
 	"github.com/MichaelMure/git-bug/entity/dag"
 )
 
 func TestLabelChangeSerialize(t *testing.T) {
-	dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author identity.Interface, unixTime int64) (*LabelChangeOperation, entity.Resolvers) {
+	dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author entity.Identity, unixTime int64) (*LabelChangeOperation, entity.Resolvers) {
 		return NewLabelChangeOperation(author, unixTime, []Label{"added"}, []Label{"removed"}), nil
 	})
-	dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author identity.Interface, unixTime int64) (*LabelChangeOperation, entity.Resolvers) {
+	dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author entity.Identity, unixTime int64) (*LabelChangeOperation, entity.Resolvers) {
 		return NewLabelChangeOperation(author, unixTime, []Label{"added"}, nil), nil
 	})
-	dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author identity.Interface, unixTime int64) (*LabelChangeOperation, entity.Resolvers) {
+	dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author entity.Identity, unixTime int64) (*LabelChangeOperation, entity.Resolvers) {
 		return NewLabelChangeOperation(author, unixTime, nil, []Label{"removed"}), nil
 	})
 }
diff --git a/entities/bug/op_set_metadata.go b/entities/bug/op_set_metadata.go
index b4aab78c..9d4f01f6 100644
--- a/entities/bug/op_set_metadata.go
+++ b/entities/bug/op_set_metadata.go
@@ -1,17 +1,16 @@
 package bug
 
 import (
-	"github.com/MichaelMure/git-bug/entities/identity"
 	"github.com/MichaelMure/git-bug/entity"
 	"github.com/MichaelMure/git-bug/entity/dag"
 )
 
-func NewSetMetadataOp(author identity.Interface, unixTime int64, target entity.Id, newMetadata map[string]string) *dag.SetMetadataOperation[*Snapshot] {
+func NewSetMetadataOp(author entity.Identity, unixTime int64, target entity.Id, newMetadata map[string]string) *dag.SetMetadataOperation[*Snapshot] {
 	return dag.NewSetMetadataOp[*Snapshot](SetMetadataOp, author, unixTime, target, newMetadata)
 }
 
 // SetMetadata is a convenience function to add metadata on another operation
-func SetMetadata(b Interface, author identity.Interface, unixTime int64, target entity.Id, newMetadata map[string]string) (*dag.SetMetadataOperation[*Snapshot], error) {
+func SetMetadata(b Interface, author entity.Identity, unixTime int64, target entity.Id, newMetadata map[string]string) (*dag.SetMetadataOperation[*Snapshot], error) {
 	op := NewSetMetadataOp(author, unixTime, target, newMetadata)
 	if err := op.Validate(); err != nil {
 		return nil, err
diff --git a/entities/bug/op_set_status.go b/entities/bug/op_set_status.go
index 68199129..0fdce8a6 100644
--- a/entities/bug/op_set_status.go
+++ b/entities/bug/op_set_status.go
@@ -4,7 +4,6 @@ import (
 	"github.com/pkg/errors"
 
 	"github.com/MichaelMure/git-bug/entities/common"
-	"github.com/MichaelMure/git-bug/entities/identity"
 	"github.com/MichaelMure/git-bug/entity"
 	"github.com/MichaelMure/git-bug/entity/dag"
 	"github.com/MichaelMure/git-bug/util/timestamp"
@@ -50,7 +49,7 @@ func (op *SetStatusOperation) Validate() error {
 	return nil
 }
 
-func NewSetStatusOp(author identity.Interface, unixTime int64, status common.Status) *SetStatusOperation {
+func NewSetStatusOp(author entity.Identity, unixTime int64, status common.Status) *SetStatusOperation {
 	return &SetStatusOperation{
 		OpBase: dag.NewOpBase(SetStatusOp, author, unixTime),
 		Status: status,
@@ -59,7 +58,7 @@ func NewSetStatusOp(author identity.Interface, unixTime int64, status common.Sta
 
 type SetStatusTimelineItem struct {
 	combinedId entity.CombinedId
-	Author     identity.Interface
+	Author     entity.Identity
 	UnixTime   timestamp.Timestamp
 	Status     common.Status
 }
@@ -72,7 +71,7 @@ func (s SetStatusTimelineItem) CombinedId() entity.CombinedId {
 func (s *SetStatusTimelineItem) IsAuthored() {}
 
 // Open is a convenience function to change a bugs state to Open
-func Open(b Interface, author identity.Interface, unixTime int64, metadata map[string]string) (*SetStatusOperation, error) {
+func Open(b Interface, author entity.Identity, unixTime int64, metadata map[string]string) (*SetStatusOperation, error) {
 	op := NewSetStatusOp(author, unixTime, common.OpenStatus)
 	for key, value := range metadata {
 		op.SetMetadata(key, value)
@@ -85,7 +84,7 @@ func Open(b Interface, author identity.Interface, unixTime int64, metadata map[s
 }
 
 // Close is a convenience function to change a bugs state to Close
-func Close(b Interface, author identity.Interface, unixTime int64, metadata map[string]string) (*SetStatusOperation, error) {
+func Close(b Interface, author entity.Identity, unixTime int64, metadata map[string]string) (*SetStatusOperation, error) {
 	op := NewSetStatusOp(author, unixTime, common.ClosedStatus)
 	for key, value := range metadata {
 		op.SetMetadata(key, value)
diff --git a/entities/bug/op_set_status_test.go b/entities/bug/op_set_status_test.go
index 0f6d358a..0670c7ce 100644
--- a/entities/bug/op_set_status_test.go
+++ b/entities/bug/op_set_status_test.go
@@ -4,13 +4,12 @@ import (
 	"testing"
 
 	"github.com/MichaelMure/git-bug/entities/common"
-	"github.com/MichaelMure/git-bug/entities/identity"
 	"github.com/MichaelMure/git-bug/entity"
 	"github.com/MichaelMure/git-bug/entity/dag"
 )
 
 func TestSetStatusSerialize(t *testing.T) {
-	dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author identity.Interface, unixTime int64) (*SetStatusOperation, entity.Resolvers) {
+	dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author entity.Identity, unixTime int64) (*SetStatusOperation, entity.Resolvers) {
 		return NewSetStatusOp(author, unixTime, common.ClosedStatus), nil
 	})
 }
diff --git a/entities/bug/op_set_title.go b/entities/bug/op_set_title.go
index 6e445aa6..60da2eae 100644
--- a/entities/bug/op_set_title.go
+++ b/entities/bug/op_set_title.go
@@ -3,7 +3,6 @@ package bug
 import (
 	"fmt"
 
-	"github.com/MichaelMure/git-bug/entities/identity"
 	"github.com/MichaelMure/git-bug/entity"
 	"github.com/MichaelMure/git-bug/entity/dag"
 	"github.com/MichaelMure/git-bug/util/timestamp"
@@ -60,7 +59,7 @@ func (op *SetTitleOperation) Validate() error {
 	return nil
 }
 
-func NewSetTitleOp(author identity.Interface, unixTime int64, title string, was string) *SetTitleOperation {
+func NewSetTitleOp(author entity.Identity, unixTime int64, title string, was string) *SetTitleOperation {
 	return &SetTitleOperation{
 		OpBase: dag.NewOpBase(SetTitleOp, author, unixTime),
 		Title:  title,
@@ -70,7 +69,7 @@ func NewSetTitleOp(author identity.Interface, unixTime int64, title string, was
 
 type SetTitleTimelineItem struct {
 	combinedId entity.CombinedId
-	Author     identity.Interface
+	Author     entity.Identity
 	UnixTime   timestamp.Timestamp
 	Title      string
 	Was        string
@@ -84,7 +83,7 @@ func (s SetTitleTimelineItem) CombinedId() entity.CombinedId {
 func (s *SetTitleTimelineItem) IsAuthored() {}
 
 // SetTitle is a convenience function to change a bugs title
-func SetTitle(b Interface, author identity.Interface, unixTime int64, title string, metadata map[string]string) (*SetTitleOperation, error) {
+func SetTitle(b Interface, author entity.Identity, unixTime int64, title string, metadata map[string]string) (*SetTitleOperation, error) {
 	var lastTitleOp *SetTitleOperation
 	for _, op := range b.Operations() {
 		switch op := op.(type) {
diff --git a/entities/bug/op_set_title_test.go b/entities/bug/op_set_title_test.go
index 82425ab4..4d59f4fb 100644
--- a/entities/bug/op_set_title_test.go
+++ b/entities/bug/op_set_title_test.go
@@ -3,13 +3,12 @@ package bug
 import (
 	"testing"
 
-	"github.com/MichaelMure/git-bug/entities/identity"
 	"github.com/MichaelMure/git-bug/entity"
 	"github.com/MichaelMure/git-bug/entity/dag"
 )
 
 func TestSetTitleSerialize(t *testing.T) {
-	dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author identity.Interface, unixTime int64) (*SetTitleOperation, entity.Resolvers) {
+	dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author entity.Identity, unixTime int64) (*SetTitleOperation, entity.Resolvers) {
 		return NewSetTitleOp(author, unixTime, "title", "was"), nil
 	})
 }
diff --git a/entities/bug/snapshot.go b/entities/bug/snapshot.go
index 9dbc7862..b458771b 100644
--- a/entities/bug/snapshot.go
+++ b/entities/bug/snapshot.go
@@ -5,7 +5,6 @@ import (
 	"time"
 
 	"github.com/MichaelMure/git-bug/entities/common"
-	"github.com/MichaelMure/git-bug/entities/identity"
 	"github.com/MichaelMure/git-bug/entity"
 )
 
@@ -19,9 +18,9 @@ type Snapshot struct {
 	Title        string
 	Comments     []Comment
 	Labels       []Label
-	Author       identity.Interface
-	Actors       []identity.Interface
-	Participants []identity.Interface
+	Author       entity.Identity
+	Actors       []entity.Identity
+	Participants []entity.Identity
 	CreateTime   time.Time
 
 	Timeline []TimelineItem
@@ -94,7 +93,7 @@ func (snap *Snapshot) SearchCommentByOpId(id entity.Id) (*Comment, error) {
 }
 
 // append the operation author to the actors list
-func (snap *Snapshot) addActor(actor identity.Interface) {
+func (snap *Snapshot) addActor(actor entity.Identity) {
 	for _, a := range snap.Actors {
 		if actor.Id() == a.Id() {
 			return
@@ -105,7 +104,7 @@ func (snap *Snapshot) addActor(actor identity.Interface) {
 }
 
 // append the operation author to the participants list
-func (snap *Snapshot) addParticipant(participant identity.Interface) {
+func (snap *Snapshot) addParticipant(participant entity.Identity) {
 	for _, p := range snap.Participants {
 		if participant.Id() == p.Id() {
 			return
diff --git a/entities/bug/timeline.go b/entities/bug/timeline.go
index 84ece262..3c425777 100644
--- a/entities/bug/timeline.go
+++ b/entities/bug/timeline.go
@@ -3,7 +3,6 @@ package bug
 import (
 	"strings"
 
-	"github.com/MichaelMure/git-bug/entities/identity"
 	"github.com/MichaelMure/git-bug/entity"
 	"github.com/MichaelMure/git-bug/repository"
 	"github.com/MichaelMure/git-bug/util/timestamp"
@@ -18,7 +17,7 @@ type TimelineItem interface {
 type CommentHistoryStep struct {
 	// The author of the edition, not necessarily the same as the author of the
 	// original comment
-	Author identity.Interface
+	Author entity.Identity
 	// The new message
 	Message  string
 	UnixTime timestamp.Timestamp
@@ -27,7 +26,7 @@ type CommentHistoryStep struct {
 // CommentTimelineItem is a TimelineItem that holds a Comment and its edition history
 type CommentTimelineItem struct {
 	combinedId entity.CombinedId
-	Author     identity.Interface
+	Author     entity.Identity
 	Message    string
 	Files      []repository.Hash
 	CreatedAt  timestamp.Timestamp
diff --git a/entities/identity/common.go b/entities/identity/common.go
index 88e30e33..749def63 100644
--- a/entities/identity/common.go
+++ b/entities/identity/common.go
@@ -3,6 +3,8 @@ package identity
 import (
 	"encoding/json"
 	"fmt"
+
+	bootstrap "github.com/MichaelMure/git-bug/entity/boostrap"
 )
 
 // Custom unmarshaling function to allow package user to delegate
@@ -10,7 +12,7 @@ import (
 // and a Bare.
 //
 // If the given message has a "id" field, it's considered being a proper Identity.
-func UnmarshalJSON(raw json.RawMessage) (Interface, error) {
+func UnmarshalJSON(raw json.RawMessage) (bootstrap.Identity, error) {
 	aux := &IdentityStub{}
 
 	// First try to decode and load as a normal Identity
diff --git a/entities/identity/identity.go b/entities/identity/identity.go
index 9effd146..f71d0e4a 100644
--- a/entities/identity/identity.go
+++ b/entities/identity/identity.go
@@ -8,6 +8,7 @@ import (
 
 	"github.com/pkg/errors"
 
+	"github.com/MichaelMure/git-bug/entity"
 	bootstrap "github.com/MichaelMure/git-bug/entity/boostrap"
 	"github.com/MichaelMure/git-bug/repository"
 	"github.com/MichaelMure/git-bug/util/lamport"
@@ -28,7 +29,7 @@ var ErrNoIdentitySet = errors.New("No identity is set.\n" +
 	"\"git bug user new\" or adopted with \"git bug user adopt\"")
 var ErrMultipleIdentitiesSet = errors.New("multiple user identities set")
 
-var _ Interface = &Identity{}
+var _ entity.Identity = &Identity{}
 var _ bootstrap.Entity = &Identity{}
 
 type Identity struct {
@@ -40,7 +41,7 @@ func NewIdentity(repo repository.RepoClock, name string, email string) (*Identit
 	return NewIdentityFull(repo, name, email, "", "", nil)
 }
 
-func NewIdentityFull(repo repository.RepoClock, name string, email string, login string, avatarUrl string, keys []*Key) (*Identity, error) {
+func NewIdentityFull(repo repository.RepoClock, name string, email string, login string, avatarUrl string, keys []bootstrap.Key) (*Identity, error) {
 	v, err := newVersion(repo, name, email, login, avatarUrl, keys)
 	if err != nil {
 		return nil, err
@@ -216,13 +217,13 @@ type Mutator struct {
 	Login     string
 	Email     string
 	AvatarUrl string
-	Keys      []*Key
+	Keys      []bootstrap.Key
 }
 
 // Mutate allow to create a new version of the Identity in one go
 func (i *Identity) Mutate(repo repository.RepoClock, f func(orig *Mutator)) error {
-	copyKeys := func(keys []*Key) []*Key {
-		result := make([]*Key, len(keys))
+	copyKeys := func(keys []bootstrap.Key) []bootstrap.Key {
+		result := make([]bootstrap.Key, len(keys))
 		for i, key := range keys {
 			result[i] = key.Clone()
 		}
@@ -466,15 +467,15 @@ func (i *Identity) AvatarUrl() string {
 }
 
 // Keys return the last version of the valid keys
-func (i *Identity) Keys() []*Key {
+func (i *Identity) Keys() []bootstrap.Key {
 	return i.lastVersion().keys
 }
 
 // SigningKey return the key that should be used to sign new messages. If no key is available, return nil.
-func (i *Identity) SigningKey(repo repository.RepoKeyring) (*Key, error) {
+func (i *Identity) SigningKey(repo repository.RepoKeyring) (bootstrap.Key, error) {
 	keys := i.Keys()
 	for _, key := range keys {
-		err := key.ensurePrivateKey(repo)
+		err := key.EnsurePrivateKey(repo)
 		if err == errNoPrivateKey {
 			continue
 		}
@@ -487,8 +488,8 @@ func (i *Identity) SigningKey(repo repository.RepoKeyring) (*Key, error) {
 }
 
 // ValidKeysAtTime return the set of keys valid at a given lamport time
-func (i *Identity) ValidKeysAtTime(clockName string, time lamport.Time) []*Key {
-	var result []*Key
+func (i *Identity) ValidKeysAtTime(clockName string, time lamport.Time) []bootstrap.Key {
+	var result []bootstrap.Key
 
 	var lastTime lamport.Time
 	for _, v := range i.versions {
diff --git a/entities/identity/identity_stub.go b/entities/identity/identity_stub.go
index 67a9db53..94208da1 100644
--- a/entities/identity/identity_stub.go
+++ b/entities/identity/identity_stub.go
@@ -9,7 +9,7 @@ import (
 	"github.com/MichaelMure/git-bug/util/timestamp"
 )
 
-var _ Interface = &IdentityStub{}
+var _ bootstrap.Identity = &IdentityStub{}
 
 // IdentityStub is an almost empty Identity, holding only the id.
 // When a normal Identity is serialized into JSON, only the id is serialized.
@@ -68,15 +68,15 @@ func (IdentityStub) AvatarUrl() string {
 	panic("identities needs to be properly loaded with identity.ReadLocal()")
 }
 
-func (IdentityStub) Keys() []*Key {
+func (IdentityStub) Keys() []bootstrap.Key {
 	panic("identities needs to be properly loaded with identity.ReadLocal()")
 }
 
-func (i *IdentityStub) SigningKey(repo repository.RepoKeyring) (*Key, error) {
+func (i *IdentityStub) SigningKey(repo repository.RepoKeyring) (bootstrap.Key, error) {
 	panic("identities needs to be properly loaded with identity.ReadLocal()")
 }
 
-func (IdentityStub) ValidKeysAtTime(_ string, _ lamport.Time) []*Key {
+func (IdentityStub) ValidKeysAtTime(_ string, _ lamport.Time) []bootstrap.Key {
 	panic("identities needs to be properly loaded with identity.ReadLocal()")
 }
 
diff --git a/entities/identity/identity_test.go b/entities/identity/identity_test.go
index e4ecfbe9..3da0c83e 100644
--- a/entities/identity/identity_test.go
+++ b/entities/identity/identity_test.go
@@ -37,18 +37,18 @@ func TestIdentityCommitLoad(t *testing.T) {
 
 	// multiple versions
 
-	identity, err = NewIdentityFull(repo, "René Descartes", "rene.descartes@example.com", "", "", []*Key{generatePublicKey()})
+	identity, err = NewIdentityFull(repo, "René Descartes", "rene.descartes@example.com", "", "", []bootstrap.Key{generatePublicKey()})
 	require.NoError(t, err)
 
 	idBeforeCommit = identity.Id()
 
 	err = identity.Mutate(repo, func(orig *Mutator) {
-		orig.Keys = []*Key{generatePublicKey()}
+		orig.Keys = []bootstrap.Key{generatePublicKey()}
 	})
 	require.NoError(t, err)
 
 	err = identity.Mutate(repo, func(orig *Mutator) {
-		orig.Keys = []*Key{generatePublicKey()}
+		orig.Keys = []bootstrap.Key{generatePublicKey()}
 	})
 	require.NoError(t, err)
 
@@ -71,13 +71,13 @@ func TestIdentityCommitLoad(t *testing.T) {
 
 	err = identity.Mutate(repo, func(orig *Mutator) {
 		orig.Email = "rene@descartes.com"
-		orig.Keys = []*Key{generatePublicKey()}
+		orig.Keys = []bootstrap.Key{generatePublicKey()}
 	})
 	require.NoError(t, err)
 
 	err = identity.Mutate(repo, func(orig *Mutator) {
 		orig.Email = "rene@descartes.com"
-		orig.Keys = []*Key{generatePublicKey(), generatePublicKey()}
+		orig.Keys = []bootstrap.Key{generatePublicKey(), generatePublicKey()}
 	})
 	require.NoError(t, err)
 
@@ -134,35 +134,35 @@ func TestIdentity_ValidKeysAtTime(t *testing.T) {
 		versions: []*version{
 			{
 				times: map[string]lamport.Time{"foo": 100},
-				keys:  []*Key{pubKeyA},
+				keys:  []bootstrap.Key{pubKeyA},
 			},
 			{
 				times: map[string]lamport.Time{"foo": 200},
-				keys:  []*Key{pubKeyB},
+				keys:  []bootstrap.Key{pubKeyB},
 			},
 			{
 				times: map[string]lamport.Time{"foo": 201},
-				keys:  []*Key{pubKeyC},
+				keys:  []bootstrap.Key{pubKeyC},
 			},
 			{
 				times: map[string]lamport.Time{"foo": 201},
-				keys:  []*Key{pubKeyD},
+				keys:  []bootstrap.Key{pubKeyD},
 			},
 			{
 				times: map[string]lamport.Time{"foo": 300},
-				keys:  []*Key{pubKeyE},
+				keys:  []bootstrap.Key{pubKeyE},
 			},
 		},
 	}
 
 	require.Nil(t, identity.ValidKeysAtTime("foo", 10))
-	require.Equal(t, identity.ValidKeysAtTime("foo", 100), []*Key{pubKeyA})
-	require.Equal(t, identity.ValidKeysAtTime("foo", 140), []*Key{pubKeyA})
-	require.Equal(t, identity.ValidKeysAtTime("foo", 200), []*Key{pubKeyB})
-	require.Equal(t, identity.ValidKeysAtTime("foo", 201), []*Key{pubKeyD})
-	require.Equal(t, identity.ValidKeysAtTime("foo", 202), []*Key{pubKeyD})
-	require.Equal(t, identity.ValidKeysAtTime("foo", 300), []*Key{pubKeyE})
-	require.Equal(t, identity.ValidKeysAtTime("foo", 3000), []*Key{pubKeyE})
+	require.Equal(t, identity.ValidKeysAtTime("foo", 100), []bootstrap.Key{pubKeyA})
+	require.Equal(t, identity.ValidKeysAtTime("foo", 140), []bootstrap.Key{pubKeyA})
+	require.Equal(t, identity.ValidKeysAtTime("foo", 200), []bootstrap.Key{pubKeyB})
+	require.Equal(t, identity.ValidKeysAtTime("foo", 201), []bootstrap.Key{pubKeyD})
+	require.Equal(t, identity.ValidKeysAtTime("foo", 202), []bootstrap.Key{pubKeyD})
+	require.Equal(t, identity.ValidKeysAtTime("foo", 300), []bootstrap.Key{pubKeyE})
+	require.Equal(t, identity.ValidKeysAtTime("foo", 3000), []bootstrap.Key{pubKeyE})
 }
 
 // Test the immutable or mutable metadata search
@@ -235,7 +235,7 @@ func TestJSON(t *testing.T) {
 	require.NoError(t, err)
 
 	// deserialize, got a IdentityStub with the same id
-	var i Interface
+	var i bootstrap.Identity
 	i, err = UnmarshalJSON(data)
 	require.NoError(t, err)
 	require.Equal(t, identity.Id(), i.Id())
diff --git a/entities/identity/key.go b/entities/identity/key.go
index 87271dd5..b9bcc8c3 100644
--- a/entities/identity/key.go
+++ b/entities/identity/key.go
@@ -13,6 +13,7 @@ import (
 	"github.com/ProtonMail/go-crypto/openpgp/packet"
 	"github.com/pkg/errors"
 
+	bootstrap "github.com/MichaelMure/git-bug/entity/boostrap"
 	"github.com/MichaelMure/git-bug/repository"
 )
 
@@ -75,7 +76,7 @@ func (k *Key) Validate() error {
 	return nil
 }
 
-func (k *Key) Clone() *Key {
+func (k *Key) Clone() bootstrap.Key {
 	clone := &Key{}
 
 	pub := *k.public
@@ -185,9 +186,9 @@ func (k *Key) loadPrivate(repo repository.RepoKeyring) error {
 	return nil
 }
 
-// ensurePrivateKey attempt to load the corresponding private key if it is not loaded already.
+// EnsurePrivateKey attempt to load the corresponding private key if it is not loaded already.
 // If no private key is found, returns errNoPrivateKey
-func (k *Key) ensurePrivateKey(repo repository.RepoKeyring) error {
+func (k *Key) EnsurePrivateKey(repo repository.RepoKeyring) error {
 	if k.private != nil {
 		return nil
 	}
diff --git a/entities/identity/key_test.go b/entities/identity/key_test.go
index 6e320dc2..88301a1e 100644
--- a/entities/identity/key_test.go
+++ b/entities/identity/key_test.go
@@ -43,7 +43,7 @@ func TestStoreLoad(t *testing.T) {
 	err = json.Unmarshal(dataJSON, &read)
 	require.NoError(t, err)
 
-	err = read.ensurePrivateKey(repo)
+	err = read.EnsurePrivateKey(repo)
 	require.NoError(t, err)
 
 	require.Equal(t, k.public, read.public)
diff --git a/entities/identity/version.go b/entities/identity/version.go
index 2e30ead7..3f0a22d9 100644
--- a/entities/identity/version.go
+++ b/entities/identity/version.go
@@ -33,7 +33,7 @@ type version struct {
 	// The set of keys valid at that time, from this version onward, until they get removed
 	// in a new version. This allows to have multiple key for the same identity (e.g. one per
 	// device) as well as revoke key.
-	keys []*Key
+	keys []bootstrap.Key
 
 	// mandatory random bytes to ensure a better randomness of the data of the first
 	// version of an identity, used to later generate the ID
@@ -51,7 +51,7 @@ type version struct {
 	commitHash repository.Hash
 }
 
-func newVersion(repo repository.RepoClock, name string, email string, login string, avatarURL string, keys []*Key) (*version, error) {
+func newVersion(repo repository.RepoClock, name string, email string, login string, avatarURL string, keys []bootstrap.Key) (*version, error) {
 	clocks, err := repo.AllClocks()
 	if err != nil {
 		return nil, err
@@ -123,7 +123,7 @@ func (v *version) Clone() *version {
 		clone.times[name] = t
 	}
 
-	clone.keys = make([]*Key, len(v.keys))
+	clone.keys = make([]bootstrap.Key, len(v.keys))
 	for i, key := range v.keys {
 		clone.keys[i] = key.Clone()
 	}
diff --git a/entities/identity/version_test.go b/entities/identity/version_test.go
index 2ad43f3d..3b45ba21 100644
--- a/entities/identity/version_test.go
+++ b/entities/identity/version_test.go
@@ -32,7 +32,7 @@ func makeIdentityTestRepo(t *testing.T) repository.ClockedRepo {
 func TestVersionJSON(t *testing.T) {
 	repo := makeIdentityTestRepo(t)
 
-	keys := []*Key{
+	keys := []bootstrap.Key{
 		generatePublicKey(),
 		generatePublicKey(),
 	}
diff --git a/entities/identity/interface.go b/entity/boostrap/identity.go
similarity index 73%
rename from entities/identity/interface.go
rename to entity/boostrap/identity.go
index 88234341..92fb03bf 100644
--- a/entities/identity/interface.go
+++ b/entity/boostrap/identity.go
@@ -1,14 +1,32 @@
-package identity
+package bootstrap
 
 import (
-	bootstrap "github.com/MichaelMure/git-bug/entity/boostrap"
+	"fmt"
+
+	"github.com/ProtonMail/go-crypto/openpgp"
+	"github.com/ProtonMail/go-crypto/openpgp/packet"
+
 	"github.com/MichaelMure/git-bug/repository"
 	"github.com/MichaelMure/git-bug/util/lamport"
 	"github.com/MichaelMure/git-bug/util/timestamp"
 )
 
-type Interface interface {
-	bootstrap.Entity
+var ErrNoPrivateKey = fmt.Errorf("no private key")
+
+type Key interface {
+	Public() *packet.PublicKey
+	Private() *packet.PrivateKey
+	Validate() error
+	Clone() Key
+	PGPEntity() *openpgp.Entity
+
+	// EnsurePrivateKey attempt to load the corresponding private key if it is not loaded already.
+	// If no private key is found, returns ErrNoPrivateKey
+	EnsurePrivateKey(repo repository.RepoKeyring) error
+}
+
+type Identity interface {
+	Entity
 
 	// Name return the last version of the name
 	// Can be empty.
@@ -35,14 +53,14 @@ type Interface interface {
 
 	// Keys return the last version of the valid keys
 	// Can be empty.
-	Keys() []*Key
+	Keys() []Key
 
 	// SigningKey return the key that should be used to sign new messages. If no key is available, return nil.
-	SigningKey(repo repository.RepoKeyring) (*Key, error)
+	SigningKey(repo repository.RepoKeyring) (Key, error)
 
 	// ValidKeysAtTime return the set of keys valid at a given lamport time for a given clock of another entity
 	// Can be empty.
-	ValidKeysAtTime(clockName string, time lamport.Time) []*Key
+	ValidKeysAtTime(clockName string, time lamport.Time) []Key
 
 	// LastModification return the timestamp at which the last version of the identity became valid.
 	LastModification() timestamp.Timestamp
diff --git a/entity/dag/common_test.go b/entity/dag/common_test.go
index a5cc4009..0ca2736d 100644
--- a/entity/dag/common_test.go
+++ b/entity/dag/common_test.go
@@ -30,7 +30,7 @@ type op1 struct {
 	Files  []repository.Hash `json:"files"`
 }
 
-func newOp1(author identity.Interface, field1 string, files ...repository.Hash) *op1 {
+func newOp1(author entity.Identity, field1 string, files ...repository.Hash) *op1 {
 	return &op1{OpBase: NewOpBase(Op1, author, 0), Field1: field1, Files: files}
 }
 
@@ -49,7 +49,7 @@ type op2 struct {
 	Field2 string `json:"field_2"`
 }
 
-func newOp2(author identity.Interface, field2 string) *op2 {
+func newOp2(author entity.Identity, field2 string) *op2 {
 	return &op2{OpBase: NewOpBase(Op2, author, 0), Field2: field2}
 }
 
@@ -103,13 +103,13 @@ func wrapper(e *Entity) *Foo {
   Identities + repo + definition
 */
 
-func makeTestContext() (repository.ClockedRepo, identity.Interface, identity.Interface, entity.Resolvers, Definition) {
+func makeTestContext() (repository.ClockedRepo, entity.Identity, entity.Identity, entity.Resolvers, Definition) {
 	repo := repository.NewMockRepo()
 	id1, id2, resolvers, def := makeTestContextInternal(repo)
 	return repo, id1, id2, resolvers, def
 }
 
-func makeTestContextRemote(t *testing.T) (repository.ClockedRepo, repository.ClockedRepo, repository.ClockedRepo, identity.Interface, identity.Interface, entity.Resolvers, Definition) {
+func makeTestContextRemote(t *testing.T) (repository.ClockedRepo, repository.ClockedRepo, repository.ClockedRepo, entity.Identity, entity.Identity, entity.Resolvers, Definition) {
 	repoA := repository.CreateGoGitTestRepo(t, false)
 	repoB := repository.CreateGoGitTestRepo(t, false)
 	remote := repository.CreateGoGitTestRepo(t, true)
@@ -134,7 +134,7 @@ func makeTestContextRemote(t *testing.T) (repository.ClockedRepo, repository.Clo
 	return repoA, repoB, remote, id1, id2, resolver, def
 }
 
-func makeTestContextInternal(repo repository.ClockedRepo) (identity.Interface, identity.Interface, entity.Resolvers, Definition) {
+func makeTestContextInternal(repo repository.ClockedRepo) (entity.Identity, entity.Identity, entity.Resolvers, Definition) {
 	id1, err := identity.NewIdentity(repo, "name1", "email1")
 	if err != nil {
 		panic(err)
diff --git a/entity/dag/entity.go b/entity/dag/entity.go
index 7c5c4d3c..65dae504 100644
--- a/entity/dag/entity.go
+++ b/entity/dag/entity.go
@@ -9,7 +9,6 @@ import (
 
 	"github.com/pkg/errors"
 
-	"github.com/MichaelMure/git-bug/entities/identity"
 	"github.com/MichaelMure/git-bug/entity"
 	bootstrap "github.com/MichaelMure/git-bug/entity/boostrap"
 	"github.com/MichaelMure/git-bug/repository"
@@ -455,7 +454,7 @@ func (e *Entity) Commit(repo repository.ClockedRepo) error {
 	}
 
 	for len(e.staging) > 0 {
-		var author identity.Interface
+		var author entity.Identity
 		var toCommit []Operation
 
 		// Split into chunks with the same author
diff --git a/entity/dag/entity_actions.go b/entity/dag/entity_actions.go
index 3c3f819f..5afca124 100644
--- a/entity/dag/entity_actions.go
+++ b/entity/dag/entity_actions.go
@@ -5,7 +5,6 @@ import (
 
 	"github.com/pkg/errors"
 
-	"github.com/MichaelMure/git-bug/entities/identity"
 	"github.com/MichaelMure/git-bug/entity"
 	"github.com/MichaelMure/git-bug/repository"
 )
@@ -32,7 +31,7 @@ func Push(def Definition, repo repository.Repo, remote string) (string, error) {
 
 // Pull will do a Fetch + MergeAll
 // Contrary to MergeAll, this function will return an error if a merge fail.
-func Pull[EntityT entity.Bare](def Definition, wrapper func(e *Entity) EntityT, repo repository.ClockedRepo, resolvers entity.Resolvers, remote string, author identity.Interface) error {
+func Pull[EntityT entity.Bare](def Definition, wrapper func(e *Entity) EntityT, repo repository.ClockedRepo, resolvers entity.Resolvers, remote string, author entity.Identity) error {
 	_, err := Fetch(def, repo, remote)
 	if err != nil {
 		return err
@@ -68,7 +67,7 @@ func Pull[EntityT entity.Bare](def Definition, wrapper func(e *Entity) EntityT,
 //
 // Note: an author is necessary for the case where a merge commit is created, as this commit will
 // have an author and may be signed if a signing key is available.
-func MergeAll[EntityT entity.Bare](def Definition, wrapper func(e *Entity) EntityT, repo repository.ClockedRepo, resolvers entity.Resolvers, remote string, author identity.Interface) <-chan entity.MergeResult {
+func MergeAll[EntityT entity.Bare](def Definition, wrapper func(e *Entity) EntityT, repo repository.ClockedRepo, resolvers entity.Resolvers, remote string, author entity.Identity) <-chan entity.MergeResult {
 	out := make(chan entity.MergeResult)
 
 	go func() {
@@ -91,7 +90,7 @@ func MergeAll[EntityT entity.Bare](def Definition, wrapper func(e *Entity) Entit
 
 // merge perform a merge to make sure a local Entity is up-to-date.
 // See MergeAll for more details.
-func merge[EntityT entity.Bare](def Definition, wrapper func(e *Entity) EntityT, repo repository.ClockedRepo, resolvers entity.Resolvers, remoteRef string, author identity.Interface) entity.MergeResult {
+func merge[EntityT entity.Bare](def Definition, wrapper func(e *Entity) EntityT, repo repository.ClockedRepo, resolvers entity.Resolvers, remoteRef string, author entity.Identity) entity.MergeResult {
 	id := entity.RefToId(remoteRef)
 
 	if err := id.Validate(); err != nil {
diff --git a/entity/dag/example_test.go b/entity/dag/example_test.go
index 3a12db5f..d56f74a3 100644
--- a/entity/dag/example_test.go
+++ b/entity/dag/example_test.go
@@ -30,13 +30,13 @@ import (
 // Snapshot is the compiled view of a ProjectConfig
 type Snapshot struct {
 	// Administrator is the set of users with the higher level of access
-	Administrator map[identity.Interface]struct{}
+	Administrator map[entity.Identity]struct{}
 	// SignatureRequired indicate that all git commit need to be signed
 	SignatureRequired bool
 }
 
 // HasAdministrator returns true if the given identity is included in the administrator.
-func (snap *Snapshot) HasAdministrator(i identity.Interface) bool {
+func (snap *Snapshot) HasAdministrator(i entity.Identity) bool {
 	for admin, _ := range snap.Administrator {
 		if admin.Id() == i.Id() {
 			return true
@@ -78,7 +78,7 @@ type SetSignatureRequired struct {
 	Value bool `json:"value"`
 }
 
-func NewSetSignatureRequired(author identity.Interface, value bool) *SetSignatureRequired {
+func NewSetSignatureRequired(author entity.Identity, value bool) *SetSignatureRequired {
 	return &SetSignatureRequired{
 		OpBase: dag.NewOpBase(SetSignatureRequiredOp, author, time.Now().Unix()),
 		Value:  value,
@@ -106,10 +106,10 @@ func (ssr *SetSignatureRequired) Apply(snapshot *Snapshot) {
 // AddAdministrator is an operation to add a new administrator in the set
 type AddAdministrator struct {
 	dag.OpBase
-	ToAdd []identity.Interface `json:"to_add"`
+	ToAdd []entity.Identity `json:"to_add"`
 }
 
-func NewAddAdministratorOp(author identity.Interface, toAdd ...identity.Interface) *AddAdministrator {
+func NewAddAdministratorOp(author entity.Identity, toAdd ...entity.Identity) *AddAdministrator {
 	return &AddAdministrator{
 		OpBase: dag.NewOpBase(AddAdministratorOp, author, time.Now().Unix()),
 		ToAdd:  toAdd,
@@ -143,10 +143,10 @@ func (aa *AddAdministrator) Apply(snapshot *Snapshot) {
 // RemoveAdministrator is an operation to remove an administrator from the set
 type RemoveAdministrator struct {
 	dag.OpBase
-	ToRemove []identity.Interface `json:"to_remove"`
+	ToRemove []entity.Identity `json:"to_remove"`
 }
 
-func NewRemoveAdministratorOp(author identity.Interface, toRemove ...identity.Interface) *RemoveAdministrator {
+func NewRemoveAdministratorOp(author entity.Identity, toRemove ...entity.Identity) *RemoveAdministrator {
 	return &RemoveAdministrator{
 		OpBase:   dag.NewOpBase(RemoveAdministratorOp, author, time.Now().Unix()),
 		ToRemove: toRemove,
@@ -249,7 +249,7 @@ func operationUnmarshaler(raw json.RawMessage, resolvers entity.Resolvers) (dag.
 	case *AddAdministrator:
 		// We need to resolve identities
 		for i, stub := range op.ToAdd {
-			iden, err := entity.Resolve[identity.Interface](resolvers, stub.Id())
+			iden, err := entity.Resolve[entity.Identity](resolvers, stub.Id())
 			if err != nil {
 				return nil, err
 			}
@@ -258,7 +258,7 @@ func operationUnmarshaler(raw json.RawMessage, resolvers entity.Resolvers) (dag.
 	case *RemoveAdministrator:
 		// We need to resolve identities
 		for i, stub := range op.ToRemove {
-			iden, err := entity.Resolve[identity.Interface](resolvers, stub.Id())
+			iden, err := entity.Resolve[entity.Identity](resolvers, stub.Id())
 			if err != nil {
 				return nil, err
 			}
@@ -275,7 +275,7 @@ func (pc ProjectConfig) Compile() *Snapshot {
 	// Note: this would benefit from caching, but it's a simple example
 	snap := &Snapshot{
 		// default value
-		Administrator:     make(map[identity.Interface]struct{}),
+		Administrator:     make(map[entity.Identity]struct{}),
 		SignatureRequired: false,
 	}
 	for _, op := range pc.Operations() {
diff --git a/entity/dag/op_noop.go b/entity/dag/op_noop.go
index d8a2a05a..1b600ee0 100644
--- a/entity/dag/op_noop.go
+++ b/entity/dag/op_noop.go
@@ -1,7 +1,6 @@
 package dag
 
 import (
-	"github.com/MichaelMure/git-bug/entities/identity"
 	"github.com/MichaelMure/git-bug/entity"
 )
 
@@ -15,7 +14,7 @@ type NoOpOperation[SnapT entity.Snapshot] struct {
 	OpBase
 }
 
-func NewNoOpOp[SnapT entity.Snapshot](opType entity.OperationType, author identity.Interface, unixTime int64) *NoOpOperation[SnapT] {
+func NewNoOpOp[SnapT entity.Snapshot](opType entity.OperationType, author entity.Identity, unixTime int64) *NoOpOperation[SnapT] {
 	return &NoOpOperation[SnapT]{
 		OpBase: NewOpBase(opType, author, unixTime),
 	}
diff --git a/entity/dag/op_noop_test.go b/entity/dag/op_noop_test.go
index 61497b5b..4ce2d194 100644
--- a/entity/dag/op_noop_test.go
+++ b/entity/dag/op_noop_test.go
@@ -4,7 +4,6 @@ import (
 	"encoding/json"
 	"testing"
 
-	"github.com/MichaelMure/git-bug/entities/identity"
 	"github.com/MichaelMure/git-bug/entity"
 )
 
@@ -13,7 +12,7 @@ func TestNoopSerialize(t *testing.T) {
 		var op NoOpOperation[*snapshotMock]
 		err := json.Unmarshal(raw, &op)
 		return &op, err
-	}, func(author identity.Interface, unixTime int64) (*NoOpOperation[*snapshotMock], entity.Resolvers) {
+	}, func(author entity.Identity, unixTime int64) (*NoOpOperation[*snapshotMock], entity.Resolvers) {
 		return NewNoOpOp[*snapshotMock](1, author, unixTime), nil
 	})
 }
diff --git a/entity/dag/op_set_metadata.go b/entity/dag/op_set_metadata.go
index 19176183..7b79a5bf 100644
--- a/entity/dag/op_set_metadata.go
+++ b/entity/dag/op_set_metadata.go
@@ -5,7 +5,6 @@ import (
 
 	"github.com/pkg/errors"
 
-	"github.com/MichaelMure/git-bug/entities/identity"
 	"github.com/MichaelMure/git-bug/entity"
 	"github.com/MichaelMure/git-bug/util/text"
 )
@@ -19,7 +18,7 @@ type SetMetadataOperation[SnapT entity.Snapshot] struct {
 	NewMetadata map[string]string `json:"new_metadata"`
 }
 
-func NewSetMetadataOp[SnapT entity.Snapshot](opType entity.OperationType, author identity.Interface, unixTime int64, target entity.Id, newMetadata map[string]string) *SetMetadataOperation[SnapT] {
+func NewSetMetadataOp[SnapT entity.Snapshot](opType entity.OperationType, author entity.Identity, unixTime int64, target entity.Id, newMetadata map[string]string) *SetMetadataOperation[SnapT] {
 	return &SetMetadataOperation[SnapT]{
 		OpBase:      NewOpBase(opType, author, unixTime),
 		Target:      target,
@@ -33,13 +32,16 @@ func (op *SetMetadataOperation[SnapT]) Id() entity.Id {
 
 func (op *SetMetadataOperation[SnapT]) Apply(snapshot SnapT) {
 	for _, target := range snapshot.AllOperations() {
-		if target.Id() == op.Target {
-			// Apply the metadata in an immutable way: if a metadata already
-			// exist, it's not possible to override it.
-			for key, value := range op.NewMetadata {
-				target.setExtraMetadataImmutable(key, value)
+		// cast to dag.Operation to have the private methods
+		if target, ok := target.(Operation); ok {
+			if target.Id() == op.Target {
+				// Apply the metadata in an immutable way: if a metadata already
+				// exist, it's not possible to override it.
+				for key, value := range op.NewMetadata {
+					target.setExtraMetadataImmutable(key, value)
+				}
+				return
 			}
-			return
 		}
 	}
 }
diff --git a/entity/dag/op_set_metadata_test.go b/entity/dag/op_set_metadata_test.go
index 591ce9b2..5019d838 100644
--- a/entity/dag/op_set_metadata_test.go
+++ b/entity/dag/op_set_metadata_test.go
@@ -109,7 +109,7 @@ func TestSetMetadataSerialize(t *testing.T) {
 		var op SetMetadataOperation[*snapshotMock]
 		err := json.Unmarshal(raw, &op)
 		return &op, err
-	}, func(author identity.Interface, unixTime int64) (*SetMetadataOperation[*snapshotMock], entity.Resolvers) {
+	}, func(author entity.Identity, unixTime int64) (*SetMetadataOperation[*snapshotMock], entity.Resolvers) {
 		return NewSetMetadataOp[*snapshotMock](1, author, unixTime, "message", map[string]string{
 			"key1": "value1",
 			"key2": "value2",
diff --git a/entity/dag/operation.go b/entity/dag/operation.go
index 40bd7da8..216788c4 100644
--- a/entity/dag/operation.go
+++ b/entity/dag/operation.go
@@ -8,7 +8,6 @@ import (
 
 	"github.com/pkg/errors"
 
-	"github.com/MichaelMure/git-bug/entities/identity"
 	"github.com/MichaelMure/git-bug/entity"
 )
 
@@ -19,7 +18,7 @@ type Operation interface {
 	// setId allow to set the Id, used when unmarshalling only
 	setId(id entity.Id)
 	// setAuthor allow to set the author, used when unmarshalling only
-	setAuthor(author identity.Interface)
+	setAuthor(author entity.Identity)
 	// setExtraMetadataImmutable add a metadata not carried by the operation itself on the operation
 	setExtraMetadataImmutable(key string, value string)
 }
@@ -36,7 +35,7 @@ type OpBase struct {
 	// Not serialized. Store the op's id in memory.
 	id entity.Id
 	// Not serialized
-	author identity.Interface
+	author entity.Identity
 
 	OperationType entity.OperationType `json:"type"`
 	UnixTime      int64                `json:"timestamp"`
@@ -52,7 +51,7 @@ type OpBase struct {
 	extraMetadata map[string]string
 }
 
-func NewOpBase(opType entity.OperationType, author identity.Interface, unixTime int64) OpBase {
+func NewOpBase(opType entity.OperationType, author entity.Identity, unixTime int64) OpBase {
 	return OpBase{
 		OperationType: opType,
 		author:        author,
@@ -144,7 +143,7 @@ func (base *OpBase) Validate(op entity.Operation, opType entity.OperationType) e
 func (base *OpBase) IsAuthored() {}
 
 // Author return author identity
-func (base *OpBase) Author() identity.Interface {
+func (base *OpBase) Author() entity.Identity {
 	return base.author
 }
 
@@ -204,7 +203,7 @@ func (base *OpBase) setId(id entity.Id) {
 }
 
 // setAuthor allow to set the author, used when unmarshalling only
-func (base *OpBase) setAuthor(author identity.Interface) {
+func (base *OpBase) setAuthor(author entity.Identity) {
 	base.author = author
 }
 
diff --git a/entity/dag/operation_pack.go b/entity/dag/operation_pack.go
index 85cceb4f..aaffa03b 100644
--- a/entity/dag/operation_pack.go
+++ b/entity/dag/operation_pack.go
@@ -12,6 +12,7 @@ import (
 
 	"github.com/MichaelMure/git-bug/entities/identity"
 	"github.com/MichaelMure/git-bug/entity"
+	bootstrap "github.com/MichaelMure/git-bug/entity/boostrap"
 	"github.com/MichaelMure/git-bug/repository"
 	"github.com/MichaelMure/git-bug/util/lamport"
 )
@@ -29,7 +30,7 @@ type operationPack struct {
 	id entity.Id
 
 	// The author of the Operations. Must be the same author for all the Operations.
-	Author identity.Interface
+	Author entity.Identity
 	// The list of Operation stored in the operationPack
 	Operations []Operation
 	// Encode the entity's logical time of creation across all entities of the same type.
@@ -58,8 +59,8 @@ func (opp *operationPack) Id() entity.Id {
 
 func (opp *operationPack) MarshalJSON() ([]byte, error) {
 	return json.Marshal(struct {
-		Author     identity.Interface `json:"author"`
-		Operations []Operation        `json:"ops"`
+		Author     entity.Identity `json:"author"`
+		Operations []Operation     `json:"ops"`
 	}{
 		Author:     opp.Author,
 		Operations: opp.Operations,
@@ -235,7 +236,7 @@ func readOperationPack(def Definition, repo repository.RepoData, resolvers entit
 	}
 
 	var id entity.Id
-	var author identity.Interface
+	var author entity.Identity
 	var ops []Operation
 	var createTime lamport.Time
 	var editTime lamport.Time
@@ -323,7 +324,7 @@ func readOperationPackClock(repo repository.RepoData, commit repository.Commit)
 // unmarshallPack delegate the unmarshalling of the Operation's JSON to the decoding
 // function provided by the concrete entity. This gives access to the concrete type of each
 // Operation.
-func unmarshallPack(def Definition, resolvers entity.Resolvers, data []byte) ([]Operation, identity.Interface, error) {
+func unmarshallPack(def Definition, resolvers entity.Resolvers, data []byte) ([]Operation, entity.Identity, error) {
 	aux := struct {
 		Author     identity.IdentityStub `json:"author"`
 		Operations []json.RawMessage     `json:"ops"`
@@ -337,7 +338,7 @@ func unmarshallPack(def Definition, resolvers entity.Resolvers, data []byte) ([]
 		return nil, nil, fmt.Errorf("missing author")
 	}
 
-	author, err := entity.Resolve[identity.Interface](resolvers, aux.Author.Id())
+	author, err := entity.Resolve[entity.Identity](resolvers, aux.Author.Id())
 	if err != nil {
 		return nil, nil, err
 	}
@@ -364,7 +365,7 @@ func unmarshallPack(def Definition, resolvers entity.Resolvers, data []byte) ([]
 var _ openpgp.KeyRing = &PGPKeyring{}
 
 // PGPKeyring implement a openpgp.KeyRing from an slice of Key
-type PGPKeyring []*identity.Key
+type PGPKeyring []bootstrap.Key
 
 func (pk PGPKeyring) KeysById(id uint64) []openpgp.Key {
 	var result []openpgp.Key
diff --git a/entity/dag/operation_testing.go b/entity/dag/operation_testing.go
index 0ca47d4b..fd4d2343 100644
--- a/entity/dag/operation_testing.go
+++ b/entity/dag/operation_testing.go
@@ -17,7 +17,7 @@ import (
 func SerializeRoundTripTest[OpT Operation](
 	t *testing.T,
 	unmarshaler OperationUnmarshaler,
-	maker func(author identity.Interface, unixTime int64) (OpT, entity.Resolvers),
+	maker func(author entity.Identity, unixTime int64) (OpT, entity.Resolvers),
 ) {
 	repo := repository.NewMockRepo()
 
diff --git a/entity/identity.go b/entity/identity.go
index 71e0a6e8..2523e1f3 100644
--- a/entity/identity.go
+++ b/entity/identity.go
@@ -1,73 +1,9 @@
 package entity
 
-//
-// import (
-// 	"github.com/ProtonMail/go-crypto/openpgp"
-// 	"github.com/ProtonMail/go-crypto/openpgp/packet"
-//
-// 	"github.com/MichaelMure/git-bug/repository"
-// 	"github.com/MichaelMure/git-bug/util/lamport"
-// 	"github.com/MichaelMure/git-bug/util/timestamp"
-// )
-//
-// type Key interface {
-// 	Public() *packet.PublicKey
-// 	Private() *packet.PrivateKey
-// 	Validate() error
-// 	Clone() Key
-// 	PGPEntity() *openpgp.Entity
-// }
-//
-// type Identity interface {
-// 	Bare
-//
-// 	// Name return the last version of the name
-// 	// Can be empty.
-// 	Name() string
-//
-// 	// DisplayName return a non-empty string to display, representing the
-// 	// identity, based on the non-empty values.
-// 	DisplayName() string
-//
-// 	// Email return the last version of the email
-// 	// Can be empty.
-// 	Email() string
-//
-// 	// Login return the last version of the login
-// 	// Can be empty.
-// 	// Warning: this login can be defined when importing from a bridge but should *not* be
-// 	// used to identify an identity as multiple bridge with different login can map to the same
-// 	// identity. Use the metadata system for that usage instead.
-// 	Login() string
-//
-// 	// AvatarUrl return the last version of the Avatar URL
-// 	// Can be empty.
-// 	AvatarUrl() string
-//
-// 	// Keys return the last version of the valid keys
-// 	// Can be empty.
-// 	Keys() []Key
-//
-// 	// SigningKey return the key that should be used to sign new messages. If no key is available, return nil.
-// 	SigningKey(repo repository.RepoKeyring) (Key, error)
-//
-// 	// ValidKeysAtTime return the set of keys valid at a given lamport time for a given clock of another entity
-// 	// Can be empty.
-// 	ValidKeysAtTime(clockName string, time lamport.Time) []Key
-//
-// 	// LastModification return the timestamp at which the last version of the identity became valid.
-// 	LastModification() timestamp.Timestamp
-//
-// 	// LastModificationLamports return the lamport times at which the last version of the identity became valid.
-// 	LastModificationLamports() map[string]lamport.Time
-//
-// 	// IsProtected return true if the chain of git commits started to be signed.
-// 	// If that's the case, only signed commit with a valid key for this identity can be added.
-// 	IsProtected() bool
-//
-// 	// Validate check if the Identity data is valid
-// 	Validate() error
-//
-// 	// NeedCommit indicate that the in-memory state changed and need to be committed in the repository
-// 	NeedCommit() bool
-// }
+import bootstrap "github.com/MichaelMure/git-bug/entity/boostrap"
+
+var ErrNoPrivateKey = bootstrap.ErrNoPrivateKey
+
+type Key = bootstrap.Key
+
+type Identity = bootstrap.Identity
diff --git a/entity/operations.go b/entity/operations.go
index fd88f033..4280cf63 100644
--- a/entity/operations.go
+++ b/entity/operations.go
@@ -3,7 +3,6 @@ package entity
 import (
 	"time"
 
-	"github.com/MichaelMure/git-bug/entities/identity"
 	"github.com/MichaelMure/git-bug/repository"
 )
 
@@ -38,7 +37,7 @@ type Operation interface {
 	// Validate check if the Operation data is valid
 	Validate() error
 	// Author returns the author of this operation
-	Author() identity.Interface
+	Author() Identity
 	// Time return the time when the operation was added
 	Time() time.Time
 
diff --git a/misc/random_bugs/create_random_bugs.go b/misc/random_bugs/create_random_bugs.go
index 7e94b61a..e88e1054 100644
--- a/misc/random_bugs/create_random_bugs.go
+++ b/misc/random_bugs/create_random_bugs.go
@@ -9,10 +9,11 @@ import (
 
 	"github.com/MichaelMure/git-bug/entities/bug"
 	"github.com/MichaelMure/git-bug/entities/identity"
+	"github.com/MichaelMure/git-bug/entity"
 	"github.com/MichaelMure/git-bug/repository"
 )
 
-type opsGenerator func(bug.Interface, identity.Interface, int64)
+type opsGenerator func(bug.Interface, entity.Identity, int64)
 
 type Options struct {
 	BugNumber    int
@@ -133,7 +134,7 @@ func generateRandomPersons(repo repository.ClockedRepo, n int) {
 	}
 }
 
-func randomPerson() identity.Interface {
+func randomPerson() entity.Identity {
 	index := rand.Intn(len(persons))
 	return persons[index]
 }
@@ -143,25 +144,25 @@ func paragraphs() string {
 	return strings.Replace(p, "\t", "\n\n", -1)
 }
 
-func comment(b bug.Interface, p identity.Interface, timestamp int64) {
+func comment(b bug.Interface, p entity.Identity, timestamp int64) {
 	_, _, _ = bug.AddComment(b, p, timestamp, paragraphs(), nil, nil)
 }
 
-func title(b bug.Interface, p identity.Interface, timestamp int64) {
+func title(b bug.Interface, p entity.Identity, timestamp int64) {
 	_, _ = bug.SetTitle(b, p, timestamp, fake.Sentence(), nil)
 }
 
-func open(b bug.Interface, p identity.Interface, timestamp int64) {
+func open(b bug.Interface, p entity.Identity, timestamp int64) {
 	_, _ = bug.Open(b, p, timestamp, nil)
 }
 
-func close(b bug.Interface, p identity.Interface, timestamp int64) {
+func close(b bug.Interface, p entity.Identity, timestamp int64) {
 	_, _ = bug.Close(b, p, timestamp, nil)
 }
 
 var addedLabels []string
 
-func labels(b bug.Interface, p identity.Interface, timestamp int64) {
+func labels(b bug.Interface, p entity.Identity, timestamp int64) {
 	var removed []string
 	nbRemoved := rand.Intn(3)
 	for nbRemoved > 0 && len(addedLabels) > 0 {
-- 
GitLab