fuli-context 0.11.1 → 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/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-D-qIFRIb.js → attention-store-B1DI8wmd.js} +1 -1
- package/dist/web/assets/{catalog-UDv854JN.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-b14l2pQA.js → i18n-CVFhT_k7.js} +2 -2
- package/dist/web/assets/{identity-BWbPmZZf.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-BPTtMJTZ.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-CEQAEuug.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/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_task_models.py +2 -0
- package/graph-provider/fuli_graph/project_knowledge.py +41 -0
- package/graph-provider/fuli_graph/runtime.py +8 -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_task_activity.py +26 -0
- package/graph-provider/fuli_graph/store_project_agent_task_staffing.py +2 -1
- package/graph-provider/fuli_graph/system_agents.py +20 -1
- 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-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/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 +6 -1
- package/src/graphiti/knowledge-review.js +7 -3
- package/src/graphiti/project-agent-mapping.js +2 -0
- package/src/graphiti/project-agent-task-entry.js +2 -2
- package/src/graphiti/task-entry-preferences.js +5 -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-cW9hDghN.js +0 -1
- package/dist/web/assets/AgentConversations-CGznTWuV.js +0 -2
- package/dist/web/assets/AgentHand-DkyF4vgk.js +0 -1
- package/dist/web/assets/AgentName-C1mAVr-v.js +0 -1
- package/dist/web/assets/AgentProfilePage-pKUR0rfj.js +0 -1
- package/dist/web/assets/AgentsDirectoryPage-CUlbT2hD.js +0 -1
- package/dist/web/assets/AgentsDirectoryPage-yRWktiin.css +0 -1
- package/dist/web/assets/ConnectionsPage-CuDHIJXu.js +0 -1
- package/dist/web/assets/EmployeeRecruitDialog-D9QfGipd.js +0 -1
- package/dist/web/assets/EmployeeWorkbenchPage-D-c_Jm2z.css +0 -1
- package/dist/web/assets/EmployeeWorkbenchPage-DAtvGney.js +0 -3
- package/dist/web/assets/KnowledgeEditDialog-a-5rasG0.js +0 -1
- package/dist/web/assets/KnowledgeInspector-BGsqjNY2.js +0 -1
- package/dist/web/assets/KnowledgeOrganizerPage-BndgUsHS.js +0 -1
- package/dist/web/assets/KnowledgeOrganizerPage-DeL7Jp34.css +0 -1
- package/dist/web/assets/KnowledgePage-CQweO0YJ.js +0 -1
- package/dist/web/assets/KnowledgeWorkspace-BtUjKTLb.js +0 -4
- package/dist/web/assets/NotFoundPage-BmQTTqQn.js +0 -1
- package/dist/web/assets/OverviewPage-C73HyTsM.js +0 -1
- package/dist/web/assets/PersonalProfilePage-BMuOrppx.js +0 -1
- package/dist/web/assets/PersonalProjectsPage-Cq9_kFvs.js +0 -1
- package/dist/web/assets/ProjectAgentFirstTask-CaHvOjkp.js +0 -1
- package/dist/web/assets/ProjectAgentsPage-WVcNdzVi.js +0 -12
- package/dist/web/assets/ProjectScopePicker-zAgiaZKL.js +0 -1
- package/dist/web/assets/PublicProjectsPage-DL093f65.js +0 -1
- package/dist/web/assets/ReviewPage-D_NIhPx8.js +0 -1
- package/dist/web/assets/RoundtablesPage-CdVVyZE-.js +0 -1
- package/dist/web/assets/SettingsPage-BmYkLul4.css +0 -1
- package/dist/web/assets/SettingsPage-CbLaau8Q.js +0 -1
- package/dist/web/assets/SharedJefaReportPage-aygd6wvb.js +0 -1
- package/dist/web/assets/UiDialog-Q7fsJUoO.js +0 -1
- package/dist/web/assets/UiDisclosure-BS-muK5j.js +0 -1
- package/dist/web/assets/UiSegmented-CT28bxqw.js +0 -1
- package/dist/web/assets/UiSelect-DcGpPQQo.js +0 -1
- package/dist/web/assets/WritingTastePage-CYlwAFqV.js +0 -1
- package/dist/web/assets/client-DLS5FZBI.js +0 -1
- package/dist/web/assets/console-CqzwSo7a.js +0 -1
- package/dist/web/assets/conversation-launcher-settings-DRoIz42V.js +0 -1
- package/dist/web/assets/index-D1OHIz4c.js +0 -5
- package/dist/web/assets/index-kPB0Cf8Q.css +0 -1
- package/dist/web/assets/model-DupZy8Qb.js +0 -1
- package/dist/web/assets/shared-report-BYnLhmCZ.js +0 -1
- package/dist/web/assets/useAgentRoster-ep7SpKv1.js +0 -1
- package/dist/web/assets/vue-router-B6ZjK-Br.js +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
2
|
from typing import Literal
|
|
3
3
|
|
|
4
|
-
from pydantic import Field, model_validator
|
|
4
|
+
from pydantic import Field, field_validator, model_validator
|
|
5
5
|
|
|
6
6
|
from .models import (
|
|
7
7
|
ConfirmationStatus,
|
|
@@ -11,6 +11,7 @@ from .models import (
|
|
|
11
11
|
PersonalProfileAspect,
|
|
12
12
|
PreferenceScope,
|
|
13
13
|
StrictModel,
|
|
14
|
+
SourceApplication,
|
|
14
15
|
)
|
|
15
16
|
|
|
16
17
|
KnowledgeReviewScope = Literal[
|
|
@@ -23,7 +24,7 @@ KnowledgeReviewScope = Literal[
|
|
|
23
24
|
KnowledgeReviewStatus = Literal['active', 'paused', 'completed']
|
|
24
25
|
KnowledgeReviewDisposition = Literal['paused', 'completed']
|
|
25
26
|
KnowledgeReviewOutcome = Literal[
|
|
26
|
-
'confirmed', 'updated', 'invalidated', 'deferred', 'delegated_to_ai'
|
|
27
|
+
'confirmed', 'updated', 'invalidated', 'deferred', 'delegated_to_ai', 'ai_reviewed'
|
|
27
28
|
]
|
|
28
29
|
KnowledgeReviewReason = Literal[
|
|
29
30
|
'changed_since_last',
|
|
@@ -37,6 +38,7 @@ KnowledgeReviewReason = Literal[
|
|
|
37
38
|
class KnowledgeReviewStart(StrictModel):
|
|
38
39
|
personal_space_id: str = Field(min_length=1, max_length=128)
|
|
39
40
|
scope: KnowledgeReviewScope
|
|
41
|
+
reviewer: Literal['human', 'tonborg'] = 'human'
|
|
40
42
|
personal_project_id: str | None = Field(default=None, min_length=1, max_length=128)
|
|
41
43
|
|
|
42
44
|
@model_validator(mode='after')
|
|
@@ -55,6 +57,7 @@ class KnowledgeReviewRun(StrictModel):
|
|
|
55
57
|
scope: KnowledgeReviewScope
|
|
56
58
|
personal_project_id: str | None = None
|
|
57
59
|
scope_key: str
|
|
60
|
+
reviewer: Literal['human', 'tonborg'] = 'human'
|
|
58
61
|
status: KnowledgeReviewStatus
|
|
59
62
|
previous_completed_at: datetime | None = None
|
|
60
63
|
review_cutoff_at: datetime
|
|
@@ -64,6 +67,25 @@ class KnowledgeReviewRun(StrictModel):
|
|
|
64
67
|
resumed: bool = False
|
|
65
68
|
|
|
66
69
|
|
|
70
|
+
class KnowledgeReviewAIAssessment(StrictModel):
|
|
71
|
+
"""The bounded, version-bound result returned by the Tonborg reviewer."""
|
|
72
|
+
|
|
73
|
+
outcome: Literal['approve', 'escalate']
|
|
74
|
+
summary: str = Field(min_length=1, max_length=2000)
|
|
75
|
+
evidence: list[str] = Field(min_length=1, max_length=8)
|
|
76
|
+
confidence: float = Field(ge=0, le=1)
|
|
77
|
+
client: SourceApplication | None = None
|
|
78
|
+
model: str | None = Field(default=None, min_length=1, max_length=256)
|
|
79
|
+
session_id: str | None = Field(default=None, min_length=1, max_length=256)
|
|
80
|
+
|
|
81
|
+
@field_validator('evidence')
|
|
82
|
+
@classmethod
|
|
83
|
+
def validate_evidence(cls, value: list[str]) -> list[str]:
|
|
84
|
+
if any(not item or len(item) > 1000 for item in value):
|
|
85
|
+
raise ValueError('AI evidence entries must contain 1 to 1000 characters')
|
|
86
|
+
return value
|
|
87
|
+
|
|
88
|
+
|
|
67
89
|
class KnowledgeReviewCandidate(StrictModel):
|
|
68
90
|
candidate_key: str
|
|
69
91
|
item_id: str
|
|
@@ -90,12 +112,18 @@ class KnowledgeReviewCandidate(StrictModel):
|
|
|
90
112
|
last_feedback_kind: KnowledgeFeedbackKind | None = None
|
|
91
113
|
distinct_session_count: int = Field(ge=0)
|
|
92
114
|
changed_at: datetime | None = None
|
|
115
|
+
ai_review_evidence_token: str = Field(
|
|
116
|
+
min_length=64,
|
|
117
|
+
max_length=64,
|
|
118
|
+
pattern=r'^[0-9a-f]{64}$',
|
|
119
|
+
)
|
|
93
120
|
|
|
94
121
|
|
|
95
122
|
class KnowledgeReviewCandidateRequest(StrictModel):
|
|
96
123
|
personal_space_id: str = Field(min_length=1, max_length=128)
|
|
97
124
|
review_id: str = Field(min_length=1, max_length=256)
|
|
98
125
|
limit: int = Field(default=10, ge=1, le=50)
|
|
126
|
+
offset: int = Field(default=0, ge=0, le=1000000)
|
|
99
127
|
|
|
100
128
|
|
|
101
129
|
class KnowledgeReviewCandidatePage(StrictModel):
|
|
@@ -115,6 +143,28 @@ class KnowledgeReviewProgress(StrictModel):
|
|
|
115
143
|
)
|
|
116
144
|
outcome: KnowledgeReviewOutcome
|
|
117
145
|
note: str | None = Field(default=None, min_length=1, max_length=2000)
|
|
146
|
+
ai_review_evidence_token: str | None = Field(
|
|
147
|
+
default=None,
|
|
148
|
+
min_length=64,
|
|
149
|
+
max_length=64,
|
|
150
|
+
pattern=r'^[0-9a-f]{64}$',
|
|
151
|
+
)
|
|
152
|
+
ai_assessment: KnowledgeReviewAIAssessment | None = None
|
|
153
|
+
|
|
154
|
+
@model_validator(mode='after')
|
|
155
|
+
def validate_ai_review_fields(self):
|
|
156
|
+
has_token = self.ai_review_evidence_token is not None
|
|
157
|
+
has_assessment = self.ai_assessment is not None
|
|
158
|
+
if self.outcome == 'ai_reviewed':
|
|
159
|
+
if not has_token or not has_assessment:
|
|
160
|
+
raise ValueError(
|
|
161
|
+
'ai_reviewed requires an evidence token and assessment'
|
|
162
|
+
)
|
|
163
|
+
elif has_token or has_assessment:
|
|
164
|
+
raise ValueError(
|
|
165
|
+
'AI assessment fields are only valid for ai_reviewed outcomes'
|
|
166
|
+
)
|
|
167
|
+
return self
|
|
118
168
|
|
|
119
169
|
|
|
120
170
|
class KnowledgeReviewDecision(StrictModel):
|
|
@@ -125,6 +175,15 @@ class KnowledgeReviewDecision(StrictModel):
|
|
|
125
175
|
note: str | None = None
|
|
126
176
|
created_at: datetime
|
|
127
177
|
updated_at: datetime
|
|
178
|
+
ai_review_evidence_token: str | None = Field(
|
|
179
|
+
default=None,
|
|
180
|
+
min_length=64,
|
|
181
|
+
max_length=64,
|
|
182
|
+
pattern=r'^[0-9a-f]{64}$',
|
|
183
|
+
)
|
|
184
|
+
ai_assessment: KnowledgeReviewAIAssessment | None = None
|
|
185
|
+
ai_review_scope: KnowledgeReviewScope | None = None
|
|
186
|
+
ai_review_project_id: str | None = Field(default=None, min_length=1, max_length=128)
|
|
128
187
|
|
|
129
188
|
|
|
130
189
|
class KnowledgeReviewFinish(StrictModel):
|
|
@@ -1116,7 +1116,9 @@ OPTIONAL MATCH (old_assertion:Entity {
|
|
|
1116
1116
|
})
|
|
1117
1117
|
SET preview.used_at = $changed_at
|
|
1118
1118
|
FOREACH (_ IN CASE WHEN old_assertion IS NULL THEN [] ELSE [1] END |
|
|
1119
|
-
SET old_assertion.
|
|
1119
|
+
SET old_assertion.fuli_ai_review_lock_version =
|
|
1120
|
+
coalesce(old_assertion.fuli_ai_review_lock_version, 0) + 1,
|
|
1121
|
+
old_assertion.fuli_invalid_at = $changed_at,
|
|
1120
1122
|
old_assertion.expired_at = $changed_at
|
|
1121
1123
|
)
|
|
1122
1124
|
SET current.candidate_version = $candidate_version,
|
|
@@ -1176,6 +1178,8 @@ FOREACH (_ IN CASE WHEN $global_assertion_id IS NULL THEN [] ELSE [1] END |
|
|
|
1176
1178
|
assertion.fuli_distinct_task_count = 0,
|
|
1177
1179
|
assertion.fuli_usage_generation = 1,
|
|
1178
1180
|
assertion.fuli_invalid_at = null
|
|
1181
|
+
SET assertion.fuli_ai_review_lock_version =
|
|
1182
|
+
coalesce(assertion.fuli_ai_review_lock_version, 0) + 1
|
|
1179
1183
|
MERGE (episode:Episodic {uuid: $episode_id})
|
|
1180
1184
|
ON CREATE SET episode.group_id = $group_id,
|
|
1181
1185
|
episode.name = 'Personal-global preference human review',
|
|
@@ -19,6 +19,18 @@ from .models import (
|
|
|
19
19
|
from .provider_values import native_datetime as _native_datetime
|
|
20
20
|
|
|
21
21
|
|
|
22
|
+
def _item_match(item_kind: str, variable: str, id_parameter: str) -> str:
|
|
23
|
+
if item_kind == 'entity':
|
|
24
|
+
return (
|
|
25
|
+
f'MATCH ({variable}:Entity '
|
|
26
|
+
f'{{uuid: ${id_parameter}, group_id: $group_id}})'
|
|
27
|
+
)
|
|
28
|
+
return (
|
|
29
|
+
f'MATCH ()-[{variable}:RELATES_TO '
|
|
30
|
+
f'{{uuid: ${id_parameter}, group_id: $group_id}}]->()'
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
22
34
|
async def defer_preference_conflict(
|
|
23
35
|
store,
|
|
24
36
|
actor: dict,
|
|
@@ -44,9 +56,42 @@ async def defer_preference_conflict(
|
|
|
44
56
|
)
|
|
45
57
|
_validate_pair_scope(left, right, request)
|
|
46
58
|
deferred_at = datetime.now(timezone.utc)
|
|
59
|
+
left_match = _item_match(
|
|
60
|
+
request.left_item_kind, 'left', 'left_item_id'
|
|
61
|
+
)
|
|
62
|
+
right_match = _item_match(
|
|
63
|
+
request.right_item_kind, 'right', 'right_item_id'
|
|
64
|
+
)
|
|
47
65
|
records, _, _ = await store.runtime.driver.execute_query(
|
|
48
66
|
'''
|
|
49
67
|
MATCH (space:FuliSpace {id: $space_id, kind: 'personal'})
|
|
68
|
+
'''
|
|
69
|
+
+ left_match
|
|
70
|
+
+ '\n '
|
|
71
|
+
+ right_match
|
|
72
|
+
+ '''
|
|
73
|
+
WITH space,
|
|
74
|
+
CASE
|
|
75
|
+
WHEN $left_item_kind + ':' + left.uuid =
|
|
76
|
+
$right_item_kind + ':' + right.uuid
|
|
77
|
+
THEN [{kind: $left_item_kind, item: left}]
|
|
78
|
+
WHEN $left_item_kind + ':' + left.uuid <
|
|
79
|
+
$right_item_kind + ':' + right.uuid
|
|
80
|
+
THEN [
|
|
81
|
+
{kind: $left_item_kind, item: left},
|
|
82
|
+
{kind: $right_item_kind, item: right}
|
|
83
|
+
]
|
|
84
|
+
ELSE [
|
|
85
|
+
{kind: $right_item_kind, item: right},
|
|
86
|
+
{kind: $left_item_kind, item: left}
|
|
87
|
+
]
|
|
88
|
+
END AS lock_items
|
|
89
|
+
UNWIND lock_items AS lock_item
|
|
90
|
+
WITH space, lock_item
|
|
91
|
+
ORDER BY lock_item.kind + ':' + lock_item.item.uuid
|
|
92
|
+
SET lock_item.item.fuli_ai_review_lock_version =
|
|
93
|
+
coalesce(lock_item.item.fuli_ai_review_lock_version, 0) + 1
|
|
94
|
+
WITH space, collect(lock_item) AS _locked_items
|
|
50
95
|
MERGE (conflict:FuliPreferenceConflict {id: $conflict_id})
|
|
51
96
|
ON CREATE SET conflict.personal_space_id = $space_id,
|
|
52
97
|
conflict.preference_key = $preference_key,
|
|
@@ -70,6 +115,7 @@ async def defer_preference_conflict(
|
|
|
70
115
|
RETURN conflict
|
|
71
116
|
''',
|
|
72
117
|
space_id=space['id'],
|
|
118
|
+
group_id=space['group_id'],
|
|
73
119
|
conflict_id=request.conflict_id,
|
|
74
120
|
preference_key=request.preference_key,
|
|
75
121
|
preference_scope=request.preference_scope,
|
|
@@ -160,10 +206,12 @@ async def resolve_preference_conflict(
|
|
|
160
206
|
return await _complete(
|
|
161
207
|
store,
|
|
162
208
|
space['id'],
|
|
209
|
+
space['group_id'],
|
|
163
210
|
conflict_id,
|
|
164
211
|
request.resolution,
|
|
165
212
|
request.reason,
|
|
166
213
|
request.operation_actor,
|
|
214
|
+
conflict,
|
|
167
215
|
)
|
|
168
216
|
|
|
169
217
|
|
|
@@ -179,14 +227,16 @@ async def complete_preference_conflict(
|
|
|
179
227
|
request.personal_space_id,
|
|
180
228
|
'maintainer',
|
|
181
229
|
)
|
|
182
|
-
await _read_conflict(store, space['id'], conflict_id)
|
|
230
|
+
conflict = await _read_conflict(store, space['id'], conflict_id)
|
|
183
231
|
return await _complete(
|
|
184
232
|
store,
|
|
185
233
|
space['id'],
|
|
234
|
+
space['group_id'],
|
|
186
235
|
conflict_id,
|
|
187
236
|
request.resolution,
|
|
188
237
|
request.reason,
|
|
189
238
|
request.operation_actor,
|
|
239
|
+
conflict,
|
|
190
240
|
)
|
|
191
241
|
|
|
192
242
|
|
|
@@ -305,17 +355,52 @@ async def _split_scope(store, actor, space, conflict, left, right, request):
|
|
|
305
355
|
async def _complete(
|
|
306
356
|
store,
|
|
307
357
|
space_id,
|
|
358
|
+
group_id,
|
|
308
359
|
conflict_id,
|
|
309
360
|
resolution,
|
|
310
361
|
reason,
|
|
311
362
|
resolved_by,
|
|
363
|
+
conflict: PreferenceConflictRecord,
|
|
312
364
|
):
|
|
313
365
|
resolved_at = datetime.now(timezone.utc)
|
|
366
|
+
left_match = _item_match(
|
|
367
|
+
conflict.left_item_kind, 'left', 'left_item_id'
|
|
368
|
+
)
|
|
369
|
+
right_match = _item_match(
|
|
370
|
+
conflict.right_item_kind, 'right', 'right_item_id'
|
|
371
|
+
)
|
|
314
372
|
records, _, _ = await store.runtime.driver.execute_query(
|
|
315
373
|
'''
|
|
316
374
|
MATCH (:FuliSpace {id: $space_id, kind: 'personal'})-
|
|
317
375
|
[:HAS_PREFERENCE_CONFLICT]->
|
|
318
376
|
(conflict:FuliPreferenceConflict {id: $conflict_id})
|
|
377
|
+
'''
|
|
378
|
+
+ left_match
|
|
379
|
+
+ '\n '
|
|
380
|
+
+ right_match
|
|
381
|
+
+ '''
|
|
382
|
+
WITH conflict,
|
|
383
|
+
CASE
|
|
384
|
+
WHEN $left_item_kind + ':' + left.uuid =
|
|
385
|
+
$right_item_kind + ':' + right.uuid
|
|
386
|
+
THEN [{kind: $left_item_kind, item: left}]
|
|
387
|
+
WHEN $left_item_kind + ':' + left.uuid <
|
|
388
|
+
$right_item_kind + ':' + right.uuid
|
|
389
|
+
THEN [
|
|
390
|
+
{kind: $left_item_kind, item: left},
|
|
391
|
+
{kind: $right_item_kind, item: right}
|
|
392
|
+
]
|
|
393
|
+
ELSE [
|
|
394
|
+
{kind: $right_item_kind, item: right},
|
|
395
|
+
{kind: $left_item_kind, item: left}
|
|
396
|
+
]
|
|
397
|
+
END AS lock_items
|
|
398
|
+
UNWIND lock_items AS lock_item
|
|
399
|
+
WITH conflict, lock_item
|
|
400
|
+
ORDER BY lock_item.kind + ':' + lock_item.item.uuid
|
|
401
|
+
SET lock_item.item.fuli_ai_review_lock_version =
|
|
402
|
+
coalesce(lock_item.item.fuli_ai_review_lock_version, 0) + 1
|
|
403
|
+
WITH conflict, collect(lock_item) AS _locked_items
|
|
319
404
|
SET conflict.status = 'resolved',
|
|
320
405
|
conflict.resolution = $resolution,
|
|
321
406
|
conflict.resolved_by = $resolved_by,
|
|
@@ -325,7 +410,12 @@ async def _complete(
|
|
|
325
410
|
RETURN conflict
|
|
326
411
|
''',
|
|
327
412
|
space_id=space_id,
|
|
413
|
+
group_id=group_id,
|
|
328
414
|
conflict_id=conflict_id,
|
|
415
|
+
left_item_id=conflict.left_item_id,
|
|
416
|
+
left_item_kind=conflict.left_item_kind,
|
|
417
|
+
right_item_id=conflict.right_item_id,
|
|
418
|
+
right_item_kind=conflict.right_item_kind,
|
|
329
419
|
resolution=resolution,
|
|
330
420
|
resolved_by=resolved_by,
|
|
331
421
|
reason=reason,
|
|
@@ -494,6 +494,8 @@ class ProjectAgentTaskRecord(StrictModel):
|
|
|
494
494
|
|
|
495
495
|
class ProjectAgentTaskActivityCreate(StrictModel):
|
|
496
496
|
artifact_revision: str | None = Field(default=None, min_length=1, max_length=160)
|
|
497
|
+
expected_verification_attempt_id: str | None = Field(default=None, min_length=1, max_length=256)
|
|
498
|
+
judgment_task_context_token: str | None = Field(default=None, min_length=1, max_length=256)
|
|
497
499
|
personal_space_id: str = Field(min_length=1, max_length=128)
|
|
498
500
|
personal_project_id: str = Field(min_length=1, max_length=128)
|
|
499
501
|
task_id: str = Field(min_length=1, max_length=128)
|
|
@@ -578,11 +578,25 @@ async def _upsert_reference(
|
|
|
578
578
|
space['id'], 'knowledge-reference', item_kind, item_id, project_id
|
|
579
579
|
)
|
|
580
580
|
changed_at = datetime.now(timezone.utc)
|
|
581
|
+
item_match = (
|
|
582
|
+
'MATCH (item:Entity {uuid: $item_id, group_id: $group_id})'
|
|
583
|
+
if item_kind == 'entity'
|
|
584
|
+
else (
|
|
585
|
+
'MATCH ()-[item:RELATES_TO '
|
|
586
|
+
'{uuid: $item_id, group_id: $group_id}]->()'
|
|
587
|
+
)
|
|
588
|
+
)
|
|
581
589
|
records, _, _ = await store.runtime.driver.execute_query(
|
|
582
590
|
'''
|
|
583
591
|
MATCH (space:FuliSpace {id: $space_id, kind: 'personal'})
|
|
584
592
|
MATCH (space)-[:CONTAINS_PROJECT]->
|
|
585
593
|
(target:FuliPersonalProject {project_id: $project_id})
|
|
594
|
+
'''
|
|
595
|
+
+ item_match
|
|
596
|
+
+ '''
|
|
597
|
+
SET item.fuli_ai_review_lock_version =
|
|
598
|
+
coalesce(item.fuli_ai_review_lock_version, 0) + 1
|
|
599
|
+
WITH space, target
|
|
586
600
|
MERGE (reference:FuliKnowledgeProjectReference {
|
|
587
601
|
space_id: $space_id,
|
|
588
602
|
item_kind: $item_kind,
|
|
@@ -604,6 +618,7 @@ async def _upsert_reference(
|
|
|
604
618
|
space_id=space['id'],
|
|
605
619
|
item_kind=item_kind,
|
|
606
620
|
item_id=item_id,
|
|
621
|
+
group_id=space['group_id'],
|
|
607
622
|
project_id=project_id,
|
|
608
623
|
reference_id=reference_id,
|
|
609
624
|
source_project_id=source_project_id,
|
|
@@ -635,6 +650,31 @@ async def _upsert_conflict(
|
|
|
635
650
|
records, _, _ = await store.runtime.driver.execute_query(
|
|
636
651
|
'''
|
|
637
652
|
MATCH (space:FuliSpace {id: $space_id, kind: 'personal'})
|
|
653
|
+
MATCH (item:Entity {uuid: $item_id, group_id: $group_id})
|
|
654
|
+
MATCH (target_item:Entity {
|
|
655
|
+
uuid: $target_item_id,
|
|
656
|
+
group_id: $group_id
|
|
657
|
+
})
|
|
658
|
+
WITH space,
|
|
659
|
+
CASE
|
|
660
|
+
WHEN item.uuid = target_item.uuid
|
|
661
|
+
THEN [{kind: 'entity', item: item}]
|
|
662
|
+
WHEN item.uuid < target_item.uuid
|
|
663
|
+
THEN [
|
|
664
|
+
{kind: 'entity', item: item},
|
|
665
|
+
{kind: 'entity', item: target_item}
|
|
666
|
+
]
|
|
667
|
+
ELSE [
|
|
668
|
+
{kind: 'entity', item: target_item},
|
|
669
|
+
{kind: 'entity', item: item}
|
|
670
|
+
]
|
|
671
|
+
END AS lock_items
|
|
672
|
+
UNWIND lock_items AS lock_item
|
|
673
|
+
WITH space, lock_item
|
|
674
|
+
ORDER BY lock_item.kind + ':' + lock_item.item.uuid
|
|
675
|
+
SET lock_item.item.fuli_ai_review_lock_version =
|
|
676
|
+
coalesce(lock_item.item.fuli_ai_review_lock_version, 0) + 1
|
|
677
|
+
WITH space, collect(lock_item) AS _locked_items
|
|
638
678
|
MERGE (conflict:FuliKnowledgeConflict {id: $conflict_id})
|
|
639
679
|
ON CREATE SET conflict.created_at = $changed_at
|
|
640
680
|
SET conflict.space_id = $space_id,
|
|
@@ -651,6 +691,7 @@ async def _upsert_conflict(
|
|
|
651
691
|
RETURN conflict
|
|
652
692
|
''',
|
|
653
693
|
space_id=space['id'],
|
|
694
|
+
group_id=space['group_id'],
|
|
654
695
|
conflict_id=conflict_id,
|
|
655
696
|
item_id=item_id,
|
|
656
697
|
target_item_id=target_item_id,
|
|
@@ -656,6 +656,10 @@ class GraphitiRuntime:
|
|
|
656
656
|
OR item.fuli_usage_generation IS NULL
|
|
657
657
|
OR item.fuli_inheritance_mode IS NULL
|
|
658
658
|
OR item.fuli_inherited_project_ids IS NULL
|
|
659
|
+
WITH item
|
|
660
|
+
ORDER BY 'entity:' + item.uuid
|
|
661
|
+
SET item.fuli_ai_review_lock_version =
|
|
662
|
+
coalesce(item.fuli_ai_review_lock_version, 0) + 1
|
|
659
663
|
SET item.fuli_utility_score =
|
|
660
664
|
coalesce(item.fuli_utility_score, 0.0),
|
|
661
665
|
item.fuli_confidence_score = coalesce(
|
|
@@ -688,6 +692,10 @@ class GraphitiRuntime:
|
|
|
688
692
|
OR item.fuli_usage_generation IS NULL
|
|
689
693
|
OR item.fuli_inheritance_mode IS NULL
|
|
690
694
|
OR item.fuli_inherited_project_ids IS NULL
|
|
695
|
+
WITH item
|
|
696
|
+
ORDER BY 'relationship:' + item.uuid
|
|
697
|
+
SET item.fuli_ai_review_lock_version =
|
|
698
|
+
coalesce(item.fuli_ai_review_lock_version, 0) + 1
|
|
691
699
|
SET item.fuli_utility_score =
|
|
692
700
|
coalesce(item.fuli_utility_score, 0.0),
|
|
693
701
|
item.fuli_confidence_score = coalesce(
|
|
@@ -43,7 +43,7 @@ class StoreAgentVerification:
|
|
|
43
43
|
task = await self._quality_task(actor, request)
|
|
44
44
|
rows, _, _ = await self.runtime.driver.execute_query(
|
|
45
45
|
'MATCH (t:FuliProjectAgentTask {task_id:$task, personal_space_id:$space})-[:HAS_VERIFICATION]->(v:FuliAgentVerification) '
|
|
46
|
-
'RETURN v.record_json AS record_json ORDER BY v.sequence', task=request.task_id, space=request.personal_space_id)
|
|
46
|
+
'RETURN v.record_json AS record_json, v.run_id AS run_id ORDER BY v.sequence', task=request.task_id, space=request.personal_space_id)
|
|
47
47
|
models = await self._quality_models(actor, request, task)
|
|
48
48
|
result = evaluate_quality_policy(complexity=task.get('complexity') or 'standard',
|
|
49
49
|
current_strategy=json.loads(task.get('effective_model_strategy_json') or '{"mode":"adaptive"}'),
|
|
@@ -53,7 +53,12 @@ class StoreAgentVerification:
|
|
|
53
53
|
if selected:
|
|
54
54
|
selected['executor_id'] = next(model['executor_id'] for model in models
|
|
55
55
|
if all(model.get(key) == selected[key] for key in ('provider', 'model', 'capability_tier', 'cost_rank')))
|
|
56
|
-
|
|
56
|
+
verified = next((row for row in rows if json.loads(row['record_json']).get('attempt_id') == result['verified_attempt_id']), None)
|
|
57
|
+
attempt = json.loads(verified['record_json']) if verified else None
|
|
58
|
+
if attempt:
|
|
59
|
+
attempt = {key: attempt.get(key) for key in ('attempt_id', 'artifact_revision', 'outcome', 'evidence_refs', 'verifier_id', 'executor_id', 'provider', 'model')}
|
|
60
|
+
attempt['run_id'] = verified.get('run_id')
|
|
61
|
+
return {**result, 'verified_attempt': attempt, 'evidence_authority': 'host_reported_test_evidence',
|
|
57
62
|
'execution_started': False, 'guidance': 'Only complete the task after passed for the current artifact revision. Blocked means no authorized preflighted model proves the required capability tier. Model selection does not prove execution.'}
|
|
58
63
|
|
|
59
64
|
async def record_agent_verification(self, actor, request):
|
|
@@ -92,7 +97,8 @@ class StoreAgentVerification:
|
|
|
92
97
|
raise HTTPException(409, 'An execution run cannot count as two verification attempts')
|
|
93
98
|
identifier = stable_uuid('verification', request.personal_space_id, request.task_id, request.attempt_id)
|
|
94
99
|
record = dict(attempt_id=request.attempt_id, outcome=request.outcome, artifact_revision=request.artifact_revision,
|
|
95
|
-
evidence_refs=request.evidence_refs, capability_tier=observed['capability_tier'], verifier_id=context['project_agent_id']
|
|
100
|
+
evidence_refs=request.evidence_refs, capability_tier=observed['capability_tier'], verifier_id=context['project_agent_id'],
|
|
101
|
+
executor_id=request.executor_id, provider=request.provider, model=request.model)
|
|
96
102
|
fingerprint = store._payload_hash(request)
|
|
97
103
|
rows, _, _ = await store.runtime.driver.execute_query(
|
|
98
104
|
'MATCH (t:FuliProjectAgentTask {task_id:$task, personal_space_id:$space}) '
|
|
@@ -106,6 +112,7 @@ class StoreAgentVerification:
|
|
|
106
112
|
result = await store.query_agent_quality(actor, request)
|
|
107
113
|
await store.runtime.driver.execute_query(
|
|
108
114
|
'MATCH (t:FuliProjectAgentTask {task_id:$task, personal_space_id:$space}) '
|
|
109
|
-
'SET t.quality_gate=$status, t.verified_artifact_revision=$revision',
|
|
110
|
-
task=request.task_id, space=request.personal_space_id, status=result['status'], revision=request.artifact_revision
|
|
115
|
+
'SET t.quality_gate=$status, t.verified_artifact_revision=$revision, t.verified_attempt_id=$attempt',
|
|
116
|
+
task=request.task_id, space=request.personal_space_id, status=result['status'], revision=request.artifact_revision,
|
|
117
|
+
attempt=result.get('verified_attempt_id'))
|
|
111
118
|
return result
|
|
@@ -325,6 +325,8 @@ class StoreKnowledge:
|
|
|
325
325
|
await self.runtime.driver.execute_query(
|
|
326
326
|
'''
|
|
327
327
|
UNWIND $entities AS row
|
|
328
|
+
WITH row
|
|
329
|
+
ORDER BY 'entity:' + row.uuid
|
|
328
330
|
MERGE (entity:Entity {uuid: row.uuid})
|
|
329
331
|
ON CREATE SET entity.group_id = $group_id,
|
|
330
332
|
entity.name = row.name,
|
|
@@ -365,6 +367,8 @@ class StoreKnowledge:
|
|
|
365
367
|
entity.fuli_preference_project_id = row.preference_project_id,
|
|
366
368
|
entity.fuli_preference_agent_id = row.preference_agent_id,
|
|
367
369
|
entity.fuli_attributes_json = row.attributes_json
|
|
370
|
+
SET entity.fuli_ai_review_lock_version =
|
|
371
|
+
coalesce(entity.fuli_ai_review_lock_version, 0) + 1
|
|
368
372
|
WITH count(entity) AS entity_count
|
|
369
373
|
CREATE (episode:Episodic {
|
|
370
374
|
uuid: $episode_id,
|
|
@@ -392,12 +396,95 @@ class StoreKnowledge:
|
|
|
392
396
|
})
|
|
393
397
|
WITH episode
|
|
394
398
|
UNWIND $entities AS row
|
|
399
|
+
WITH episode, row
|
|
400
|
+
ORDER BY 'entity:' + row.uuid
|
|
395
401
|
MATCH (entity:Entity {uuid: row.uuid})
|
|
396
402
|
MERGE (episode)-[mention:MENTIONS {uuid: row.mention_uuid}]->(entity)
|
|
397
403
|
SET mention.group_id = $group_id,
|
|
398
404
|
mention.created_at = $created_at
|
|
399
405
|
WITH episode
|
|
406
|
+
CALL {
|
|
407
|
+
WITH episode
|
|
408
|
+
OPTIONAL MATCH ()-[old:RELATES_TO {group_id: $group_id}]->()
|
|
409
|
+
WITH collect(old) AS candidate_olds
|
|
410
|
+
UNWIND candidate_olds AS old
|
|
411
|
+
WITH old
|
|
412
|
+
WHERE old IS NOT NULL
|
|
413
|
+
AND old.invalid_at IS NULL
|
|
414
|
+
AND NOT (old.uuid IN $relationship_ids)
|
|
415
|
+
AND (
|
|
416
|
+
EXISTS {
|
|
417
|
+
MATCH (:FuliSpace {id: $space_id})-
|
|
418
|
+
[:HAS_KNOWLEDGE_ASSIGNMENT]->
|
|
419
|
+
(assignment:FuliKnowledgeAssignment {
|
|
420
|
+
item_kind: 'relationship',
|
|
421
|
+
item_id: old.uuid
|
|
422
|
+
})
|
|
423
|
+
WHERE assignment.project_id = $personal_project_id
|
|
424
|
+
}
|
|
425
|
+
OR (
|
|
426
|
+
NOT EXISTS {
|
|
427
|
+
MATCH (:FuliSpace {id: $space_id})-
|
|
428
|
+
[:HAS_KNOWLEDGE_ASSIGNMENT]->
|
|
429
|
+
(:FuliKnowledgeAssignment {
|
|
430
|
+
item_kind: 'relationship',
|
|
431
|
+
item_id: old.uuid
|
|
432
|
+
})
|
|
433
|
+
}
|
|
434
|
+
AND EXISTS {
|
|
435
|
+
MATCH (old_episode:Episodic {group_id: $group_id})
|
|
436
|
+
WHERE old_episode.uuid IN coalesce(old.episodes, [])
|
|
437
|
+
AND (
|
|
438
|
+
old_episode.fuli_personal_project_id =
|
|
439
|
+
$personal_project_id
|
|
440
|
+
OR (
|
|
441
|
+
old_episode.fuli_personal_project_id IS NULL
|
|
442
|
+
AND $personal_project_id IS NULL
|
|
443
|
+
)
|
|
444
|
+
)
|
|
445
|
+
}
|
|
446
|
+
)
|
|
447
|
+
)
|
|
448
|
+
AND EXISTS {
|
|
449
|
+
MATCH (agent_episode:Episodic {group_id: $group_id})
|
|
450
|
+
WHERE agent_episode.uuid IN coalesce(old.episodes, [])
|
|
451
|
+
AND (
|
|
452
|
+
agent_episode.fuli_project_agent_id = $project_agent_id
|
|
453
|
+
OR (
|
|
454
|
+
agent_episode.fuli_project_agent_id IS NULL
|
|
455
|
+
AND $project_agent_id IS NULL
|
|
456
|
+
)
|
|
457
|
+
)
|
|
458
|
+
}
|
|
459
|
+
AND any(
|
|
460
|
+
replacement IN $superseded_relationships
|
|
461
|
+
WHERE replacement.key = old.fuli_key
|
|
462
|
+
)
|
|
463
|
+
WITH collect(old) AS superseded_edges
|
|
464
|
+
OPTIONAL MATCH ()-[existing:RELATES_TO {
|
|
465
|
+
group_id: $group_id
|
|
466
|
+
}]->()
|
|
467
|
+
WITH superseded_edges, collect(existing) AS candidate_existing
|
|
468
|
+
RETURN superseded_edges,
|
|
469
|
+
[edge IN candidate_existing
|
|
470
|
+
WHERE edge IS NOT NULL
|
|
471
|
+
AND edge.uuid IN $relationship_ids]
|
|
472
|
+
AS existing_edges
|
|
473
|
+
}
|
|
474
|
+
CALL {
|
|
475
|
+
WITH superseded_edges, existing_edges
|
|
476
|
+
UNWIND superseded_edges + existing_edges AS lock_edge
|
|
477
|
+
WITH lock_edge
|
|
478
|
+
WHERE lock_edge IS NOT NULL
|
|
479
|
+
ORDER BY 'relationship:' + lock_edge.uuid
|
|
480
|
+
SET lock_edge.fuli_ai_review_lock_version =
|
|
481
|
+
coalesce(lock_edge.fuli_ai_review_lock_version, 0) + 1
|
|
482
|
+
RETURN count(*) AS _locked_relationship_count
|
|
483
|
+
}
|
|
484
|
+
WITH episode, superseded_edges
|
|
400
485
|
UNWIND $relationships AS row
|
|
486
|
+
WITH episode, superseded_edges, row
|
|
487
|
+
ORDER BY 'relationship:' + row.uuid
|
|
401
488
|
MATCH (source:Entity {uuid: row.source_uuid})
|
|
402
489
|
MATCH (target:Entity {uuid: row.target_uuid})
|
|
403
490
|
MERGE (source)-[edge:RELATES_TO {uuid: row.uuid}]->(target)
|
|
@@ -435,73 +522,26 @@ class StoreKnowledge:
|
|
|
435
522
|
edge.fuli_workflow_confirmation_authority =
|
|
436
523
|
row.workflow_confirmation_authority,
|
|
437
524
|
edge.fuli_workflow_session_authority =
|
|
438
|
-
row.workflow_session_authority
|
|
525
|
+
row.workflow_session_authority,
|
|
526
|
+
edge.fuli_ai_review_lock_version = 1
|
|
439
527
|
SET edge.episodes =
|
|
440
528
|
CASE WHEN $episode_id IN coalesce(edge.episodes, [])
|
|
441
529
|
THEN edge.episodes
|
|
442
530
|
ELSE coalesce(edge.episodes, []) + $episode_id END
|
|
443
|
-
WITH count(edge) AS edge_count
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
EXISTS {
|
|
449
|
-
MATCH (:FuliSpace {id: $space_id})-
|
|
450
|
-
[:HAS_KNOWLEDGE_ASSIGNMENT]->
|
|
451
|
-
(assignment:FuliKnowledgeAssignment {
|
|
452
|
-
item_kind: 'relationship',
|
|
453
|
-
item_id: old.uuid
|
|
454
|
-
})
|
|
455
|
-
WHERE assignment.project_id = $personal_project_id
|
|
456
|
-
}
|
|
457
|
-
OR (
|
|
458
|
-
NOT EXISTS {
|
|
459
|
-
MATCH (:FuliSpace {id: $space_id})-
|
|
460
|
-
[:HAS_KNOWLEDGE_ASSIGNMENT]->
|
|
461
|
-
(:FuliKnowledgeAssignment {
|
|
462
|
-
item_kind: 'relationship',
|
|
463
|
-
item_id: old.uuid
|
|
464
|
-
})
|
|
465
|
-
}
|
|
466
|
-
AND EXISTS {
|
|
467
|
-
MATCH (old_episode:Episodic {group_id: $group_id})
|
|
468
|
-
WHERE old_episode.uuid IN coalesce(old.episodes, [])
|
|
469
|
-
AND (
|
|
470
|
-
old_episode.fuli_personal_project_id =
|
|
471
|
-
$personal_project_id
|
|
472
|
-
OR (
|
|
473
|
-
old_episode.fuli_personal_project_id IS NULL
|
|
474
|
-
AND $personal_project_id IS NULL
|
|
475
|
-
)
|
|
476
|
-
)
|
|
477
|
-
}
|
|
478
|
-
)
|
|
479
|
-
)
|
|
480
|
-
AND EXISTS {
|
|
481
|
-
MATCH (agent_episode:Episodic {group_id: $group_id})
|
|
482
|
-
WHERE agent_episode.uuid IN coalesce(old.episodes, [])
|
|
483
|
-
AND (
|
|
484
|
-
agent_episode.fuli_project_agent_id = $project_agent_id
|
|
485
|
-
OR (
|
|
486
|
-
agent_episode.fuli_project_agent_id IS NULL
|
|
487
|
-
AND $project_agent_id IS NULL
|
|
488
|
-
)
|
|
489
|
-
)
|
|
490
|
-
}
|
|
491
|
-
AND any(
|
|
531
|
+
WITH episode, superseded_edges, count(edge) AS edge_count
|
|
532
|
+
CALL {
|
|
533
|
+
WITH superseded_edges
|
|
534
|
+
UNWIND superseded_edges AS old
|
|
535
|
+
WITH old, head([
|
|
492
536
|
replacement IN $superseded_relationships
|
|
493
|
-
WHERE replacement.key = old.fuli_key
|
|
494
|
-
)
|
|
495
|
-
WITH edge_count, old, head([
|
|
496
|
-
replacement IN $superseded_relationships
|
|
497
|
-
WHERE replacement.key = old.fuli_key | replacement
|
|
498
|
-
]) AS replacement
|
|
499
|
-
FOREACH (_ IN CASE WHEN old IS NULL THEN [] ELSE [1] END |
|
|
537
|
+
WHERE replacement.key = old.fuli_key | replacement
|
|
538
|
+
]) AS replacement
|
|
500
539
|
SET old.invalid_at = $reference_time,
|
|
501
540
|
old.expired_at = $created_at,
|
|
502
541
|
old.fuli_replaced_by_item_id = replacement.replacement_id,
|
|
503
542
|
old.fuli_replaced_by_item_kind = 'relationship'
|
|
504
|
-
|
|
543
|
+
RETURN count(*) AS _superseded_relationship_count
|
|
544
|
+
}
|
|
505
545
|
RETURN edge_count
|
|
506
546
|
''',
|
|
507
547
|
entities=[
|
|
@@ -24,7 +24,7 @@ class StoreProjectAgentAttention:
|
|
|
24
24
|
self, actor, space, request.personal_project_id, agent_id,
|
|
25
25
|
require_active=True,
|
|
26
26
|
allow_unassigned=agent_id in {
|
|
27
|
-
'employee.bole', 'employee.jefa', 'fuli-project-coordinator',
|
|
27
|
+
'employee.bole', 'employee.jefa', 'employee.tonborg', 'fuli-project-coordinator',
|
|
28
28
|
},
|
|
29
29
|
)
|
|
30
30
|
if request.task_id:
|