feat: Engineering Knowledge Layer V1 — entities + relationships
Layer 2 of the AtoCore architecture. Adds typed engineering entities
with relationships on top of the flat memory/state/chunk substrate.
Schema:
- entities table: id, entity_type, name, project, description,
properties (JSON), status, confidence, source_refs, timestamps
- relationships table: source_entity_id, target_entity_id,
relationship_type, confidence, source_refs
15 entity types: project, system, subsystem, component, interface,
requirement, constraint, decision, material, parameter,
analysis_model, result, validation_claim, vendor, process
12 relationship types: contains, part_of, interfaces_with,
satisfies, constrained_by, affected_by_decision, analyzed_by,
validated_by, depends_on, uses_material, described_by, supersedes
Service layer: full CRUD + get_entity_with_context (returns an
entity with its relationships and all related entities in one call).
API endpoints:
- POST /entities — create entity
- GET /entities — list/filter by type, project, status, name
- GET /entities/{id} — entity + relationships + related entities
- POST /relationships — create relationship
Schema auto-initialized on app startup via init_engineering_schema().
7 tests covering entity CRUD, relationships, context traversal,
filtering, name search, and validation.
Test count: 290 -> 297.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 09:50:58 -04:00
|
|
|
"""Engineering entity and relationship CRUD."""
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import json
|
|
|
|
|
import uuid
|
|
|
|
|
from dataclasses import dataclass, field
|
|
|
|
|
from datetime import datetime, timezone
|
|
|
|
|
|
|
|
|
|
from atocore.models.database import get_connection
|
|
|
|
|
from atocore.observability.logger import get_logger
|
feat: Phase 5A — Engineering V1 foundation
First slice of the Engineering V1 sprint. Lays the schema + lifecycle
plumbing so the 10 canonical queries, memory graduation, and conflict
detection can land cleanly on top.
Schema (src/atocore/models/database.py):
- conflicts + conflict_members tables per conflict-model.md (with 5
indexes on status/project/slot/members)
- memory_audit.entity_kind discriminator — same audit table serves
both memories ("memory") and entities ("entity"); unified history
without duplicating infrastructure
- memories.graduated_to_entity_id forward pointer for graduated
memories (M → E transition preserves the memory as historical
pointer)
Memory (src/atocore/memory/service.py):
- MEMORY_STATUSES gains "graduated" — memory-entity graduation flow
ready to wire in Phase 5F
Engineering service (src/atocore/engineering/service.py):
- RELATIONSHIP_TYPES organized into 4 families per ontology-v1.md:
+ Structural: contains, part_of, interfaces_with
+ Intent: satisfies, constrained_by, affected_by_decision,
based_on_assumption (new), supersedes
+ Validation: analyzed_by, validated_by, supports (new),
conflicts_with (new), depends_on
+ Provenance: described_by, updated_by_session (new),
evidenced_by (new), summarized_in (new)
- create_entity + create_relationship now call resolve_project_name()
on write (canonicalization contract per doc)
- Both accept actor= parameter for audit provenance
- _audit_entity() helper uses shared memory_audit table with
entity_kind="entity" — one observability layer for everything
- promote_entity / reject_entity_candidate / supersede_entity —
mirror the memory lifecycle exactly (same pattern, same naming)
- get_entity_audit() reads from the shared table filtered by
entity_kind
API (src/atocore/api/routes.py):
- POST /entities/{id}/promote (candidate → active)
- POST /entities/{id}/reject (candidate → invalid)
- GET /entities/{id}/audit (full history for one entity)
- POST /entities passes actor="api-http" through
Tests: 317 → 326 (9 new):
- test_entity_project_canonicalization (p04 → p04-gigabit)
- test_promote_entity_candidate_to_active
- test_reject_entity_candidate
- test_promote_active_entity_noop (only candidates promote)
- test_entity_audit_log_captures_lifecycle (before/after snapshots)
- test_new_relationship_types_available (6 new types present)
- test_conflicts_tables_exist
- test_memory_audit_has_entity_kind
- test_graduated_status_accepted
What's next (5B-5I, deferred): entity triage UI tab, core structure
queries, the 3 killer queries, memory graduation script, conflict
detection, MCP + context pack integration. See plan file.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 07:01:28 -04:00
|
|
|
from atocore.projects.registry import resolve_project_name
|
feat: Engineering Knowledge Layer V1 — entities + relationships
Layer 2 of the AtoCore architecture. Adds typed engineering entities
with relationships on top of the flat memory/state/chunk substrate.
Schema:
- entities table: id, entity_type, name, project, description,
properties (JSON), status, confidence, source_refs, timestamps
- relationships table: source_entity_id, target_entity_id,
relationship_type, confidence, source_refs
15 entity types: project, system, subsystem, component, interface,
requirement, constraint, decision, material, parameter,
analysis_model, result, validation_claim, vendor, process
12 relationship types: contains, part_of, interfaces_with,
satisfies, constrained_by, affected_by_decision, analyzed_by,
validated_by, depends_on, uses_material, described_by, supersedes
Service layer: full CRUD + get_entity_with_context (returns an
entity with its relationships and all related entities in one call).
API endpoints:
- POST /entities — create entity
- GET /entities — list/filter by type, project, status, name
- GET /entities/{id} — entity + relationships + related entities
- POST /relationships — create relationship
Schema auto-initialized on app startup via init_engineering_schema().
7 tests covering entity CRUD, relationships, context traversal,
filtering, name search, and validation.
Test count: 290 -> 297.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 09:50:58 -04:00
|
|
|
|
|
|
|
|
log = get_logger("engineering")
|
|
|
|
|
|
|
|
|
|
ENTITY_TYPES = [
|
|
|
|
|
"project",
|
|
|
|
|
"system",
|
|
|
|
|
"subsystem",
|
|
|
|
|
"component",
|
|
|
|
|
"interface",
|
|
|
|
|
"requirement",
|
|
|
|
|
"constraint",
|
|
|
|
|
"decision",
|
|
|
|
|
"material",
|
|
|
|
|
"parameter",
|
|
|
|
|
"analysis_model",
|
|
|
|
|
"result",
|
|
|
|
|
"validation_claim",
|
|
|
|
|
"vendor",
|
|
|
|
|
"process",
|
feat(assets): binary asset store + artifact entity + wiki evidence (Issue F)
Wires visual evidence into the knowledge graph. Images, PDFs, and CAD
exports can now be uploaded, deduped by SHA-256, thumbnailed, linked to
entities via EVIDENCED_BY, and rendered inline on wiki pages. Unblocks
AKC uploading voice-session screenshots alongside extracted entities.
- assets/ module: store_asset (hash dedup + MIME allowlist + 20 MB cap),
get_asset_binary, get_thumbnail (Pillow, on-disk cache under
.thumbnails/<size>/), list_orphan_assets, invalidate_asset
- models/database.py: new `assets` table + indexes
- engineering/service.py: `artifact` added to ENTITY_TYPES
- api/routes.py: POST /assets (multipart), GET /assets/{id},
/assets/{id}/thumbnail, /assets/{id}/meta, /admin/assets/orphans,
DELETE /assets/{id} (409 if still referenced),
GET /entities/{id}/evidence (EVIDENCED_BY artifacts with asset meta)
- main.py: all new paths aliased under /v1
- engineering/wiki.py: entity pages render EVIDENCED_BY → artifact as a
"Visual evidence" thumbnail strip; artifact pages render the full
image + caption + capture_context
- deploy/dalidou/docker-compose.yml: bind-mount ${ATOCORE_ASSETS_DIR}
- config.py: assets_dir + assets_max_upload_bytes settings
- requirements.txt + pyproject.toml: python-multipart, Pillow>=10.0.0
- tests/test_assets.py: 16 tests (dedup, cap, thumbnail cache, orphan
detection, invalidate gating, API upload/fetch, evidence, v1 aliases,
wiki rendering)
- DEV-LEDGER.md: session log + cleanup note + test_count 478 -> 494
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 21:46:52 -04:00
|
|
|
# Issue F (visual evidence): images, PDFs, CAD exports attached to
|
|
|
|
|
# other entities via EVIDENCED_BY. properties carries kind +
|
|
|
|
|
# asset_id + caption + capture_context.
|
|
|
|
|
"artifact",
|
feat: Engineering Knowledge Layer V1 — entities + relationships
Layer 2 of the AtoCore architecture. Adds typed engineering entities
with relationships on top of the flat memory/state/chunk substrate.
Schema:
- entities table: id, entity_type, name, project, description,
properties (JSON), status, confidence, source_refs, timestamps
- relationships table: source_entity_id, target_entity_id,
relationship_type, confidence, source_refs
15 entity types: project, system, subsystem, component, interface,
requirement, constraint, decision, material, parameter,
analysis_model, result, validation_claim, vendor, process
12 relationship types: contains, part_of, interfaces_with,
satisfies, constrained_by, affected_by_decision, analyzed_by,
validated_by, depends_on, uses_material, described_by, supersedes
Service layer: full CRUD + get_entity_with_context (returns an
entity with its relationships and all related entities in one call).
API endpoints:
- POST /entities — create entity
- GET /entities — list/filter by type, project, status, name
- GET /entities/{id} — entity + relationships + related entities
- POST /relationships — create relationship
Schema auto-initialized on app startup via init_engineering_schema().
7 tests covering entity CRUD, relationships, context traversal,
filtering, name search, and validation.
Test count: 290 -> 297.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 09:50:58 -04:00
|
|
|
]
|
|
|
|
|
|
|
|
|
|
RELATIONSHIP_TYPES = [
|
feat: Phase 5A — Engineering V1 foundation
First slice of the Engineering V1 sprint. Lays the schema + lifecycle
plumbing so the 10 canonical queries, memory graduation, and conflict
detection can land cleanly on top.
Schema (src/atocore/models/database.py):
- conflicts + conflict_members tables per conflict-model.md (with 5
indexes on status/project/slot/members)
- memory_audit.entity_kind discriminator — same audit table serves
both memories ("memory") and entities ("entity"); unified history
without duplicating infrastructure
- memories.graduated_to_entity_id forward pointer for graduated
memories (M → E transition preserves the memory as historical
pointer)
Memory (src/atocore/memory/service.py):
- MEMORY_STATUSES gains "graduated" — memory-entity graduation flow
ready to wire in Phase 5F
Engineering service (src/atocore/engineering/service.py):
- RELATIONSHIP_TYPES organized into 4 families per ontology-v1.md:
+ Structural: contains, part_of, interfaces_with
+ Intent: satisfies, constrained_by, affected_by_decision,
based_on_assumption (new), supersedes
+ Validation: analyzed_by, validated_by, supports (new),
conflicts_with (new), depends_on
+ Provenance: described_by, updated_by_session (new),
evidenced_by (new), summarized_in (new)
- create_entity + create_relationship now call resolve_project_name()
on write (canonicalization contract per doc)
- Both accept actor= parameter for audit provenance
- _audit_entity() helper uses shared memory_audit table with
entity_kind="entity" — one observability layer for everything
- promote_entity / reject_entity_candidate / supersede_entity —
mirror the memory lifecycle exactly (same pattern, same naming)
- get_entity_audit() reads from the shared table filtered by
entity_kind
API (src/atocore/api/routes.py):
- POST /entities/{id}/promote (candidate → active)
- POST /entities/{id}/reject (candidate → invalid)
- GET /entities/{id}/audit (full history for one entity)
- POST /entities passes actor="api-http" through
Tests: 317 → 326 (9 new):
- test_entity_project_canonicalization (p04 → p04-gigabit)
- test_promote_entity_candidate_to_active
- test_reject_entity_candidate
- test_promote_active_entity_noop (only candidates promote)
- test_entity_audit_log_captures_lifecycle (before/after snapshots)
- test_new_relationship_types_available (6 new types present)
- test_conflicts_tables_exist
- test_memory_audit_has_entity_kind
- test_graduated_status_accepted
What's next (5B-5I, deferred): entity triage UI tab, core structure
queries, the 3 killer queries, memory graduation script, conflict
detection, MCP + context pack integration. See plan file.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 07:01:28 -04:00
|
|
|
# Structural family
|
feat: Engineering Knowledge Layer V1 — entities + relationships
Layer 2 of the AtoCore architecture. Adds typed engineering entities
with relationships on top of the flat memory/state/chunk substrate.
Schema:
- entities table: id, entity_type, name, project, description,
properties (JSON), status, confidence, source_refs, timestamps
- relationships table: source_entity_id, target_entity_id,
relationship_type, confidence, source_refs
15 entity types: project, system, subsystem, component, interface,
requirement, constraint, decision, material, parameter,
analysis_model, result, validation_claim, vendor, process
12 relationship types: contains, part_of, interfaces_with,
satisfies, constrained_by, affected_by_decision, analyzed_by,
validated_by, depends_on, uses_material, described_by, supersedes
Service layer: full CRUD + get_entity_with_context (returns an
entity with its relationships and all related entities in one call).
API endpoints:
- POST /entities — create entity
- GET /entities — list/filter by type, project, status, name
- GET /entities/{id} — entity + relationships + related entities
- POST /relationships — create relationship
Schema auto-initialized on app startup via init_engineering_schema().
7 tests covering entity CRUD, relationships, context traversal,
filtering, name search, and validation.
Test count: 290 -> 297.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 09:50:58 -04:00
|
|
|
"contains",
|
|
|
|
|
"part_of",
|
|
|
|
|
"interfaces_with",
|
feat: Phase 5A — Engineering V1 foundation
First slice of the Engineering V1 sprint. Lays the schema + lifecycle
plumbing so the 10 canonical queries, memory graduation, and conflict
detection can land cleanly on top.
Schema (src/atocore/models/database.py):
- conflicts + conflict_members tables per conflict-model.md (with 5
indexes on status/project/slot/members)
- memory_audit.entity_kind discriminator — same audit table serves
both memories ("memory") and entities ("entity"); unified history
without duplicating infrastructure
- memories.graduated_to_entity_id forward pointer for graduated
memories (M → E transition preserves the memory as historical
pointer)
Memory (src/atocore/memory/service.py):
- MEMORY_STATUSES gains "graduated" — memory-entity graduation flow
ready to wire in Phase 5F
Engineering service (src/atocore/engineering/service.py):
- RELATIONSHIP_TYPES organized into 4 families per ontology-v1.md:
+ Structural: contains, part_of, interfaces_with
+ Intent: satisfies, constrained_by, affected_by_decision,
based_on_assumption (new), supersedes
+ Validation: analyzed_by, validated_by, supports (new),
conflicts_with (new), depends_on
+ Provenance: described_by, updated_by_session (new),
evidenced_by (new), summarized_in (new)
- create_entity + create_relationship now call resolve_project_name()
on write (canonicalization contract per doc)
- Both accept actor= parameter for audit provenance
- _audit_entity() helper uses shared memory_audit table with
entity_kind="entity" — one observability layer for everything
- promote_entity / reject_entity_candidate / supersede_entity —
mirror the memory lifecycle exactly (same pattern, same naming)
- get_entity_audit() reads from the shared table filtered by
entity_kind
API (src/atocore/api/routes.py):
- POST /entities/{id}/promote (candidate → active)
- POST /entities/{id}/reject (candidate → invalid)
- GET /entities/{id}/audit (full history for one entity)
- POST /entities passes actor="api-http" through
Tests: 317 → 326 (9 new):
- test_entity_project_canonicalization (p04 → p04-gigabit)
- test_promote_entity_candidate_to_active
- test_reject_entity_candidate
- test_promote_active_entity_noop (only candidates promote)
- test_entity_audit_log_captures_lifecycle (before/after snapshots)
- test_new_relationship_types_available (6 new types present)
- test_conflicts_tables_exist
- test_memory_audit_has_entity_kind
- test_graduated_status_accepted
What's next (5B-5I, deferred): entity triage UI tab, core structure
queries, the 3 killer queries, memory graduation script, conflict
detection, MCP + context pack integration. See plan file.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 07:01:28 -04:00
|
|
|
# Intent family
|
feat: Engineering Knowledge Layer V1 — entities + relationships
Layer 2 of the AtoCore architecture. Adds typed engineering entities
with relationships on top of the flat memory/state/chunk substrate.
Schema:
- entities table: id, entity_type, name, project, description,
properties (JSON), status, confidence, source_refs, timestamps
- relationships table: source_entity_id, target_entity_id,
relationship_type, confidence, source_refs
15 entity types: project, system, subsystem, component, interface,
requirement, constraint, decision, material, parameter,
analysis_model, result, validation_claim, vendor, process
12 relationship types: contains, part_of, interfaces_with,
satisfies, constrained_by, affected_by_decision, analyzed_by,
validated_by, depends_on, uses_material, described_by, supersedes
Service layer: full CRUD + get_entity_with_context (returns an
entity with its relationships and all related entities in one call).
API endpoints:
- POST /entities — create entity
- GET /entities — list/filter by type, project, status, name
- GET /entities/{id} — entity + relationships + related entities
- POST /relationships — create relationship
Schema auto-initialized on app startup via init_engineering_schema().
7 tests covering entity CRUD, relationships, context traversal,
filtering, name search, and validation.
Test count: 290 -> 297.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 09:50:58 -04:00
|
|
|
"satisfies",
|
|
|
|
|
"constrained_by",
|
|
|
|
|
"affected_by_decision",
|
feat: Phase 5A — Engineering V1 foundation
First slice of the Engineering V1 sprint. Lays the schema + lifecycle
plumbing so the 10 canonical queries, memory graduation, and conflict
detection can land cleanly on top.
Schema (src/atocore/models/database.py):
- conflicts + conflict_members tables per conflict-model.md (with 5
indexes on status/project/slot/members)
- memory_audit.entity_kind discriminator — same audit table serves
both memories ("memory") and entities ("entity"); unified history
without duplicating infrastructure
- memories.graduated_to_entity_id forward pointer for graduated
memories (M → E transition preserves the memory as historical
pointer)
Memory (src/atocore/memory/service.py):
- MEMORY_STATUSES gains "graduated" — memory-entity graduation flow
ready to wire in Phase 5F
Engineering service (src/atocore/engineering/service.py):
- RELATIONSHIP_TYPES organized into 4 families per ontology-v1.md:
+ Structural: contains, part_of, interfaces_with
+ Intent: satisfies, constrained_by, affected_by_decision,
based_on_assumption (new), supersedes
+ Validation: analyzed_by, validated_by, supports (new),
conflicts_with (new), depends_on
+ Provenance: described_by, updated_by_session (new),
evidenced_by (new), summarized_in (new)
- create_entity + create_relationship now call resolve_project_name()
on write (canonicalization contract per doc)
- Both accept actor= parameter for audit provenance
- _audit_entity() helper uses shared memory_audit table with
entity_kind="entity" — one observability layer for everything
- promote_entity / reject_entity_candidate / supersede_entity —
mirror the memory lifecycle exactly (same pattern, same naming)
- get_entity_audit() reads from the shared table filtered by
entity_kind
API (src/atocore/api/routes.py):
- POST /entities/{id}/promote (candidate → active)
- POST /entities/{id}/reject (candidate → invalid)
- GET /entities/{id}/audit (full history for one entity)
- POST /entities passes actor="api-http" through
Tests: 317 → 326 (9 new):
- test_entity_project_canonicalization (p04 → p04-gigabit)
- test_promote_entity_candidate_to_active
- test_reject_entity_candidate
- test_promote_active_entity_noop (only candidates promote)
- test_entity_audit_log_captures_lifecycle (before/after snapshots)
- test_new_relationship_types_available (6 new types present)
- test_conflicts_tables_exist
- test_memory_audit_has_entity_kind
- test_graduated_status_accepted
What's next (5B-5I, deferred): entity triage UI tab, core structure
queries, the 3 killer queries, memory graduation script, conflict
detection, MCP + context pack integration. See plan file.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 07:01:28 -04:00
|
|
|
"based_on_assumption", # Phase 5 — Q-009 killer query
|
|
|
|
|
"supersedes",
|
|
|
|
|
# Validation family
|
feat: Engineering Knowledge Layer V1 — entities + relationships
Layer 2 of the AtoCore architecture. Adds typed engineering entities
with relationships on top of the flat memory/state/chunk substrate.
Schema:
- entities table: id, entity_type, name, project, description,
properties (JSON), status, confidence, source_refs, timestamps
- relationships table: source_entity_id, target_entity_id,
relationship_type, confidence, source_refs
15 entity types: project, system, subsystem, component, interface,
requirement, constraint, decision, material, parameter,
analysis_model, result, validation_claim, vendor, process
12 relationship types: contains, part_of, interfaces_with,
satisfies, constrained_by, affected_by_decision, analyzed_by,
validated_by, depends_on, uses_material, described_by, supersedes
Service layer: full CRUD + get_entity_with_context (returns an
entity with its relationships and all related entities in one call).
API endpoints:
- POST /entities — create entity
- GET /entities — list/filter by type, project, status, name
- GET /entities/{id} — entity + relationships + related entities
- POST /relationships — create relationship
Schema auto-initialized on app startup via init_engineering_schema().
7 tests covering entity CRUD, relationships, context traversal,
filtering, name search, and validation.
Test count: 290 -> 297.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 09:50:58 -04:00
|
|
|
"analyzed_by",
|
|
|
|
|
"validated_by",
|
feat: Phase 5A — Engineering V1 foundation
First slice of the Engineering V1 sprint. Lays the schema + lifecycle
plumbing so the 10 canonical queries, memory graduation, and conflict
detection can land cleanly on top.
Schema (src/atocore/models/database.py):
- conflicts + conflict_members tables per conflict-model.md (with 5
indexes on status/project/slot/members)
- memory_audit.entity_kind discriminator — same audit table serves
both memories ("memory") and entities ("entity"); unified history
without duplicating infrastructure
- memories.graduated_to_entity_id forward pointer for graduated
memories (M → E transition preserves the memory as historical
pointer)
Memory (src/atocore/memory/service.py):
- MEMORY_STATUSES gains "graduated" — memory-entity graduation flow
ready to wire in Phase 5F
Engineering service (src/atocore/engineering/service.py):
- RELATIONSHIP_TYPES organized into 4 families per ontology-v1.md:
+ Structural: contains, part_of, interfaces_with
+ Intent: satisfies, constrained_by, affected_by_decision,
based_on_assumption (new), supersedes
+ Validation: analyzed_by, validated_by, supports (new),
conflicts_with (new), depends_on
+ Provenance: described_by, updated_by_session (new),
evidenced_by (new), summarized_in (new)
- create_entity + create_relationship now call resolve_project_name()
on write (canonicalization contract per doc)
- Both accept actor= parameter for audit provenance
- _audit_entity() helper uses shared memory_audit table with
entity_kind="entity" — one observability layer for everything
- promote_entity / reject_entity_candidate / supersede_entity —
mirror the memory lifecycle exactly (same pattern, same naming)
- get_entity_audit() reads from the shared table filtered by
entity_kind
API (src/atocore/api/routes.py):
- POST /entities/{id}/promote (candidate → active)
- POST /entities/{id}/reject (candidate → invalid)
- GET /entities/{id}/audit (full history for one entity)
- POST /entities passes actor="api-http" through
Tests: 317 → 326 (9 new):
- test_entity_project_canonicalization (p04 → p04-gigabit)
- test_promote_entity_candidate_to_active
- test_reject_entity_candidate
- test_promote_active_entity_noop (only candidates promote)
- test_entity_audit_log_captures_lifecycle (before/after snapshots)
- test_new_relationship_types_available (6 new types present)
- test_conflicts_tables_exist
- test_memory_audit_has_entity_kind
- test_graduated_status_accepted
What's next (5B-5I, deferred): entity triage UI tab, core structure
queries, the 3 killer queries, memory graduation script, conflict
detection, MCP + context pack integration. See plan file.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 07:01:28 -04:00
|
|
|
"supports", # Phase 5 — Q-011 killer query
|
|
|
|
|
"conflicts_with", # Phase 5 — Q-012 future
|
feat: Engineering Knowledge Layer V1 — entities + relationships
Layer 2 of the AtoCore architecture. Adds typed engineering entities
with relationships on top of the flat memory/state/chunk substrate.
Schema:
- entities table: id, entity_type, name, project, description,
properties (JSON), status, confidence, source_refs, timestamps
- relationships table: source_entity_id, target_entity_id,
relationship_type, confidence, source_refs
15 entity types: project, system, subsystem, component, interface,
requirement, constraint, decision, material, parameter,
analysis_model, result, validation_claim, vendor, process
12 relationship types: contains, part_of, interfaces_with,
satisfies, constrained_by, affected_by_decision, analyzed_by,
validated_by, depends_on, uses_material, described_by, supersedes
Service layer: full CRUD + get_entity_with_context (returns an
entity with its relationships and all related entities in one call).
API endpoints:
- POST /entities — create entity
- GET /entities — list/filter by type, project, status, name
- GET /entities/{id} — entity + relationships + related entities
- POST /relationships — create relationship
Schema auto-initialized on app startup via init_engineering_schema().
7 tests covering entity CRUD, relationships, context traversal,
filtering, name search, and validation.
Test count: 290 -> 297.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 09:50:58 -04:00
|
|
|
"depends_on",
|
feat: Phase 5A — Engineering V1 foundation
First slice of the Engineering V1 sprint. Lays the schema + lifecycle
plumbing so the 10 canonical queries, memory graduation, and conflict
detection can land cleanly on top.
Schema (src/atocore/models/database.py):
- conflicts + conflict_members tables per conflict-model.md (with 5
indexes on status/project/slot/members)
- memory_audit.entity_kind discriminator — same audit table serves
both memories ("memory") and entities ("entity"); unified history
without duplicating infrastructure
- memories.graduated_to_entity_id forward pointer for graduated
memories (M → E transition preserves the memory as historical
pointer)
Memory (src/atocore/memory/service.py):
- MEMORY_STATUSES gains "graduated" — memory-entity graduation flow
ready to wire in Phase 5F
Engineering service (src/atocore/engineering/service.py):
- RELATIONSHIP_TYPES organized into 4 families per ontology-v1.md:
+ Structural: contains, part_of, interfaces_with
+ Intent: satisfies, constrained_by, affected_by_decision,
based_on_assumption (new), supersedes
+ Validation: analyzed_by, validated_by, supports (new),
conflicts_with (new), depends_on
+ Provenance: described_by, updated_by_session (new),
evidenced_by (new), summarized_in (new)
- create_entity + create_relationship now call resolve_project_name()
on write (canonicalization contract per doc)
- Both accept actor= parameter for audit provenance
- _audit_entity() helper uses shared memory_audit table with
entity_kind="entity" — one observability layer for everything
- promote_entity / reject_entity_candidate / supersede_entity —
mirror the memory lifecycle exactly (same pattern, same naming)
- get_entity_audit() reads from the shared table filtered by
entity_kind
API (src/atocore/api/routes.py):
- POST /entities/{id}/promote (candidate → active)
- POST /entities/{id}/reject (candidate → invalid)
- GET /entities/{id}/audit (full history for one entity)
- POST /entities passes actor="api-http" through
Tests: 317 → 326 (9 new):
- test_entity_project_canonicalization (p04 → p04-gigabit)
- test_promote_entity_candidate_to_active
- test_reject_entity_candidate
- test_promote_active_entity_noop (only candidates promote)
- test_entity_audit_log_captures_lifecycle (before/after snapshots)
- test_new_relationship_types_available (6 new types present)
- test_conflicts_tables_exist
- test_memory_audit_has_entity_kind
- test_graduated_status_accepted
What's next (5B-5I, deferred): entity triage UI tab, core structure
queries, the 3 killer queries, memory graduation script, conflict
detection, MCP + context pack integration. See plan file.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 07:01:28 -04:00
|
|
|
# Provenance family
|
feat: Engineering Knowledge Layer V1 — entities + relationships
Layer 2 of the AtoCore architecture. Adds typed engineering entities
with relationships on top of the flat memory/state/chunk substrate.
Schema:
- entities table: id, entity_type, name, project, description,
properties (JSON), status, confidence, source_refs, timestamps
- relationships table: source_entity_id, target_entity_id,
relationship_type, confidence, source_refs
15 entity types: project, system, subsystem, component, interface,
requirement, constraint, decision, material, parameter,
analysis_model, result, validation_claim, vendor, process
12 relationship types: contains, part_of, interfaces_with,
satisfies, constrained_by, affected_by_decision, analyzed_by,
validated_by, depends_on, uses_material, described_by, supersedes
Service layer: full CRUD + get_entity_with_context (returns an
entity with its relationships and all related entities in one call).
API endpoints:
- POST /entities — create entity
- GET /entities — list/filter by type, project, status, name
- GET /entities/{id} — entity + relationships + related entities
- POST /relationships — create relationship
Schema auto-initialized on app startup via init_engineering_schema().
7 tests covering entity CRUD, relationships, context traversal,
filtering, name search, and validation.
Test count: 290 -> 297.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 09:50:58 -04:00
|
|
|
"described_by",
|
feat: Phase 5A — Engineering V1 foundation
First slice of the Engineering V1 sprint. Lays the schema + lifecycle
plumbing so the 10 canonical queries, memory graduation, and conflict
detection can land cleanly on top.
Schema (src/atocore/models/database.py):
- conflicts + conflict_members tables per conflict-model.md (with 5
indexes on status/project/slot/members)
- memory_audit.entity_kind discriminator — same audit table serves
both memories ("memory") and entities ("entity"); unified history
without duplicating infrastructure
- memories.graduated_to_entity_id forward pointer for graduated
memories (M → E transition preserves the memory as historical
pointer)
Memory (src/atocore/memory/service.py):
- MEMORY_STATUSES gains "graduated" — memory-entity graduation flow
ready to wire in Phase 5F
Engineering service (src/atocore/engineering/service.py):
- RELATIONSHIP_TYPES organized into 4 families per ontology-v1.md:
+ Structural: contains, part_of, interfaces_with
+ Intent: satisfies, constrained_by, affected_by_decision,
based_on_assumption (new), supersedes
+ Validation: analyzed_by, validated_by, supports (new),
conflicts_with (new), depends_on
+ Provenance: described_by, updated_by_session (new),
evidenced_by (new), summarized_in (new)
- create_entity + create_relationship now call resolve_project_name()
on write (canonicalization contract per doc)
- Both accept actor= parameter for audit provenance
- _audit_entity() helper uses shared memory_audit table with
entity_kind="entity" — one observability layer for everything
- promote_entity / reject_entity_candidate / supersede_entity —
mirror the memory lifecycle exactly (same pattern, same naming)
- get_entity_audit() reads from the shared table filtered by
entity_kind
API (src/atocore/api/routes.py):
- POST /entities/{id}/promote (candidate → active)
- POST /entities/{id}/reject (candidate → invalid)
- GET /entities/{id}/audit (full history for one entity)
- POST /entities passes actor="api-http" through
Tests: 317 → 326 (9 new):
- test_entity_project_canonicalization (p04 → p04-gigabit)
- test_promote_entity_candidate_to_active
- test_reject_entity_candidate
- test_promote_active_entity_noop (only candidates promote)
- test_entity_audit_log_captures_lifecycle (before/after snapshots)
- test_new_relationship_types_available (6 new types present)
- test_conflicts_tables_exist
- test_memory_audit_has_entity_kind
- test_graduated_status_accepted
What's next (5B-5I, deferred): entity triage UI tab, core structure
queries, the 3 killer queries, memory graduation script, conflict
detection, MCP + context pack integration. See plan file.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 07:01:28 -04:00
|
|
|
"updated_by_session", # Phase 5 — session→entity provenance
|
|
|
|
|
"evidenced_by", # Phase 5 — Q-017 evidence trace
|
|
|
|
|
"summarized_in", # Phase 5 — mirror caches
|
|
|
|
|
# Domain-specific (pre-existing, retained)
|
|
|
|
|
"uses_material",
|
feat: Engineering Knowledge Layer V1 — entities + relationships
Layer 2 of the AtoCore architecture. Adds typed engineering entities
with relationships on top of the flat memory/state/chunk substrate.
Schema:
- entities table: id, entity_type, name, project, description,
properties (JSON), status, confidence, source_refs, timestamps
- relationships table: source_entity_id, target_entity_id,
relationship_type, confidence, source_refs
15 entity types: project, system, subsystem, component, interface,
requirement, constraint, decision, material, parameter,
analysis_model, result, validation_claim, vendor, process
12 relationship types: contains, part_of, interfaces_with,
satisfies, constrained_by, affected_by_decision, analyzed_by,
validated_by, depends_on, uses_material, described_by, supersedes
Service layer: full CRUD + get_entity_with_context (returns an
entity with its relationships and all related entities in one call).
API endpoints:
- POST /entities — create entity
- GET /entities — list/filter by type, project, status, name
- GET /entities/{id} — entity + relationships + related entities
- POST /relationships — create relationship
Schema auto-initialized on app startup via init_engineering_schema().
7 tests covering entity CRUD, relationships, context traversal,
filtering, name search, and validation.
Test count: 290 -> 297.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 09:50:58 -04:00
|
|
|
]
|
|
|
|
|
|
|
|
|
|
ENTITY_STATUSES = ["candidate", "active", "superseded", "invalid"]
|
|
|
|
|
|
2026-04-22 14:59:17 -04:00
|
|
|
# V1-0: extractor version this module writes into new entity rows.
|
|
|
|
|
# Per promotion-rules.md:268, every candidate must record the version of
|
|
|
|
|
# the extractor that produced it so later re-evaluation is auditable.
|
|
|
|
|
# Bump this when extraction logic materially changes.
|
|
|
|
|
EXTRACTOR_VERSION = "v1.0.0"
|
|
|
|
|
|
feat: Engineering Knowledge Layer V1 — entities + relationships
Layer 2 of the AtoCore architecture. Adds typed engineering entities
with relationships on top of the flat memory/state/chunk substrate.
Schema:
- entities table: id, entity_type, name, project, description,
properties (JSON), status, confidence, source_refs, timestamps
- relationships table: source_entity_id, target_entity_id,
relationship_type, confidence, source_refs
15 entity types: project, system, subsystem, component, interface,
requirement, constraint, decision, material, parameter,
analysis_model, result, validation_claim, vendor, process
12 relationship types: contains, part_of, interfaces_with,
satisfies, constrained_by, affected_by_decision, analyzed_by,
validated_by, depends_on, uses_material, described_by, supersedes
Service layer: full CRUD + get_entity_with_context (returns an
entity with its relationships and all related entities in one call).
API endpoints:
- POST /entities — create entity
- GET /entities — list/filter by type, project, status, name
- GET /entities/{id} — entity + relationships + related entities
- POST /relationships — create relationship
Schema auto-initialized on app startup via init_engineering_schema().
7 tests covering entity CRUD, relationships, context traversal,
filtering, name search, and validation.
Test count: 290 -> 297.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 09:50:58 -04:00
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
|
class Entity:
|
|
|
|
|
id: str
|
|
|
|
|
entity_type: str
|
|
|
|
|
name: str
|
|
|
|
|
project: str
|
|
|
|
|
description: str = ""
|
|
|
|
|
properties: dict = field(default_factory=dict)
|
|
|
|
|
status: str = "active"
|
|
|
|
|
confidence: float = 1.0
|
|
|
|
|
source_refs: list[str] = field(default_factory=list)
|
|
|
|
|
created_at: str = ""
|
|
|
|
|
updated_at: str = ""
|
2026-04-22 14:59:17 -04:00
|
|
|
# V1-0 shared-header fields per engineering-v1-acceptance.md:45.
|
|
|
|
|
extractor_version: str = ""
|
|
|
|
|
canonical_home: str = "entity"
|
|
|
|
|
hand_authored: bool = False
|
feat: Engineering Knowledge Layer V1 — entities + relationships
Layer 2 of the AtoCore architecture. Adds typed engineering entities
with relationships on top of the flat memory/state/chunk substrate.
Schema:
- entities table: id, entity_type, name, project, description,
properties (JSON), status, confidence, source_refs, timestamps
- relationships table: source_entity_id, target_entity_id,
relationship_type, confidence, source_refs
15 entity types: project, system, subsystem, component, interface,
requirement, constraint, decision, material, parameter,
analysis_model, result, validation_claim, vendor, process
12 relationship types: contains, part_of, interfaces_with,
satisfies, constrained_by, affected_by_decision, analyzed_by,
validated_by, depends_on, uses_material, described_by, supersedes
Service layer: full CRUD + get_entity_with_context (returns an
entity with its relationships and all related entities in one call).
API endpoints:
- POST /entities — create entity
- GET /entities — list/filter by type, project, status, name
- GET /entities/{id} — entity + relationships + related entities
- POST /relationships — create relationship
Schema auto-initialized on app startup via init_engineering_schema().
7 tests covering entity CRUD, relationships, context traversal,
filtering, name search, and validation.
Test count: 290 -> 297.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 09:50:58 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
|
class Relationship:
|
|
|
|
|
id: str
|
|
|
|
|
source_entity_id: str
|
|
|
|
|
target_entity_id: str
|
|
|
|
|
relationship_type: str
|
|
|
|
|
confidence: float = 1.0
|
|
|
|
|
source_refs: list[str] = field(default_factory=list)
|
|
|
|
|
created_at: str = ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def init_engineering_schema() -> None:
|
|
|
|
|
with get_connection() as conn:
|
|
|
|
|
conn.execute("""
|
|
|
|
|
CREATE TABLE IF NOT EXISTS entities (
|
|
|
|
|
id TEXT PRIMARY KEY,
|
|
|
|
|
entity_type TEXT NOT NULL,
|
|
|
|
|
name TEXT NOT NULL,
|
|
|
|
|
project TEXT NOT NULL DEFAULT '',
|
|
|
|
|
description TEXT NOT NULL DEFAULT '',
|
|
|
|
|
properties TEXT NOT NULL DEFAULT '{}',
|
|
|
|
|
status TEXT NOT NULL DEFAULT 'active',
|
|
|
|
|
confidence REAL NOT NULL DEFAULT 1.0,
|
|
|
|
|
source_refs TEXT NOT NULL DEFAULT '[]',
|
2026-04-22 14:59:17 -04:00
|
|
|
extractor_version TEXT NOT NULL DEFAULT '',
|
|
|
|
|
canonical_home TEXT NOT NULL DEFAULT 'entity',
|
|
|
|
|
hand_authored INTEGER NOT NULL DEFAULT 0,
|
feat: Engineering Knowledge Layer V1 — entities + relationships
Layer 2 of the AtoCore architecture. Adds typed engineering entities
with relationships on top of the flat memory/state/chunk substrate.
Schema:
- entities table: id, entity_type, name, project, description,
properties (JSON), status, confidence, source_refs, timestamps
- relationships table: source_entity_id, target_entity_id,
relationship_type, confidence, source_refs
15 entity types: project, system, subsystem, component, interface,
requirement, constraint, decision, material, parameter,
analysis_model, result, validation_claim, vendor, process
12 relationship types: contains, part_of, interfaces_with,
satisfies, constrained_by, affected_by_decision, analyzed_by,
validated_by, depends_on, uses_material, described_by, supersedes
Service layer: full CRUD + get_entity_with_context (returns an
entity with its relationships and all related entities in one call).
API endpoints:
- POST /entities — create entity
- GET /entities — list/filter by type, project, status, name
- GET /entities/{id} — entity + relationships + related entities
- POST /relationships — create relationship
Schema auto-initialized on app startup via init_engineering_schema().
7 tests covering entity CRUD, relationships, context traversal,
filtering, name search, and validation.
Test count: 290 -> 297.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 09:50:58 -04:00
|
|
|
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
|
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
|
|
|
)
|
|
|
|
|
""")
|
2026-04-22 14:59:17 -04:00
|
|
|
# V1-0 (Engineering V1 completion): the three shared-header fields
|
|
|
|
|
# per engineering-v1-acceptance.md:45. Idempotent ALTERs for
|
|
|
|
|
# databases created before V1-0 land these columns without a full
|
|
|
|
|
# migration. Fresh DBs get them via the CREATE TABLE above; the
|
|
|
|
|
# ALTERs below are a no-op there.
|
|
|
|
|
from atocore.models.database import _column_exists # late import; avoids cycle
|
|
|
|
|
if not _column_exists(conn, "entities", "extractor_version"):
|
|
|
|
|
conn.execute("ALTER TABLE entities ADD COLUMN extractor_version TEXT DEFAULT ''")
|
|
|
|
|
if not _column_exists(conn, "entities", "canonical_home"):
|
|
|
|
|
conn.execute("ALTER TABLE entities ADD COLUMN canonical_home TEXT DEFAULT 'entity'")
|
|
|
|
|
if not _column_exists(conn, "entities", "hand_authored"):
|
|
|
|
|
conn.execute("ALTER TABLE entities ADD COLUMN hand_authored INTEGER DEFAULT 0")
|
feat: Engineering Knowledge Layer V1 — entities + relationships
Layer 2 of the AtoCore architecture. Adds typed engineering entities
with relationships on top of the flat memory/state/chunk substrate.
Schema:
- entities table: id, entity_type, name, project, description,
properties (JSON), status, confidence, source_refs, timestamps
- relationships table: source_entity_id, target_entity_id,
relationship_type, confidence, source_refs
15 entity types: project, system, subsystem, component, interface,
requirement, constraint, decision, material, parameter,
analysis_model, result, validation_claim, vendor, process
12 relationship types: contains, part_of, interfaces_with,
satisfies, constrained_by, affected_by_decision, analyzed_by,
validated_by, depends_on, uses_material, described_by, supersedes
Service layer: full CRUD + get_entity_with_context (returns an
entity with its relationships and all related entities in one call).
API endpoints:
- POST /entities — create entity
- GET /entities — list/filter by type, project, status, name
- GET /entities/{id} — entity + relationships + related entities
- POST /relationships — create relationship
Schema auto-initialized on app startup via init_engineering_schema().
7 tests covering entity CRUD, relationships, context traversal,
filtering, name search, and validation.
Test count: 290 -> 297.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 09:50:58 -04:00
|
|
|
conn.execute("""
|
|
|
|
|
CREATE TABLE IF NOT EXISTS relationships (
|
|
|
|
|
id TEXT PRIMARY KEY,
|
|
|
|
|
source_entity_id TEXT NOT NULL,
|
|
|
|
|
target_entity_id TEXT NOT NULL,
|
|
|
|
|
relationship_type TEXT NOT NULL,
|
|
|
|
|
confidence REAL NOT NULL DEFAULT 1.0,
|
|
|
|
|
source_refs TEXT NOT NULL DEFAULT '[]',
|
|
|
|
|
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
|
FOREIGN KEY (source_entity_id) REFERENCES entities(id),
|
|
|
|
|
FOREIGN KEY (target_entity_id) REFERENCES entities(id)
|
|
|
|
|
)
|
|
|
|
|
""")
|
|
|
|
|
conn.execute("""
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_entities_project
|
|
|
|
|
ON entities(project)
|
|
|
|
|
""")
|
|
|
|
|
conn.execute("""
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_entities_type
|
|
|
|
|
ON entities(entity_type)
|
|
|
|
|
""")
|
|
|
|
|
conn.execute("""
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_relationships_source
|
|
|
|
|
ON relationships(source_entity_id)
|
|
|
|
|
""")
|
|
|
|
|
conn.execute("""
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_relationships_target
|
|
|
|
|
ON relationships(target_entity_id)
|
|
|
|
|
""")
|
|
|
|
|
log.info("engineering_schema_initialized")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def create_entity(
|
|
|
|
|
entity_type: str,
|
|
|
|
|
name: str,
|
|
|
|
|
project: str = "",
|
|
|
|
|
description: str = "",
|
|
|
|
|
properties: dict | None = None,
|
|
|
|
|
status: str = "active",
|
|
|
|
|
confidence: float = 1.0,
|
|
|
|
|
source_refs: list[str] | None = None,
|
feat: Phase 5A — Engineering V1 foundation
First slice of the Engineering V1 sprint. Lays the schema + lifecycle
plumbing so the 10 canonical queries, memory graduation, and conflict
detection can land cleanly on top.
Schema (src/atocore/models/database.py):
- conflicts + conflict_members tables per conflict-model.md (with 5
indexes on status/project/slot/members)
- memory_audit.entity_kind discriminator — same audit table serves
both memories ("memory") and entities ("entity"); unified history
without duplicating infrastructure
- memories.graduated_to_entity_id forward pointer for graduated
memories (M → E transition preserves the memory as historical
pointer)
Memory (src/atocore/memory/service.py):
- MEMORY_STATUSES gains "graduated" — memory-entity graduation flow
ready to wire in Phase 5F
Engineering service (src/atocore/engineering/service.py):
- RELATIONSHIP_TYPES organized into 4 families per ontology-v1.md:
+ Structural: contains, part_of, interfaces_with
+ Intent: satisfies, constrained_by, affected_by_decision,
based_on_assumption (new), supersedes
+ Validation: analyzed_by, validated_by, supports (new),
conflicts_with (new), depends_on
+ Provenance: described_by, updated_by_session (new),
evidenced_by (new), summarized_in (new)
- create_entity + create_relationship now call resolve_project_name()
on write (canonicalization contract per doc)
- Both accept actor= parameter for audit provenance
- _audit_entity() helper uses shared memory_audit table with
entity_kind="entity" — one observability layer for everything
- promote_entity / reject_entity_candidate / supersede_entity —
mirror the memory lifecycle exactly (same pattern, same naming)
- get_entity_audit() reads from the shared table filtered by
entity_kind
API (src/atocore/api/routes.py):
- POST /entities/{id}/promote (candidate → active)
- POST /entities/{id}/reject (candidate → invalid)
- GET /entities/{id}/audit (full history for one entity)
- POST /entities passes actor="api-http" through
Tests: 317 → 326 (9 new):
- test_entity_project_canonicalization (p04 → p04-gigabit)
- test_promote_entity_candidate_to_active
- test_reject_entity_candidate
- test_promote_active_entity_noop (only candidates promote)
- test_entity_audit_log_captures_lifecycle (before/after snapshots)
- test_new_relationship_types_available (6 new types present)
- test_conflicts_tables_exist
- test_memory_audit_has_entity_kind
- test_graduated_status_accepted
What's next (5B-5I, deferred): entity triage UI tab, core structure
queries, the 3 killer queries, memory graduation script, conflict
detection, MCP + context pack integration. See plan file.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 07:01:28 -04:00
|
|
|
actor: str = "api",
|
2026-04-22 14:59:17 -04:00
|
|
|
hand_authored: bool = False,
|
|
|
|
|
extractor_version: str | None = None,
|
feat: Engineering Knowledge Layer V1 — entities + relationships
Layer 2 of the AtoCore architecture. Adds typed engineering entities
with relationships on top of the flat memory/state/chunk substrate.
Schema:
- entities table: id, entity_type, name, project, description,
properties (JSON), status, confidence, source_refs, timestamps
- relationships table: source_entity_id, target_entity_id,
relationship_type, confidence, source_refs
15 entity types: project, system, subsystem, component, interface,
requirement, constraint, decision, material, parameter,
analysis_model, result, validation_claim, vendor, process
12 relationship types: contains, part_of, interfaces_with,
satisfies, constrained_by, affected_by_decision, analyzed_by,
validated_by, depends_on, uses_material, described_by, supersedes
Service layer: full CRUD + get_entity_with_context (returns an
entity with its relationships and all related entities in one call).
API endpoints:
- POST /entities — create entity
- GET /entities — list/filter by type, project, status, name
- GET /entities/{id} — entity + relationships + related entities
- POST /relationships — create relationship
Schema auto-initialized on app startup via init_engineering_schema().
7 tests covering entity CRUD, relationships, context traversal,
filtering, name search, and validation.
Test count: 290 -> 297.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 09:50:58 -04:00
|
|
|
) -> Entity:
|
|
|
|
|
if entity_type not in ENTITY_TYPES:
|
|
|
|
|
raise ValueError(f"Invalid entity type: {entity_type}. Must be one of {ENTITY_TYPES}")
|
|
|
|
|
if status not in ENTITY_STATUSES:
|
|
|
|
|
raise ValueError(f"Invalid status: {status}. Must be one of {ENTITY_STATUSES}")
|
|
|
|
|
if not name or not name.strip():
|
|
|
|
|
raise ValueError("Entity name must be non-empty")
|
|
|
|
|
|
2026-04-22 14:59:17 -04:00
|
|
|
refs = list(source_refs) if source_refs else []
|
|
|
|
|
|
|
|
|
|
# V1-0 (F-8 provenance enforcement, engineering-v1-acceptance.md:147):
|
|
|
|
|
# every new entity row must carry non-empty source_refs OR be explicitly
|
|
|
|
|
# flagged hand_authored. This is the non-negotiable invariant every
|
|
|
|
|
# later V1 phase depends on — without it, active entities can escape
|
|
|
|
|
# into the graph with no traceable origin. Raises at the write seam so
|
|
|
|
|
# the bug is impossible to introduce silently.
|
|
|
|
|
if not refs and not hand_authored:
|
|
|
|
|
raise ValueError(
|
|
|
|
|
"source_refs required: every entity must carry provenance "
|
|
|
|
|
"(source_chunk_id / source_interaction_id / kb_cad_export_id / ...) "
|
|
|
|
|
"or set hand_authored=True to explicitly flag a direct human write"
|
|
|
|
|
)
|
|
|
|
|
|
feat: Phase 5A — Engineering V1 foundation
First slice of the Engineering V1 sprint. Lays the schema + lifecycle
plumbing so the 10 canonical queries, memory graduation, and conflict
detection can land cleanly on top.
Schema (src/atocore/models/database.py):
- conflicts + conflict_members tables per conflict-model.md (with 5
indexes on status/project/slot/members)
- memory_audit.entity_kind discriminator — same audit table serves
both memories ("memory") and entities ("entity"); unified history
without duplicating infrastructure
- memories.graduated_to_entity_id forward pointer for graduated
memories (M → E transition preserves the memory as historical
pointer)
Memory (src/atocore/memory/service.py):
- MEMORY_STATUSES gains "graduated" — memory-entity graduation flow
ready to wire in Phase 5F
Engineering service (src/atocore/engineering/service.py):
- RELATIONSHIP_TYPES organized into 4 families per ontology-v1.md:
+ Structural: contains, part_of, interfaces_with
+ Intent: satisfies, constrained_by, affected_by_decision,
based_on_assumption (new), supersedes
+ Validation: analyzed_by, validated_by, supports (new),
conflicts_with (new), depends_on
+ Provenance: described_by, updated_by_session (new),
evidenced_by (new), summarized_in (new)
- create_entity + create_relationship now call resolve_project_name()
on write (canonicalization contract per doc)
- Both accept actor= parameter for audit provenance
- _audit_entity() helper uses shared memory_audit table with
entity_kind="entity" — one observability layer for everything
- promote_entity / reject_entity_candidate / supersede_entity —
mirror the memory lifecycle exactly (same pattern, same naming)
- get_entity_audit() reads from the shared table filtered by
entity_kind
API (src/atocore/api/routes.py):
- POST /entities/{id}/promote (candidate → active)
- POST /entities/{id}/reject (candidate → invalid)
- GET /entities/{id}/audit (full history for one entity)
- POST /entities passes actor="api-http" through
Tests: 317 → 326 (9 new):
- test_entity_project_canonicalization (p04 → p04-gigabit)
- test_promote_entity_candidate_to_active
- test_reject_entity_candidate
- test_promote_active_entity_noop (only candidates promote)
- test_entity_audit_log_captures_lifecycle (before/after snapshots)
- test_new_relationship_types_available (6 new types present)
- test_conflicts_tables_exist
- test_memory_audit_has_entity_kind
- test_graduated_status_accepted
What's next (5B-5I, deferred): entity triage UI tab, core structure
queries, the 3 killer queries, memory graduation script, conflict
detection, MCP + context pack integration. See plan file.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 07:01:28 -04:00
|
|
|
# Phase 5: enforce project canonicalization contract at the write seam.
|
|
|
|
|
# Aliases like "p04" become "p04-gigabit" so downstream reads stay
|
|
|
|
|
# consistent with the registry.
|
|
|
|
|
project = resolve_project_name(project) if project else ""
|
|
|
|
|
|
feat: Engineering Knowledge Layer V1 — entities + relationships
Layer 2 of the AtoCore architecture. Adds typed engineering entities
with relationships on top of the flat memory/state/chunk substrate.
Schema:
- entities table: id, entity_type, name, project, description,
properties (JSON), status, confidence, source_refs, timestamps
- relationships table: source_entity_id, target_entity_id,
relationship_type, confidence, source_refs
15 entity types: project, system, subsystem, component, interface,
requirement, constraint, decision, material, parameter,
analysis_model, result, validation_claim, vendor, process
12 relationship types: contains, part_of, interfaces_with,
satisfies, constrained_by, affected_by_decision, analyzed_by,
validated_by, depends_on, uses_material, described_by, supersedes
Service layer: full CRUD + get_entity_with_context (returns an
entity with its relationships and all related entities in one call).
API endpoints:
- POST /entities — create entity
- GET /entities — list/filter by type, project, status, name
- GET /entities/{id} — entity + relationships + related entities
- POST /relationships — create relationship
Schema auto-initialized on app startup via init_engineering_schema().
7 tests covering entity CRUD, relationships, context traversal,
filtering, name search, and validation.
Test count: 290 -> 297.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 09:50:58 -04:00
|
|
|
entity_id = str(uuid.uuid4())
|
|
|
|
|
now = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S")
|
|
|
|
|
props = properties or {}
|
2026-04-22 14:59:17 -04:00
|
|
|
ev = extractor_version if extractor_version is not None else EXTRACTOR_VERSION
|
feat: Engineering Knowledge Layer V1 — entities + relationships
Layer 2 of the AtoCore architecture. Adds typed engineering entities
with relationships on top of the flat memory/state/chunk substrate.
Schema:
- entities table: id, entity_type, name, project, description,
properties (JSON), status, confidence, source_refs, timestamps
- relationships table: source_entity_id, target_entity_id,
relationship_type, confidence, source_refs
15 entity types: project, system, subsystem, component, interface,
requirement, constraint, decision, material, parameter,
analysis_model, result, validation_claim, vendor, process
12 relationship types: contains, part_of, interfaces_with,
satisfies, constrained_by, affected_by_decision, analyzed_by,
validated_by, depends_on, uses_material, described_by, supersedes
Service layer: full CRUD + get_entity_with_context (returns an
entity with its relationships and all related entities in one call).
API endpoints:
- POST /entities — create entity
- GET /entities — list/filter by type, project, status, name
- GET /entities/{id} — entity + relationships + related entities
- POST /relationships — create relationship
Schema auto-initialized on app startup via init_engineering_schema().
7 tests covering entity CRUD, relationships, context traversal,
filtering, name search, and validation.
Test count: 290 -> 297.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 09:50:58 -04:00
|
|
|
|
|
|
|
|
with get_connection() as conn:
|
|
|
|
|
conn.execute(
|
|
|
|
|
"""INSERT INTO entities
|
|
|
|
|
(id, entity_type, name, project, description, properties,
|
2026-04-22 14:59:17 -04:00
|
|
|
status, confidence, source_refs,
|
|
|
|
|
extractor_version, canonical_home, hand_authored,
|
|
|
|
|
created_at, updated_at)
|
|
|
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
|
feat: Engineering Knowledge Layer V1 — entities + relationships
Layer 2 of the AtoCore architecture. Adds typed engineering entities
with relationships on top of the flat memory/state/chunk substrate.
Schema:
- entities table: id, entity_type, name, project, description,
properties (JSON), status, confidence, source_refs, timestamps
- relationships table: source_entity_id, target_entity_id,
relationship_type, confidence, source_refs
15 entity types: project, system, subsystem, component, interface,
requirement, constraint, decision, material, parameter,
analysis_model, result, validation_claim, vendor, process
12 relationship types: contains, part_of, interfaces_with,
satisfies, constrained_by, affected_by_decision, analyzed_by,
validated_by, depends_on, uses_material, described_by, supersedes
Service layer: full CRUD + get_entity_with_context (returns an
entity with its relationships and all related entities in one call).
API endpoints:
- POST /entities — create entity
- GET /entities — list/filter by type, project, status, name
- GET /entities/{id} — entity + relationships + related entities
- POST /relationships — create relationship
Schema auto-initialized on app startup via init_engineering_schema().
7 tests covering entity CRUD, relationships, context traversal,
filtering, name search, and validation.
Test count: 290 -> 297.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 09:50:58 -04:00
|
|
|
(
|
|
|
|
|
entity_id, entity_type, name.strip(), project,
|
|
|
|
|
description, json.dumps(props), status, confidence,
|
2026-04-22 14:59:17 -04:00
|
|
|
json.dumps(refs),
|
|
|
|
|
ev, "entity", 1 if hand_authored else 0,
|
|
|
|
|
now, now,
|
feat: Engineering Knowledge Layer V1 — entities + relationships
Layer 2 of the AtoCore architecture. Adds typed engineering entities
with relationships on top of the flat memory/state/chunk substrate.
Schema:
- entities table: id, entity_type, name, project, description,
properties (JSON), status, confidence, source_refs, timestamps
- relationships table: source_entity_id, target_entity_id,
relationship_type, confidence, source_refs
15 entity types: project, system, subsystem, component, interface,
requirement, constraint, decision, material, parameter,
analysis_model, result, validation_claim, vendor, process
12 relationship types: contains, part_of, interfaces_with,
satisfies, constrained_by, affected_by_decision, analyzed_by,
validated_by, depends_on, uses_material, described_by, supersedes
Service layer: full CRUD + get_entity_with_context (returns an
entity with its relationships and all related entities in one call).
API endpoints:
- POST /entities — create entity
- GET /entities — list/filter by type, project, status, name
- GET /entities/{id} — entity + relationships + related entities
- POST /relationships — create relationship
Schema auto-initialized on app startup via init_engineering_schema().
7 tests covering entity CRUD, relationships, context traversal,
filtering, name search, and validation.
Test count: 290 -> 297.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 09:50:58 -04:00
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
log.info("entity_created", entity_id=entity_id, entity_type=entity_type, name=name)
|
feat: Phase 5A — Engineering V1 foundation
First slice of the Engineering V1 sprint. Lays the schema + lifecycle
plumbing so the 10 canonical queries, memory graduation, and conflict
detection can land cleanly on top.
Schema (src/atocore/models/database.py):
- conflicts + conflict_members tables per conflict-model.md (with 5
indexes on status/project/slot/members)
- memory_audit.entity_kind discriminator — same audit table serves
both memories ("memory") and entities ("entity"); unified history
without duplicating infrastructure
- memories.graduated_to_entity_id forward pointer for graduated
memories (M → E transition preserves the memory as historical
pointer)
Memory (src/atocore/memory/service.py):
- MEMORY_STATUSES gains "graduated" — memory-entity graduation flow
ready to wire in Phase 5F
Engineering service (src/atocore/engineering/service.py):
- RELATIONSHIP_TYPES organized into 4 families per ontology-v1.md:
+ Structural: contains, part_of, interfaces_with
+ Intent: satisfies, constrained_by, affected_by_decision,
based_on_assumption (new), supersedes
+ Validation: analyzed_by, validated_by, supports (new),
conflicts_with (new), depends_on
+ Provenance: described_by, updated_by_session (new),
evidenced_by (new), summarized_in (new)
- create_entity + create_relationship now call resolve_project_name()
on write (canonicalization contract per doc)
- Both accept actor= parameter for audit provenance
- _audit_entity() helper uses shared memory_audit table with
entity_kind="entity" — one observability layer for everything
- promote_entity / reject_entity_candidate / supersede_entity —
mirror the memory lifecycle exactly (same pattern, same naming)
- get_entity_audit() reads from the shared table filtered by
entity_kind
API (src/atocore/api/routes.py):
- POST /entities/{id}/promote (candidate → active)
- POST /entities/{id}/reject (candidate → invalid)
- GET /entities/{id}/audit (full history for one entity)
- POST /entities passes actor="api-http" through
Tests: 317 → 326 (9 new):
- test_entity_project_canonicalization (p04 → p04-gigabit)
- test_promote_entity_candidate_to_active
- test_reject_entity_candidate
- test_promote_active_entity_noop (only candidates promote)
- test_entity_audit_log_captures_lifecycle (before/after snapshots)
- test_new_relationship_types_available (6 new types present)
- test_conflicts_tables_exist
- test_memory_audit_has_entity_kind
- test_graduated_status_accepted
What's next (5B-5I, deferred): entity triage UI tab, core structure
queries, the 3 killer queries, memory graduation script, conflict
detection, MCP + context pack integration. See plan file.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 07:01:28 -04:00
|
|
|
|
|
|
|
|
# Phase 5: entity audit rows share the memory_audit table via
|
|
|
|
|
# entity_kind="entity" discriminator. Same infrastructure, unified history.
|
|
|
|
|
_audit_entity(
|
|
|
|
|
entity_id=entity_id,
|
|
|
|
|
action="created",
|
|
|
|
|
actor=actor,
|
|
|
|
|
after={
|
|
|
|
|
"entity_type": entity_type,
|
|
|
|
|
"name": name.strip(),
|
|
|
|
|
"project": project,
|
|
|
|
|
"status": status,
|
|
|
|
|
"confidence": confidence,
|
2026-04-22 14:59:17 -04:00
|
|
|
"hand_authored": hand_authored,
|
|
|
|
|
"extractor_version": ev,
|
feat: Phase 5A — Engineering V1 foundation
First slice of the Engineering V1 sprint. Lays the schema + lifecycle
plumbing so the 10 canonical queries, memory graduation, and conflict
detection can land cleanly on top.
Schema (src/atocore/models/database.py):
- conflicts + conflict_members tables per conflict-model.md (with 5
indexes on status/project/slot/members)
- memory_audit.entity_kind discriminator — same audit table serves
both memories ("memory") and entities ("entity"); unified history
without duplicating infrastructure
- memories.graduated_to_entity_id forward pointer for graduated
memories (M → E transition preserves the memory as historical
pointer)
Memory (src/atocore/memory/service.py):
- MEMORY_STATUSES gains "graduated" — memory-entity graduation flow
ready to wire in Phase 5F
Engineering service (src/atocore/engineering/service.py):
- RELATIONSHIP_TYPES organized into 4 families per ontology-v1.md:
+ Structural: contains, part_of, interfaces_with
+ Intent: satisfies, constrained_by, affected_by_decision,
based_on_assumption (new), supersedes
+ Validation: analyzed_by, validated_by, supports (new),
conflicts_with (new), depends_on
+ Provenance: described_by, updated_by_session (new),
evidenced_by (new), summarized_in (new)
- create_entity + create_relationship now call resolve_project_name()
on write (canonicalization contract per doc)
- Both accept actor= parameter for audit provenance
- _audit_entity() helper uses shared memory_audit table with
entity_kind="entity" — one observability layer for everything
- promote_entity / reject_entity_candidate / supersede_entity —
mirror the memory lifecycle exactly (same pattern, same naming)
- get_entity_audit() reads from the shared table filtered by
entity_kind
API (src/atocore/api/routes.py):
- POST /entities/{id}/promote (candidate → active)
- POST /entities/{id}/reject (candidate → invalid)
- GET /entities/{id}/audit (full history for one entity)
- POST /entities passes actor="api-http" through
Tests: 317 → 326 (9 new):
- test_entity_project_canonicalization (p04 → p04-gigabit)
- test_promote_entity_candidate_to_active
- test_reject_entity_candidate
- test_promote_active_entity_noop (only candidates promote)
- test_entity_audit_log_captures_lifecycle (before/after snapshots)
- test_new_relationship_types_available (6 new types present)
- test_conflicts_tables_exist
- test_memory_audit_has_entity_kind
- test_graduated_status_accepted
What's next (5B-5I, deferred): entity triage UI tab, core structure
queries, the 3 killer queries, memory graduation script, conflict
detection, MCP + context pack integration. See plan file.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 07:01:28 -04:00
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
|
2026-04-22 14:59:17 -04:00
|
|
|
# V1-0 (F-5 hook, engineering-v1-acceptance.md:99): synchronous
|
|
|
|
|
# conflict detection on any active-entity write. The promote path
|
|
|
|
|
# already had this hook (see promote_entity below); V1-0 adds it to
|
|
|
|
|
# direct-active creates so every active row — however it got that
|
|
|
|
|
# way — is checked. Fail-open per "flag, never block" rule in
|
|
|
|
|
# conflict-model.md:256: detector errors log but never fail the write.
|
|
|
|
|
if status == "active":
|
|
|
|
|
try:
|
|
|
|
|
from atocore.engineering.conflicts import detect_conflicts_for_entity
|
|
|
|
|
detect_conflicts_for_entity(entity_id)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
log.warning("conflict_detection_failed", entity_id=entity_id, error=str(e))
|
|
|
|
|
|
feat: Engineering Knowledge Layer V1 — entities + relationships
Layer 2 of the AtoCore architecture. Adds typed engineering entities
with relationships on top of the flat memory/state/chunk substrate.
Schema:
- entities table: id, entity_type, name, project, description,
properties (JSON), status, confidence, source_refs, timestamps
- relationships table: source_entity_id, target_entity_id,
relationship_type, confidence, source_refs
15 entity types: project, system, subsystem, component, interface,
requirement, constraint, decision, material, parameter,
analysis_model, result, validation_claim, vendor, process
12 relationship types: contains, part_of, interfaces_with,
satisfies, constrained_by, affected_by_decision, analyzed_by,
validated_by, depends_on, uses_material, described_by, supersedes
Service layer: full CRUD + get_entity_with_context (returns an
entity with its relationships and all related entities in one call).
API endpoints:
- POST /entities — create entity
- GET /entities — list/filter by type, project, status, name
- GET /entities/{id} — entity + relationships + related entities
- POST /relationships — create relationship
Schema auto-initialized on app startup via init_engineering_schema().
7 tests covering entity CRUD, relationships, context traversal,
filtering, name search, and validation.
Test count: 290 -> 297.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 09:50:58 -04:00
|
|
|
return Entity(
|
|
|
|
|
id=entity_id, entity_type=entity_type, name=name.strip(),
|
|
|
|
|
project=project, description=description, properties=props,
|
|
|
|
|
status=status, confidence=confidence, source_refs=refs,
|
|
|
|
|
created_at=now, updated_at=now,
|
2026-04-22 14:59:17 -04:00
|
|
|
extractor_version=ev, canonical_home="entity",
|
|
|
|
|
hand_authored=hand_authored,
|
feat: Engineering Knowledge Layer V1 — entities + relationships
Layer 2 of the AtoCore architecture. Adds typed engineering entities
with relationships on top of the flat memory/state/chunk substrate.
Schema:
- entities table: id, entity_type, name, project, description,
properties (JSON), status, confidence, source_refs, timestamps
- relationships table: source_entity_id, target_entity_id,
relationship_type, confidence, source_refs
15 entity types: project, system, subsystem, component, interface,
requirement, constraint, decision, material, parameter,
analysis_model, result, validation_claim, vendor, process
12 relationship types: contains, part_of, interfaces_with,
satisfies, constrained_by, affected_by_decision, analyzed_by,
validated_by, depends_on, uses_material, described_by, supersedes
Service layer: full CRUD + get_entity_with_context (returns an
entity with its relationships and all related entities in one call).
API endpoints:
- POST /entities — create entity
- GET /entities — list/filter by type, project, status, name
- GET /entities/{id} — entity + relationships + related entities
- POST /relationships — create relationship
Schema auto-initialized on app startup via init_engineering_schema().
7 tests covering entity CRUD, relationships, context traversal,
filtering, name search, and validation.
Test count: 290 -> 297.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 09:50:58 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
feat: Phase 5A — Engineering V1 foundation
First slice of the Engineering V1 sprint. Lays the schema + lifecycle
plumbing so the 10 canonical queries, memory graduation, and conflict
detection can land cleanly on top.
Schema (src/atocore/models/database.py):
- conflicts + conflict_members tables per conflict-model.md (with 5
indexes on status/project/slot/members)
- memory_audit.entity_kind discriminator — same audit table serves
both memories ("memory") and entities ("entity"); unified history
without duplicating infrastructure
- memories.graduated_to_entity_id forward pointer for graduated
memories (M → E transition preserves the memory as historical
pointer)
Memory (src/atocore/memory/service.py):
- MEMORY_STATUSES gains "graduated" — memory-entity graduation flow
ready to wire in Phase 5F
Engineering service (src/atocore/engineering/service.py):
- RELATIONSHIP_TYPES organized into 4 families per ontology-v1.md:
+ Structural: contains, part_of, interfaces_with
+ Intent: satisfies, constrained_by, affected_by_decision,
based_on_assumption (new), supersedes
+ Validation: analyzed_by, validated_by, supports (new),
conflicts_with (new), depends_on
+ Provenance: described_by, updated_by_session (new),
evidenced_by (new), summarized_in (new)
- create_entity + create_relationship now call resolve_project_name()
on write (canonicalization contract per doc)
- Both accept actor= parameter for audit provenance
- _audit_entity() helper uses shared memory_audit table with
entity_kind="entity" — one observability layer for everything
- promote_entity / reject_entity_candidate / supersede_entity —
mirror the memory lifecycle exactly (same pattern, same naming)
- get_entity_audit() reads from the shared table filtered by
entity_kind
API (src/atocore/api/routes.py):
- POST /entities/{id}/promote (candidate → active)
- POST /entities/{id}/reject (candidate → invalid)
- GET /entities/{id}/audit (full history for one entity)
- POST /entities passes actor="api-http" through
Tests: 317 → 326 (9 new):
- test_entity_project_canonicalization (p04 → p04-gigabit)
- test_promote_entity_candidate_to_active
- test_reject_entity_candidate
- test_promote_active_entity_noop (only candidates promote)
- test_entity_audit_log_captures_lifecycle (before/after snapshots)
- test_new_relationship_types_available (6 new types present)
- test_conflicts_tables_exist
- test_memory_audit_has_entity_kind
- test_graduated_status_accepted
What's next (5B-5I, deferred): entity triage UI tab, core structure
queries, the 3 killer queries, memory graduation script, conflict
detection, MCP + context pack integration. See plan file.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 07:01:28 -04:00
|
|
|
def _audit_entity(
|
|
|
|
|
entity_id: str,
|
|
|
|
|
action: str,
|
|
|
|
|
actor: str = "api",
|
|
|
|
|
before: dict | None = None,
|
|
|
|
|
after: dict | None = None,
|
|
|
|
|
note: str = "",
|
|
|
|
|
) -> None:
|
|
|
|
|
"""Append an entity mutation row to the shared memory_audit table."""
|
|
|
|
|
try:
|
|
|
|
|
with get_connection() as conn:
|
|
|
|
|
conn.execute(
|
|
|
|
|
"INSERT INTO memory_audit (id, memory_id, action, actor, "
|
|
|
|
|
"before_json, after_json, note, entity_kind) "
|
|
|
|
|
"VALUES (?, ?, ?, ?, ?, ?, ?, 'entity')",
|
|
|
|
|
(
|
|
|
|
|
str(uuid.uuid4()),
|
|
|
|
|
entity_id,
|
|
|
|
|
action,
|
|
|
|
|
actor or "api",
|
|
|
|
|
json.dumps(before or {}),
|
|
|
|
|
json.dumps(after or {}),
|
|
|
|
|
(note or "")[:500],
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
log.warning("entity_audit_failed", entity_id=entity_id, action=action, error=str(e))
|
|
|
|
|
|
|
|
|
|
|
feat: Engineering Knowledge Layer V1 — entities + relationships
Layer 2 of the AtoCore architecture. Adds typed engineering entities
with relationships on top of the flat memory/state/chunk substrate.
Schema:
- entities table: id, entity_type, name, project, description,
properties (JSON), status, confidence, source_refs, timestamps
- relationships table: source_entity_id, target_entity_id,
relationship_type, confidence, source_refs
15 entity types: project, system, subsystem, component, interface,
requirement, constraint, decision, material, parameter,
analysis_model, result, validation_claim, vendor, process
12 relationship types: contains, part_of, interfaces_with,
satisfies, constrained_by, affected_by_decision, analyzed_by,
validated_by, depends_on, uses_material, described_by, supersedes
Service layer: full CRUD + get_entity_with_context (returns an
entity with its relationships and all related entities in one call).
API endpoints:
- POST /entities — create entity
- GET /entities — list/filter by type, project, status, name
- GET /entities/{id} — entity + relationships + related entities
- POST /relationships — create relationship
Schema auto-initialized on app startup via init_engineering_schema().
7 tests covering entity CRUD, relationships, context traversal,
filtering, name search, and validation.
Test count: 290 -> 297.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 09:50:58 -04:00
|
|
|
def create_relationship(
|
|
|
|
|
source_entity_id: str,
|
|
|
|
|
target_entity_id: str,
|
|
|
|
|
relationship_type: str,
|
|
|
|
|
confidence: float = 1.0,
|
|
|
|
|
source_refs: list[str] | None = None,
|
|
|
|
|
) -> Relationship:
|
|
|
|
|
if relationship_type not in RELATIONSHIP_TYPES:
|
|
|
|
|
raise ValueError(f"Invalid relationship type: {relationship_type}")
|
|
|
|
|
|
|
|
|
|
rel_id = str(uuid.uuid4())
|
|
|
|
|
now = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S")
|
|
|
|
|
refs = source_refs or []
|
|
|
|
|
|
|
|
|
|
with get_connection() as conn:
|
|
|
|
|
conn.execute(
|
|
|
|
|
"""INSERT INTO relationships
|
|
|
|
|
(id, source_entity_id, target_entity_id, relationship_type,
|
|
|
|
|
confidence, source_refs, created_at)
|
|
|
|
|
VALUES (?, ?, ?, ?, ?, ?, ?)""",
|
|
|
|
|
(rel_id, source_entity_id, target_entity_id,
|
|
|
|
|
relationship_type, confidence, json.dumps(refs), now),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
log.info(
|
|
|
|
|
"relationship_created",
|
|
|
|
|
rel_id=rel_id,
|
|
|
|
|
source=source_entity_id,
|
|
|
|
|
target=target_entity_id,
|
|
|
|
|
rel_type=relationship_type,
|
|
|
|
|
)
|
feat: Phase 5A — Engineering V1 foundation
First slice of the Engineering V1 sprint. Lays the schema + lifecycle
plumbing so the 10 canonical queries, memory graduation, and conflict
detection can land cleanly on top.
Schema (src/atocore/models/database.py):
- conflicts + conflict_members tables per conflict-model.md (with 5
indexes on status/project/slot/members)
- memory_audit.entity_kind discriminator — same audit table serves
both memories ("memory") and entities ("entity"); unified history
without duplicating infrastructure
- memories.graduated_to_entity_id forward pointer for graduated
memories (M → E transition preserves the memory as historical
pointer)
Memory (src/atocore/memory/service.py):
- MEMORY_STATUSES gains "graduated" — memory-entity graduation flow
ready to wire in Phase 5F
Engineering service (src/atocore/engineering/service.py):
- RELATIONSHIP_TYPES organized into 4 families per ontology-v1.md:
+ Structural: contains, part_of, interfaces_with
+ Intent: satisfies, constrained_by, affected_by_decision,
based_on_assumption (new), supersedes
+ Validation: analyzed_by, validated_by, supports (new),
conflicts_with (new), depends_on
+ Provenance: described_by, updated_by_session (new),
evidenced_by (new), summarized_in (new)
- create_entity + create_relationship now call resolve_project_name()
on write (canonicalization contract per doc)
- Both accept actor= parameter for audit provenance
- _audit_entity() helper uses shared memory_audit table with
entity_kind="entity" — one observability layer for everything
- promote_entity / reject_entity_candidate / supersede_entity —
mirror the memory lifecycle exactly (same pattern, same naming)
- get_entity_audit() reads from the shared table filtered by
entity_kind
API (src/atocore/api/routes.py):
- POST /entities/{id}/promote (candidate → active)
- POST /entities/{id}/reject (candidate → invalid)
- GET /entities/{id}/audit (full history for one entity)
- POST /entities passes actor="api-http" through
Tests: 317 → 326 (9 new):
- test_entity_project_canonicalization (p04 → p04-gigabit)
- test_promote_entity_candidate_to_active
- test_reject_entity_candidate
- test_promote_active_entity_noop (only candidates promote)
- test_entity_audit_log_captures_lifecycle (before/after snapshots)
- test_new_relationship_types_available (6 new types present)
- test_conflicts_tables_exist
- test_memory_audit_has_entity_kind
- test_graduated_status_accepted
What's next (5B-5I, deferred): entity triage UI tab, core structure
queries, the 3 killer queries, memory graduation script, conflict
detection, MCP + context pack integration. See plan file.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 07:01:28 -04:00
|
|
|
# Phase 5: relationship audit as an entity action on the source
|
|
|
|
|
_audit_entity(
|
|
|
|
|
entity_id=source_entity_id,
|
|
|
|
|
action="relationship_added",
|
|
|
|
|
actor="api",
|
|
|
|
|
after={
|
|
|
|
|
"rel_id": rel_id,
|
|
|
|
|
"rel_type": relationship_type,
|
|
|
|
|
"target": target_entity_id,
|
|
|
|
|
},
|
|
|
|
|
)
|
feat: Engineering Knowledge Layer V1 — entities + relationships
Layer 2 of the AtoCore architecture. Adds typed engineering entities
with relationships on top of the flat memory/state/chunk substrate.
Schema:
- entities table: id, entity_type, name, project, description,
properties (JSON), status, confidence, source_refs, timestamps
- relationships table: source_entity_id, target_entity_id,
relationship_type, confidence, source_refs
15 entity types: project, system, subsystem, component, interface,
requirement, constraint, decision, material, parameter,
analysis_model, result, validation_claim, vendor, process
12 relationship types: contains, part_of, interfaces_with,
satisfies, constrained_by, affected_by_decision, analyzed_by,
validated_by, depends_on, uses_material, described_by, supersedes
Service layer: full CRUD + get_entity_with_context (returns an
entity with its relationships and all related entities in one call).
API endpoints:
- POST /entities — create entity
- GET /entities — list/filter by type, project, status, name
- GET /entities/{id} — entity + relationships + related entities
- POST /relationships — create relationship
Schema auto-initialized on app startup via init_engineering_schema().
7 tests covering entity CRUD, relationships, context traversal,
filtering, name search, and validation.
Test count: 290 -> 297.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 09:50:58 -04:00
|
|
|
return Relationship(
|
|
|
|
|
id=rel_id, source_entity_id=source_entity_id,
|
|
|
|
|
target_entity_id=target_entity_id,
|
|
|
|
|
relationship_type=relationship_type,
|
|
|
|
|
confidence=confidence, source_refs=refs, created_at=now,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
feat: Phase 5A — Engineering V1 foundation
First slice of the Engineering V1 sprint. Lays the schema + lifecycle
plumbing so the 10 canonical queries, memory graduation, and conflict
detection can land cleanly on top.
Schema (src/atocore/models/database.py):
- conflicts + conflict_members tables per conflict-model.md (with 5
indexes on status/project/slot/members)
- memory_audit.entity_kind discriminator — same audit table serves
both memories ("memory") and entities ("entity"); unified history
without duplicating infrastructure
- memories.graduated_to_entity_id forward pointer for graduated
memories (M → E transition preserves the memory as historical
pointer)
Memory (src/atocore/memory/service.py):
- MEMORY_STATUSES gains "graduated" — memory-entity graduation flow
ready to wire in Phase 5F
Engineering service (src/atocore/engineering/service.py):
- RELATIONSHIP_TYPES organized into 4 families per ontology-v1.md:
+ Structural: contains, part_of, interfaces_with
+ Intent: satisfies, constrained_by, affected_by_decision,
based_on_assumption (new), supersedes
+ Validation: analyzed_by, validated_by, supports (new),
conflicts_with (new), depends_on
+ Provenance: described_by, updated_by_session (new),
evidenced_by (new), summarized_in (new)
- create_entity + create_relationship now call resolve_project_name()
on write (canonicalization contract per doc)
- Both accept actor= parameter for audit provenance
- _audit_entity() helper uses shared memory_audit table with
entity_kind="entity" — one observability layer for everything
- promote_entity / reject_entity_candidate / supersede_entity —
mirror the memory lifecycle exactly (same pattern, same naming)
- get_entity_audit() reads from the shared table filtered by
entity_kind
API (src/atocore/api/routes.py):
- POST /entities/{id}/promote (candidate → active)
- POST /entities/{id}/reject (candidate → invalid)
- GET /entities/{id}/audit (full history for one entity)
- POST /entities passes actor="api-http" through
Tests: 317 → 326 (9 new):
- test_entity_project_canonicalization (p04 → p04-gigabit)
- test_promote_entity_candidate_to_active
- test_reject_entity_candidate
- test_promote_active_entity_noop (only candidates promote)
- test_entity_audit_log_captures_lifecycle (before/after snapshots)
- test_new_relationship_types_available (6 new types present)
- test_conflicts_tables_exist
- test_memory_audit_has_entity_kind
- test_graduated_status_accepted
What's next (5B-5I, deferred): entity triage UI tab, core structure
queries, the 3 killer queries, memory graduation script, conflict
detection, MCP + context pack integration. See plan file.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 07:01:28 -04:00
|
|
|
# --- Phase 5: Entity promote/reject lifecycle ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _set_entity_status(
|
|
|
|
|
entity_id: str,
|
|
|
|
|
new_status: str,
|
|
|
|
|
actor: str = "api",
|
|
|
|
|
note: str = "",
|
|
|
|
|
) -> bool:
|
|
|
|
|
"""Transition an entity's status with audit."""
|
|
|
|
|
if new_status not in ENTITY_STATUSES:
|
|
|
|
|
raise ValueError(f"Invalid status: {new_status}")
|
|
|
|
|
|
|
|
|
|
with get_connection() as conn:
|
|
|
|
|
row = conn.execute(
|
|
|
|
|
"SELECT status FROM entities WHERE id = ?", (entity_id,)
|
|
|
|
|
).fetchone()
|
|
|
|
|
if row is None:
|
|
|
|
|
return False
|
|
|
|
|
old_status = row["status"]
|
|
|
|
|
if old_status == new_status:
|
|
|
|
|
return False
|
|
|
|
|
now = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S")
|
|
|
|
|
conn.execute(
|
|
|
|
|
"UPDATE entities SET status = ?, updated_at = ? WHERE id = ?",
|
|
|
|
|
(new_status, now, entity_id),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Action verb mirrors memory pattern
|
|
|
|
|
if new_status == "active" and old_status == "candidate":
|
|
|
|
|
action = "promoted"
|
|
|
|
|
elif new_status == "invalid" and old_status == "candidate":
|
|
|
|
|
action = "rejected"
|
|
|
|
|
elif new_status == "invalid":
|
|
|
|
|
action = "invalidated"
|
|
|
|
|
elif new_status == "superseded":
|
|
|
|
|
action = "superseded"
|
|
|
|
|
else:
|
|
|
|
|
action = "status_changed"
|
|
|
|
|
|
|
|
|
|
_audit_entity(
|
|
|
|
|
entity_id=entity_id,
|
|
|
|
|
action=action,
|
|
|
|
|
actor=actor,
|
|
|
|
|
before={"status": old_status},
|
|
|
|
|
after={"status": new_status},
|
|
|
|
|
note=note,
|
|
|
|
|
)
|
|
|
|
|
log.info("entity_status_changed", entity_id=entity_id,
|
|
|
|
|
old=old_status, new=new_status, action=action)
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
feat(entities): inbox + cross-project (project="") support (Issue C)
Makes `inbox` a reserved pseudo-project and `project=""` a first-class
cross-project bucket. Unblocks AKC capturing pre-project leads/quotes
and cross-project facts (materials, vendors) that don't fit a single
registered project.
- projects/registry.py: INBOX_PROJECT/GLOBAL_PROJECT constants,
is_reserved_project(), register/update guards, resolve_project_name
passthrough for "inbox"
- engineering/service.py: get_entities scoping rules (inbox-only,
global-only, real+global default, scope_only=true strict).
promote_entity accepts target_project to retarget on promote
- api/routes.py: GET /entities gains scope_only; POST /entities accepts
project=null as ""; POST /entities/{id}/promote accepts
{target_project, note}
- engineering/wiki.py: homepage shows "Inbox & Global" cards with live
counts linking to scoped lists
- tests/test_inbox_crossproject.py: 15 tests (reserved enforcement,
scoping rules, API shape, promote retargeting)
- DEV-LEDGER.md: session log, test_count 463 -> 478
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 20:17:32 -04:00
|
|
|
def promote_entity(
|
|
|
|
|
entity_id: str,
|
|
|
|
|
actor: str = "api",
|
|
|
|
|
note: str = "",
|
|
|
|
|
target_project: str | None = None,
|
|
|
|
|
) -> bool:
|
feat: Phase 5F/5G/5H — graduation, conflicts, MCP engineering tools
The population move + the safety net + the universal consumer hookup,
all shipped together. This is where the engineering graph becomes
genuinely useful against the real 262-memory corpus.
5F: Memory → Entity graduation (THE population move)
- src/atocore/engineering/_graduation_prompt.py: stdlib-only shared
prompt module mirroring _llm_prompt.py pattern (container + host
use same system prompt, no drift)
- scripts/graduate_memories.py: host-side batch driver that asks
claude-p "does this memory describe a typed entity?" and creates
entity candidates with source_refs pointing back to the memory
- promote_entity() now scans source_refs for memory:* prefix; if
found, flips source memory to status='graduated' with
graduated_to_entity_id forward pointer + writes memory_audit row
- GET /admin/graduation/stats exposes graduation rate for dashboard
5G: Sync conflict detection on entity promote
- src/atocore/engineering/conflicts.py: detect_conflicts_for_entity()
runs on every active promote. V1 checks 3 slot kinds narrowly to
avoid false positives:
* component.material (multiple USES_MATERIAL edges)
* component.part_of (multiple PART_OF edges)
* requirement.name (duplicate active Requirements in same project)
- Conflicts + members persist via the tables built in 5A
- Fires a "warning" alert via Phase 4 framework
- Deduplicates: same (slot_kind, slot_key) won't get a new row
- resolve_conflict(action="dismiss|supersede_others|no_action"):
supersede_others marks non-winner members as status='superseded'
- GET /admin/conflicts + POST /admin/conflicts/{id}/resolve
5H: MCP + context pack integration
- scripts/atocore_mcp.py: 7 new engineering tools exposed to every
MCP-aware client (Claude Desktop, Claude Code, Cursor, Zed):
* atocore_engineering_map (Q-001/004 system tree)
* atocore_engineering_gaps (Q-006/009/011 killer queries — THE
director's question surfaced as a built-in tool)
* atocore_engineering_requirements_for_component (Q-005)
* atocore_engineering_decisions (Q-008)
* atocore_engineering_changes (Q-013 — reads entity audit log)
* atocore_engineering_impact (Q-016 BFS downstream)
* atocore_engineering_evidence (Q-017 inbound provenance)
- MCP tools total: 14 (7 memory/state/health + 7 engineering)
- context/builder.py _build_engineering_context now appends a compact
gaps summary ("Gaps: N orphan reqs, M risky decisions, K unsupported
claims") so every project-scoped LLM call sees "what we're missing"
Tests: 341 → 356 (15 new):
- 5F: graduation prompt parses positive/negative decisions, rejects
unknown entity types, tolerates markdown fences; promote_entity
marks source memory graduated with forward pointer; entity without
memory refs promotes cleanly
- 5G: component.material + component.part_of + requirement.name
conflicts detected; clean component triggers nothing; dedup works;
supersede_others resolution marks losers; dismiss leaves both
active; end-to-end promote triggers detection
- 5H: graduation user message includes project + type + content
No regressions across the 341 prior tests. The MCP server now answers
"which p05 requirements aren't satisfied?" directly from any Claude
session — no user prompt engineering, no context hacks.
Next to kick off from user: run graduation script on Dalidou to
populate the graph from 262 existing memories:
ssh papa@dalidou 'cd /srv/storage/atocore/app && PYTHONPATH=src \
python3 scripts/graduate_memories.py --project p05-interferometer --limit 30 --dry-run'
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 07:53:03 -04:00
|
|
|
"""Promote a candidate entity to active.
|
|
|
|
|
|
feat(entities): inbox + cross-project (project="") support (Issue C)
Makes `inbox` a reserved pseudo-project and `project=""` a first-class
cross-project bucket. Unblocks AKC capturing pre-project leads/quotes
and cross-project facts (materials, vendors) that don't fit a single
registered project.
- projects/registry.py: INBOX_PROJECT/GLOBAL_PROJECT constants,
is_reserved_project(), register/update guards, resolve_project_name
passthrough for "inbox"
- engineering/service.py: get_entities scoping rules (inbox-only,
global-only, real+global default, scope_only=true strict).
promote_entity accepts target_project to retarget on promote
- api/routes.py: GET /entities gains scope_only; POST /entities accepts
project=null as ""; POST /entities/{id}/promote accepts
{target_project, note}
- engineering/wiki.py: homepage shows "Inbox & Global" cards with live
counts linking to scoped lists
- tests/test_inbox_crossproject.py: 15 tests (reserved enforcement,
scoping rules, API shape, promote retargeting)
- DEV-LEDGER.md: session log, test_count 463 -> 478
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 20:17:32 -04:00
|
|
|
When ``target_project`` is provided (Issue C), also retarget the
|
|
|
|
|
entity's project before flipping the status. Use this to graduate an
|
|
|
|
|
inbox/global lead into a real project (e.g. when a vendor quote
|
|
|
|
|
becomes a contract). ``target_project`` is canonicalized through the
|
|
|
|
|
registry; reserved ids (``inbox``) and ``""`` are accepted verbatim.
|
|
|
|
|
|
feat: Phase 5F/5G/5H — graduation, conflicts, MCP engineering tools
The population move + the safety net + the universal consumer hookup,
all shipped together. This is where the engineering graph becomes
genuinely useful against the real 262-memory corpus.
5F: Memory → Entity graduation (THE population move)
- src/atocore/engineering/_graduation_prompt.py: stdlib-only shared
prompt module mirroring _llm_prompt.py pattern (container + host
use same system prompt, no drift)
- scripts/graduate_memories.py: host-side batch driver that asks
claude-p "does this memory describe a typed entity?" and creates
entity candidates with source_refs pointing back to the memory
- promote_entity() now scans source_refs for memory:* prefix; if
found, flips source memory to status='graduated' with
graduated_to_entity_id forward pointer + writes memory_audit row
- GET /admin/graduation/stats exposes graduation rate for dashboard
5G: Sync conflict detection on entity promote
- src/atocore/engineering/conflicts.py: detect_conflicts_for_entity()
runs on every active promote. V1 checks 3 slot kinds narrowly to
avoid false positives:
* component.material (multiple USES_MATERIAL edges)
* component.part_of (multiple PART_OF edges)
* requirement.name (duplicate active Requirements in same project)
- Conflicts + members persist via the tables built in 5A
- Fires a "warning" alert via Phase 4 framework
- Deduplicates: same (slot_kind, slot_key) won't get a new row
- resolve_conflict(action="dismiss|supersede_others|no_action"):
supersede_others marks non-winner members as status='superseded'
- GET /admin/conflicts + POST /admin/conflicts/{id}/resolve
5H: MCP + context pack integration
- scripts/atocore_mcp.py: 7 new engineering tools exposed to every
MCP-aware client (Claude Desktop, Claude Code, Cursor, Zed):
* atocore_engineering_map (Q-001/004 system tree)
* atocore_engineering_gaps (Q-006/009/011 killer queries — THE
director's question surfaced as a built-in tool)
* atocore_engineering_requirements_for_component (Q-005)
* atocore_engineering_decisions (Q-008)
* atocore_engineering_changes (Q-013 — reads entity audit log)
* atocore_engineering_impact (Q-016 BFS downstream)
* atocore_engineering_evidence (Q-017 inbound provenance)
- MCP tools total: 14 (7 memory/state/health + 7 engineering)
- context/builder.py _build_engineering_context now appends a compact
gaps summary ("Gaps: N orphan reqs, M risky decisions, K unsupported
claims") so every project-scoped LLM call sees "what we're missing"
Tests: 341 → 356 (15 new):
- 5F: graduation prompt parses positive/negative decisions, rejects
unknown entity types, tolerates markdown fences; promote_entity
marks source memory graduated with forward pointer; entity without
memory refs promotes cleanly
- 5G: component.material + component.part_of + requirement.name
conflicts detected; clean component triggers nothing; dedup works;
supersede_others resolution marks losers; dismiss leaves both
active; end-to-end promote triggers detection
- 5H: graduation user message includes project + type + content
No regressions across the 341 prior tests. The MCP server now answers
"which p05 requirements aren't satisfied?" directly from any Claude
session — no user prompt engineering, no context hacks.
Next to kick off from user: run graduation script on Dalidou to
populate the graph from 262 existing memories:
ssh papa@dalidou 'cd /srv/storage/atocore/app && PYTHONPATH=src \
python3 scripts/graduate_memories.py --project p05-interferometer --limit 30 --dry-run'
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 07:53:03 -04:00
|
|
|
Phase 5F graduation hook: if this entity has source_refs pointing at
|
|
|
|
|
memories (format "memory:<uuid>"), mark those source memories as
|
|
|
|
|
``status=graduated`` and set their ``graduated_to_entity_id`` forward
|
|
|
|
|
pointer. This preserves the memory as an immutable historical record
|
|
|
|
|
while signalling that it's been absorbed into the typed graph.
|
|
|
|
|
"""
|
|
|
|
|
entity = get_entity(entity_id)
|
|
|
|
|
if entity is None or entity.status != "candidate":
|
|
|
|
|
return False
|
|
|
|
|
|
2026-04-22 14:59:17 -04:00
|
|
|
# V1-0 (F-8 provenance re-check at promote). The invariant must hold at
|
|
|
|
|
# BOTH create_entity AND promote_entity per the plan, because candidate
|
|
|
|
|
# rows can exist in the DB from before V1-0 (no enforcement at their
|
|
|
|
|
# create time) or can be inserted by code paths that bypass the service
|
|
|
|
|
# layer. Block any candidate with empty source_refs that is NOT flagged
|
|
|
|
|
# hand_authored from ever becoming active. Same error shape as the
|
|
|
|
|
# create-side check for symmetry.
|
|
|
|
|
if not (entity.source_refs or []) and not entity.hand_authored:
|
|
|
|
|
raise ValueError(
|
|
|
|
|
"source_refs required: cannot promote a candidate with no "
|
|
|
|
|
"provenance. Attach source_refs via PATCH /entities/{id}, "
|
|
|
|
|
"or flag hand_authored=true before promoting."
|
|
|
|
|
)
|
|
|
|
|
|
feat(entities): inbox + cross-project (project="") support (Issue C)
Makes `inbox` a reserved pseudo-project and `project=""` a first-class
cross-project bucket. Unblocks AKC capturing pre-project leads/quotes
and cross-project facts (materials, vendors) that don't fit a single
registered project.
- projects/registry.py: INBOX_PROJECT/GLOBAL_PROJECT constants,
is_reserved_project(), register/update guards, resolve_project_name
passthrough for "inbox"
- engineering/service.py: get_entities scoping rules (inbox-only,
global-only, real+global default, scope_only=true strict).
promote_entity accepts target_project to retarget on promote
- api/routes.py: GET /entities gains scope_only; POST /entities accepts
project=null as ""; POST /entities/{id}/promote accepts
{target_project, note}
- engineering/wiki.py: homepage shows "Inbox & Global" cards with live
counts linking to scoped lists
- tests/test_inbox_crossproject.py: 15 tests (reserved enforcement,
scoping rules, API shape, promote retargeting)
- DEV-LEDGER.md: session log, test_count 463 -> 478
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 20:17:32 -04:00
|
|
|
if target_project is not None:
|
|
|
|
|
new_project = (
|
|
|
|
|
resolve_project_name(target_project) if target_project else ""
|
|
|
|
|
)
|
|
|
|
|
if new_project != entity.project:
|
|
|
|
|
now = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S")
|
|
|
|
|
with get_connection() as conn:
|
|
|
|
|
conn.execute(
|
|
|
|
|
"UPDATE entities SET project = ?, updated_at = ? "
|
|
|
|
|
"WHERE id = ?",
|
|
|
|
|
(new_project, now, entity_id),
|
|
|
|
|
)
|
|
|
|
|
_audit_entity(
|
|
|
|
|
entity_id=entity_id,
|
|
|
|
|
action="retargeted",
|
|
|
|
|
actor=actor,
|
|
|
|
|
before={"project": entity.project},
|
|
|
|
|
after={"project": new_project},
|
|
|
|
|
note=note,
|
|
|
|
|
)
|
|
|
|
|
|
feat: Phase 5F/5G/5H — graduation, conflicts, MCP engineering tools
The population move + the safety net + the universal consumer hookup,
all shipped together. This is where the engineering graph becomes
genuinely useful against the real 262-memory corpus.
5F: Memory → Entity graduation (THE population move)
- src/atocore/engineering/_graduation_prompt.py: stdlib-only shared
prompt module mirroring _llm_prompt.py pattern (container + host
use same system prompt, no drift)
- scripts/graduate_memories.py: host-side batch driver that asks
claude-p "does this memory describe a typed entity?" and creates
entity candidates with source_refs pointing back to the memory
- promote_entity() now scans source_refs for memory:* prefix; if
found, flips source memory to status='graduated' with
graduated_to_entity_id forward pointer + writes memory_audit row
- GET /admin/graduation/stats exposes graduation rate for dashboard
5G: Sync conflict detection on entity promote
- src/atocore/engineering/conflicts.py: detect_conflicts_for_entity()
runs on every active promote. V1 checks 3 slot kinds narrowly to
avoid false positives:
* component.material (multiple USES_MATERIAL edges)
* component.part_of (multiple PART_OF edges)
* requirement.name (duplicate active Requirements in same project)
- Conflicts + members persist via the tables built in 5A
- Fires a "warning" alert via Phase 4 framework
- Deduplicates: same (slot_kind, slot_key) won't get a new row
- resolve_conflict(action="dismiss|supersede_others|no_action"):
supersede_others marks non-winner members as status='superseded'
- GET /admin/conflicts + POST /admin/conflicts/{id}/resolve
5H: MCP + context pack integration
- scripts/atocore_mcp.py: 7 new engineering tools exposed to every
MCP-aware client (Claude Desktop, Claude Code, Cursor, Zed):
* atocore_engineering_map (Q-001/004 system tree)
* atocore_engineering_gaps (Q-006/009/011 killer queries — THE
director's question surfaced as a built-in tool)
* atocore_engineering_requirements_for_component (Q-005)
* atocore_engineering_decisions (Q-008)
* atocore_engineering_changes (Q-013 — reads entity audit log)
* atocore_engineering_impact (Q-016 BFS downstream)
* atocore_engineering_evidence (Q-017 inbound provenance)
- MCP tools total: 14 (7 memory/state/health + 7 engineering)
- context/builder.py _build_engineering_context now appends a compact
gaps summary ("Gaps: N orphan reqs, M risky decisions, K unsupported
claims") so every project-scoped LLM call sees "what we're missing"
Tests: 341 → 356 (15 new):
- 5F: graduation prompt parses positive/negative decisions, rejects
unknown entity types, tolerates markdown fences; promote_entity
marks source memory graduated with forward pointer; entity without
memory refs promotes cleanly
- 5G: component.material + component.part_of + requirement.name
conflicts detected; clean component triggers nothing; dedup works;
supersede_others resolution marks losers; dismiss leaves both
active; end-to-end promote triggers detection
- 5H: graduation user message includes project + type + content
No regressions across the 341 prior tests. The MCP server now answers
"which p05 requirements aren't satisfied?" directly from any Claude
session — no user prompt engineering, no context hacks.
Next to kick off from user: run graduation script on Dalidou to
populate the graph from 262 existing memories:
ssh papa@dalidou 'cd /srv/storage/atocore/app && PYTHONPATH=src \
python3 scripts/graduate_memories.py --project p05-interferometer --limit 30 --dry-run'
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 07:53:03 -04:00
|
|
|
ok = _set_entity_status(entity_id, "active", actor=actor, note=note)
|
|
|
|
|
if not ok:
|
feat: Phase 5A — Engineering V1 foundation
First slice of the Engineering V1 sprint. Lays the schema + lifecycle
plumbing so the 10 canonical queries, memory graduation, and conflict
detection can land cleanly on top.
Schema (src/atocore/models/database.py):
- conflicts + conflict_members tables per conflict-model.md (with 5
indexes on status/project/slot/members)
- memory_audit.entity_kind discriminator — same audit table serves
both memories ("memory") and entities ("entity"); unified history
without duplicating infrastructure
- memories.graduated_to_entity_id forward pointer for graduated
memories (M → E transition preserves the memory as historical
pointer)
Memory (src/atocore/memory/service.py):
- MEMORY_STATUSES gains "graduated" — memory-entity graduation flow
ready to wire in Phase 5F
Engineering service (src/atocore/engineering/service.py):
- RELATIONSHIP_TYPES organized into 4 families per ontology-v1.md:
+ Structural: contains, part_of, interfaces_with
+ Intent: satisfies, constrained_by, affected_by_decision,
based_on_assumption (new), supersedes
+ Validation: analyzed_by, validated_by, supports (new),
conflicts_with (new), depends_on
+ Provenance: described_by, updated_by_session (new),
evidenced_by (new), summarized_in (new)
- create_entity + create_relationship now call resolve_project_name()
on write (canonicalization contract per doc)
- Both accept actor= parameter for audit provenance
- _audit_entity() helper uses shared memory_audit table with
entity_kind="entity" — one observability layer for everything
- promote_entity / reject_entity_candidate / supersede_entity —
mirror the memory lifecycle exactly (same pattern, same naming)
- get_entity_audit() reads from the shared table filtered by
entity_kind
API (src/atocore/api/routes.py):
- POST /entities/{id}/promote (candidate → active)
- POST /entities/{id}/reject (candidate → invalid)
- GET /entities/{id}/audit (full history for one entity)
- POST /entities passes actor="api-http" through
Tests: 317 → 326 (9 new):
- test_entity_project_canonicalization (p04 → p04-gigabit)
- test_promote_entity_candidate_to_active
- test_reject_entity_candidate
- test_promote_active_entity_noop (only candidates promote)
- test_entity_audit_log_captures_lifecycle (before/after snapshots)
- test_new_relationship_types_available (6 new types present)
- test_conflicts_tables_exist
- test_memory_audit_has_entity_kind
- test_graduated_status_accepted
What's next (5B-5I, deferred): entity triage UI tab, core structure
queries, the 3 killer queries, memory graduation script, conflict
detection, MCP + context pack integration. See plan file.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 07:01:28 -04:00
|
|
|
return False
|
feat: Phase 5F/5G/5H — graduation, conflicts, MCP engineering tools
The population move + the safety net + the universal consumer hookup,
all shipped together. This is where the engineering graph becomes
genuinely useful against the real 262-memory corpus.
5F: Memory → Entity graduation (THE population move)
- src/atocore/engineering/_graduation_prompt.py: stdlib-only shared
prompt module mirroring _llm_prompt.py pattern (container + host
use same system prompt, no drift)
- scripts/graduate_memories.py: host-side batch driver that asks
claude-p "does this memory describe a typed entity?" and creates
entity candidates with source_refs pointing back to the memory
- promote_entity() now scans source_refs for memory:* prefix; if
found, flips source memory to status='graduated' with
graduated_to_entity_id forward pointer + writes memory_audit row
- GET /admin/graduation/stats exposes graduation rate for dashboard
5G: Sync conflict detection on entity promote
- src/atocore/engineering/conflicts.py: detect_conflicts_for_entity()
runs on every active promote. V1 checks 3 slot kinds narrowly to
avoid false positives:
* component.material (multiple USES_MATERIAL edges)
* component.part_of (multiple PART_OF edges)
* requirement.name (duplicate active Requirements in same project)
- Conflicts + members persist via the tables built in 5A
- Fires a "warning" alert via Phase 4 framework
- Deduplicates: same (slot_kind, slot_key) won't get a new row
- resolve_conflict(action="dismiss|supersede_others|no_action"):
supersede_others marks non-winner members as status='superseded'
- GET /admin/conflicts + POST /admin/conflicts/{id}/resolve
5H: MCP + context pack integration
- scripts/atocore_mcp.py: 7 new engineering tools exposed to every
MCP-aware client (Claude Desktop, Claude Code, Cursor, Zed):
* atocore_engineering_map (Q-001/004 system tree)
* atocore_engineering_gaps (Q-006/009/011 killer queries — THE
director's question surfaced as a built-in tool)
* atocore_engineering_requirements_for_component (Q-005)
* atocore_engineering_decisions (Q-008)
* atocore_engineering_changes (Q-013 — reads entity audit log)
* atocore_engineering_impact (Q-016 BFS downstream)
* atocore_engineering_evidence (Q-017 inbound provenance)
- MCP tools total: 14 (7 memory/state/health + 7 engineering)
- context/builder.py _build_engineering_context now appends a compact
gaps summary ("Gaps: N orphan reqs, M risky decisions, K unsupported
claims") so every project-scoped LLM call sees "what we're missing"
Tests: 341 → 356 (15 new):
- 5F: graduation prompt parses positive/negative decisions, rejects
unknown entity types, tolerates markdown fences; promote_entity
marks source memory graduated with forward pointer; entity without
memory refs promotes cleanly
- 5G: component.material + component.part_of + requirement.name
conflicts detected; clean component triggers nothing; dedup works;
supersede_others resolution marks losers; dismiss leaves both
active; end-to-end promote triggers detection
- 5H: graduation user message includes project + type + content
No regressions across the 341 prior tests. The MCP server now answers
"which p05 requirements aren't satisfied?" directly from any Claude
session — no user prompt engineering, no context hacks.
Next to kick off from user: run graduation script on Dalidou to
populate the graph from 262 existing memories:
ssh papa@dalidou 'cd /srv/storage/atocore/app && PYTHONPATH=src \
python3 scripts/graduate_memories.py --project p05-interferometer --limit 30 --dry-run'
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 07:53:03 -04:00
|
|
|
|
|
|
|
|
# Phase 5F: mark source memories as graduated
|
|
|
|
|
memory_ids = [
|
|
|
|
|
ref.split(":", 1)[1]
|
|
|
|
|
for ref in (entity.source_refs or [])
|
|
|
|
|
if isinstance(ref, str) and ref.startswith("memory:")
|
|
|
|
|
]
|
|
|
|
|
if memory_ids:
|
|
|
|
|
_graduate_source_memories(memory_ids, entity_id, actor=actor)
|
|
|
|
|
|
|
|
|
|
# Phase 5G: sync conflict detection on promote. Fail-open — detection
|
|
|
|
|
# errors log but never undo the successful promote.
|
|
|
|
|
try:
|
|
|
|
|
from atocore.engineering.conflicts import detect_conflicts_for_entity
|
|
|
|
|
detect_conflicts_for_entity(entity_id)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
log.warning("conflict_detection_failed", entity_id=entity_id, error=str(e))
|
|
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _graduate_source_memories(memory_ids: list[str], entity_id: str, actor: str) -> None:
|
|
|
|
|
"""Mark source memories as graduated and set forward pointer."""
|
|
|
|
|
if not memory_ids:
|
|
|
|
|
return
|
|
|
|
|
now = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S")
|
|
|
|
|
with get_connection() as conn:
|
|
|
|
|
for mid in memory_ids:
|
|
|
|
|
try:
|
|
|
|
|
row = conn.execute(
|
|
|
|
|
"SELECT status FROM memories WHERE id = ?", (mid,)
|
|
|
|
|
).fetchone()
|
|
|
|
|
if row is None:
|
|
|
|
|
continue
|
|
|
|
|
old_status = row["status"]
|
|
|
|
|
if old_status == "graduated":
|
|
|
|
|
continue # already graduated — maybe by a different entity
|
|
|
|
|
conn.execute(
|
|
|
|
|
"UPDATE memories SET status = 'graduated', "
|
|
|
|
|
"graduated_to_entity_id = ?, updated_at = ? WHERE id = ?",
|
|
|
|
|
(entity_id, now, mid),
|
|
|
|
|
)
|
|
|
|
|
# Write a memory_audit row for the graduation
|
|
|
|
|
conn.execute(
|
|
|
|
|
"INSERT INTO memory_audit (id, memory_id, action, actor, "
|
|
|
|
|
"before_json, after_json, note, entity_kind) "
|
|
|
|
|
"VALUES (?, ?, 'graduated', ?, ?, ?, ?, 'memory')",
|
|
|
|
|
(
|
|
|
|
|
str(uuid.uuid4()),
|
|
|
|
|
mid,
|
|
|
|
|
actor or "api",
|
|
|
|
|
json.dumps({"status": old_status}),
|
|
|
|
|
json.dumps({
|
|
|
|
|
"status": "graduated",
|
|
|
|
|
"graduated_to_entity_id": entity_id,
|
|
|
|
|
}),
|
|
|
|
|
f"graduated to entity {entity_id[:8]}",
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
log.info("memory_graduated", memory_id=mid,
|
|
|
|
|
entity_id=entity_id, old_status=old_status)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
log.warning("memory_graduation_failed",
|
|
|
|
|
memory_id=mid, entity_id=entity_id, error=str(e))
|
feat: Phase 5A — Engineering V1 foundation
First slice of the Engineering V1 sprint. Lays the schema + lifecycle
plumbing so the 10 canonical queries, memory graduation, and conflict
detection can land cleanly on top.
Schema (src/atocore/models/database.py):
- conflicts + conflict_members tables per conflict-model.md (with 5
indexes on status/project/slot/members)
- memory_audit.entity_kind discriminator — same audit table serves
both memories ("memory") and entities ("entity"); unified history
without duplicating infrastructure
- memories.graduated_to_entity_id forward pointer for graduated
memories (M → E transition preserves the memory as historical
pointer)
Memory (src/atocore/memory/service.py):
- MEMORY_STATUSES gains "graduated" — memory-entity graduation flow
ready to wire in Phase 5F
Engineering service (src/atocore/engineering/service.py):
- RELATIONSHIP_TYPES organized into 4 families per ontology-v1.md:
+ Structural: contains, part_of, interfaces_with
+ Intent: satisfies, constrained_by, affected_by_decision,
based_on_assumption (new), supersedes
+ Validation: analyzed_by, validated_by, supports (new),
conflicts_with (new), depends_on
+ Provenance: described_by, updated_by_session (new),
evidenced_by (new), summarized_in (new)
- create_entity + create_relationship now call resolve_project_name()
on write (canonicalization contract per doc)
- Both accept actor= parameter for audit provenance
- _audit_entity() helper uses shared memory_audit table with
entity_kind="entity" — one observability layer for everything
- promote_entity / reject_entity_candidate / supersede_entity —
mirror the memory lifecycle exactly (same pattern, same naming)
- get_entity_audit() reads from the shared table filtered by
entity_kind
API (src/atocore/api/routes.py):
- POST /entities/{id}/promote (candidate → active)
- POST /entities/{id}/reject (candidate → invalid)
- GET /entities/{id}/audit (full history for one entity)
- POST /entities passes actor="api-http" through
Tests: 317 → 326 (9 new):
- test_entity_project_canonicalization (p04 → p04-gigabit)
- test_promote_entity_candidate_to_active
- test_reject_entity_candidate
- test_promote_active_entity_noop (only candidates promote)
- test_entity_audit_log_captures_lifecycle (before/after snapshots)
- test_new_relationship_types_available (6 new types present)
- test_conflicts_tables_exist
- test_memory_audit_has_entity_kind
- test_graduated_status_accepted
What's next (5B-5I, deferred): entity triage UI tab, core structure
queries, the 3 killer queries, memory graduation script, conflict
detection, MCP + context pack integration. See plan file.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 07:01:28 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def reject_entity_candidate(entity_id: str, actor: str = "api", note: str = "") -> bool:
|
|
|
|
|
"""Reject a candidate entity (status → invalid)."""
|
|
|
|
|
with get_connection() as conn:
|
|
|
|
|
row = conn.execute(
|
|
|
|
|
"SELECT status FROM entities WHERE id = ?", (entity_id,)
|
|
|
|
|
).fetchone()
|
|
|
|
|
if row is None or row["status"] != "candidate":
|
|
|
|
|
return False
|
|
|
|
|
return _set_entity_status(entity_id, "invalid", actor=actor, note=note)
|
|
|
|
|
|
|
|
|
|
|
2026-04-21 21:56:24 -04:00
|
|
|
def supersede_entity(
|
|
|
|
|
entity_id: str,
|
|
|
|
|
actor: str = "api",
|
|
|
|
|
note: str = "",
|
|
|
|
|
superseded_by: str | None = None,
|
|
|
|
|
) -> bool:
|
|
|
|
|
"""Mark an active entity as superseded.
|
|
|
|
|
|
|
|
|
|
When ``superseded_by`` names a real entity, also create a
|
|
|
|
|
``supersedes`` relationship from the new entity to the old one
|
|
|
|
|
(semantics: ``new SUPERSEDES old``). This keeps the graph
|
|
|
|
|
navigable without the caller remembering to make that edge.
|
|
|
|
|
"""
|
|
|
|
|
if superseded_by:
|
|
|
|
|
new_entity = get_entity(superseded_by)
|
|
|
|
|
if new_entity is None:
|
|
|
|
|
raise ValueError(
|
|
|
|
|
f"superseded_by entity not found: {superseded_by}"
|
|
|
|
|
)
|
|
|
|
|
if new_entity.id == entity_id:
|
|
|
|
|
raise ValueError("entity cannot supersede itself")
|
|
|
|
|
|
|
|
|
|
ok = _set_entity_status(entity_id, "superseded", actor=actor, note=note)
|
|
|
|
|
if not ok:
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
if superseded_by:
|
|
|
|
|
try:
|
|
|
|
|
create_relationship(
|
|
|
|
|
source_entity_id=superseded_by,
|
|
|
|
|
target_entity_id=entity_id,
|
|
|
|
|
relationship_type="supersedes",
|
|
|
|
|
source_refs=[f"supersede-api:{actor}"],
|
|
|
|
|
)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
log.warning(
|
|
|
|
|
"supersede_relationship_create_failed",
|
|
|
|
|
entity_id=entity_id,
|
|
|
|
|
superseded_by=superseded_by,
|
|
|
|
|
error=str(e),
|
|
|
|
|
)
|
2026-04-22 14:59:17 -04:00
|
|
|
|
|
|
|
|
# V1-0 (F-5 hook on supersede, per plan's "every active-entity
|
|
|
|
|
# write path"). Supersede demotes `entity_id` AND adds a
|
|
|
|
|
# `supersedes` relationship rooted at the already-active
|
|
|
|
|
# `superseded_by`. That new edge can create a conflict the
|
|
|
|
|
# detector should catch synchronously. Fail-open per
|
|
|
|
|
# conflict-model.md:256.
|
|
|
|
|
try:
|
|
|
|
|
from atocore.engineering.conflicts import detect_conflicts_for_entity
|
|
|
|
|
detect_conflicts_for_entity(superseded_by)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
log.warning(
|
|
|
|
|
"conflict_detection_failed",
|
|
|
|
|
entity_id=superseded_by,
|
|
|
|
|
error=str(e),
|
|
|
|
|
)
|
2026-04-21 21:56:24 -04:00
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def invalidate_active_entity(
|
|
|
|
|
entity_id: str,
|
|
|
|
|
actor: str = "api",
|
|
|
|
|
reason: str = "",
|
|
|
|
|
) -> tuple[bool, str]:
|
|
|
|
|
"""Mark an active entity as invalid (Issue E — retraction path).
|
|
|
|
|
|
|
|
|
|
Returns (success, status_code) where status_code is one of:
|
|
|
|
|
- "invalidated" — happy path
|
|
|
|
|
- "not_found" — no such entity
|
|
|
|
|
- "already_invalid" — already invalid (idempotent)
|
|
|
|
|
- "not_active" — entity is candidate/superseded; use the
|
|
|
|
|
appropriate other endpoint
|
|
|
|
|
|
|
|
|
|
This is the public retraction API distinct from
|
|
|
|
|
``reject_entity_candidate`` (which only handles candidate→invalid).
|
|
|
|
|
"""
|
|
|
|
|
entity = get_entity(entity_id)
|
|
|
|
|
if entity is None:
|
|
|
|
|
return False, "not_found"
|
|
|
|
|
if entity.status == "invalid":
|
|
|
|
|
return True, "already_invalid"
|
|
|
|
|
if entity.status != "active":
|
|
|
|
|
return False, "not_active"
|
|
|
|
|
ok = _set_entity_status(entity_id, "invalid", actor=actor, note=reason)
|
|
|
|
|
return ok, "invalidated" if ok else "not_active"
|
feat: Phase 5A — Engineering V1 foundation
First slice of the Engineering V1 sprint. Lays the schema + lifecycle
plumbing so the 10 canonical queries, memory graduation, and conflict
detection can land cleanly on top.
Schema (src/atocore/models/database.py):
- conflicts + conflict_members tables per conflict-model.md (with 5
indexes on status/project/slot/members)
- memory_audit.entity_kind discriminator — same audit table serves
both memories ("memory") and entities ("entity"); unified history
without duplicating infrastructure
- memories.graduated_to_entity_id forward pointer for graduated
memories (M → E transition preserves the memory as historical
pointer)
Memory (src/atocore/memory/service.py):
- MEMORY_STATUSES gains "graduated" — memory-entity graduation flow
ready to wire in Phase 5F
Engineering service (src/atocore/engineering/service.py):
- RELATIONSHIP_TYPES organized into 4 families per ontology-v1.md:
+ Structural: contains, part_of, interfaces_with
+ Intent: satisfies, constrained_by, affected_by_decision,
based_on_assumption (new), supersedes
+ Validation: analyzed_by, validated_by, supports (new),
conflicts_with (new), depends_on
+ Provenance: described_by, updated_by_session (new),
evidenced_by (new), summarized_in (new)
- create_entity + create_relationship now call resolve_project_name()
on write (canonicalization contract per doc)
- Both accept actor= parameter for audit provenance
- _audit_entity() helper uses shared memory_audit table with
entity_kind="entity" — one observability layer for everything
- promote_entity / reject_entity_candidate / supersede_entity —
mirror the memory lifecycle exactly (same pattern, same naming)
- get_entity_audit() reads from the shared table filtered by
entity_kind
API (src/atocore/api/routes.py):
- POST /entities/{id}/promote (candidate → active)
- POST /entities/{id}/reject (candidate → invalid)
- GET /entities/{id}/audit (full history for one entity)
- POST /entities passes actor="api-http" through
Tests: 317 → 326 (9 new):
- test_entity_project_canonicalization (p04 → p04-gigabit)
- test_promote_entity_candidate_to_active
- test_reject_entity_candidate
- test_promote_active_entity_noop (only candidates promote)
- test_entity_audit_log_captures_lifecycle (before/after snapshots)
- test_new_relationship_types_available (6 new types present)
- test_conflicts_tables_exist
- test_memory_audit_has_entity_kind
- test_graduated_status_accepted
What's next (5B-5I, deferred): entity triage UI tab, core structure
queries, the 3 killer queries, memory graduation script, conflict
detection, MCP + context pack integration. See plan file.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 07:01:28 -04:00
|
|
|
|
|
|
|
|
|
feat(api): PATCH /entities/{id} + /v1/engineering/* aliases
PATCH lets users edit an active entity's description, properties,
confidence, and source_refs without cloning — closes the duplicate-trap
half-fixed by /invalidate + /supersede. Issue D just adds the
/engineering/* query surface to the /v1 allowlist.
- engineering/service.py: update_entity supports description replace,
properties shallow merge with null-delete semantics, confidence
0..1 bounds check, source_refs dedup-append. Writes audit row
- api/routes.py: PATCH /entities/{id} with EntityPatchRequest
- main.py: engineering/* query endpoints aliased under /v1 (Issue D)
- tests/test_patch_entity.py: 12 tests (merge, null-delete, bounds,
dedup, 404, audit, v1 alias)
- DEV-LEDGER.md: session log + test_count 509 -> 521
Forbidden fields via PATCH (by design): entity_type, project, name,
status. Use supersede+create or the dedicated status endpoints.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 09:02:13 -04:00
|
|
|
def update_entity(
|
|
|
|
|
entity_id: str,
|
|
|
|
|
*,
|
|
|
|
|
description: str | None = None,
|
|
|
|
|
properties_patch: dict | None = None,
|
|
|
|
|
confidence: float | None = None,
|
|
|
|
|
append_source_refs: list[str] | None = None,
|
|
|
|
|
actor: str = "api",
|
|
|
|
|
note: str = "",
|
|
|
|
|
) -> Entity | None:
|
|
|
|
|
"""Update mutable fields on an existing entity (Issue E follow-up).
|
|
|
|
|
|
|
|
|
|
Field rules (kept narrow on purpose):
|
|
|
|
|
|
|
|
|
|
- ``description``: replaces the current value when provided.
|
|
|
|
|
- ``properties_patch``: merged into the existing ``properties`` dict,
|
|
|
|
|
shallow. Pass ``None`` as a value to delete a key; pass a new
|
|
|
|
|
value to overwrite it.
|
|
|
|
|
- ``confidence``: replaces when provided. Must be in [0, 1].
|
|
|
|
|
- ``append_source_refs``: appended verbatim to the existing list
|
|
|
|
|
(duplicates are filtered out, order preserved).
|
|
|
|
|
|
|
|
|
|
What you cannot change via this path:
|
|
|
|
|
|
|
|
|
|
- ``entity_type`` — requires supersede+create (a new type is a new
|
|
|
|
|
thing).
|
|
|
|
|
- ``project`` — use ``promote_entity`` with ``target_project`` for
|
|
|
|
|
inbox→project graduation, or supersede+create for anything else.
|
|
|
|
|
- ``name`` — renames are destructive to cross-references;
|
|
|
|
|
supersede+create.
|
|
|
|
|
- ``status`` — use the dedicated promote/reject/invalidate/supersede
|
|
|
|
|
endpoints.
|
|
|
|
|
|
|
|
|
|
Returns the updated entity, or None if no such entity exists.
|
|
|
|
|
"""
|
|
|
|
|
entity = get_entity(entity_id)
|
|
|
|
|
if entity is None:
|
|
|
|
|
return None
|
|
|
|
|
if confidence is not None and not (0.0 <= confidence <= 1.0):
|
|
|
|
|
raise ValueError("confidence must be in [0, 1]")
|
|
|
|
|
|
|
|
|
|
before = {
|
|
|
|
|
"description": entity.description,
|
|
|
|
|
"properties": dict(entity.properties or {}),
|
|
|
|
|
"confidence": entity.confidence,
|
|
|
|
|
"source_refs": list(entity.source_refs or []),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
new_description = entity.description if description is None else description
|
|
|
|
|
new_confidence = entity.confidence if confidence is None else confidence
|
|
|
|
|
new_properties = dict(entity.properties or {})
|
|
|
|
|
if properties_patch:
|
|
|
|
|
for key, value in properties_patch.items():
|
|
|
|
|
if value is None:
|
|
|
|
|
new_properties.pop(key, None)
|
|
|
|
|
else:
|
|
|
|
|
new_properties[key] = value
|
|
|
|
|
new_refs = list(entity.source_refs or [])
|
|
|
|
|
if append_source_refs:
|
|
|
|
|
existing = set(new_refs)
|
|
|
|
|
for ref in append_source_refs:
|
|
|
|
|
if ref and ref not in existing:
|
|
|
|
|
new_refs.append(ref)
|
|
|
|
|
existing.add(ref)
|
|
|
|
|
|
|
|
|
|
now = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S")
|
|
|
|
|
with get_connection() as conn:
|
|
|
|
|
conn.execute(
|
|
|
|
|
"""UPDATE entities
|
|
|
|
|
SET description = ?, properties = ?, confidence = ?,
|
|
|
|
|
source_refs = ?, updated_at = ?
|
|
|
|
|
WHERE id = ?""",
|
|
|
|
|
(
|
|
|
|
|
new_description,
|
|
|
|
|
json.dumps(new_properties),
|
|
|
|
|
new_confidence,
|
|
|
|
|
json.dumps(new_refs),
|
|
|
|
|
now,
|
|
|
|
|
entity_id,
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
after = {
|
|
|
|
|
"description": new_description,
|
|
|
|
|
"properties": new_properties,
|
|
|
|
|
"confidence": new_confidence,
|
|
|
|
|
"source_refs": new_refs,
|
|
|
|
|
}
|
|
|
|
|
_audit_entity(
|
|
|
|
|
entity_id=entity_id,
|
|
|
|
|
action="updated",
|
|
|
|
|
actor=actor,
|
|
|
|
|
before=before,
|
|
|
|
|
after=after,
|
|
|
|
|
note=note,
|
|
|
|
|
)
|
|
|
|
|
log.info("entity_updated", entity_id=entity_id, actor=actor)
|
|
|
|
|
return get_entity(entity_id)
|
|
|
|
|
|
|
|
|
|
|
feat: Phase 5A — Engineering V1 foundation
First slice of the Engineering V1 sprint. Lays the schema + lifecycle
plumbing so the 10 canonical queries, memory graduation, and conflict
detection can land cleanly on top.
Schema (src/atocore/models/database.py):
- conflicts + conflict_members tables per conflict-model.md (with 5
indexes on status/project/slot/members)
- memory_audit.entity_kind discriminator — same audit table serves
both memories ("memory") and entities ("entity"); unified history
without duplicating infrastructure
- memories.graduated_to_entity_id forward pointer for graduated
memories (M → E transition preserves the memory as historical
pointer)
Memory (src/atocore/memory/service.py):
- MEMORY_STATUSES gains "graduated" — memory-entity graduation flow
ready to wire in Phase 5F
Engineering service (src/atocore/engineering/service.py):
- RELATIONSHIP_TYPES organized into 4 families per ontology-v1.md:
+ Structural: contains, part_of, interfaces_with
+ Intent: satisfies, constrained_by, affected_by_decision,
based_on_assumption (new), supersedes
+ Validation: analyzed_by, validated_by, supports (new),
conflicts_with (new), depends_on
+ Provenance: described_by, updated_by_session (new),
evidenced_by (new), summarized_in (new)
- create_entity + create_relationship now call resolve_project_name()
on write (canonicalization contract per doc)
- Both accept actor= parameter for audit provenance
- _audit_entity() helper uses shared memory_audit table with
entity_kind="entity" — one observability layer for everything
- promote_entity / reject_entity_candidate / supersede_entity —
mirror the memory lifecycle exactly (same pattern, same naming)
- get_entity_audit() reads from the shared table filtered by
entity_kind
API (src/atocore/api/routes.py):
- POST /entities/{id}/promote (candidate → active)
- POST /entities/{id}/reject (candidate → invalid)
- GET /entities/{id}/audit (full history for one entity)
- POST /entities passes actor="api-http" through
Tests: 317 → 326 (9 new):
- test_entity_project_canonicalization (p04 → p04-gigabit)
- test_promote_entity_candidate_to_active
- test_reject_entity_candidate
- test_promote_active_entity_noop (only candidates promote)
- test_entity_audit_log_captures_lifecycle (before/after snapshots)
- test_new_relationship_types_available (6 new types present)
- test_conflicts_tables_exist
- test_memory_audit_has_entity_kind
- test_graduated_status_accepted
What's next (5B-5I, deferred): entity triage UI tab, core structure
queries, the 3 killer queries, memory graduation script, conflict
detection, MCP + context pack integration. See plan file.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 07:01:28 -04:00
|
|
|
def get_entity_audit(entity_id: str, limit: int = 100) -> list[dict]:
|
|
|
|
|
"""Fetch audit entries for an entity from the shared audit table."""
|
|
|
|
|
with get_connection() as conn:
|
|
|
|
|
rows = conn.execute(
|
|
|
|
|
"SELECT id, memory_id AS entity_id, action, actor, before_json, "
|
|
|
|
|
"after_json, note, timestamp FROM memory_audit "
|
|
|
|
|
"WHERE entity_kind = 'entity' AND memory_id = ? "
|
|
|
|
|
"ORDER BY timestamp DESC LIMIT ?",
|
|
|
|
|
(entity_id, limit),
|
|
|
|
|
).fetchall()
|
|
|
|
|
out = []
|
|
|
|
|
for r in rows:
|
|
|
|
|
try:
|
|
|
|
|
before = json.loads(r["before_json"] or "{}")
|
|
|
|
|
except Exception:
|
|
|
|
|
before = {}
|
|
|
|
|
try:
|
|
|
|
|
after = json.loads(r["after_json"] or "{}")
|
|
|
|
|
except Exception:
|
|
|
|
|
after = {}
|
|
|
|
|
out.append({
|
|
|
|
|
"id": r["id"],
|
|
|
|
|
"entity_id": r["entity_id"],
|
|
|
|
|
"action": r["action"],
|
|
|
|
|
"actor": r["actor"] or "api",
|
|
|
|
|
"before": before,
|
|
|
|
|
"after": after,
|
|
|
|
|
"note": r["note"] or "",
|
|
|
|
|
"timestamp": r["timestamp"],
|
|
|
|
|
})
|
|
|
|
|
return out
|
|
|
|
|
|
|
|
|
|
|
feat: Engineering Knowledge Layer V1 — entities + relationships
Layer 2 of the AtoCore architecture. Adds typed engineering entities
with relationships on top of the flat memory/state/chunk substrate.
Schema:
- entities table: id, entity_type, name, project, description,
properties (JSON), status, confidence, source_refs, timestamps
- relationships table: source_entity_id, target_entity_id,
relationship_type, confidence, source_refs
15 entity types: project, system, subsystem, component, interface,
requirement, constraint, decision, material, parameter,
analysis_model, result, validation_claim, vendor, process
12 relationship types: contains, part_of, interfaces_with,
satisfies, constrained_by, affected_by_decision, analyzed_by,
validated_by, depends_on, uses_material, described_by, supersedes
Service layer: full CRUD + get_entity_with_context (returns an
entity with its relationships and all related entities in one call).
API endpoints:
- POST /entities — create entity
- GET /entities — list/filter by type, project, status, name
- GET /entities/{id} — entity + relationships + related entities
- POST /relationships — create relationship
Schema auto-initialized on app startup via init_engineering_schema().
7 tests covering entity CRUD, relationships, context traversal,
filtering, name search, and validation.
Test count: 290 -> 297.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 09:50:58 -04:00
|
|
|
def get_entities(
|
|
|
|
|
entity_type: str | None = None,
|
|
|
|
|
project: str | None = None,
|
|
|
|
|
status: str = "active",
|
|
|
|
|
name_contains: str | None = None,
|
|
|
|
|
limit: int = 100,
|
feat(entities): inbox + cross-project (project="") support (Issue C)
Makes `inbox` a reserved pseudo-project and `project=""` a first-class
cross-project bucket. Unblocks AKC capturing pre-project leads/quotes
and cross-project facts (materials, vendors) that don't fit a single
registered project.
- projects/registry.py: INBOX_PROJECT/GLOBAL_PROJECT constants,
is_reserved_project(), register/update guards, resolve_project_name
passthrough for "inbox"
- engineering/service.py: get_entities scoping rules (inbox-only,
global-only, real+global default, scope_only=true strict).
promote_entity accepts target_project to retarget on promote
- api/routes.py: GET /entities gains scope_only; POST /entities accepts
project=null as ""; POST /entities/{id}/promote accepts
{target_project, note}
- engineering/wiki.py: homepage shows "Inbox & Global" cards with live
counts linking to scoped lists
- tests/test_inbox_crossproject.py: 15 tests (reserved enforcement,
scoping rules, API shape, promote retargeting)
- DEV-LEDGER.md: session log, test_count 463 -> 478
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 20:17:32 -04:00
|
|
|
scope_only: bool = False,
|
feat: Engineering Knowledge Layer V1 — entities + relationships
Layer 2 of the AtoCore architecture. Adds typed engineering entities
with relationships on top of the flat memory/state/chunk substrate.
Schema:
- entities table: id, entity_type, name, project, description,
properties (JSON), status, confidence, source_refs, timestamps
- relationships table: source_entity_id, target_entity_id,
relationship_type, confidence, source_refs
15 entity types: project, system, subsystem, component, interface,
requirement, constraint, decision, material, parameter,
analysis_model, result, validation_claim, vendor, process
12 relationship types: contains, part_of, interfaces_with,
satisfies, constrained_by, affected_by_decision, analyzed_by,
validated_by, depends_on, uses_material, described_by, supersedes
Service layer: full CRUD + get_entity_with_context (returns an
entity with its relationships and all related entities in one call).
API endpoints:
- POST /entities — create entity
- GET /entities — list/filter by type, project, status, name
- GET /entities/{id} — entity + relationships + related entities
- POST /relationships — create relationship
Schema auto-initialized on app startup via init_engineering_schema().
7 tests covering entity CRUD, relationships, context traversal,
filtering, name search, and validation.
Test count: 290 -> 297.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 09:50:58 -04:00
|
|
|
) -> list[Entity]:
|
feat(entities): inbox + cross-project (project="") support (Issue C)
Makes `inbox` a reserved pseudo-project and `project=""` a first-class
cross-project bucket. Unblocks AKC capturing pre-project leads/quotes
and cross-project facts (materials, vendors) that don't fit a single
registered project.
- projects/registry.py: INBOX_PROJECT/GLOBAL_PROJECT constants,
is_reserved_project(), register/update guards, resolve_project_name
passthrough for "inbox"
- engineering/service.py: get_entities scoping rules (inbox-only,
global-only, real+global default, scope_only=true strict).
promote_entity accepts target_project to retarget on promote
- api/routes.py: GET /entities gains scope_only; POST /entities accepts
project=null as ""; POST /entities/{id}/promote accepts
{target_project, note}
- engineering/wiki.py: homepage shows "Inbox & Global" cards with live
counts linking to scoped lists
- tests/test_inbox_crossproject.py: 15 tests (reserved enforcement,
scoping rules, API shape, promote retargeting)
- DEV-LEDGER.md: session log, test_count 463 -> 478
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 20:17:32 -04:00
|
|
|
"""List entities with optional filters.
|
|
|
|
|
|
|
|
|
|
Project scoping rules (Issue C — inbox + cross-project):
|
|
|
|
|
|
|
|
|
|
- ``project=None``: no project filter, return everything matching status.
|
|
|
|
|
- ``project=""``: return only cross-project (global) entities.
|
|
|
|
|
- ``project="inbox"``: return only inbox entities.
|
|
|
|
|
- ``project="<real>"`` and ``scope_only=False`` (default): return entities
|
|
|
|
|
scoped to that project PLUS cross-project (``project=""``) entities.
|
|
|
|
|
- ``project="<real>"`` and ``scope_only=True``: return only that project,
|
|
|
|
|
without the cross-project bleed.
|
|
|
|
|
"""
|
|
|
|
|
from atocore.projects.registry import (
|
|
|
|
|
INBOX_PROJECT, GLOBAL_PROJECT, is_reserved_project,
|
|
|
|
|
)
|
|
|
|
|
|
feat: Engineering Knowledge Layer V1 — entities + relationships
Layer 2 of the AtoCore architecture. Adds typed engineering entities
with relationships on top of the flat memory/state/chunk substrate.
Schema:
- entities table: id, entity_type, name, project, description,
properties (JSON), status, confidence, source_refs, timestamps
- relationships table: source_entity_id, target_entity_id,
relationship_type, confidence, source_refs
15 entity types: project, system, subsystem, component, interface,
requirement, constraint, decision, material, parameter,
analysis_model, result, validation_claim, vendor, process
12 relationship types: contains, part_of, interfaces_with,
satisfies, constrained_by, affected_by_decision, analyzed_by,
validated_by, depends_on, uses_material, described_by, supersedes
Service layer: full CRUD + get_entity_with_context (returns an
entity with its relationships and all related entities in one call).
API endpoints:
- POST /entities — create entity
- GET /entities — list/filter by type, project, status, name
- GET /entities/{id} — entity + relationships + related entities
- POST /relationships — create relationship
Schema auto-initialized on app startup via init_engineering_schema().
7 tests covering entity CRUD, relationships, context traversal,
filtering, name search, and validation.
Test count: 290 -> 297.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 09:50:58 -04:00
|
|
|
query = "SELECT * FROM entities WHERE status = ?"
|
|
|
|
|
params: list = [status]
|
|
|
|
|
|
|
|
|
|
if entity_type:
|
|
|
|
|
query += " AND entity_type = ?"
|
|
|
|
|
params.append(entity_type)
|
|
|
|
|
if project is not None:
|
feat(entities): inbox + cross-project (project="") support (Issue C)
Makes `inbox` a reserved pseudo-project and `project=""` a first-class
cross-project bucket. Unblocks AKC capturing pre-project leads/quotes
and cross-project facts (materials, vendors) that don't fit a single
registered project.
- projects/registry.py: INBOX_PROJECT/GLOBAL_PROJECT constants,
is_reserved_project(), register/update guards, resolve_project_name
passthrough for "inbox"
- engineering/service.py: get_entities scoping rules (inbox-only,
global-only, real+global default, scope_only=true strict).
promote_entity accepts target_project to retarget on promote
- api/routes.py: GET /entities gains scope_only; POST /entities accepts
project=null as ""; POST /entities/{id}/promote accepts
{target_project, note}
- engineering/wiki.py: homepage shows "Inbox & Global" cards with live
counts linking to scoped lists
- tests/test_inbox_crossproject.py: 15 tests (reserved enforcement,
scoping rules, API shape, promote retargeting)
- DEV-LEDGER.md: session log, test_count 463 -> 478
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 20:17:32 -04:00
|
|
|
p = (project or "").strip()
|
|
|
|
|
if p == GLOBAL_PROJECT or is_reserved_project(p) or scope_only:
|
|
|
|
|
query += " AND project = ?"
|
|
|
|
|
params.append(p)
|
|
|
|
|
else:
|
|
|
|
|
# Real project — include cross-project entities by default.
|
|
|
|
|
query += " AND (project = ? OR project = ?)"
|
|
|
|
|
params.extend([p, GLOBAL_PROJECT])
|
feat: Engineering Knowledge Layer V1 — entities + relationships
Layer 2 of the AtoCore architecture. Adds typed engineering entities
with relationships on top of the flat memory/state/chunk substrate.
Schema:
- entities table: id, entity_type, name, project, description,
properties (JSON), status, confidence, source_refs, timestamps
- relationships table: source_entity_id, target_entity_id,
relationship_type, confidence, source_refs
15 entity types: project, system, subsystem, component, interface,
requirement, constraint, decision, material, parameter,
analysis_model, result, validation_claim, vendor, process
12 relationship types: contains, part_of, interfaces_with,
satisfies, constrained_by, affected_by_decision, analyzed_by,
validated_by, depends_on, uses_material, described_by, supersedes
Service layer: full CRUD + get_entity_with_context (returns an
entity with its relationships and all related entities in one call).
API endpoints:
- POST /entities — create entity
- GET /entities — list/filter by type, project, status, name
- GET /entities/{id} — entity + relationships + related entities
- POST /relationships — create relationship
Schema auto-initialized on app startup via init_engineering_schema().
7 tests covering entity CRUD, relationships, context traversal,
filtering, name search, and validation.
Test count: 290 -> 297.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 09:50:58 -04:00
|
|
|
if name_contains:
|
|
|
|
|
query += " AND name LIKE ?"
|
|
|
|
|
params.append(f"%{name_contains}%")
|
|
|
|
|
|
|
|
|
|
query += " ORDER BY entity_type, name LIMIT ?"
|
|
|
|
|
params.append(min(limit, 500))
|
|
|
|
|
|
|
|
|
|
with get_connection() as conn:
|
|
|
|
|
rows = conn.execute(query, params).fetchall()
|
|
|
|
|
return [_row_to_entity(r) for r in rows]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_entity(entity_id: str) -> Entity | None:
|
|
|
|
|
with get_connection() as conn:
|
|
|
|
|
row = conn.execute(
|
|
|
|
|
"SELECT * FROM entities WHERE id = ?", (entity_id,)
|
|
|
|
|
).fetchone()
|
|
|
|
|
if row is None:
|
|
|
|
|
return None
|
|
|
|
|
return _row_to_entity(row)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_relationships(
|
|
|
|
|
entity_id: str,
|
|
|
|
|
direction: str = "both",
|
|
|
|
|
) -> list[Relationship]:
|
|
|
|
|
results = []
|
|
|
|
|
with get_connection() as conn:
|
|
|
|
|
if direction in ("outgoing", "both"):
|
|
|
|
|
rows = conn.execute(
|
|
|
|
|
"SELECT * FROM relationships WHERE source_entity_id = ?",
|
|
|
|
|
(entity_id,),
|
|
|
|
|
).fetchall()
|
|
|
|
|
results.extend(_row_to_relationship(r) for r in rows)
|
|
|
|
|
if direction in ("incoming", "both"):
|
|
|
|
|
rows = conn.execute(
|
|
|
|
|
"SELECT * FROM relationships WHERE target_entity_id = ?",
|
|
|
|
|
(entity_id,),
|
|
|
|
|
).fetchall()
|
|
|
|
|
results.extend(_row_to_relationship(r) for r in rows)
|
|
|
|
|
return results
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_entity_with_context(entity_id: str) -> dict | None:
|
|
|
|
|
entity = get_entity(entity_id)
|
|
|
|
|
if entity is None:
|
|
|
|
|
return None
|
|
|
|
|
relationships = get_relationships(entity_id)
|
|
|
|
|
related_ids = set()
|
|
|
|
|
for rel in relationships:
|
|
|
|
|
related_ids.add(rel.source_entity_id)
|
|
|
|
|
related_ids.add(rel.target_entity_id)
|
|
|
|
|
related_ids.discard(entity_id)
|
|
|
|
|
|
|
|
|
|
related_entities = {}
|
|
|
|
|
for rid in related_ids:
|
|
|
|
|
e = get_entity(rid)
|
|
|
|
|
if e:
|
|
|
|
|
related_entities[rid] = e
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
"entity": entity,
|
|
|
|
|
"relationships": relationships,
|
|
|
|
|
"related_entities": related_entities,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _row_to_entity(row) -> Entity:
|
2026-04-22 14:59:17 -04:00
|
|
|
# V1-0 shared-header fields are optional on read — rows that predate
|
|
|
|
|
# V1-0 migration have NULL / missing values, so defaults kick in and
|
|
|
|
|
# older tests that build Entity() without the new fields keep passing.
|
|
|
|
|
# `row.keys()` lets us tolerate SQLite rows that lack the columns
|
|
|
|
|
# entirely (pre-migration sqlite3.Row).
|
|
|
|
|
keys = set(row.keys())
|
|
|
|
|
extractor_version = (row["extractor_version"] or "") if "extractor_version" in keys else ""
|
|
|
|
|
canonical_home = (row["canonical_home"] or "entity") if "canonical_home" in keys else "entity"
|
|
|
|
|
hand_authored = bool(row["hand_authored"]) if "hand_authored" in keys and row["hand_authored"] is not None else False
|
feat: Engineering Knowledge Layer V1 — entities + relationships
Layer 2 of the AtoCore architecture. Adds typed engineering entities
with relationships on top of the flat memory/state/chunk substrate.
Schema:
- entities table: id, entity_type, name, project, description,
properties (JSON), status, confidence, source_refs, timestamps
- relationships table: source_entity_id, target_entity_id,
relationship_type, confidence, source_refs
15 entity types: project, system, subsystem, component, interface,
requirement, constraint, decision, material, parameter,
analysis_model, result, validation_claim, vendor, process
12 relationship types: contains, part_of, interfaces_with,
satisfies, constrained_by, affected_by_decision, analyzed_by,
validated_by, depends_on, uses_material, described_by, supersedes
Service layer: full CRUD + get_entity_with_context (returns an
entity with its relationships and all related entities in one call).
API endpoints:
- POST /entities — create entity
- GET /entities — list/filter by type, project, status, name
- GET /entities/{id} — entity + relationships + related entities
- POST /relationships — create relationship
Schema auto-initialized on app startup via init_engineering_schema().
7 tests covering entity CRUD, relationships, context traversal,
filtering, name search, and validation.
Test count: 290 -> 297.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 09:50:58 -04:00
|
|
|
return Entity(
|
|
|
|
|
id=row["id"],
|
|
|
|
|
entity_type=row["entity_type"],
|
|
|
|
|
name=row["name"],
|
|
|
|
|
project=row["project"] or "",
|
|
|
|
|
description=row["description"] or "",
|
|
|
|
|
properties=json.loads(row["properties"] or "{}"),
|
|
|
|
|
status=row["status"],
|
|
|
|
|
confidence=row["confidence"],
|
|
|
|
|
source_refs=json.loads(row["source_refs"] or "[]"),
|
|
|
|
|
created_at=row["created_at"] or "",
|
|
|
|
|
updated_at=row["updated_at"] or "",
|
2026-04-22 14:59:17 -04:00
|
|
|
extractor_version=extractor_version,
|
|
|
|
|
canonical_home=canonical_home,
|
|
|
|
|
hand_authored=hand_authored,
|
feat: Engineering Knowledge Layer V1 — entities + relationships
Layer 2 of the AtoCore architecture. Adds typed engineering entities
with relationships on top of the flat memory/state/chunk substrate.
Schema:
- entities table: id, entity_type, name, project, description,
properties (JSON), status, confidence, source_refs, timestamps
- relationships table: source_entity_id, target_entity_id,
relationship_type, confidence, source_refs
15 entity types: project, system, subsystem, component, interface,
requirement, constraint, decision, material, parameter,
analysis_model, result, validation_claim, vendor, process
12 relationship types: contains, part_of, interfaces_with,
satisfies, constrained_by, affected_by_decision, analyzed_by,
validated_by, depends_on, uses_material, described_by, supersedes
Service layer: full CRUD + get_entity_with_context (returns an
entity with its relationships and all related entities in one call).
API endpoints:
- POST /entities — create entity
- GET /entities — list/filter by type, project, status, name
- GET /entities/{id} — entity + relationships + related entities
- POST /relationships — create relationship
Schema auto-initialized on app startup via init_engineering_schema().
7 tests covering entity CRUD, relationships, context traversal,
filtering, name search, and validation.
Test count: 290 -> 297.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 09:50:58 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _row_to_relationship(row) -> Relationship:
|
|
|
|
|
return Relationship(
|
|
|
|
|
id=row["id"],
|
|
|
|
|
source_entity_id=row["source_entity_id"],
|
|
|
|
|
target_entity_id=row["target_entity_id"],
|
|
|
|
|
relationship_type=row["relationship_type"],
|
|
|
|
|
confidence=row["confidence"],
|
|
|
|
|
source_refs=json.loads(row["source_refs"] or "[]"),
|
|
|
|
|
created_at=row["created_at"] or "",
|
|
|
|
|
)
|