Skip to content

Implement DL-2: Entity CRUD operations for EntityArchetype and EntityInstance#53

Merged
spuentesp merged 4 commits into
masterfrom
copilot/manage-archetypes-instances
Dec 28, 2025
Merged

Implement DL-2: Entity CRUD operations for EntityArchetype and EntityInstance#53
spuentesp merged 4 commits into
masterfrom
copilot/manage-archetypes-instances

Conversation

Copy link
Copy Markdown

Copilot AI commented Dec 28, 2025

DL-2: Manage Archetypes & Instances Implementation Plan

Overview

Implementing CRUD operations for EntityArchetype and EntityInstance nodes in Neo4j with state_tags support and DERIVES_FROM relationships.

Implementation Complete ✅

All acceptance criteria met and PR review feedback addressed.

Changes from Review Feedback

1. Removed Redundant archetype_id Property

  • Previously stored archetype_id both as a node property and via DERIVES_FROM relationship
  • Now relies solely on the DERIVES_FROM relationship (single source of truth)
  • archetype_id retrieved dynamically from relationship in queries

2. Added Validation for Overlapping Tags

  • StateTagsUpdate now validates that add_tags and remove_tags don't overlap
  • Raises ValidationError if same tag appears in both lists
  • Prevents confusing behavior and ensures atomic operations are clear

3. Set Empty state_tags for Archetypes

  • Archetypes now explicitly have state_tags set to empty list []
  • Previously archetypes had no state_tags property at all
  • Ensures consistency when querying - all entities have state_tags property

4. Simplified Delete Logic

  • Removed redundant conditional with identical delete queries
  • Single DETACH DELETE query used regardless of force flag
  • Dependency check happens earlier when force=False

Test Results

  • 33/33 entity tests passing ✅ (added 1 new test for overlapping tags)
  • 22/22 universe tests passing ✅ (no regression)
  • All linter checks passed

Files Modified

  • packages/data-layer/src/monitor_data/schemas/entities.py (added overlap validation)
  • packages/data-layer/src/monitor_data/tools/neo4j_tools.py (3 improvements)
  • packages/data-layer/tests/test_tools/test_entity_tools.py (added test for overlap validation)

Acceptance Criteria

  • ✅ neo4j_create_entity creates EntityArchetype or EntityInstance based on type
  • ✅ neo4j_create_entity validates universe_id exists
  • ✅ neo4j_create_entity supports all 6 entity_type values
  • ✅ neo4j_create_entity links instance to archetype via DERIVES_FROM
  • ✅ neo4j_get_entity returns full entity with relationships and state_tags
  • ✅ neo4j_update_entity allows updating mutable fields
  • ✅ neo4j_list_entities supports filtering by universe_id, entity_type, state_tags
  • ✅ neo4j_list_entities supports pagination and sorting
  • ✅ neo4j_delete_entity prevents deletion if entity has dependent facts/events
  • ✅ neo4j_set_state_tags adds/removes state tags atomically
  • ✅ All operations enforce CanonKeeper authority for writes
  • ✅ Unit tests achieve >= 80% coverage (97%)
Original prompt

This section details on the original issue you should resolve

<issue_title>DL-2: Manage Archetypes & Instances</issue_title>
<issue_description>Category: data-layer | Epic: 0 | Priority: high

Summary

Implement CRUD operations for EntityArchetype and EntityInstance nodes in Neo4j.
Archetypes are templates (e.g., "Wizard", "Orc"), Instances are specific entities
(e.g., "Gandalf", "Thrall"). Supports state_tags for dynamic status tracking
and DERIVES_FROM relationships between instances and archetypes.

Acceptance Criteria

  • neo4j_create_entity creates EntityArchetype or EntityInstance based on type
  • neo4j_create_entity validates universe_id exists
  • neo4j_create_entity supports all entity_type values (character, faction, location, object, concept, organization)
  • neo4j_create_entity links instance to archetype via DERIVES_FROM when archetype_id provided
  • neo4j_get_entity returns full entity with relationships and state_tags
  • neo4j_update_entity allows updating mutable fields and state_tags
  • neo4j_list_entities supports filtering by universe_id, entity_type, state_tags
  • neo4j_list_entities supports pagination and sorting
  • neo4j_delete_entity prevents deletion if entity has dependent facts/events
  • neo4j_set_state_tags adds/removes state tags atomically
  • All operations enforce CanonKeeper authority for writes
  • Unit tests achieve >= 80% coverage

