fuli-context 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +178 -0
- package/compose.graphiti.yml +86 -0
- package/dist/web/assets/ConnectionsPage-CY6H_mJJ.js +1 -0
- package/dist/web/assets/KnowledgeInspector-BSpz5Sfm.js +1 -0
- package/dist/web/assets/KnowledgeInspector-CHbB_0rC.css +1 -0
- package/dist/web/assets/KnowledgeOrganizerPage-BlEvhPrv.css +1 -0
- package/dist/web/assets/KnowledgeOrganizerPage-CvjGPlUc.js +1 -0
- package/dist/web/assets/KnowledgePage-Khuo3cNs.js +1 -0
- package/dist/web/assets/KnowledgeWorkspace-DVZusvem.js +4 -0
- package/dist/web/assets/OverviewPage-DC4C4Fhn.js +1 -0
- package/dist/web/assets/PersonalProfilePage-CSV9Mtgz.js +1 -0
- package/dist/web/assets/PersonalProfilePage-CVJ3TcRi.css +1 -0
- package/dist/web/assets/PersonalProjectsPage-A50EaseQ.js +1 -0
- package/dist/web/assets/PublicProjectsPage-BAdO_bae.js +1 -0
- package/dist/web/assets/ReviewPage-Dztk2jDG.js +1 -0
- package/dist/web/assets/console-tbvTUi6p.js +1 -0
- package/dist/web/assets/fuli-logo-DGuyqchQ.png +0 -0
- package/dist/web/assets/identity-B3s5CXhv.js +1 -0
- package/dist/web/assets/index-Devf7glZ.css +1 -0
- package/dist/web/assets/index-DwUDhQKc.js +6 -0
- package/dist/web/assets/model-DVT8sjy9.js +1 -0
- package/dist/web/assets/paths-B790iniO.js +1 -0
- package/dist/web/assets/runtime-dom.esm-bundler-DttsK21F.js +1 -0
- package/dist/web/assets/vue-router-BYTAoWT3.js +1 -0
- package/dist/web/index.html +22 -0
- package/graph-provider/Dockerfile +16 -0
- package/graph-provider/fuli_graph/__init__.py +5 -0
- package/graph-provider/fuli_graph/app.py +547 -0
- package/graph-provider/fuli_graph/auth.py +17 -0
- package/graph-provider/fuli_graph/collaboration_context.py +290 -0
- package/graph-provider/fuli_graph/config.py +34 -0
- package/graph-provider/fuli_graph/graph_models.py +123 -0
- package/graph-provider/fuli_graph/graph_projection.py +405 -0
- package/graph-provider/fuli_graph/graph_query.py +561 -0
- package/graph-provider/fuli_graph/knowledge_audit.py +765 -0
- package/graph-provider/fuli_graph/knowledge_batch_confirmation.py +246 -0
- package/graph-provider/fuli_graph/knowledge_management.py +816 -0
- package/graph-provider/fuli_graph/knowledge_records.py +65 -0
- package/graph-provider/fuli_graph/knowledge_search.py +723 -0
- package/graph-provider/fuli_graph/knowledge_usage_models.py +52 -0
- package/graph-provider/fuli_graph/model_validation.py +58 -0
- package/graph-provider/fuli_graph/models.py +1179 -0
- package/graph-provider/fuli_graph/personal_project_access.py +29 -0
- package/graph-provider/fuli_graph/preference_conflicts.py +420 -0
- package/graph-provider/fuli_graph/project_action_models.py +142 -0
- package/graph-provider/fuli_graph/project_knowledge.py +707 -0
- package/graph-provider/fuli_graph/project_management.py +251 -0
- package/graph-provider/fuli_graph/provider_values.py +40 -0
- package/graph-provider/fuli_graph/runtime.py +350 -0
- package/graph-provider/fuli_graph/search_projection.py +156 -0
- package/graph-provider/fuli_graph/store.py +24 -0
- package/graph-provider/fuli_graph/store_identity.py +237 -0
- package/graph-provider/fuli_graph/store_knowledge.py +447 -0
- package/graph-provider/fuli_graph/store_projects.py +242 -0
- package/graph-provider/fuli_graph/store_publication.py +370 -0
- package/graph-provider/fuli_graph/store_records.py +134 -0
- package/graph-provider/pyproject.toml +35 -0
- package/npm-shrinkwrap.json +6045 -0
- package/package.json +99 -0
- package/skills/capturing-session-knowledge/SKILL.md +226 -0
- package/skills/capturing-session-knowledge/agents/openai.yaml +13 -0
- package/skills/grilling-project/SKILL.md +129 -0
- package/skills/grilling-project/agents/openai.yaml +4 -0
- package/skills/grilling-project/references/project-profile.md +105 -0
- package/src/agent/candidate-read-service.js +53 -0
- package/src/agent/context-pack-service.js +83 -0
- package/src/agent/public-read-policy.js +39 -0
- package/src/agent/public-read-service.js +69 -0
- package/src/agent/read-projection.js +107 -0
- package/src/agent/search-service.js +61 -0
- package/src/agent/space-boundary.js +33 -0
- package/src/agent-tools/graph-definitions.js +479 -0
- package/src/agent-tools/graph-handlers.js +96 -0
- package/src/agent-tools/schema.js +37 -0
- package/src/agent-tools.js +16 -0
- package/src/app/application-error.js +13 -0
- package/src/app/bootstrap.js +8 -0
- package/src/app/create-application.js +143 -0
- package/src/app/state-service.js +38 -0
- package/src/candidates.js +78 -0
- package/src/classifier.js +63 -0
- package/src/cli/command-arguments.js +14 -0
- package/src/cli/command-registry.js +78 -0
- package/src/cli/commands/candidates.js +17 -0
- package/src/cli/commands/ingestion.js +32 -0
- package/src/cli/commands/reads.js +50 -0
- package/src/cli/commands/spaces.js +20 -0
- package/src/cli/invocation.js +47 -0
- package/src/cli/local-runtime-command.js +94 -0
- package/src/cli/local-runtime-options.js +64 -0
- package/src/cli/migrate-command.js +71 -0
- package/src/cli/migration-destination.js +117 -0
- package/src/cli/setup-agent-selection.js +195 -0
- package/src/cli/setup-command.js +85 -0
- package/src/cli/uninstall-command.js +67 -0
- package/src/cli.js +90 -0
- package/src/context-pack.js +206 -0
- package/src/defaults.js +1 -0
- package/src/extractor.js +77 -0
- package/src/fact-history.js +39 -0
- package/src/facts/fact-service.js +89 -0
- package/src/graphiti/agent-access-policy.js +88 -0
- package/src/graphiti/capture-policy.js +84 -0
- package/src/graphiti/federated-application.js +1292 -0
- package/src/graphiti/personal-context.js +29 -0
- package/src/graphiti/project-path-context.js +159 -0
- package/src/graphiti/provider-client.js +288 -0
- package/src/graphiti/runtime-config.js +85 -0
- package/src/graphiti/setup.js +43 -0
- package/src/graphiti/source-marker.js +142 -0
- package/src/http/api-router.js +133 -0
- package/src/http/bounded-json-array.js +19 -0
- package/src/http/error-mapping.js +36 -0
- package/src/http/graph-api-router.js +257 -0
- package/src/http/lens-route.js +50 -0
- package/src/http/request-policy.js +41 -0
- package/src/http/response.js +29 -0
- package/src/http/static-handler.js +55 -0
- package/src/ingestion.js +161 -0
- package/src/lens/lens-correction-service.js +81 -0
- package/src/lens/lens-fact-writer.js +85 -0
- package/src/lens/lens-input.js +62 -0
- package/src/lens/lens-query-projection.js +87 -0
- package/src/lens/lens-query-ranking.js +62 -0
- package/src/lens/lens-query.js +143 -0
- package/src/lens/lens-resource-projection.js +93 -0
- package/src/lens/lens-resource-selection.js +45 -0
- package/src/lens/lens-resource-service.js +103 -0
- package/src/lens/lens-search-query.js +122 -0
- package/src/lens/lens-service.js +183 -0
- package/src/local-runtime/lifecycle.js +254 -0
- package/src/mcp/create-mcp-server.js +85 -0
- package/src/mcp/instructions.js +10 -0
- package/src/mcp/project-action-preview-tokens.js +72 -0
- package/src/mcp/runtime.js +71 -0
- package/src/mcp/tool-annotations.js +63 -0
- package/src/mcp/tool-result.js +167 -0
- package/src/mcp/tool-schema.js +60 -0
- package/src/mcp-server.js +76 -0
- package/src/models.js +44 -0
- package/src/observer.js +83 -0
- package/src/package-metadata.js +6 -0
- package/src/project-rules.js +33 -0
- package/src/publication/canonical-json.js +39 -0
- package/src/publication/outbox-service.js +46 -0
- package/src/publication/publication-envelope-schema.js +75 -0
- package/src/publication/publication-envelope.js +37 -0
- package/src/publication/publication-policy.js +82 -0
- package/src/publication/publication-service.js +48 -0
- package/src/router.js +58 -0
- package/src/runtime-options.js +139 -0
- package/src/security/episode-visibility.js +14 -0
- package/src/security/fact-visibility.js +15 -0
- package/src/security/sensitive-content.js +18 -0
- package/src/security/source-metadata-policy.js +8 -0
- package/src/server/application-lifecycle.js +34 -0
- package/src/server/listen.js +24 -0
- package/src/server.js +135 -0
- package/src/setup/agent-installation-status.js +97 -0
- package/src/setup/agents.js +123 -0
- package/src/setup/codex-bootstrap.js +118 -0
- package/src/setup/codex-config.js +122 -0
- package/src/setup/config-backup.js +22 -0
- package/src/setup/container-runtime.js +388 -0
- package/src/setup/cursor-config.js +61 -0
- package/src/setup/graph-runtime.js +360 -0
- package/src/setup/node-runtime.js +24 -0
- package/src/setup/options.js +91 -0
- package/src/setup/paths.js +47 -0
- package/src/setup/session-skill.js +91 -0
- package/src/setup/setup.js +85 -0
- package/src/setup/uninstall.js +90 -0
- package/src/storage/episode-evidence-preview.js +100 -0
- package/src/storage/fact-search-order.js +18 -0
- package/src/storage/file/episode-evidence.js +37 -0
- package/src/storage/file/episode-index.js +59 -0
- package/src/storage/file/fact-search.js +60 -0
- package/src/storage/file-store.js +437 -0
- package/src/storage/import-json.js +70 -0
- package/src/storage/json-file.js +41 -0
- package/src/storage/legacy-record-validation.js +152 -0
- package/src/storage/legacy-snapshot-validation.js +32 -0
- package/src/storage/migrate.js +128 -0
- package/src/storage/migrations/001-initial.sql +83 -0
- package/src/storage/migrations/002-storage-integrity.sql +113 -0
- package/src/storage/record-validation.js +46 -0
- package/src/storage/snapshot-defaults.js +54 -0
- package/src/storage/sqlite/candidates-repository.js +84 -0
- package/src/storage/sqlite/episode-evidence-repository.js +154 -0
- package/src/storage/sqlite/episode-privacy-sql.js +33 -0
- package/src/storage/sqlite/episodes-repository.js +52 -0
- package/src/storage/sqlite/fact-search-repository.js +162 -0
- package/src/storage/sqlite/facts-repository.js +146 -0
- package/src/storage/sqlite/imports-repository.js +39 -0
- package/src/storage/sqlite/mapper.js +84 -0
- package/src/storage/sqlite/open-database.js +15 -0
- package/src/storage/sqlite/outbox-repository.js +103 -0
- package/src/storage/sqlite/snapshot-repository.js +69 -0
- package/src/storage/sqlite/spaces-repository.js +52 -0
- package/src/storage/sqlite/subscriptions-repository.js +54 -0
- package/src/storage/sqlite-store.js +159 -0
- package/src/storage/stable-json.js +24 -0
- package/src/storage/store-port.js +20 -0
- package/src/store.js +2 -0
- package/src/workspace-protocol/array-validation.js +19 -0
- package/src/workspace-protocol/base64url-contract.js +27 -0
- package/src/workspace-protocol/constants.js +37 -0
- package/src/workspace-protocol/error-contract.js +24 -0
- package/src/workspace-protocol/fact-contract.js +110 -0
- package/src/workspace-protocol/governance-contract.js +47 -0
- package/src/workspace-protocol/index.js +53 -0
- package/src/workspace-protocol/json-budget.js +19 -0
- package/src/workspace-protocol/manifest-contract.js +186 -0
- package/src/workspace-protocol/publication-contract.js +212 -0
- package/src/workspace-protocol/query-contract.js +149 -0
- package/src/workspace-protocol/scalar-contract.js +192 -0
- package/src/workspace-protocol/sync-contract.js +184 -0
- package/src/workspace-protocol/text-contract.js +39 -0
- package/src/workspace-protocol/validation.js +66 -0
|
@@ -0,0 +1,561 @@
|
|
|
1
|
+
from .graph_projection import (
|
|
2
|
+
coalesce_personal_project_identities,
|
|
3
|
+
coalesce_personal_project_identity,
|
|
4
|
+
management_projection,
|
|
5
|
+
personal_project_projection,
|
|
6
|
+
)
|
|
7
|
+
from .graph_models import GraphEdge, GraphNode, GraphResult
|
|
8
|
+
from .models import (
|
|
9
|
+
ConfirmationBasis,
|
|
10
|
+
GraphEvidence,
|
|
11
|
+
)
|
|
12
|
+
from .knowledge_audit import read_knowledge_audits
|
|
13
|
+
from .knowledge_records import assignment_record, revision_record
|
|
14
|
+
from .personal_project_access import authorize_personal_project
|
|
15
|
+
from .provider_values import json_object, native_datetime as _native_datetime
|
|
16
|
+
from .project_knowledge import (
|
|
17
|
+
read_knowledge_conflicts,
|
|
18
|
+
read_personal_project_relations,
|
|
19
|
+
read_project_references,
|
|
20
|
+
)
|
|
21
|
+
from .search_projection import effective_confirmation_status
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
async def read_graph(
|
|
25
|
+
store,
|
|
26
|
+
actor: dict,
|
|
27
|
+
space_id: str,
|
|
28
|
+
limit: int | None = None,
|
|
29
|
+
personal_project_id: str | None = None,
|
|
30
|
+
) -> GraphResult:
|
|
31
|
+
space = await store.authorize(actor, space_id, 'reader')
|
|
32
|
+
project = None
|
|
33
|
+
if personal_project_id:
|
|
34
|
+
project = await authorize_personal_project(store, actor, space, personal_project_id)
|
|
35
|
+
bounded_limit = min(limit or store.settings.graph_limit, store.settings.graph_limit)
|
|
36
|
+
node_records, _, _ = await store.runtime.driver.execute_query(
|
|
37
|
+
_node_query(bool(project)),
|
|
38
|
+
space_id=space_id,
|
|
39
|
+
group_id=space['group_id'],
|
|
40
|
+
project_id=personal_project_id,
|
|
41
|
+
limit=bounded_limit + 1,
|
|
42
|
+
routing_='r',
|
|
43
|
+
)
|
|
44
|
+
truncated = len(node_records) > bounded_limit
|
|
45
|
+
node_records = node_records[:bounded_limit]
|
|
46
|
+
node_ids = [record['id'] for record in node_records]
|
|
47
|
+
episode_ids = list({
|
|
48
|
+
episode_id
|
|
49
|
+
for record in node_records
|
|
50
|
+
for episode_id in (record.get('episodes') or [])
|
|
51
|
+
})
|
|
52
|
+
edge_records, _, _ = await store.runtime.driver.execute_query(
|
|
53
|
+
_edge_query(bool(project)),
|
|
54
|
+
space_id=space_id,
|
|
55
|
+
group_id=space['group_id'],
|
|
56
|
+
project_id=personal_project_id,
|
|
57
|
+
node_ids=node_ids,
|
|
58
|
+
episode_ids=episode_ids,
|
|
59
|
+
limit=bounded_limit,
|
|
60
|
+
routing_='r',
|
|
61
|
+
)
|
|
62
|
+
all_episode_ids = list({
|
|
63
|
+
*episode_ids,
|
|
64
|
+
*(episode_id for record in edge_records for episode_id in (record.get('episodes') or [])),
|
|
65
|
+
})
|
|
66
|
+
evidence = await _read_evidence(store, all_episode_ids)
|
|
67
|
+
item_ids = node_ids + [record['id'] for record in edge_records]
|
|
68
|
+
revisions = await _read_revisions(store, space_id, item_ids)
|
|
69
|
+
assignments = await _read_assignments(store, space_id, item_ids)
|
|
70
|
+
references = await read_project_references(store, space_id, item_ids)
|
|
71
|
+
conflicts = await read_knowledge_conflicts(store, space_id, item_ids)
|
|
72
|
+
audits = await read_knowledge_audits(store, space_id, item_ids)
|
|
73
|
+
knowledge_nodes = [
|
|
74
|
+
_graph_node(
|
|
75
|
+
record,
|
|
76
|
+
evidence,
|
|
77
|
+
revisions,
|
|
78
|
+
assignments,
|
|
79
|
+
references,
|
|
80
|
+
conflicts,
|
|
81
|
+
audits,
|
|
82
|
+
)
|
|
83
|
+
for record in node_records
|
|
84
|
+
]
|
|
85
|
+
knowledge_edges = [
|
|
86
|
+
_graph_edge(
|
|
87
|
+
record,
|
|
88
|
+
evidence,
|
|
89
|
+
revisions,
|
|
90
|
+
assignments,
|
|
91
|
+
references,
|
|
92
|
+
conflicts,
|
|
93
|
+
audits,
|
|
94
|
+
)
|
|
95
|
+
for record in edge_records
|
|
96
|
+
]
|
|
97
|
+
|
|
98
|
+
personal_relations = (
|
|
99
|
+
await read_personal_project_relations(store, space_id)
|
|
100
|
+
if space['kind'] == 'personal' else []
|
|
101
|
+
)
|
|
102
|
+
if project:
|
|
103
|
+
management_nodes, management_edges = personal_project_projection(
|
|
104
|
+
space, project, personal_relations
|
|
105
|
+
)
|
|
106
|
+
graph_nodes, graph_edges = coalesce_personal_project_identity(
|
|
107
|
+
project,
|
|
108
|
+
management_nodes,
|
|
109
|
+
management_edges,
|
|
110
|
+
knowledge_nodes,
|
|
111
|
+
knowledge_edges,
|
|
112
|
+
)
|
|
113
|
+
else:
|
|
114
|
+
personal_projects = await _personal_projects(store, space)
|
|
115
|
+
management_nodes, management_edges = management_projection(
|
|
116
|
+
space, personal_projects, personal_relations
|
|
117
|
+
)
|
|
118
|
+
graph_nodes, graph_edges = coalesce_personal_project_identities(
|
|
119
|
+
personal_projects,
|
|
120
|
+
management_nodes,
|
|
121
|
+
management_edges,
|
|
122
|
+
knowledge_nodes,
|
|
123
|
+
knowledge_edges,
|
|
124
|
+
)
|
|
125
|
+
return GraphResult(
|
|
126
|
+
space_id=space_id,
|
|
127
|
+
nodes=graph_nodes,
|
|
128
|
+
edges=graph_edges,
|
|
129
|
+
truncated=truncated,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _node_query(project_scoped: bool) -> str:
|
|
134
|
+
if project_scoped:
|
|
135
|
+
match = '''
|
|
136
|
+
MATCH (node:Entity {group_id: $group_id})
|
|
137
|
+
OPTIONAL MATCH (episode:Episodic {group_id: $group_id})-[:MENTIONS]->(node)
|
|
138
|
+
WITH node, collect(DISTINCT episode) AS knowledge_episodes
|
|
139
|
+
OPTIONAL MATCH (assignment:FuliKnowledgeAssignment {
|
|
140
|
+
space_id: $space_id,
|
|
141
|
+
item_kind: 'entity',
|
|
142
|
+
item_id: node.uuid
|
|
143
|
+
})
|
|
144
|
+
OPTIONAL MATCH (:FuliSpace {id: $space_id})-[:HAS_KNOWLEDGE_REFERENCE]->
|
|
145
|
+
(reference:FuliKnowledgeProjectReference {
|
|
146
|
+
item_kind: 'entity', item_id: node.uuid
|
|
147
|
+
})
|
|
148
|
+
WITH node, knowledge_episodes, assignment, collect(reference) AS references
|
|
149
|
+
WHERE (
|
|
150
|
+
(
|
|
151
|
+
node.fuli_profile_aspect IS NOT NULL
|
|
152
|
+
AND node.fuli_preference_scope = 'project'
|
|
153
|
+
AND node.fuli_preference_project_id = $project_id
|
|
154
|
+
)
|
|
155
|
+
OR (
|
|
156
|
+
node.fuli_profile_aspect IS NULL
|
|
157
|
+
AND (
|
|
158
|
+
(assignment.project_id IS NOT NULL AND assignment.project_id = $project_id)
|
|
159
|
+
OR (assignment.project_id IS NULL AND any(
|
|
160
|
+
episode IN knowledge_episodes
|
|
161
|
+
WHERE episode.fuli_personal_project_id = $project_id
|
|
162
|
+
))
|
|
163
|
+
OR any(reference IN references WHERE
|
|
164
|
+
reference.project_id = $project_id AND reference.status = 'active')
|
|
165
|
+
)
|
|
166
|
+
)
|
|
167
|
+
)
|
|
168
|
+
AND NOT EXISTS {
|
|
169
|
+
MATCH (:FuliSpace {id: $space_id})-[:HAS_KNOWLEDGE_CONFLICT]->
|
|
170
|
+
(conflict:FuliKnowledgeConflict)
|
|
171
|
+
WHERE conflict.target_project_id = $project_id
|
|
172
|
+
AND conflict.target_item_id = node.uuid
|
|
173
|
+
AND conflict.status = 'resolved'
|
|
174
|
+
AND conflict.resolution = 'use_source'
|
|
175
|
+
}
|
|
176
|
+
WITH node, knowledge_episodes AS episodes
|
|
177
|
+
'''
|
|
178
|
+
else:
|
|
179
|
+
match = '''
|
|
180
|
+
MATCH (node:Entity {group_id: $group_id})
|
|
181
|
+
OPTIONAL MATCH (episode:Episodic {group_id: $group_id})-[:MENTIONS]->(node)
|
|
182
|
+
WITH node, collect(DISTINCT episode) AS episodes
|
|
183
|
+
'''
|
|
184
|
+
return match + '''
|
|
185
|
+
RETURN node.uuid AS id, node.name AS name, node.group_id AS group_id,
|
|
186
|
+
coalesce(node.fuli_type, 'Entity') AS type,
|
|
187
|
+
coalesce(node.summary, '') AS summary,
|
|
188
|
+
coalesce(node.fuli_origin_quadrant, 'known_known') AS origin_quadrant,
|
|
189
|
+
coalesce(node.fuli_current_quadrant, 'known_known') AS current_quadrant,
|
|
190
|
+
coalesce(node.fuli_epistemic_status, 'confirmed') AS epistemic_status,
|
|
191
|
+
node.fuli_origin_quadrant IS NOT NULL AS epistemic_state_explicit,
|
|
192
|
+
coalesce(node.fuli_confirmation_status, 'pending')
|
|
193
|
+
AS confirmation_status,
|
|
194
|
+
node.fuli_confirmation_status IS NOT NULL
|
|
195
|
+
AND node.fuli_confirmation_basis_json IS NOT NULL
|
|
196
|
+
AS confirmation_state_explicit,
|
|
197
|
+
node.fuli_confirmation_basis_json AS confirmation_basis_json,
|
|
198
|
+
node.fuli_reasoning_summary AS reasoning_summary,
|
|
199
|
+
node.fuli_profile_aspect AS profile_aspect,
|
|
200
|
+
CASE WHEN node.fuli_profile_aspect IS NULL THEN NULL
|
|
201
|
+
ELSE coalesce(node.fuli_preference_scope, 'global') END
|
|
202
|
+
AS preference_scope,
|
|
203
|
+
node.fuli_preference_project_id AS preference_project_id,
|
|
204
|
+
coalesce(node.fuli_inheritance_mode, 'local_only')
|
|
205
|
+
AS inheritance_mode,
|
|
206
|
+
coalesce(node.fuli_inherited_project_ids, [])
|
|
207
|
+
AS inherited_project_ids,
|
|
208
|
+
coalesce(node.fuli_human_edited, false) AS human_edited,
|
|
209
|
+
coalesce(node.fuli_human_change_status, 'none')
|
|
210
|
+
AS human_change_status,
|
|
211
|
+
coalesce(node.fuli_human_change_version, 0)
|
|
212
|
+
AS human_change_version,
|
|
213
|
+
node.fuli_last_human_changed_at AS last_human_changed_at,
|
|
214
|
+
node.fuli_last_agent_viewed_at AS last_agent_viewed_at,
|
|
215
|
+
node.fuli_last_agent_reviewed_at AS last_agent_reviewed_at,
|
|
216
|
+
coalesce(node.fuli_utility_score, 0.0) AS utility_score,
|
|
217
|
+
coalesce(node.fuli_confidence_score, 0.5) AS confidence_score,
|
|
218
|
+
coalesce(node.fuli_qualified_use_count, 0)
|
|
219
|
+
AS qualified_use_count,
|
|
220
|
+
coalesce(node.fuli_distinct_task_count, 0)
|
|
221
|
+
AS distinct_task_count,
|
|
222
|
+
node.fuli_last_used_at AS last_used_at,
|
|
223
|
+
coalesce(node.fuli_usage_generation, 1) AS usage_generation,
|
|
224
|
+
coalesce(node.fuli_attributes_json, '{}') AS attributes_json,
|
|
225
|
+
[episode IN episodes WHERE episode IS NOT NULL | episode.uuid] AS episodes,
|
|
226
|
+
node.created_at AS created_at,
|
|
227
|
+
node.fuli_invalid_at AS invalid_at,
|
|
228
|
+
node.fuli_replaced_by_item_id AS replaced_by_item_id,
|
|
229
|
+
node.fuli_replaced_by_item_kind AS replaced_by_item_kind
|
|
230
|
+
ORDER BY created_at DESC LIMIT $limit
|
|
231
|
+
'''
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def _edge_query(project_scoped: bool) -> str:
|
|
235
|
+
project_filter = '''
|
|
236
|
+
OPTIONAL MATCH (assignment:FuliKnowledgeAssignment {
|
|
237
|
+
space_id: $space_id,
|
|
238
|
+
item_kind: 'relationship',
|
|
239
|
+
item_id: edge.uuid
|
|
240
|
+
})
|
|
241
|
+
WITH source, edge, target, assignment
|
|
242
|
+
WHERE (
|
|
243
|
+
edge.fuli_profile_aspect IS NOT NULL
|
|
244
|
+
AND edge.fuli_preference_scope = 'project'
|
|
245
|
+
AND edge.fuli_preference_project_id = $project_id
|
|
246
|
+
) OR (
|
|
247
|
+
edge.fuli_profile_aspect IS NULL
|
|
248
|
+
AND (
|
|
249
|
+
(assignment.project_id IS NOT NULL AND assignment.project_id = $project_id)
|
|
250
|
+
OR (assignment.project_id IS NULL AND any(
|
|
251
|
+
episode_id IN coalesce(edge.episodes, [])
|
|
252
|
+
WHERE episode_id IN $episode_ids
|
|
253
|
+
))
|
|
254
|
+
)
|
|
255
|
+
)
|
|
256
|
+
''' if project_scoped else ''
|
|
257
|
+
return '''
|
|
258
|
+
MATCH (source:Entity)-[edge:RELATES_TO {group_id: $group_id}]->(target:Entity)
|
|
259
|
+
WHERE source.uuid IN $node_ids AND target.uuid IN $node_ids
|
|
260
|
+
''' + project_filter + '''
|
|
261
|
+
RETURN edge.uuid AS id, source.uuid AS source, target.uuid AS target,
|
|
262
|
+
edge.name AS type, edge.fact AS fact,
|
|
263
|
+
coalesce(edge.fuli_origin_quadrant, 'known_known') AS origin_quadrant,
|
|
264
|
+
coalesce(edge.fuli_current_quadrant, 'known_known') AS current_quadrant,
|
|
265
|
+
coalesce(edge.fuli_epistemic_status, 'confirmed') AS epistemic_status,
|
|
266
|
+
edge.fuli_origin_quadrant IS NOT NULL AS epistemic_state_explicit,
|
|
267
|
+
coalesce(edge.fuli_confirmation_status, 'pending')
|
|
268
|
+
AS confirmation_status,
|
|
269
|
+
edge.fuli_confirmation_status IS NOT NULL
|
|
270
|
+
AND edge.fuli_confirmation_basis_json IS NOT NULL
|
|
271
|
+
AS confirmation_state_explicit,
|
|
272
|
+
edge.fuli_confirmation_basis_json AS confirmation_basis_json,
|
|
273
|
+
edge.fuli_reasoning_summary AS reasoning_summary,
|
|
274
|
+
edge.fuli_profile_aspect AS profile_aspect,
|
|
275
|
+
CASE WHEN edge.fuli_profile_aspect IS NULL THEN NULL
|
|
276
|
+
ELSE coalesce(edge.fuli_preference_scope, 'global') END
|
|
277
|
+
AS preference_scope,
|
|
278
|
+
edge.fuli_preference_project_id AS preference_project_id,
|
|
279
|
+
coalesce(edge.fuli_inheritance_mode, 'local_only')
|
|
280
|
+
AS inheritance_mode,
|
|
281
|
+
coalesce(edge.fuli_inherited_project_ids, [])
|
|
282
|
+
AS inherited_project_ids,
|
|
283
|
+
coalesce(edge.fuli_human_edited, false) AS human_edited,
|
|
284
|
+
coalesce(edge.fuli_human_change_status, 'none')
|
|
285
|
+
AS human_change_status,
|
|
286
|
+
coalesce(edge.fuli_human_change_version, 0)
|
|
287
|
+
AS human_change_version,
|
|
288
|
+
edge.fuli_last_human_changed_at AS last_human_changed_at,
|
|
289
|
+
edge.fuli_last_agent_viewed_at AS last_agent_viewed_at,
|
|
290
|
+
edge.fuli_last_agent_reviewed_at AS last_agent_reviewed_at,
|
|
291
|
+
coalesce(edge.fuli_utility_score, 0.0) AS utility_score,
|
|
292
|
+
coalesce(edge.fuli_confidence_score, 0.5) AS confidence_score,
|
|
293
|
+
coalesce(edge.fuli_qualified_use_count, 0)
|
|
294
|
+
AS qualified_use_count,
|
|
295
|
+
coalesce(edge.fuli_distinct_task_count, 0)
|
|
296
|
+
AS distinct_task_count,
|
|
297
|
+
edge.fuli_last_used_at AS last_used_at,
|
|
298
|
+
coalesce(edge.fuli_usage_generation, 1) AS usage_generation,
|
|
299
|
+
edge.valid_at AS valid_at, edge.invalid_at AS invalid_at,
|
|
300
|
+
edge.fuli_replaced_by_item_id AS replaced_by_item_id,
|
|
301
|
+
edge.fuli_replaced_by_item_kind AS replaced_by_item_kind,
|
|
302
|
+
edge.created_at AS created_at,
|
|
303
|
+
edge.fuli_confidence AS confidence,
|
|
304
|
+
coalesce(edge.fuli_attributes_json, '{}') AS attributes_json,
|
|
305
|
+
coalesce(edge.episodes, []) AS episodes
|
|
306
|
+
ORDER BY edge.created_at DESC LIMIT $limit
|
|
307
|
+
'''
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
async def _personal_projects(store, space: dict) -> list[dict]:
|
|
311
|
+
if space['kind'] != 'personal':
|
|
312
|
+
return []
|
|
313
|
+
records, _, _ = await store.runtime.driver.execute_query(
|
|
314
|
+
'''
|
|
315
|
+
MATCH (:FuliSpace {id: $space_id, kind: 'personal'})-
|
|
316
|
+
[:CONTAINS_PROJECT]->(project:FuliPersonalProject)
|
|
317
|
+
RETURN project ORDER BY project.updated_at DESC
|
|
318
|
+
''',
|
|
319
|
+
space_id=space['id'],
|
|
320
|
+
routing_='r',
|
|
321
|
+
)
|
|
322
|
+
return [dict(record['project']) for record in records]
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
async def _read_evidence(store, episode_ids: list[str]) -> dict[str, GraphEvidence]:
|
|
326
|
+
if not episode_ids:
|
|
327
|
+
return {}
|
|
328
|
+
records, _, _ = await store.runtime.driver.execute_query(
|
|
329
|
+
'''
|
|
330
|
+
MATCH (episode:Episodic) WHERE episode.uuid IN $episode_ids
|
|
331
|
+
RETURN episode.uuid AS id, episode.name AS name,
|
|
332
|
+
episode.source_description AS source_description,
|
|
333
|
+
coalesce(episode.fuli_source_kind, 'unknown') AS source_kind,
|
|
334
|
+
coalesce(episode.fuli_summary, '') AS summary,
|
|
335
|
+
episode.fuli_session_id AS session_id,
|
|
336
|
+
episode.fuli_source_application AS source_application,
|
|
337
|
+
episode.fuli_source_turn_id AS source_turn_id,
|
|
338
|
+
episode.fuli_source_excerpt AS source_excerpt,
|
|
339
|
+
episode.fuli_personal_project_id AS personal_project_id,
|
|
340
|
+
episode.valid_at AS reference_time,
|
|
341
|
+
episode.created_at AS created_at
|
|
342
|
+
''',
|
|
343
|
+
episode_ids=episode_ids,
|
|
344
|
+
routing_='r',
|
|
345
|
+
)
|
|
346
|
+
return {
|
|
347
|
+
record['id']: GraphEvidence(
|
|
348
|
+
**{
|
|
349
|
+
**dict(record),
|
|
350
|
+
'reference_time': _native_datetime(record.get('reference_time')),
|
|
351
|
+
'created_at': _native_datetime(record.get('created_at')),
|
|
352
|
+
}
|
|
353
|
+
)
|
|
354
|
+
for record in records
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
async def _read_revisions(store, space_id: str, item_ids: list[str]):
|
|
359
|
+
if not item_ids:
|
|
360
|
+
return {}
|
|
361
|
+
records, _, _ = await store.runtime.driver.execute_query(
|
|
362
|
+
'''
|
|
363
|
+
MATCH (:FuliSpace {id: $space_id, kind: 'personal'})-
|
|
364
|
+
[:HAS_KNOWLEDGE_REVISION]->(revision:FuliKnowledgeRevision)
|
|
365
|
+
WHERE revision.item_id IN $item_ids
|
|
366
|
+
RETURN revision ORDER BY revision.created_at DESC
|
|
367
|
+
''',
|
|
368
|
+
space_id=space_id,
|
|
369
|
+
item_ids=item_ids,
|
|
370
|
+
routing_='r',
|
|
371
|
+
)
|
|
372
|
+
result = {}
|
|
373
|
+
for record in records:
|
|
374
|
+
value = dict(record['revision'])
|
|
375
|
+
result.setdefault(value['item_id'], []).append(revision_record(value))
|
|
376
|
+
return result
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
async def _read_assignments(store, space_id: str, item_ids: list[str]):
|
|
380
|
+
if not item_ids:
|
|
381
|
+
return {}
|
|
382
|
+
records, _, _ = await store.runtime.driver.execute_query(
|
|
383
|
+
'''
|
|
384
|
+
MATCH (:FuliSpace {id: $space_id, kind: 'personal'})-
|
|
385
|
+
[:HAS_KNOWLEDGE_ASSIGNMENT]->(assignment:FuliKnowledgeAssignment)
|
|
386
|
+
WHERE assignment.item_id IN $item_ids
|
|
387
|
+
RETURN assignment
|
|
388
|
+
''',
|
|
389
|
+
space_id=space_id,
|
|
390
|
+
item_ids=item_ids,
|
|
391
|
+
routing_='r',
|
|
392
|
+
)
|
|
393
|
+
result = {}
|
|
394
|
+
for record in records:
|
|
395
|
+
value = dict(record['assignment'])
|
|
396
|
+
result.setdefault(value['item_id'], []).append(assignment_record(value))
|
|
397
|
+
return result
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
def _graph_node(
|
|
401
|
+
record,
|
|
402
|
+
evidence,
|
|
403
|
+
revisions,
|
|
404
|
+
assignments,
|
|
405
|
+
references,
|
|
406
|
+
conflicts,
|
|
407
|
+
audits=None,
|
|
408
|
+
) -> GraphNode:
|
|
409
|
+
episode_ids = record.get('episodes') or []
|
|
410
|
+
status, basis, confidence, confirmation_explicit = _confirmation_projection(
|
|
411
|
+
record
|
|
412
|
+
)
|
|
413
|
+
return GraphNode(
|
|
414
|
+
id=record['id'],
|
|
415
|
+
name=record['name'],
|
|
416
|
+
type=record['type'],
|
|
417
|
+
group_id=record['group_id'],
|
|
418
|
+
summary=record['summary'],
|
|
419
|
+
origin_quadrant=record.get('origin_quadrant') or 'known_known',
|
|
420
|
+
current_quadrant=record.get('current_quadrant') or 'known_known',
|
|
421
|
+
epistemic_status=record.get('epistemic_status') or 'confirmed',
|
|
422
|
+
epistemic_state_explicit=record.get('epistemic_state_explicit') is True,
|
|
423
|
+
confirmation_status=status,
|
|
424
|
+
confirmation_state_explicit=confirmation_explicit,
|
|
425
|
+
confirmation_basis=basis,
|
|
426
|
+
reasoning_summary=record.get('reasoning_summary'),
|
|
427
|
+
profile_aspect=record.get('profile_aspect'),
|
|
428
|
+
preference_scope=record.get('preference_scope'),
|
|
429
|
+
preference_project_id=record.get('preference_project_id'),
|
|
430
|
+
inheritance_mode=record.get('inheritance_mode') or 'local_only',
|
|
431
|
+
inherited_project_ids=record.get('inherited_project_ids') or [],
|
|
432
|
+
human_edited=record.get('human_edited') is True,
|
|
433
|
+
human_change_status=record.get('human_change_status') or 'none',
|
|
434
|
+
human_change_version=int(record.get('human_change_version') or 0),
|
|
435
|
+
last_human_changed_at=_native_datetime(
|
|
436
|
+
record.get('last_human_changed_at')
|
|
437
|
+
),
|
|
438
|
+
last_agent_viewed_at=_native_datetime(
|
|
439
|
+
record.get('last_agent_viewed_at')
|
|
440
|
+
),
|
|
441
|
+
last_agent_reviewed_at=_native_datetime(
|
|
442
|
+
record.get('last_agent_reviewed_at')
|
|
443
|
+
),
|
|
444
|
+
utility_score=float(record.get('utility_score') or 0),
|
|
445
|
+
confidence_score=confidence,
|
|
446
|
+
qualified_use_count=int(record.get('qualified_use_count') or 0),
|
|
447
|
+
distinct_task_count=int(record.get('distinct_task_count') or 0),
|
|
448
|
+
last_used_at=_native_datetime(record.get('last_used_at')),
|
|
449
|
+
usage_generation=int(record.get('usage_generation') or 1),
|
|
450
|
+
attributes=json_object(record.get('attributes_json')),
|
|
451
|
+
evidence=[evidence[item] for item in episode_ids if item in evidence],
|
|
452
|
+
created_at=_native_datetime(record.get('created_at')),
|
|
453
|
+
invalid_at=_native_datetime(record.get('invalid_at')),
|
|
454
|
+
replaced_by_item_id=record.get('replaced_by_item_id'),
|
|
455
|
+
replaced_by_item_kind=record.get('replaced_by_item_kind'),
|
|
456
|
+
revisions=revisions.get(record['id'], []),
|
|
457
|
+
assignments=assignments.get(record['id'], []),
|
|
458
|
+
project_references=references.get(record['id'], []),
|
|
459
|
+
conflicts=conflicts.get(record['id'], []),
|
|
460
|
+
audit_events=(audits or {}).get(record['id'], []),
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
def _graph_edge(
|
|
465
|
+
record,
|
|
466
|
+
evidence,
|
|
467
|
+
revisions,
|
|
468
|
+
assignments,
|
|
469
|
+
references,
|
|
470
|
+
conflicts,
|
|
471
|
+
audits=None,
|
|
472
|
+
) -> GraphEdge:
|
|
473
|
+
episode_ids = record.get('episodes') or []
|
|
474
|
+
status, basis, confidence, confirmation_explicit = _confirmation_projection(
|
|
475
|
+
record
|
|
476
|
+
)
|
|
477
|
+
return GraphEdge(
|
|
478
|
+
id=record['id'],
|
|
479
|
+
source=record['source'],
|
|
480
|
+
target=record['target'],
|
|
481
|
+
type=record['type'],
|
|
482
|
+
fact=record['fact'],
|
|
483
|
+
origin_quadrant=record.get('origin_quadrant') or 'known_known',
|
|
484
|
+
current_quadrant=record.get('current_quadrant') or 'known_known',
|
|
485
|
+
epistemic_status=record.get('epistemic_status') or 'confirmed',
|
|
486
|
+
epistemic_state_explicit=record.get('epistemic_state_explicit') is True,
|
|
487
|
+
confirmation_status=status,
|
|
488
|
+
confirmation_state_explicit=confirmation_explicit,
|
|
489
|
+
confirmation_basis=basis,
|
|
490
|
+
reasoning_summary=record.get('reasoning_summary'),
|
|
491
|
+
profile_aspect=record.get('profile_aspect'),
|
|
492
|
+
preference_scope=record.get('preference_scope'),
|
|
493
|
+
preference_project_id=record.get('preference_project_id'),
|
|
494
|
+
inheritance_mode=record.get('inheritance_mode') or 'local_only',
|
|
495
|
+
inherited_project_ids=record.get('inherited_project_ids') or [],
|
|
496
|
+
human_edited=record.get('human_edited') is True,
|
|
497
|
+
human_change_status=record.get('human_change_status') or 'none',
|
|
498
|
+
human_change_version=int(record.get('human_change_version') or 0),
|
|
499
|
+
last_human_changed_at=_native_datetime(
|
|
500
|
+
record.get('last_human_changed_at')
|
|
501
|
+
),
|
|
502
|
+
last_agent_viewed_at=_native_datetime(
|
|
503
|
+
record.get('last_agent_viewed_at')
|
|
504
|
+
),
|
|
505
|
+
last_agent_reviewed_at=_native_datetime(
|
|
506
|
+
record.get('last_agent_reviewed_at')
|
|
507
|
+
),
|
|
508
|
+
utility_score=float(record.get('utility_score') or 0),
|
|
509
|
+
confidence_score=confidence,
|
|
510
|
+
qualified_use_count=int(record.get('qualified_use_count') or 0),
|
|
511
|
+
distinct_task_count=int(record.get('distinct_task_count') or 0),
|
|
512
|
+
last_used_at=_native_datetime(record.get('last_used_at')),
|
|
513
|
+
usage_generation=int(record.get('usage_generation') or 1),
|
|
514
|
+
valid_at=_native_datetime(record.get('valid_at')),
|
|
515
|
+
invalid_at=_native_datetime(record.get('invalid_at')),
|
|
516
|
+
replaced_by_item_id=record.get('replaced_by_item_id'),
|
|
517
|
+
replaced_by_item_kind=record.get('replaced_by_item_kind'),
|
|
518
|
+
created_at=_native_datetime(record.get('created_at')),
|
|
519
|
+
confidence=record.get('confidence'),
|
|
520
|
+
attributes=json_object(record.get('attributes_json')),
|
|
521
|
+
episodes=episode_ids,
|
|
522
|
+
evidence=[evidence[item] for item in episode_ids if item in evidence],
|
|
523
|
+
revisions=revisions.get(record['id'], []),
|
|
524
|
+
assignments=assignments.get(record['id'], []),
|
|
525
|
+
project_references=references.get(record['id'], []),
|
|
526
|
+
conflicts=conflicts.get(record['id'], []),
|
|
527
|
+
audit_events=(audits or {}).get(record['id'], []),
|
|
528
|
+
)
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
def _confirmation_basis(value) -> ConfirmationBasis | None:
|
|
532
|
+
data = json_object(value)
|
|
533
|
+
if not data:
|
|
534
|
+
return None
|
|
535
|
+
try:
|
|
536
|
+
return ConfirmationBasis.model_validate(data)
|
|
537
|
+
except ValueError:
|
|
538
|
+
return None
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
def _confirmation_projection(record) -> tuple[
|
|
542
|
+
str,
|
|
543
|
+
ConfirmationBasis | None,
|
|
544
|
+
float,
|
|
545
|
+
bool,
|
|
546
|
+
]:
|
|
547
|
+
raw_status = record.get('confirmation_status') or 'pending'
|
|
548
|
+
status = effective_confirmation_status(record)
|
|
549
|
+
confidence = float(
|
|
550
|
+
record['confidence_score']
|
|
551
|
+
if record.get('confidence_score') is not None else 0.5
|
|
552
|
+
)
|
|
553
|
+
if status != raw_status:
|
|
554
|
+
confidence = min(confidence, 0.5)
|
|
555
|
+
basis = _confirmation_basis(record.get('confirmation_basis_json'))
|
|
556
|
+
explicit = (
|
|
557
|
+
record.get('confirmation_state_explicit') is True
|
|
558
|
+
and status == raw_status
|
|
559
|
+
and basis is not None
|
|
560
|
+
)
|
|
561
|
+
return status, basis, confidence, explicit
|