siyuan-note/siyuan · error

database [ ] moved across notebook encryption boundaries

Error message

database [%s] moved across notebook encryption boundaries

What it means

readAttributeViewForMutation resolves which notebook actually carries the attribute view's anchor block. When the exact carrier is found (resolveAttributeViewCarrierBoxID returned exact=true) but its box ID differs from the boxID passed by the caller, the database block has been moved into a notebook with different encryption settings, and the transaction refuses to mutate it under the stale box context. This protects encrypted-notebook key-envelope boundaries from cross-notebook writes.

Solutions

  1. Refresh the block's current carrier: re-resolve the block's notebook ID and re-issue the transaction with the correct boxID
  2. If the move was unintended, move the database block back to its original notebook before applying the mutation
  3. Ensure the client serializes operations correctly — do not run queued mutations that assumed the pre-move notebook

Example fix

// before
_, err := tx.readAttributeViewForMutation(avID, blockID, oldBoxID)
// after
carrierBox, _, resolveErr := resolveAttributeViewCarrierBoxID(blockID)
if resolveErr == nil {
    _, err = tx.readAttributeViewForMutation(avID, blockID, carrierBox)
}
Defensive patterns

Strategy: validation

Validate before calling

const carrier = await resolveCarrierBox(blockID);
if (carrier !== txBoxID) {
  throw new Error("database moved across notebooks; re-resolve boxID before mutating");
}

Try / catch

try {
  await api.mutateAttributeView({ avID, blockID, boxID });
} catch (e) {
  if (String(e.msg).includes("moved across notebook encryption boundaries")) {
    const boxID = await resolveCarrierBox(blockID); // re-resolve and retry
    await api.mutateAttributeView({ avID, blockID, boxID });
  }
}

Prevention

When it happens

Trigger: Calling any mutation path that goes through readAttributeViewForMutation (replaceAttributeViewBinding, restoreEmbeddedAttributeViewHistory, flushAttributeViewBlockDeletions, restoreDeletedAttributeViewBlocks, removeAttributeViewField, or inline transaction handlers) with a boxID that no longer matches the block's actual carrier notebook, after the block (e.g. a database block) was dragged or moved between notebooks with different encryption states.

Common situations: A database/embed block was moved across notebooks before a queued transaction ran; replaying an undo/redo or history-restore operation recorded before the move; a client cached the old box ID after a move operation.

Understand the failure class

Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/5c531f8ca02bc3f3. Report an issue: GitHub.

Appendix: source

Thrown at kernel/model/attribute_view_transaction.go:43

	"github.com/siyuan-note/siyuan/kernel/av"
	"github.com/siyuan-note/siyuan/kernel/filesys"
	"github.com/siyuan-note/siyuan/kernel/util"
)

// 事务失败时恢复本次写入涉及的数据库和文档,租约保持到提交或回滚结束。
type attributeViewRollback struct {
	views  map[string]*av.AttributeView
	trees  map[string]*parse.Tree
	leases map[string]bool
}

func (tx *Transaction) readAttributeViewForMutation(avID, blockID, boxID string) (*av.AttributeView, error) {
	carrierBoxID, exact, err := resolveAttributeViewCarrierBoxID(blockID)
	if err != nil {
		return nil, err
	}
	if exact && carrierBoxID != boxID {
		return nil, fmt.Errorf("database [%s] moved across notebook encryption boundaries", avID)
	}
	if tx.attributeViewRollback == nil {
		tx.attributeViewRollback = &attributeViewRollback{views: map[string]*av.AttributeView{},
			trees: map[string]*parse.Tree{}, leases: map[string]bool{}}
	}
	if boxID != "" && !tx.attributeViewRollback.leases[boxID] {
		if err = AcquireEncryptedBoxOperation(boxID); err != nil {
			return nil, err
		}
		tx.attributeViewRollback.leases[boxID] = true
	}
	current, err := av.ParseAttributeViewForIndexInBox(avID, boxID)
	if err == nil && current == nil {
		err = av.ErrViewNotFound
	}
	return current, err
}

View on GitHub (pinned to 9f775e8a12)