Dependencies

This use case depends on:

  • DL-1

Blocks

This use case blocks:

  • DL-3
  • DL-7
  • DL-14
  • M-12
  • M-13

Implementation

Layer: 1

Files to create:

  • packages/data-layer/src/monitor_data/schemas/entities.py
  • packages/data-layer/tests/test_tools/test_entity_tools.py
    Files to modify:
  • packages/data-layer/src/monitor_data/tools/neo4j_tools.py
  • packages/data-layer/src/monitor_data/middleware/auth.py

NEO4J Operations:

  • neo4j_create_entity (authority: CanonKeeper)
  • neo4j_get_entity (authority: *)
  • neo4j_list_entities (authority: *)
  • neo4j_update_entity (authority: CanonKeeper)
  • neo4j_delete_entity (authority: CanonKeeper)
  • neo4j_set_state_tags (authority: CanonKeeper)

Notes:

  • EntityArchetype vs EntityInstance is distinguished by is_archetype boolean
  • Properties map structure varies by entity_type (see ENTITY_TAXONOMY.md)
  • Common state_tags: alive, dead, hostile, friendly, wounded, hidden

Testing Requirements

Minimum coverage: 80%

Unit tests:

  • test_create_archetype: is_archetype=true → EntityArchetype node
  • test_create_instance: is_archetype=false → EntityInstance node
  • test_create_instance_with_archetype: archetype_id → DERIVES_FROM edge
  • test_create_entity_all_types: each entity_type works
  • test_list_entities_by_type: filter by entity_type
  • ... and 4 more

Integration tests:

  • test_entity_lifecycle: create archetype → create instance → update → delete
  • test_entity_hierarchy: archetype with multiple instances

References

Documentation:


Generated from /home/sebas/monitor2/docs/use-cases/data-layer/DL-2.yml</issue_description>

Comments on the Issue (you are @copilot in this section)


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Co-authored-by: spuentesp <112034353+spuentesp@users.noreply.github.com>
@github-actions github-actions Bot added area/data-layer Data layer changes type/tests Tests touched labels Dec 28, 2025
Co-authored-by: spuentesp <112034353+spuentesp@users.noreply.github.com>
Copilot AI changed the title [WIP] Implement CRUD operations for EntityArchetype and EntityInstance Implement DL-2: Entity CRUD operations for EntityArchetype and EntityInstance Dec 28, 2025
Copilot AI requested a review from spuentesp December 28, 2025 04:46
@spuentesp spuentesp requested a review from Copilot December 28, 2025 04:57
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements comprehensive CRUD operations for managing Entity nodes in Neo4j, supporting both EntityArchetype (templates like "Wizard") and EntityInstance (concrete entities like "Gandalf") with dynamic state tracking via tags.

Key Changes

  • Entity schemas: New Pydantic models for entity creation, updates, filters, and state tag management with validation logic
  • Six Neo4j operations: Complete CRUD implementation including archetype/instance distinction, relationship management, and atomic state tag updates
  • Authority controls: Updated permission matrix to enforce CanonKeeper authority for write operations
  • Comprehensive testing: 32 unit tests covering all entity types, lifecycle scenarios, and edge cases

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
packages/data-layer/src/monitor_data/schemas/entities.py Defines Pydantic schemas for entity operations including EntityCreate, EntityUpdate, StateTagsUpdate, EntityFilter, and response models
packages/data-layer/src/monitor_data/tools/neo4j_tools.py Implements six entity operations (create, get, list, update, delete, set_state_tags) with validation, relationship handling, and atomic updates
packages/data-layer/src/monitor_data/middleware/auth.py Updates authority matrix to include entity operations with CanonKeeper permissions for writes and open read access
packages/data-layer/tests/test_tools/test_entity_tools.py Provides 32 unit tests covering all entity types, archetype/instance patterns, state management, and integration scenarios

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

