(AI generated. Not reviewed.)
Node Model
This page documents the parts of the node model that are already shipped in the merged code. It does not restate the larger staging plan except where a planned piece matters for understanding what is not built yet.
Three different axes on a node
The backend Document model now carries three different classification axes:
doc_type: the structural shape of the node. In current code this is still the main hierarchy axis: file, folder, page, chunk, group, and so on.node_kind: the behavioral role of the node. The default is"document", but shipped special cases now include"saved_search","alias","workspace","plan","task","step", and"room".prototype_key: an optional class/prototype tag. It names a prototype definition or a conventional subtype such as"saved_search"or"bookmark".
Those axes are intentionally separate. A shipped saved search is a good example:
it is stored as a Document with doc_type=folder, node_kind="saved_search",
and prototype_key="saved_search". A shipped bookmark is different again: it is
an alias node with node_kind="alias" and prototype_key="bookmark".
Prototype definitions and inheritance
Prototype resolution is implemented in
fichero-server/src/fichero_server/models/node_prototypes.py, and the built-in prototype
definitions are seeded from _BUILTIN_DOCUMENT_PROTOTYPE_SEEDS in
fichero-server/src/fichero_server/db/. The current shipped behavior is attribute
inheritance, not a full behavior system.
- A prototype definition is a
ClassificationValuerow withdimension=document_prototype. - Prototypes can point to a parent through
parent_key. resolve_prototype_attributeswalks that parent chain, merges attributes root to leaf, and lets the child override inherited values.- The shipped built-ins include plain types such as
bookandletter, plus container/workspace types such asfolder,research_workspace, androom. folderis the current built-in container base. Its seeded attributes arecontainer_kind="folder"andsupports_children=True.research_workspaceandroomboth inherit fromfolderthroughparent_key="folder". The seeded room-specific attributes arespatial_layout=Trueandworkspace_kind="room".- Unknown keys, missing parents, and cycles raise
PrototypeResolutionErrorinstead of silently returning partial data.
The unit tests in test_node_prototypes.py verify the current contract:
inheritance works across multiple levels, child attributes override parent
attributes, and invalid chains fail loudly.
Prototype assignment is also shipped as part of the documents API:
PUT /api/documents/{doc_id}/prototypeinfichero-server/src/fichero_server/api/routes/document/documents.pyvalidates the requested key against seeded/user-definedClassificationValuerows.- The same route can apply a prototype to descendants, and can restrict that assignment to descendant page nodes within a page range.
Aliases
Alias nodes are implemented in
fichero-server/src/fichero_server/models/node_aliases.py.
- An alias is a
Documentwithnode_kind == "alias"and a non-emptyalias_target_id. make_aliascopies the target’s structuraldoc_type, keeps a normalparent_id, and does not duplicate the target’s content.resolve_aliasreturns the live target node.- Missing targets raise
DanglingAliasErrorrather than degrading silently.
This is the shared foundation the bookmark fold now reuses.
Saved searches as document nodes
Saved searches are now folded into document nodes in the database layer.
The relevant implementation is in fichero-server/src/fichero_server/db/:
_save_saved_search_documentmirrors aSavedSearchinto a same-idDocument.- The mirrored node is written with
node_kind="saved_search",doc_type=DocType.folder, andprototype_key="saved_search". - The search payload is stored in
attributes, includingquery,filters,search_type,sort_by,sort_direction, andfolder_path. - A small
curated_itemsrecord also stores the query payload, and_saved_search_from_documentuses it as a fallback if theattributes["query"]value is missing. metadatais still populated with saved-search-specific fields such asnode_class="smart_folder"andsaved_search_query.
The public saved-search API still lives under api/routes/search.py as
/api/search/saved CRUD and reorder routes. The fold did not replace that API
surface; it changed the storage representation under it.
Mind-palace rooms as node-backed folders
Mind-palace rooms now have a node-backed representation in the database layer.
The relevant implementation is split between fichero-server/src/fichero_server/db/
and the now-removed mind-palace room routes:
Database.save(...)special-casesSpatialRoomand mirrors it through_save_spatial_room_document.- The mirrored node is written with
node_kind="room",doc_type=DocType.folder, andprototype_key="room". - The room payload lives in
attributes, includingdescription,room_type,owner_id, and roommetadata. - Reads are symmetric:
Database.get(SpatialRoom, ...),Database.all(SpatialRoom), andDatabase.query(SpatialRoom, ...)hydrate from document nodes whoseprototype_keyis"room". - Room nodes resolve effective prototype attributes through the same
resolve_prototype_attributes(...)path as other prototype-backed nodes, so a room inherits the current folder/container attributes from the built-inroom -> folderchain. - On reopen,
_backfill_spatial_room_documentsmirrors legacySpatialRoomrows into room documents if the old table still exists.
The /api/mind-palace/rooms* route surface has been REMOVED —
fichero-server/tests/unit/api/test_mind_palace_route_guard.py asserts it
stays removed. Room behavior survives through the node-backed room bridge:
rooms are workspace nodes, not a separate storage path or API namespace.
Research workspaces as workspace nodes
Research workspaces are also folded into Document rows in the database layer.
The relevant implementation is in fichero-server/src/fichero_server/db/:
Database.save(...)special-casesResearchProjectand mirrors it through_save_research_workspace_document.- The mirrored node is written with
node_kind="workspace",doc_type=DocType.folder, andprototype_key="research_workspace". - The fold also sets
is_workspace=True, so the node presents as a workspace folder rather than as a plain library folder. - Workspace-specific payload lives in
attributes, includingdescription,status,created_by,library_destination_folder_id, and the project’smetadata. metadatais also marked withnode_class="research_workspace"plus the originalresearch_project_id.- Reads are symmetric:
Database.get(ResearchProject, ...),Database.all(ResearchProject), andDatabase.query(ResearchProject, ...)hydrate from document nodes whoseprototype_keyis"research_workspace". - On reopen,
_backfill_research_workspace_documentsmirrors legacyResearchProjectrows into workspace documents if the old table still exists.
The unit tests in test_db.py and test_routes_research_agents.py verify the
current contract: saving a ResearchProject produces a same-id workspace node,
reading can hydrate a project back from that node, and reopen backfills the
mirror when needed.
Research plans, tasks, and steps
Research plans, tasks, and steps are now folded into Document rows in the
current merged code.
What the shipped code does today:
Database.save(...)mirrorsResearchPlan,ResearchTask, andResearchStepthrough_save_research_plan_document,_save_research_task_document, and_save_research_step_document.- Plans are mirrored as
node_kind="plan"plusprototype_key="research_plan". - Tasks are mirrored as
node_kind="task"plusprototype_key="research_task". - Steps are mirrored as
node_kind="step"plusprototype_key="research_step". - Containment is represented through
parent_id: a plan’s parent is its project/workspace, a task’s parent is its plan, and a step’s parent is its task. Database.get(...),Database.all(...), andDatabase.query(...)now have folded-document read paths for all three model types.- On reopen,
_backfill_research_plan_task_step_documentsmirrors legacy rows into document nodes if the legacy tables still exist.
Important boundary:
BackgroundTaskinfichero-server/src/fichero_server/workflows/task_types.pyandfichero-server/src/fichero_server/workflows/tasks.pyis workflow/task-run infrastructure. It is not part of the research node-model fold and should not be described as a plan/task/step node.
Bookmarks as alias-backed nodes
Bookmark nodes ship as backend routes in
fichero-server/src/fichero_server/api/routes/system/bookmarks.py.
POST /api/bookmarkscreates a bookmark by callingmake_alias(...)and then settingprototype_key="bookmark".GET /api/bookmarkslists only nodes that are bothnode_kind="alias"andprototype_key="bookmark".GET /api/bookmarks/{bookmark_id}/resolveresolves the bookmark through the shared alias resolver and returns404on a dangling target.
Tests in test_routes_bookmarks.py verify those semantics directly.
Planned, not yet built:
- The backend and OpenAPI surface are shipped.
- SwiftUI wiring is still explicitly deferred; the endpoint allowlist records
/api/bookmarksand/api/bookmarks/{bookmark_id}/resolveas backend-only for now.
What is still planned
The larger fold plan in docs/contributor/architecture/node_model_fold_staging.md is still
mostly a staging document, not a completion record.
What is shipped now:
- prototype attribute resolution
- built-in document-prototype seeding, including
folder,room, andresearch_workspace - alias nodes
- saved-search document folding
- mind-palace room document folding with the existing
/api/mind-palace/roomsroutes still intact - research-workspace document folding
- research plan/task/step document folding
- bookmark routes built on alias nodes
What should still be described as planned unless more code lands:
- broader prototype-driven behavior beyond attribute inheritance
- SwiftUI bookmark UI wiring
- the remaining staged subsystem folds described in the architecture note
Fold status
This is the current closeout status for EPIC #2591, based on merged backend code rather than the original staging plan.
Folded into the node model now:
- F1 saved searches:
Database.save(SavedSearch)mirrors each saved search into aDocumentwithnode_kind="saved_search"andprototype_key="saved_search", and the database read paths route both the legacy model and the folded node through that bridge. - F2 research workspaces:
Database.save(ResearchProject)mirrors each workspace into aDocumentwithnode_kind="workspace"andprototype_key="research_workspace", and the built-inresearch_workspaceprototype inherits fromfolder. - F3 research plans, tasks, and steps:
Database.save(...)foldsResearchPlan,ResearchTask, andResearchStepinto document nodes withparent_idcontainment linking workspace -> plan -> task -> step. - F4 bookmarks:
POST /api/bookmarkscreates alias nodes throughmake_alias(...), and bookmark listing / resolution is implemented by filtering alias nodes withprototype_key="bookmark". - P3 notes and milestones:
Database.save(Note)andDatabase.save(Milestone)mirror both models intoDocumentrows withprototype_key="note"andprototype_key="milestone", and those nodes appear in document-child reads. - P4 entities filable in folders: a
KnowledgeEntityonly gets a mirroredDocumentrow when it has aparent_id; moving or clearing thatparent_idupdates or removes the folded node, so filing is represented through normal document containment. - P5 folder and room prototypes: built-in prototype seeds now include
folderandroom, andfichero-server/src/fichero_server/models/node_prototypes.pyresolves inherited attributes through the parent chain rather than hard-coding per-type behavior. - F5 slice 1 room-node bridge:
Database.save(SpatialRoom)mirrors each room into aDocumentwithnode_kind="room"andprototype_key="room", while the existing/api/mind-palace/rooms*routes keep reading and writing the legacySpatialRoommodel through that bridge.
Intentionally not folded:
BackgroundTaskinfichero-server/src/fichero_server/workflows/task_types.pyandfichero-server/src/fichero_server/workflows/tasks.pyis task-queue infrastructure, not a node-model task type.- The workflow runner in
fichero-server/src/fichero_server/execution/runner.pyand the workflow execution routes remain execution infrastructure, not document nodes. - The action registry in
fichero-server/src/fichero_server/actions/registry.pyremains the audited write path for mutations; it is not a node fold. - Provider configuration in
fichero-server/src/fichero_server/llm/providers.pyremains backend/provider infrastructure, not document content. - Authorization and ACL enforcement in
fichero-server/src/fichero_server/security/authz.pyremain access-control infrastructure, not part of the node hierarchy.
Still in progress or pending:
- P6 chat scopes are not fully folded yet. The shipped code only seeds a
chat_scope="container"prototype attribute onresearch_workspace; broader chat-scope folding should still be described as in progress until more code lands. - The
/api/mind-palace/rooms*endpoints are retired (REMOVED);fichero-server/tests/unit/api/test_mind_palace_route_guard.pyasserts they stay removed. Room behavior parity rests on the room <-> room-node bridge.