fuli-context 0.11.0 → 0.12.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/dist/web/assets/AboutPage-BAD0iPVX.js +1 -0
- package/dist/web/assets/AgentConversations-1iGTd4sz.js +2 -0
- package/dist/web/assets/AgentHand-D_RafKOM.js +1 -0
- package/dist/web/assets/AgentName-B5LKQ09q.js +1 -0
- package/dist/web/assets/AgentProfilePage-CioOdkBM.js +1 -0
- package/dist/web/assets/AgentsDirectoryPage--1A9nvNA.js +1 -0
- package/dist/web/assets/AgentsDirectoryPage-C7OUQH9v.css +1 -0
- package/dist/web/assets/ConnectionsPage-Tr2rRVOZ.js +1 -0
- package/dist/web/assets/EmployeeRecruitDialog-1QevD4Qv.js +1 -0
- package/dist/web/assets/EmployeeWorkbenchPage-B0vQIgwA.js +3 -0
- package/dist/web/assets/EmployeeWorkbenchPage-BQes4L1f.css +1 -0
- package/dist/web/assets/KnowledgeEditDialog-ClnrfPZi.js +1 -0
- package/dist/web/assets/KnowledgeInspector-Te1hJZbx.js +1 -0
- package/dist/web/assets/KnowledgeOrganizerPage-CCCrWl6Y.js +1 -0
- package/dist/web/assets/KnowledgeOrganizerPage-ccgscrPd.css +1 -0
- package/dist/web/assets/KnowledgePage-DGOcBwr8.js +1 -0
- package/dist/web/assets/KnowledgeWorkspace-cKn1NCRe.js +4 -0
- package/dist/web/assets/NotFoundPage-fetSnp0N.js +1 -0
- package/dist/web/assets/OverviewPage-BD4yY2Xz.js +1 -0
- package/dist/web/assets/PersonalProfilePage-DGJVP_VB.js +1 -0
- package/dist/web/assets/PersonalProjectsPage-D25HYMgD.js +1 -0
- package/dist/web/assets/ProjectAgentFirstTask-BcxqmKXQ.js +1 -0
- package/dist/web/assets/ProjectAgentsPage-BWDJBESb.js +12 -0
- package/dist/web/assets/{ProjectAgentsPage-DNLNjYp-.css → ProjectAgentsPage-DWGkgIsr.css} +1 -1
- package/dist/web/assets/ProjectScopePicker-yodM7lF_.js +1 -0
- package/dist/web/assets/PublicProjectsPage-CyNxVzW3.js +1 -0
- package/dist/web/assets/ReviewPage-Cy0Rt4CO.js +1 -0
- package/dist/web/assets/RoundtablesPage-BjVhvwMc.js +1 -0
- package/dist/web/assets/SettingsPage-CCZqZtnH.js +1 -0
- package/dist/web/assets/SettingsPage-DYz5bTwa.css +1 -0
- package/dist/web/assets/SharedJefaReportPage-C56kPciB.js +1 -0
- package/dist/web/assets/UiDialog-CvDxg3B1.js +1 -0
- package/dist/web/assets/UiDisclosure-D0iMTplF.js +1 -0
- package/dist/web/assets/UiSegmented-DIJW6wv3.js +1 -0
- package/dist/web/assets/UiSelect-CMR0QqF6.js +1 -0
- package/dist/web/assets/WritingTastePage-QgNzssfF.js +1 -0
- package/dist/web/assets/agent-pins-BcJ_qr8n.js +1 -0
- package/dist/web/assets/agent-work-summary-C-yW7x7A.js +1 -0
- package/dist/web/assets/{attention-store-BfxtL9EV.js → attention-store-B1DI8wmd.js} +1 -1
- package/dist/web/assets/{catalog-6SN9i3WR.js → catalog-DbXv0_bR.js} +1 -1
- package/dist/web/assets/client-CR-W5Wzp.js +1 -0
- package/dist/web/assets/console-D7GHOMEC.js +1 -0
- package/dist/web/assets/conversation-launcher-settings-Dj2GMzCI.js +1 -0
- package/dist/web/assets/{i18n-CCMR4gDS.js → i18n-CVFhT_k7.js} +3 -3
- package/dist/web/assets/{identity-C1eN-FSB.js → identity-CM6Dmy00.js} +1 -1
- package/dist/web/assets/index-CJ6Jmok9.css +1 -0
- package/dist/web/assets/index-CRa3GnYi.js +5 -0
- package/dist/web/assets/judgment-types-CJBEyJs1.js +1 -0
- package/dist/web/assets/model-WFhQZ3v_.js +1 -0
- package/dist/web/assets/{personal-profile-graph-BD1Y6TDS.js → personal-profile-graph-BwCh2M9G.js} +1 -1
- package/dist/web/assets/shared-report-CeIvrL9a.js +1 -0
- package/dist/web/assets/{agent-work-summary-LeW-Al_S.js → task-evidence-DTiPCs7r.js} +1 -1
- package/dist/web/assets/useAgentRoster-DCArHlxK.js +1 -0
- package/dist/web/assets/{useApi-CROJJdhE-DlOiu8lP.js → useApi-CROJJdhE-BYhbjaZB.js} +1 -1
- package/dist/web/assets/vue-router-DaxrQwX3.js +1 -0
- package/dist/web/index.html +16 -12
- package/docs/agent-roundtable.md +17 -1
- package/docs/employee-agents.md +1 -1
- package/docs/judgment-and-autonomy.md +33 -0
- package/graph-provider/fuli_graph/ai_review.py +154 -0
- package/graph-provider/fuli_graph/common_knowledge.py +12 -0
- package/graph-provider/fuli_graph/graph_models.py +14 -0
- package/graph-provider/fuli_graph/graph_query.py +217 -0
- package/graph-provider/fuli_graph/knowledge_audit.py +6 -0
- package/graph-provider/fuli_graph/knowledge_batch_confirmation.py +6 -0
- package/graph-provider/fuli_graph/knowledge_management.py +23 -0
- package/graph-provider/fuli_graph/knowledge_review.py +383 -4
- package/graph-provider/fuli_graph/knowledge_review_models.py +61 -2
- package/graph-provider/fuli_graph/personal_global_preferences.py +5 -1
- package/graph-provider/fuli_graph/preference_conflicts.py +91 -1
- package/graph-provider/fuli_graph/project_agent_context_models.py +5 -1
- package/graph-provider/fuli_graph/project_agent_delegations.py +118 -0
- package/graph-provider/fuli_graph/project_agent_employee_numbers.py +41 -0
- package/graph-provider/fuli_graph/project_agent_memory_routes.py +13 -0
- package/graph-provider/fuli_graph/project_agent_models.py +1 -0
- package/graph-provider/fuli_graph/project_agent_task_models.py +2 -0
- package/graph-provider/fuli_graph/project_agent_team_growth.py +2 -2
- package/graph-provider/fuli_graph/project_knowledge.py +41 -0
- package/graph-provider/fuli_graph/runtime.py +15 -0
- package/graph-provider/fuli_graph/store_agent_verification.py +12 -5
- package/graph-provider/fuli_graph/store_knowledge.py +98 -58
- package/graph-provider/fuli_graph/store_project_agent_attention.py +1 -1
- package/graph-provider/fuli_graph/store_project_agent_context.py +52 -4
- package/graph-provider/fuli_graph/store_project_agent_coordination_policy.py +16 -1
- package/graph-provider/fuli_graph/store_project_agent_task_activity.py +26 -0
- package/graph-provider/fuli_graph/store_project_agent_task_staffing.py +5 -3
- package/graph-provider/fuli_graph/store_project_agents.py +31 -5
- package/graph-provider/fuli_graph/system_agents.py +23 -2
- package/graph-provider/fuli_graph/system_hr_identity.py +19 -5
- package/graph-provider/pyproject.toml +1 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +3 -2
- package/src/agent-roundtable/service.js +34 -5
- package/src/agent-roundtable/wake.js +2 -1
- package/src/agent-tools/graph-definitions.js +2 -0
- package/src/agent-tools/graph-handlers.js +7 -0
- package/src/agent-tools/interface-catalog.js +6 -0
- package/src/agent-tools/judgment-definitions.js +12 -0
- package/src/agents/claude-code/wake.js +7 -0
- package/src/agents/codex/wake.js +7 -0
- package/src/agents/identity-receipt.js +20 -2
- package/src/employees/catalog/tonborg.json +20 -0
- package/src/employees/manifest.js +2 -2
- package/src/employees/recruitment.js +20 -1
- package/src/graphiti/agent-collaboration.js +8 -2
- package/src/graphiti/federated-application.js +11 -2
- package/src/graphiti/knowledge-review.js +7 -3
- package/src/graphiti/project-agent-mapping.js +4 -0
- package/src/graphiti/project-agent-task-entry.js +23 -5
- package/src/graphiti/project-agent-team-context.js +4 -1
- package/src/graphiti/provider-client.js +9 -0
- package/src/graphiti/task-entry-preferences.js +17 -4
- package/src/graphiti/task-entry-project-scope.js +67 -0
- package/src/http/api-router.js +2 -0
- package/src/judgment/acceptance.js +91 -0
- package/src/judgment/engine.js +168 -0
- package/src/judgment/http.js +24 -0
- package/src/judgment/model.js +24 -0
- package/src/judgment/routing.js +96 -0
- package/src/judgment/scheduler.js +25 -0
- package/src/judgment/service.js +66 -0
- package/src/judgment/store.js +123 -0
- package/src/mcp/session-id.js +1 -0
- package/src/mcp/tool-annotations.js +4 -2
- package/src/server.js +9 -2
- package/dist/web/assets/AboutPage-CKq4IRHd.js +0 -1
- package/dist/web/assets/AgentConversations-DfXiFvBN.js +0 -2
- package/dist/web/assets/AgentHand-BBM-sKh2.js +0 -1
- package/dist/web/assets/AgentName-C_6R80nP.js +0 -1
- package/dist/web/assets/AgentProfilePage-Cz-i1fRD.js +0 -1
- package/dist/web/assets/AgentsDirectoryPage-BI0EFz6y.js +0 -1
- package/dist/web/assets/AgentsDirectoryPage-yRWktiin.css +0 -1
- package/dist/web/assets/ConnectionsPage-BoC6kXgs.js +0 -1
- package/dist/web/assets/EmployeeRecruitDialog-BVyclQAJ.js +0 -1
- package/dist/web/assets/EmployeeWorkbenchPage-D-c_Jm2z.css +0 -1
- package/dist/web/assets/EmployeeWorkbenchPage-D2GvYraI.js +0 -3
- package/dist/web/assets/KnowledgeEditDialog-a0AKWics.js +0 -1
- package/dist/web/assets/KnowledgeInspector-DMg9yJZ2.js +0 -1
- package/dist/web/assets/KnowledgeOrganizerPage-BgsHM3Qw.js +0 -1
- package/dist/web/assets/KnowledgeOrganizerPage-DeL7Jp34.css +0 -1
- package/dist/web/assets/KnowledgePage-kn8MqUCO.js +0 -1
- package/dist/web/assets/KnowledgeWorkspace-BtRq9fvC.js +0 -4
- package/dist/web/assets/NotFoundPage-D-7jLz3o.js +0 -1
- package/dist/web/assets/OverviewPage-BHPc-XxY.js +0 -1
- package/dist/web/assets/PersonalProfilePage-C00iV0Kx.js +0 -1
- package/dist/web/assets/PersonalProjectsPage-CFNkdHSo.js +0 -1
- package/dist/web/assets/ProjectAgentFirstTask-CrCWEYgn.js +0 -1
- package/dist/web/assets/ProjectAgentsPage-BzztLMH-.js +0 -12
- package/dist/web/assets/ProjectScopePicker-CMxn3txv.js +0 -1
- package/dist/web/assets/PublicProjectsPage-DQVtvhOS.js +0 -1
- package/dist/web/assets/ReviewPage-BZhvyDWc.js +0 -1
- package/dist/web/assets/RoundtablesPage-CVebTXQa.js +0 -1
- package/dist/web/assets/SettingsPage-1knvfOW6.js +0 -1
- package/dist/web/assets/SettingsPage-BmYkLul4.css +0 -1
- package/dist/web/assets/SharedJefaReportPage-BCVq8u0P.js +0 -1
- package/dist/web/assets/UiDialog-Da1P10f4.js +0 -1
- package/dist/web/assets/UiDisclosure-8wE8YuE4.js +0 -1
- package/dist/web/assets/UiSegmented-BKZXc8QF.js +0 -1
- package/dist/web/assets/UiSelect-D5Xj5Ec4.js +0 -1
- package/dist/web/assets/WritingTastePage-B8-etOBn.js +0 -1
- package/dist/web/assets/client-CUTkF3t4.js +0 -1
- package/dist/web/assets/console-CG8tdtSs.js +0 -1
- package/dist/web/assets/conversation-launcher-settings-CyZkLJg7.js +0 -1
- package/dist/web/assets/index-kPB0Cf8Q.css +0 -1
- package/dist/web/assets/index-sLDKI6R-.js +0 -5
- package/dist/web/assets/model-l-sAqtW_.js +0 -1
- package/dist/web/assets/shared-report-B46dxlCR.js +0 -1
- package/dist/web/assets/useAgentRoster-DdxoKDEE.js +0 -1
- package/dist/web/assets/vue-router-DjqEU4W2.js +0 -1
|
@@ -201,6 +201,9 @@ CALL {
|
|
|
201
201
|
WITH space, valid_entities
|
|
202
202
|
UNWIND valid_entities AS valid
|
|
203
203
|
WITH space, valid.item AS item, valid.row AS row
|
|
204
|
+
ORDER BY 'entity:' + row.item_id
|
|
205
|
+
SET item.fuli_ai_review_lock_version =
|
|
206
|
+
coalesce(item.fuli_ai_review_lock_version, 0) + 1
|
|
204
207
|
SET item.fuli_epistemic_status = 'confirmed',
|
|
205
208
|
item.fuli_confirmation_status = 'confirmed',
|
|
206
209
|
item.fuli_confirmation_basis_json = row.confirmation_basis_json
|
|
@@ -223,6 +226,9 @@ CALL {
|
|
|
223
226
|
WITH space, valid_relationships
|
|
224
227
|
UNWIND valid_relationships AS valid
|
|
225
228
|
WITH space, valid.item AS item, valid.row AS row
|
|
229
|
+
ORDER BY 'relationship:' + row.item_id
|
|
230
|
+
SET item.fuli_ai_review_lock_version =
|
|
231
|
+
coalesce(item.fuli_ai_review_lock_version, 0) + 1
|
|
226
232
|
SET item.fuli_epistemic_status = 'confirmed',
|
|
227
233
|
item.fuli_confirmation_status = 'confirmed',
|
|
228
234
|
item.fuli_confirmation_basis_json = row.confirmation_basis_json
|
|
@@ -211,11 +211,25 @@ async def reassign_knowledge_item(
|
|
|
211
211
|
assignment_id = existing.id if existing else _stable_uuid(
|
|
212
212
|
space['id'], 'knowledge-assignment', request.item_kind, item_id
|
|
213
213
|
)
|
|
214
|
+
item_match = (
|
|
215
|
+
'MATCH (item:Entity {uuid: $item_id, group_id: $group_id})'
|
|
216
|
+
if request.item_kind == 'entity'
|
|
217
|
+
else (
|
|
218
|
+
'MATCH ()-[item:RELATES_TO '
|
|
219
|
+
'{uuid: $item_id, group_id: $group_id}]->()'
|
|
220
|
+
)
|
|
221
|
+
)
|
|
214
222
|
records, _, _ = await store.runtime.driver.execute_query(
|
|
215
223
|
'''
|
|
216
224
|
MATCH (space:FuliSpace {id: $space_id, kind: 'personal'})
|
|
217
225
|
MATCH (space)-[:CONTAINS_PROJECT]->
|
|
218
226
|
(target:FuliPersonalProject {project_id: $target_project_id})
|
|
227
|
+
'''
|
|
228
|
+
+ item_match
|
|
229
|
+
+ '''
|
|
230
|
+
SET item.fuli_ai_review_lock_version =
|
|
231
|
+
coalesce(item.fuli_ai_review_lock_version, 0) + 1
|
|
232
|
+
WITH space, target
|
|
219
233
|
MERGE (assignment:FuliKnowledgeAssignment {
|
|
220
234
|
space_id: $space_id,
|
|
221
235
|
item_kind: $item_kind,
|
|
@@ -236,6 +250,7 @@ async def reassign_knowledge_item(
|
|
|
236
250
|
RETURN assignment
|
|
237
251
|
''',
|
|
238
252
|
space_id=space['id'],
|
|
253
|
+
group_id=space['group_id'],
|
|
239
254
|
target_project_id=request.target_project_id,
|
|
240
255
|
item_kind=request.item_kind,
|
|
241
256
|
item_id=item_id,
|
|
@@ -712,6 +727,8 @@ async def _update_item(store, space, item_id, item_kind, value, embedding):
|
|
|
712
727
|
if item_kind == 'entity':
|
|
713
728
|
query = '''
|
|
714
729
|
MATCH (item:Entity {uuid: $item_id, group_id: $group_id})
|
|
730
|
+
SET item.fuli_ai_review_lock_version =
|
|
731
|
+
coalesce(item.fuli_ai_review_lock_version, 0) + 1
|
|
715
732
|
SET item.name = $name,
|
|
716
733
|
item.summary = $summary,
|
|
717
734
|
item.fuli_invalid_at = $invalid_at,
|
|
@@ -744,6 +761,8 @@ async def _update_item(store, space, item_id, item_kind, value, embedding):
|
|
|
744
761
|
else:
|
|
745
762
|
query = '''
|
|
746
763
|
MATCH ()-[item:RELATES_TO {uuid: $item_id, group_id: $group_id}]->()
|
|
764
|
+
SET item.fuli_ai_review_lock_version =
|
|
765
|
+
coalesce(item.fuli_ai_review_lock_version, 0) + 1
|
|
747
766
|
SET item.fact = $fact,
|
|
748
767
|
item.invalid_at = $invalid_at,
|
|
749
768
|
item.fuli_origin_quadrant = $origin_quadrant,
|
|
@@ -844,12 +863,16 @@ async def _update_preference_scope(
|
|
|
844
863
|
if item_kind == 'entity':
|
|
845
864
|
query = '''
|
|
846
865
|
MATCH (item:Entity {uuid: $item_id, group_id: $group_id})
|
|
866
|
+
SET item.fuli_ai_review_lock_version =
|
|
867
|
+
coalesce(item.fuli_ai_review_lock_version, 0) + 1
|
|
847
868
|
SET item.fuli_preference_scope = $scope,
|
|
848
869
|
item.fuli_preference_project_id = $project_id
|
|
849
870
|
'''
|
|
850
871
|
else:
|
|
851
872
|
query = '''
|
|
852
873
|
MATCH ()-[item:RELATES_TO {uuid: $item_id, group_id: $group_id}]->()
|
|
874
|
+
SET item.fuli_ai_review_lock_version =
|
|
875
|
+
coalesce(item.fuli_ai_review_lock_version, 0) + 1
|
|
853
876
|
SET item.fuli_preference_scope = $scope,
|
|
854
877
|
item.fuli_preference_project_id = $project_id
|
|
855
878
|
'''
|
|
@@ -4,6 +4,7 @@ from typing import Any
|
|
|
4
4
|
from fastapi import HTTPException
|
|
5
5
|
|
|
6
6
|
from .knowledge_review_models import (
|
|
7
|
+
KnowledgeReviewAIAssessment,
|
|
7
8
|
KnowledgeReviewCandidate,
|
|
8
9
|
KnowledgeReviewCandidatePage,
|
|
9
10
|
KnowledgeReviewCandidateRequest,
|
|
@@ -13,8 +14,10 @@ from .knowledge_review_models import (
|
|
|
13
14
|
KnowledgeReviewRun,
|
|
14
15
|
KnowledgeReviewStart,
|
|
15
16
|
)
|
|
17
|
+
from .ai_review import ai_review_evidence_token
|
|
16
18
|
from .personal_project_access import authorize_personal_project
|
|
17
19
|
from .provider_values import native_datetime, stable_uuid
|
|
20
|
+
from .store_transactions import query_store_transaction
|
|
18
21
|
|
|
19
22
|
# These are Provider ranking conventions, not product requirements. Keeping them
|
|
20
23
|
# here gives every Agent the same deterministic policy and makes tuning explicit.
|
|
@@ -37,6 +40,8 @@ async def start_knowledge_review(
|
|
|
37
40
|
store, actor, space, request.personal_project_id
|
|
38
41
|
)
|
|
39
42
|
scope_key = _scope_key(request.scope, request.personal_project_id)
|
|
43
|
+
if request.reviewer == 'tonborg':
|
|
44
|
+
scope_key += ':tonborg'
|
|
40
45
|
changed_at = started_at or datetime.now(timezone.utc)
|
|
41
46
|
active_records, _, _ = await store.runtime.driver.execute_query(
|
|
42
47
|
'''
|
|
@@ -84,6 +89,10 @@ async def start_knowledge_review(
|
|
|
84
89
|
completed_records[0].get('previous_completed_at')
|
|
85
90
|
if completed_records else None
|
|
86
91
|
)
|
|
92
|
+
# Tonborg has its own paginated queue and versioned decision ledger. It must
|
|
93
|
+
# not advance the human review watermark or lose items beyond one batch.
|
|
94
|
+
if request.reviewer == 'tonborg':
|
|
95
|
+
previous_completed_at = None
|
|
87
96
|
review_id = stable_uuid(
|
|
88
97
|
space['id'], 'knowledge-review', scope_key, changed_at.isoformat()
|
|
89
98
|
)
|
|
@@ -96,6 +105,7 @@ async def start_knowledge_review(
|
|
|
96
105
|
run.scope = $scope,
|
|
97
106
|
run.personal_project_id = $personal_project_id,
|
|
98
107
|
run.scope_key = $scope_key,
|
|
108
|
+
run.reviewer = $reviewer,
|
|
99
109
|
run.status = 'active',
|
|
100
110
|
run.previous_completed_at = $previous_completed_at,
|
|
101
111
|
run.review_cutoff_at = $started_at,
|
|
@@ -112,6 +122,7 @@ async def start_knowledge_review(
|
|
|
112
122
|
scope=request.scope,
|
|
113
123
|
personal_project_id=request.personal_project_id,
|
|
114
124
|
scope_key=scope_key,
|
|
125
|
+
reviewer=request.reviewer,
|
|
115
126
|
previous_completed_at=previous_completed_at,
|
|
116
127
|
started_at=changed_at,
|
|
117
128
|
)
|
|
@@ -148,6 +159,7 @@ async def list_knowledge_review_candidates(
|
|
|
148
159
|
conflict_rows, _, _ = await store.runtime.driver.execute_query(
|
|
149
160
|
_CONFLICT_ITEM_QUERY,
|
|
150
161
|
space_id=space['id'],
|
|
162
|
+
project_id=run.personal_project_id,
|
|
151
163
|
routing_='r',
|
|
152
164
|
)
|
|
153
165
|
decision_rows, _, _ = await store.runtime.driver.execute_query(
|
|
@@ -187,12 +199,12 @@ async def list_knowledge_review_candidates(
|
|
|
187
199
|
if key
|
|
188
200
|
},
|
|
189
201
|
)
|
|
190
|
-
selected = candidates[:request.limit]
|
|
202
|
+
selected = candidates[request.offset:request.offset + request.limit]
|
|
191
203
|
return KnowledgeReviewCandidatePage(
|
|
192
204
|
review=run,
|
|
193
205
|
candidates=selected,
|
|
194
206
|
total_candidate_count=len(candidates),
|
|
195
|
-
remaining_candidate_count=max(len(candidates) - len(selected), 0),
|
|
207
|
+
remaining_candidate_count=max(len(candidates) - request.offset - len(selected), 0),
|
|
196
208
|
)
|
|
197
209
|
|
|
198
210
|
|
|
@@ -204,7 +216,7 @@ async def record_knowledge_review_progress(
|
|
|
204
216
|
changed_at: datetime | None = None,
|
|
205
217
|
) -> KnowledgeReviewDecision:
|
|
206
218
|
store._require_personal()
|
|
207
|
-
await _personal_space(store, actor, request.personal_space_id, 'maintainer')
|
|
219
|
+
space = await _personal_space(store, actor, request.personal_space_id, 'maintainer')
|
|
208
220
|
updated_at = changed_at or datetime.now(timezone.utc)
|
|
209
221
|
decision_id = stable_uuid(
|
|
210
222
|
request.personal_space_id,
|
|
@@ -212,6 +224,15 @@ async def record_knowledge_review_progress(
|
|
|
212
224
|
request.review_id,
|
|
213
225
|
request.candidate_key,
|
|
214
226
|
)
|
|
227
|
+
if request.outcome == 'ai_reviewed':
|
|
228
|
+
return await _record_ai_review_progress(
|
|
229
|
+
store,
|
|
230
|
+
space,
|
|
231
|
+
request,
|
|
232
|
+
decision_id=decision_id,
|
|
233
|
+
updated_at=updated_at,
|
|
234
|
+
)
|
|
235
|
+
|
|
215
236
|
records, _, _ = await store.runtime.driver.execute_query(
|
|
216
237
|
'''
|
|
217
238
|
MATCH (space:FuliSpace {id: $space_id, kind: 'personal'})-
|
|
@@ -241,6 +262,97 @@ async def record_knowledge_review_progress(
|
|
|
241
262
|
return _review_decision(dict(records[0]['decision']))
|
|
242
263
|
|
|
243
264
|
|
|
265
|
+
async def _record_ai_review_progress(
|
|
266
|
+
store,
|
|
267
|
+
space: dict,
|
|
268
|
+
request: KnowledgeReviewProgress,
|
|
269
|
+
*,
|
|
270
|
+
decision_id: str,
|
|
271
|
+
updated_at: datetime,
|
|
272
|
+
) -> KnowledgeReviewDecision:
|
|
273
|
+
"""CAS an AI result against a locked, freshly-read item snapshot."""
|
|
274
|
+
|
|
275
|
+
item_kind, separator, item_id = request.candidate_key.partition(':')
|
|
276
|
+
if not separator or item_kind not in {'entity', 'relationship'} or not item_id:
|
|
277
|
+
raise HTTPException(status_code=422, detail='invalid review candidate')
|
|
278
|
+
|
|
279
|
+
async with query_store_transaction(store) as scoped:
|
|
280
|
+
run_records, _, _ = await scoped.runtime.driver.execute_query(
|
|
281
|
+
_AI_REVIEW_RUN_QUERY,
|
|
282
|
+
space_id=space['id'],
|
|
283
|
+
review_id=request.review_id,
|
|
284
|
+
)
|
|
285
|
+
if not run_records:
|
|
286
|
+
raise HTTPException(status_code=404, detail='active knowledge review not found')
|
|
287
|
+
run = dict(run_records[0]['run'])
|
|
288
|
+
if run.get('reviewer') != 'tonborg' and not str(
|
|
289
|
+
run.get('scope_key', '')
|
|
290
|
+
).endswith(':tonborg'):
|
|
291
|
+
raise HTTPException(
|
|
292
|
+
status_code=403,
|
|
293
|
+
detail='AI review progress is only valid for a Tonborg review run',
|
|
294
|
+
)
|
|
295
|
+
if run.get('status') not in {'active', 'paused'}:
|
|
296
|
+
raise HTTPException(status_code=404, detail='active knowledge review not found')
|
|
297
|
+
|
|
298
|
+
current_records, _, _ = await scoped.runtime.driver.execute_query(
|
|
299
|
+
_AI_REVIEW_CURRENT_ENTITY_QUERY
|
|
300
|
+
if item_kind == 'entity' else _AI_REVIEW_CURRENT_RELATIONSHIP_QUERY,
|
|
301
|
+
space_id=space['id'],
|
|
302
|
+
group_id=space['group_id'],
|
|
303
|
+
item_id=item_id,
|
|
304
|
+
project_id=run.get('personal_project_id'),
|
|
305
|
+
)
|
|
306
|
+
if not current_records:
|
|
307
|
+
raise HTTPException(status_code=404, detail='review candidate not found')
|
|
308
|
+
current = dict(current_records[0])
|
|
309
|
+
normalized = _normalize_item_row(current)
|
|
310
|
+
if not _item_in_scope(
|
|
311
|
+
normalized,
|
|
312
|
+
run['scope'],
|
|
313
|
+
run.get('personal_project_id'),
|
|
314
|
+
):
|
|
315
|
+
raise HTTPException(
|
|
316
|
+
status_code=409,
|
|
317
|
+
detail='review candidate is outside the current project scope',
|
|
318
|
+
)
|
|
319
|
+
has_conflict = bool(current.get('has_conflict'))
|
|
320
|
+
current_token = ai_review_evidence_token(
|
|
321
|
+
normalized,
|
|
322
|
+
scope=run['scope'],
|
|
323
|
+
personal_project_id=run.get('personal_project_id'),
|
|
324
|
+
has_conflict=has_conflict,
|
|
325
|
+
)
|
|
326
|
+
if current_token != request.ai_review_evidence_token:
|
|
327
|
+
raise HTTPException(
|
|
328
|
+
status_code=409,
|
|
329
|
+
detail='knowledge review evidence changed; reload candidate',
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
assessment_json = request.ai_assessment.model_dump_json()
|
|
333
|
+
write_query = (
|
|
334
|
+
_AI_REVIEW_ENTITY_WRITE_QUERY
|
|
335
|
+
if item_kind == 'entity' else _AI_REVIEW_RELATIONSHIP_WRITE_QUERY
|
|
336
|
+
)
|
|
337
|
+
records, _, _ = await scoped.runtime.driver.execute_query(
|
|
338
|
+
write_query,
|
|
339
|
+
space_id=space['id'],
|
|
340
|
+
group_id=space['group_id'],
|
|
341
|
+
item_id=item_id,
|
|
342
|
+
review_id=request.review_id,
|
|
343
|
+
decision_id=decision_id,
|
|
344
|
+
candidate_key=request.candidate_key,
|
|
345
|
+
outcome=request.outcome,
|
|
346
|
+
note=request.note,
|
|
347
|
+
ai_review_evidence_token=current_token,
|
|
348
|
+
ai_assessment_json=assessment_json,
|
|
349
|
+
updated_at=updated_at,
|
|
350
|
+
)
|
|
351
|
+
if not records:
|
|
352
|
+
raise HTTPException(status_code=404, detail='active knowledge review not found')
|
|
353
|
+
return _review_decision(dict(records[0]['decision']))
|
|
354
|
+
|
|
355
|
+
|
|
244
356
|
async def finish_knowledge_review(
|
|
245
357
|
store,
|
|
246
358
|
actor: dict,
|
|
@@ -300,6 +412,12 @@ def build_review_candidates(
|
|
|
300
412
|
):
|
|
301
413
|
continue
|
|
302
414
|
normalized['_candidate_key'] = candidate_key
|
|
415
|
+
normalized['ai_review_evidence_token'] = ai_review_evidence_token(
|
|
416
|
+
normalized,
|
|
417
|
+
scope=scope,
|
|
418
|
+
personal_project_id=personal_project_id,
|
|
419
|
+
has_conflict=candidate_key in conflict_item_keys,
|
|
420
|
+
)
|
|
303
421
|
prepared.append(normalized)
|
|
304
422
|
|
|
305
423
|
# One stable entity usually accumulates several sessions, while repeated
|
|
@@ -389,6 +507,10 @@ def _review_run(value: dict, *, resumed: bool = False) -> KnowledgeReviewRun:
|
|
|
389
507
|
scope=value['scope'],
|
|
390
508
|
personal_project_id=value.get('personal_project_id'),
|
|
391
509
|
scope_key=value['scope_key'],
|
|
510
|
+
reviewer=value.get('reviewer') or (
|
|
511
|
+
'tonborg' if str(value.get('scope_key', '')).endswith(':tonborg')
|
|
512
|
+
else 'human'
|
|
513
|
+
),
|
|
392
514
|
status=value['status'],
|
|
393
515
|
previous_completed_at=native_datetime(value.get('previous_completed_at')),
|
|
394
516
|
review_cutoff_at=native_datetime(
|
|
@@ -402,6 +524,17 @@ def _review_run(value: dict, *, resumed: bool = False) -> KnowledgeReviewRun:
|
|
|
402
524
|
|
|
403
525
|
|
|
404
526
|
def _review_decision(value: dict) -> KnowledgeReviewDecision:
|
|
527
|
+
assessment = value.get('ai_assessment')
|
|
528
|
+
if assessment is None:
|
|
529
|
+
assessment = value.get('ai_assessment_json')
|
|
530
|
+
if isinstance(assessment, str):
|
|
531
|
+
try:
|
|
532
|
+
import json
|
|
533
|
+
assessment = json.loads(assessment)
|
|
534
|
+
except (TypeError, ValueError):
|
|
535
|
+
assessment = None
|
|
536
|
+
if assessment is not None:
|
|
537
|
+
assessment = KnowledgeReviewAIAssessment.model_validate(assessment)
|
|
405
538
|
return KnowledgeReviewDecision(
|
|
406
539
|
decision_id=value['id'],
|
|
407
540
|
review_id=value['review_id'],
|
|
@@ -410,6 +543,10 @@ def _review_decision(value: dict) -> KnowledgeReviewDecision:
|
|
|
410
543
|
note=value.get('note'),
|
|
411
544
|
created_at=native_datetime(value['created_at']),
|
|
412
545
|
updated_at=native_datetime(value['updated_at']),
|
|
546
|
+
ai_review_evidence_token=value.get('ai_review_evidence_token'),
|
|
547
|
+
ai_assessment=assessment,
|
|
548
|
+
ai_review_scope=value.get('ai_review_scope'),
|
|
549
|
+
ai_review_project_id=value.get('ai_review_project_id'),
|
|
413
550
|
)
|
|
414
551
|
|
|
415
552
|
|
|
@@ -420,7 +557,12 @@ def _scope_key(scope: str, personal_project_id: str | None) -> str:
|
|
|
420
557
|
def _normalize_item_row(row: dict[str, Any]) -> dict[str, Any]:
|
|
421
558
|
project_ids = sorted({
|
|
422
559
|
value
|
|
423
|
-
for key in (
|
|
560
|
+
for key in (
|
|
561
|
+
'project_ids',
|
|
562
|
+
'assigned_project_ids',
|
|
563
|
+
'reference_project_ids',
|
|
564
|
+
'evidence_project_ids',
|
|
565
|
+
)
|
|
424
566
|
for value in (row.get(key) or [])
|
|
425
567
|
if value
|
|
426
568
|
})
|
|
@@ -439,8 +581,11 @@ def _normalize_item_row(row: dict[str, Any]) -> dict[str, Any]:
|
|
|
439
581
|
'profile_aspect': row.get('profile_aspect'),
|
|
440
582
|
'preference_scope': row.get('preference_scope'),
|
|
441
583
|
'preference_project_id': row.get('preference_project_id'),
|
|
584
|
+
'preference_agent_id': row.get('preference_agent_id'),
|
|
585
|
+
'inheritance_mode': row.get('inheritance_mode') or 'local_only',
|
|
442
586
|
'project_ids': project_ids,
|
|
443
587
|
'confirmation_status': row.get('confirmation_status') or 'pending',
|
|
588
|
+
'confirmation_basis_json': row.get('confirmation_basis_json'),
|
|
444
589
|
'current_quadrant': row.get('current_quadrant') or 'known_known',
|
|
445
590
|
'utility_score': _float_or_default(row.get('utility_score'), 0),
|
|
446
591
|
'confidence_score': _float_or_default(row.get('confidence_score'), 0.5),
|
|
@@ -451,6 +596,10 @@ def _normalize_item_row(row: dict[str, Any]) -> dict[str, Any]:
|
|
|
451
596
|
'last_feedback_kind': row.get('last_feedback_kind'),
|
|
452
597
|
'distinct_session_count': len(session_ids),
|
|
453
598
|
'changed_at': changed_at,
|
|
599
|
+
'valid_at': row.get('valid_at'),
|
|
600
|
+
'invalid_at': row.get('invalid_at'),
|
|
601
|
+
'human_change_version': int(row.get('human_change_version') or 0),
|
|
602
|
+
'attributes_json': row.get('attributes_json'),
|
|
454
603
|
'_session_ids': session_ids,
|
|
455
604
|
}
|
|
456
605
|
|
|
@@ -550,6 +699,10 @@ OPTIONAL MATCH (episode:Episodic {group_id: $group_id})-[:MENTIONS]->(item)
|
|
|
550
699
|
OPTIONAL MATCH (assignment:FuliKnowledgeAssignment {
|
|
551
700
|
space_id: $space_id, item_kind: 'entity', item_id: item.uuid
|
|
552
701
|
})
|
|
702
|
+
OPTIONAL MATCH (:FuliSpace {id: $space_id})-[:HAS_KNOWLEDGE_REFERENCE]->
|
|
703
|
+
(reference:FuliKnowledgeProjectReference {
|
|
704
|
+
item_kind: 'entity', item_id: item.uuid, status: 'active'
|
|
705
|
+
})
|
|
553
706
|
OPTIONAL MATCH (:FuliSpace {id: $space_id})-[:HAS_KNOWLEDGE_REVISION]->
|
|
554
707
|
(revision:FuliKnowledgeRevision {item_kind: 'entity', item_id: item.uuid})
|
|
555
708
|
RETURN item.uuid AS item_id,
|
|
@@ -559,12 +712,20 @@ RETURN item.uuid AS item_id,
|
|
|
559
712
|
item.fuli_profile_aspect AS profile_aspect,
|
|
560
713
|
item.fuli_preference_scope AS preference_scope,
|
|
561
714
|
item.fuli_preference_project_id AS preference_project_id,
|
|
715
|
+
item.fuli_preference_agent_id AS preference_agent_id,
|
|
716
|
+
coalesce(item.fuli_inheritance_mode, 'local_only') AS inheritance_mode,
|
|
562
717
|
coalesce(
|
|
563
718
|
item.fuli_current_quadrant,
|
|
564
719
|
item.fuli_origin_quadrant,
|
|
565
720
|
'known_known'
|
|
566
721
|
) AS current_quadrant,
|
|
722
|
+
item.fuli_confirmation_basis_json AS confirmation_basis_json,
|
|
723
|
+
NULL AS valid_at,
|
|
724
|
+
item.fuli_invalid_at AS invalid_at,
|
|
725
|
+
coalesce(item.fuli_human_change_version, 0) AS human_change_version,
|
|
726
|
+
coalesce(item.fuli_attributes_json, '{}') AS attributes_json,
|
|
567
727
|
collect(DISTINCT assignment.project_id) AS assigned_project_ids,
|
|
728
|
+
collect(DISTINCT reference.project_id) AS reference_project_ids,
|
|
568
729
|
collect(DISTINCT episode.fuli_personal_project_id) AS evidence_project_ids,
|
|
569
730
|
collect(DISTINCT episode.fuli_session_id) AS session_ids,
|
|
570
731
|
coalesce(item.fuli_confirmation_status, 'pending') AS confirmation_status,
|
|
@@ -590,6 +751,10 @@ WHERE episode.uuid IN coalesce(item.episodes, [])
|
|
|
590
751
|
OPTIONAL MATCH (assignment:FuliKnowledgeAssignment {
|
|
591
752
|
space_id: $space_id, item_kind: 'relationship', item_id: item.uuid
|
|
592
753
|
})
|
|
754
|
+
OPTIONAL MATCH (:FuliSpace {id: $space_id})-[:HAS_KNOWLEDGE_REFERENCE]->
|
|
755
|
+
(reference:FuliKnowledgeProjectReference {
|
|
756
|
+
item_kind: 'relationship', item_id: item.uuid, status: 'active'
|
|
757
|
+
})
|
|
593
758
|
OPTIONAL MATCH (:FuliSpace {id: $space_id})-[:HAS_KNOWLEDGE_REVISION]->
|
|
594
759
|
(revision:FuliKnowledgeRevision {
|
|
595
760
|
item_kind: 'relationship', item_id: item.uuid
|
|
@@ -601,12 +766,20 @@ RETURN item.uuid AS item_id,
|
|
|
601
766
|
item.fuli_profile_aspect AS profile_aspect,
|
|
602
767
|
item.fuli_preference_scope AS preference_scope,
|
|
603
768
|
item.fuli_preference_project_id AS preference_project_id,
|
|
769
|
+
item.fuli_preference_agent_id AS preference_agent_id,
|
|
770
|
+
coalesce(item.fuli_inheritance_mode, 'local_only') AS inheritance_mode,
|
|
604
771
|
coalesce(
|
|
605
772
|
item.fuli_current_quadrant,
|
|
606
773
|
item.fuli_origin_quadrant,
|
|
607
774
|
'known_known'
|
|
608
775
|
) AS current_quadrant,
|
|
776
|
+
item.fuli_confirmation_basis_json AS confirmation_basis_json,
|
|
777
|
+
item.valid_at AS valid_at,
|
|
778
|
+
item.invalid_at AS invalid_at,
|
|
779
|
+
coalesce(item.fuli_human_change_version, 0) AS human_change_version,
|
|
780
|
+
coalesce(item.fuli_attributes_json, '{}') AS attributes_json,
|
|
609
781
|
collect(DISTINCT assignment.project_id) AS assigned_project_ids,
|
|
782
|
+
collect(DISTINCT reference.project_id) AS reference_project_ids,
|
|
610
783
|
collect(DISTINCT episode.fuli_personal_project_id) AS evidence_project_ids,
|
|
611
784
|
collect(DISTINCT episode.fuli_session_id) AS session_ids,
|
|
612
785
|
coalesce(item.fuli_confirmation_status, 'pending') AS confirmation_status,
|
|
@@ -628,6 +801,9 @@ _CONFLICT_ITEM_QUERY = '''
|
|
|
628
801
|
MATCH (space:FuliSpace {id: $space_id, kind: 'personal'})
|
|
629
802
|
OPTIONAL MATCH (space)-[:HAS_KNOWLEDGE_CONFLICT]->
|
|
630
803
|
(knowledge:FuliKnowledgeConflict {status: 'pending'})
|
|
804
|
+
WHERE ($project_id IS NULL
|
|
805
|
+
OR knowledge.target_project_id = $project_id
|
|
806
|
+
OR knowledge.source_project_id = $project_id)
|
|
631
807
|
WITH space, collect(DISTINCT 'entity:' + knowledge.item_id) +
|
|
632
808
|
collect(DISTINCT 'relationship:' + knowledge.item_id) +
|
|
633
809
|
collect(DISTINCT 'entity:' + knowledge.target_item_id) +
|
|
@@ -635,6 +811,7 @@ WITH space, collect(DISTINCT 'entity:' + knowledge.item_id) +
|
|
|
635
811
|
AS knowledge_keys
|
|
636
812
|
OPTIONAL MATCH (space)-[:HAS_PREFERENCE_CONFLICT]->
|
|
637
813
|
(preference:FuliPreferenceConflict {status: 'ai_pending'})
|
|
814
|
+
WHERE $project_id IS NULL OR preference.preference_project_id = $project_id
|
|
638
815
|
WITH knowledge_keys,
|
|
639
816
|
collect(DISTINCT preference.left_item_kind + ':' + preference.left_item_id) +
|
|
640
817
|
collect(DISTINCT preference.right_item_kind + ':' + preference.right_item_id)
|
|
@@ -655,3 +832,205 @@ WITH candidate_key, collect(decision)[0] AS latest_decision
|
|
|
655
832
|
WHERE latest_decision.outcome = 'deferred'
|
|
656
833
|
RETURN collect(candidate_key) AS candidate_keys
|
|
657
834
|
'''
|
|
835
|
+
|
|
836
|
+
|
|
837
|
+
# These queries intentionally perform a property write before the dependent
|
|
838
|
+
# read. Neo4j then holds a write lock on the item until the surrounding
|
|
839
|
+
# query_store_transaction commits or rolls back, closing the read/decision CAS
|
|
840
|
+
# window even when a knowledge edit has not yet written its revision metadata.
|
|
841
|
+
_AI_REVIEW_CURRENT_ENTITY_QUERY = '''
|
|
842
|
+
MATCH (item:Entity {uuid: $item_id, group_id: $group_id})
|
|
843
|
+
SET item.fuli_ai_review_lock_version =
|
|
844
|
+
coalesce(item.fuli_ai_review_lock_version, 0) + 1
|
|
845
|
+
WITH item
|
|
846
|
+
OPTIONAL MATCH (episode:Episodic {group_id: $group_id})-[:MENTIONS]->(item)
|
|
847
|
+
OPTIONAL MATCH (assignment:FuliKnowledgeAssignment {
|
|
848
|
+
space_id: $space_id, item_kind: 'entity', item_id: item.uuid
|
|
849
|
+
})
|
|
850
|
+
OPTIONAL MATCH (:FuliSpace {id: $space_id})-[:HAS_KNOWLEDGE_REFERENCE]->
|
|
851
|
+
(reference:FuliKnowledgeProjectReference {
|
|
852
|
+
item_kind: 'entity', item_id: item.uuid, status: 'active'
|
|
853
|
+
})
|
|
854
|
+
WITH item,
|
|
855
|
+
collect(DISTINCT assignment.project_id) +
|
|
856
|
+
collect(DISTINCT reference.project_id) +
|
|
857
|
+
collect(DISTINCT episode.fuli_personal_project_id) AS project_ids
|
|
858
|
+
OPTIONAL MATCH (space:FuliSpace {id: $space_id, kind: 'personal'})-
|
|
859
|
+
[:HAS_KNOWLEDGE_CONFLICT]->
|
|
860
|
+
(knowledge:FuliKnowledgeConflict {status: 'pending'})
|
|
861
|
+
WHERE (knowledge.item_id = item.uuid OR knowledge.target_item_id = item.uuid)
|
|
862
|
+
AND ($project_id IS NULL
|
|
863
|
+
OR knowledge.target_project_id = $project_id
|
|
864
|
+
OR knowledge.source_project_id = $project_id)
|
|
865
|
+
WITH item, project_ids, collect(DISTINCT knowledge.id) AS knowledge_conflicts
|
|
866
|
+
OPTIONAL MATCH (space:FuliSpace {id: $space_id, kind: 'personal'})-
|
|
867
|
+
[:HAS_PREFERENCE_CONFLICT]->
|
|
868
|
+
(preference:FuliPreferenceConflict {status: 'ai_pending'})
|
|
869
|
+
WHERE (preference.left_item_id = item.uuid OR preference.right_item_id = item.uuid)
|
|
870
|
+
AND ($project_id IS NULL OR preference.preference_project_id = $project_id)
|
|
871
|
+
WITH item, project_ids, knowledge_conflicts,
|
|
872
|
+
collect(DISTINCT preference.id) AS preference_conflicts
|
|
873
|
+
RETURN item.uuid AS item_id,
|
|
874
|
+
'entity' AS item_kind,
|
|
875
|
+
item.name AS title,
|
|
876
|
+
coalesce(item.summary, '') AS content,
|
|
877
|
+
item.fuli_profile_aspect AS profile_aspect,
|
|
878
|
+
item.fuli_preference_scope AS preference_scope,
|
|
879
|
+
item.fuli_preference_project_id AS preference_project_id,
|
|
880
|
+
item.fuli_preference_agent_id AS preference_agent_id,
|
|
881
|
+
coalesce(item.fuli_inheritance_mode, 'local_only') AS inheritance_mode,
|
|
882
|
+
coalesce(item.fuli_current_quadrant, item.fuli_origin_quadrant,
|
|
883
|
+
'known_known') AS current_quadrant,
|
|
884
|
+
item.fuli_confirmation_status AS confirmation_status,
|
|
885
|
+
item.fuli_confirmation_basis_json AS confirmation_basis_json,
|
|
886
|
+
NULL AS valid_at,
|
|
887
|
+
item.fuli_invalid_at AS invalid_at,
|
|
888
|
+
coalesce(item.fuli_utility_score, 0.0) AS utility_score,
|
|
889
|
+
coalesce(item.fuli_confidence_score, 0.5) AS confidence_score,
|
|
890
|
+
coalesce(item.fuli_qualified_use_count, 0) AS qualified_use_count,
|
|
891
|
+
coalesce(item.fuli_distinct_task_count, 0) AS distinct_task_count,
|
|
892
|
+
coalesce(item.fuli_negative_evidence_count, 0) AS negative_evidence_count,
|
|
893
|
+
coalesce(item.fuli_requires_attention, false) AS requires_attention,
|
|
894
|
+
coalesce(item.fuli_human_change_version, 0) AS human_change_version,
|
|
895
|
+
coalesce(item.fuli_attributes_json, '{}') AS attributes_json,
|
|
896
|
+
project_ids,
|
|
897
|
+
CASE WHEN size(knowledge_conflicts) + size(preference_conflicts) > 0
|
|
898
|
+
THEN true ELSE false END AS has_conflict,
|
|
899
|
+
item.created_at AS created_at,
|
|
900
|
+
item.fuli_last_human_changed_at AS last_human_changed_at,
|
|
901
|
+
item.fuli_last_feedback_at AS last_feedback_at,
|
|
902
|
+
item.fuli_last_feedback_kind AS last_feedback_kind,
|
|
903
|
+
[] AS session_ids
|
|
904
|
+
'''
|
|
905
|
+
|
|
906
|
+
|
|
907
|
+
_AI_REVIEW_CURRENT_RELATIONSHIP_QUERY = '''
|
|
908
|
+
MATCH ()-[item:RELATES_TO {uuid: $item_id, group_id: $group_id}]->()
|
|
909
|
+
SET item.fuli_ai_review_lock_version =
|
|
910
|
+
coalesce(item.fuli_ai_review_lock_version, 0) + 1
|
|
911
|
+
WITH item
|
|
912
|
+
OPTIONAL MATCH (episode:Episodic {group_id: $group_id})
|
|
913
|
+
WHERE episode.uuid IN coalesce(item.episodes, [])
|
|
914
|
+
OPTIONAL MATCH (assignment:FuliKnowledgeAssignment {
|
|
915
|
+
space_id: $space_id, item_kind: 'relationship', item_id: item.uuid
|
|
916
|
+
})
|
|
917
|
+
OPTIONAL MATCH (:FuliSpace {id: $space_id})-[:HAS_KNOWLEDGE_REFERENCE]->
|
|
918
|
+
(reference:FuliKnowledgeProjectReference {
|
|
919
|
+
item_kind: 'relationship', item_id: item.uuid, status: 'active'
|
|
920
|
+
})
|
|
921
|
+
WITH item,
|
|
922
|
+
collect(DISTINCT assignment.project_id) +
|
|
923
|
+
collect(DISTINCT reference.project_id) +
|
|
924
|
+
collect(DISTINCT episode.fuli_personal_project_id) AS project_ids
|
|
925
|
+
OPTIONAL MATCH (space:FuliSpace {id: $space_id, kind: 'personal'})-
|
|
926
|
+
[:HAS_KNOWLEDGE_CONFLICT]->
|
|
927
|
+
(knowledge:FuliKnowledgeConflict {status: 'pending'})
|
|
928
|
+
WHERE (knowledge.item_id = item.uuid OR knowledge.target_item_id = item.uuid)
|
|
929
|
+
AND ($project_id IS NULL
|
|
930
|
+
OR knowledge.target_project_id = $project_id
|
|
931
|
+
OR knowledge.source_project_id = $project_id)
|
|
932
|
+
WITH item, project_ids, collect(DISTINCT knowledge.id) AS knowledge_conflicts
|
|
933
|
+
OPTIONAL MATCH (space:FuliSpace {id: $space_id, kind: 'personal'})-
|
|
934
|
+
[:HAS_PREFERENCE_CONFLICT]->
|
|
935
|
+
(preference:FuliPreferenceConflict {status: 'ai_pending'})
|
|
936
|
+
WHERE (preference.left_item_id = item.uuid OR preference.right_item_id = item.uuid)
|
|
937
|
+
AND ($project_id IS NULL OR preference.preference_project_id = $project_id)
|
|
938
|
+
WITH item, project_ids, knowledge_conflicts,
|
|
939
|
+
collect(DISTINCT preference.id) AS preference_conflicts
|
|
940
|
+
RETURN item.uuid AS item_id,
|
|
941
|
+
'relationship' AS item_kind,
|
|
942
|
+
coalesce(item.name, 'RELATES_TO') AS title,
|
|
943
|
+
coalesce(item.fact, '') AS content,
|
|
944
|
+
item.fuli_profile_aspect AS profile_aspect,
|
|
945
|
+
item.fuli_preference_scope AS preference_scope,
|
|
946
|
+
item.fuli_preference_project_id AS preference_project_id,
|
|
947
|
+
item.fuli_preference_agent_id AS preference_agent_id,
|
|
948
|
+
coalesce(item.fuli_inheritance_mode, 'local_only') AS inheritance_mode,
|
|
949
|
+
coalesce(item.fuli_current_quadrant, item.fuli_origin_quadrant,
|
|
950
|
+
'known_known') AS current_quadrant,
|
|
951
|
+
item.fuli_confirmation_status AS confirmation_status,
|
|
952
|
+
item.fuli_confirmation_basis_json AS confirmation_basis_json,
|
|
953
|
+
item.valid_at AS valid_at,
|
|
954
|
+
item.invalid_at AS invalid_at,
|
|
955
|
+
coalesce(item.fuli_utility_score, 0.0) AS utility_score,
|
|
956
|
+
coalesce(item.fuli_confidence_score, 0.5) AS confidence_score,
|
|
957
|
+
coalesce(item.fuli_qualified_use_count, 0) AS qualified_use_count,
|
|
958
|
+
coalesce(item.fuli_distinct_task_count, 0) AS distinct_task_count,
|
|
959
|
+
coalesce(item.fuli_negative_evidence_count, 0) AS negative_evidence_count,
|
|
960
|
+
coalesce(item.fuli_requires_attention, false) AS requires_attention,
|
|
961
|
+
coalesce(item.fuli_human_change_version, 0) AS human_change_version,
|
|
962
|
+
coalesce(item.fuli_attributes_json, '{}') AS attributes_json,
|
|
963
|
+
project_ids,
|
|
964
|
+
CASE WHEN size(knowledge_conflicts) + size(preference_conflicts) > 0
|
|
965
|
+
THEN true ELSE false END AS has_conflict,
|
|
966
|
+
item.created_at AS created_at,
|
|
967
|
+
item.fuli_last_human_changed_at AS last_human_changed_at,
|
|
968
|
+
item.fuli_last_feedback_at AS last_feedback_at,
|
|
969
|
+
item.fuli_last_feedback_kind AS last_feedback_kind,
|
|
970
|
+
[] AS session_ids
|
|
971
|
+
'''
|
|
972
|
+
|
|
973
|
+
|
|
974
|
+
_AI_REVIEW_RUN_QUERY = '''
|
|
975
|
+
MATCH (:FuliSpace {id: $space_id, kind: 'personal'})-
|
|
976
|
+
[:HAS_KNOWLEDGE_REVIEW]->(run:FuliKnowledgeReviewRun {id: $review_id})
|
|
977
|
+
RETURN run
|
|
978
|
+
'''
|
|
979
|
+
|
|
980
|
+
|
|
981
|
+
_AI_REVIEW_ENTITY_WRITE_QUERY = '''
|
|
982
|
+
MATCH (space:FuliSpace {id: $space_id, kind: 'personal'})-
|
|
983
|
+
[:HAS_KNOWLEDGE_REVIEW]->(run:FuliKnowledgeReviewRun {id: $review_id})
|
|
984
|
+
MATCH (item:Entity {uuid: $item_id, group_id: $group_id})
|
|
985
|
+
WHERE run.status IN ['active', 'paused']
|
|
986
|
+
MERGE (decision:FuliKnowledgeReviewDecision {id: $decision_id})
|
|
987
|
+
ON CREATE SET decision.created_at = $updated_at
|
|
988
|
+
SET item.fuli_ai_review_evidence_token = $ai_review_evidence_token,
|
|
989
|
+
item.fuli_ai_assessment_json = $ai_assessment_json,
|
|
990
|
+
item.fuli_ai_review_scope = run.scope,
|
|
991
|
+
item.fuli_ai_review_project_id = run.personal_project_id,
|
|
992
|
+
item.fuli_ai_reviewed_at = $updated_at,
|
|
993
|
+
item.fuli_ai_review_id = $review_id,
|
|
994
|
+
item.fuli_ai_review_decision_id = $decision_id,
|
|
995
|
+
decision.review_id = $review_id,
|
|
996
|
+
decision.candidate_key = $candidate_key,
|
|
997
|
+
decision.outcome = $outcome,
|
|
998
|
+
decision.note = $note,
|
|
999
|
+
decision.ai_review_evidence_token = $ai_review_evidence_token,
|
|
1000
|
+
decision.ai_assessment_json = $ai_assessment_json,
|
|
1001
|
+
decision.ai_review_scope = run.scope,
|
|
1002
|
+
decision.ai_review_project_id = run.personal_project_id,
|
|
1003
|
+
decision.updated_at = $updated_at,
|
|
1004
|
+
run.updated_at = $updated_at
|
|
1005
|
+
MERGE (run)-[:HAS_REVIEW_DECISION]->(decision)
|
|
1006
|
+
RETURN decision
|
|
1007
|
+
'''
|
|
1008
|
+
|
|
1009
|
+
|
|
1010
|
+
_AI_REVIEW_RELATIONSHIP_WRITE_QUERY = '''
|
|
1011
|
+
MATCH (space:FuliSpace {id: $space_id, kind: 'personal'})-
|
|
1012
|
+
[:HAS_KNOWLEDGE_REVIEW]->(run:FuliKnowledgeReviewRun {id: $review_id})
|
|
1013
|
+
MATCH ()-[item:RELATES_TO {uuid: $item_id, group_id: $group_id}]->()
|
|
1014
|
+
WHERE run.status IN ['active', 'paused']
|
|
1015
|
+
MERGE (decision:FuliKnowledgeReviewDecision {id: $decision_id})
|
|
1016
|
+
ON CREATE SET decision.created_at = $updated_at
|
|
1017
|
+
SET item.fuli_ai_review_evidence_token = $ai_review_evidence_token,
|
|
1018
|
+
item.fuli_ai_assessment_json = $ai_assessment_json,
|
|
1019
|
+
item.fuli_ai_review_scope = run.scope,
|
|
1020
|
+
item.fuli_ai_review_project_id = run.personal_project_id,
|
|
1021
|
+
item.fuli_ai_reviewed_at = $updated_at,
|
|
1022
|
+
item.fuli_ai_review_id = $review_id,
|
|
1023
|
+
item.fuli_ai_review_decision_id = $decision_id,
|
|
1024
|
+
decision.review_id = $review_id,
|
|
1025
|
+
decision.candidate_key = $candidate_key,
|
|
1026
|
+
decision.outcome = $outcome,
|
|
1027
|
+
decision.note = $note,
|
|
1028
|
+
decision.ai_review_evidence_token = $ai_review_evidence_token,
|
|
1029
|
+
decision.ai_assessment_json = $ai_assessment_json,
|
|
1030
|
+
decision.ai_review_scope = run.scope,
|
|
1031
|
+
decision.ai_review_project_id = run.personal_project_id,
|
|
1032
|
+
decision.updated_at = $updated_at,
|
|
1033
|
+
run.updated_at = $updated_at
|
|
1034
|
+
MERGE (run)-[:HAS_REVIEW_DECISION]->(decision)
|
|
1035
|
+
RETURN decision
|
|
1036
|
+
'''
|