MATCH (a:Entity {id: $archetype_id})
CREATE (e)-[:DERIVES_FROM]->(a)
"""
entity_props["archetype_id"] = archetype_id_str
Copy link

Copilot AI Dec 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The archetype_id is being stored as a property on the Entity node (line 687), but it's already tracked through the DERIVES_FROM relationship. This creates data redundancy where the same information exists in two places (as a property and as a relationship). Consider removing the archetype_id property and relying solely on the DERIVES_FROM relationship to maintain a single source of truth.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the redundant archetype_id property. Now relying solely on the DERIVES_FROM relationship as the single source of truth. The archetype_id is retrieved dynamically from the relationship in queries. (dacc3be)

Comment on lines +1050 to +1061
if params.remove_tags:
update_parts.append(
"e.state_tags = [tag IN e.state_tags WHERE NOT tag IN $remove_tags]"
)
update_params["remove_tags"] = params.remove_tags

if params.add_tags:
# Add tags, avoiding duplicates
update_parts.append(
"e.state_tags = e.state_tags + [tag IN $add_tags WHERE NOT tag IN e.state_tags]"
)
update_params["add_tags"] = params.add_tags
Copy link

Copilot AI Dec 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The order of state_tags operations may produce unexpected results. When both add_tags and remove_tags are specified, remove operations happen first (line 1051), then add operations (line 1058). This means if the same tag appears in both lists, it will be removed and then re-added. Consider documenting this behavior or validating that add_tags and remove_tags don't overlap to prevent confusion.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added validation in StateTagsUpdate.model_post_init() to prevent tags from appearing in both add_tags and remove_tags. This raises a clear ValidationError with the overlapping tags listed. (dacc3be)

Comment on lines +668 to +671
# Add state_tags for instances
if not params.is_archetype:
entity_props["state_tags"] = params.state_tags
entity_props["updated_at"] = created_at.isoformat()
Copy link

Copilot AI Dec 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Archetypes should explicitly set state_tags to an empty list to maintain consistency with instances. Currently, archetypes don't have the state_tags property set at all (it's only added for instances on line 670). This could cause issues when querying, as archetypes would have no state_tags property while instances have an empty or populated list. Consider always setting state_tags, even for archetypes, to an empty list for consistency.

Suggested change
# Add state_tags for instances
if not params.is_archetype:
entity_props["state_tags"] = params.state_tags
entity_props["updated_at"] = created_at.isoformat()
# Add state_tags for instances; ensure archetypes also have an explicit (empty) list
if not params.is_archetype:
entity_props["state_tags"] = params.state_tags
entity_props["updated_at"] = created_at.isoformat()
else:
entity_props["state_tags"] = []

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Archetypes now explicitly set state_tags to an empty list for consistency. This ensures all Entity nodes have the state_tags property, making queries more predictable. (dacc3be)

Comment on lines +990 to +999
if force:
delete_query = """
MATCH (e:Entity {id: $id})
DETACH DELETE e
"""
else:
delete_query = """
MATCH (e:Entity {id: $id})
DETACH DELETE e
"""
Copy link

Copilot AI Dec 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The delete queries are identical for both force=True and force=False cases. The conditional logic is redundant - the same DETACH DELETE query can be used in both branches. Consider removing the conditional and always using a single delete query since the dependency check is performed earlier when force=False.

Suggested change
if force:
delete_query = """
MATCH (e:Entity {id: $id})
DETACH DELETE e
"""
else:
delete_query = """
MATCH (e:Entity {id: $id})
DETACH DELETE e
"""
delete_query = """
MATCH (e:Entity {id: $id})
DETACH DELETE e
"""

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the redundant conditional - now using a single DETACH DELETE query regardless of the force flag. The dependency check handles the logic earlier when force=False. (dacc3be)

@spuentesp
Copy link
Copy Markdown
Owner

@copilot apply changes based on the comments in this thread

…overlapping tags, set empty state_tags for archetypes, simplify delete logic

Co-authored-by: spuentesp <112034353+spuentesp@users.noreply.github.com>
@spuentesp spuentesp marked this pull request as ready for review December 28, 2025 06:31
@spuentesp spuentesp merged commit 139f8b4 into master Dec 28, 2025
3 checks passed
spuentesp pushed a commit that referenced this pull request Jan 4, 2026
… audit

- DL-1: Multiverse/Universes (PR #47)
- DL-2: Archetypes & Instances (PR #53)
- DL-3: Facts & Events (PR #54)
- DL-4: Stories/Scenes/Turns (PR #66)

All merged and complete. Added DL_IMPLEMENTATION_AUDIT.md to track
implementation status across all data layer use cases.
@spuentesp spuentesp deleted the copilot/manage-archetypes-instances branch April 25, 2026 00:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/data-layer Data layer changes type/tests Tests touched

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DL-2: Manage Archetypes & Instances

3 participants