fuli-context 0.7.3 → 0.7.4-beta.1
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/README.md +170 -18
- package/README.zh-CN.md +146 -12
- package/compose.graphiti.yml +2 -0
- package/dist/web/assets/{AboutPage-qeO_vm0T.js → AboutPage-CftRexSY.js} +1 -1
- package/dist/web/assets/{ConnectionsPage-DO0GR9Xg.js → ConnectionsPage-D5J91x6d.js} +1 -1
- package/dist/web/assets/{useMinimumLoadingDisplay-BpCyBeko.css → KnowledgeEditDialog-DWUV_NPp.css} +1 -1
- package/dist/web/assets/KnowledgeEditDialog-wotVahvr.js +1 -0
- package/dist/web/assets/{KnowledgeInspector-Bjhdu2mf.css → KnowledgeInspector--1NujtXW.css} +1 -1
- package/dist/web/assets/KnowledgeInspector-BzkW0mVs.js +1 -0
- package/dist/web/assets/KnowledgeOrganizerPage-BebUDiHt.js +1 -0
- package/dist/web/assets/KnowledgePage-OUT07aKH.js +1 -0
- package/dist/web/assets/KnowledgeWorkspace-1pHJ0MNZ.js +4 -0
- package/dist/web/assets/{OverviewPage-CmCcDCAs.js → OverviewPage-CHE6XhdS.js} +1 -1
- package/dist/web/assets/PersonalProfilePage-B7T09bow.css +1 -0
- package/dist/web/assets/PersonalProfilePage-CXXiabUy.js +1 -0
- package/dist/web/assets/PersonalProjectsPage-d2wYGW8n.js +1 -0
- package/dist/web/assets/PublicProjectsPage-hxLShtnZ.js +1 -0
- package/dist/web/assets/{ReviewPage-B3vnZ6gn.js → ReviewPage-DUeyu7E8.js} +1 -1
- package/dist/web/assets/{SearchableSelect-ih69qbcS.js → SearchableSelect-C5RiB5F8.js} +1 -1
- package/dist/web/assets/SettingsPage-BMuASSzQ.js +1 -0
- package/dist/web/assets/SettingsPage-CAfz4qiV.css +1 -0
- package/dist/web/assets/WritingTastePage-B9BCYIwQ.js +1 -0
- package/dist/web/assets/WritingTastePage-DSgvTH6x.css +1 -0
- package/dist/web/assets/{console-DE40hXGC.js → console-DMvHxKf9.js} +1 -1
- package/dist/web/assets/conversation-launcher-settings-Dnlcw7Rj.js +1 -0
- package/dist/web/assets/{i18n-CDxBs1sz.js → i18n-CWfd8Dtt.js} +1 -1
- package/dist/web/assets/{identity-BCxb1mcy.js → identity-C9mLdR8L.js} +1 -1
- package/dist/web/assets/index-F5e2sQ7p.js +2 -0
- package/dist/web/assets/{index-CCOGfZzE.css → index-X2j29j8i.css} +1 -1
- package/dist/web/assets/{model-CCMdt10Q.js → model-CsYAR8O3.js} +1 -1
- package/dist/web/assets/paths-CDREouhi.js +1 -0
- package/dist/web/assets/useApi-CROJJdhE-BLQiPVw0.js +1 -0
- package/dist/web/assets/vue-router-DN8x6iY1.js +1 -0
- package/dist/web/index.html +7 -6
- package/graph-provider/fuli_graph/app.py +238 -2
- package/graph-provider/fuli_graph/auth.py +18 -0
- package/graph-provider/fuli_graph/collaboration_context.py +253 -17
- package/graph-provider/fuli_graph/common_knowledge.py +6 -2
- package/graph-provider/fuli_graph/config.py +5 -0
- package/graph-provider/fuli_graph/graph_models.py +6 -0
- package/graph-provider/fuli_graph/graph_projection.py +14 -0
- package/graph-provider/fuli_graph/graph_query.py +27 -3
- package/graph-provider/fuli_graph/knowledge_management.py +19 -1
- package/graph-provider/fuli_graph/knowledge_search.py +11 -0
- package/graph-provider/fuli_graph/models.py +15 -2
- package/graph-provider/fuli_graph/personal_global_preference_models.py +234 -0
- package/graph-provider/fuli_graph/personal_global_preferences.py +1247 -0
- package/graph-provider/fuli_graph/project_action_models.py +22 -0
- package/graph-provider/fuli_graph/project_knowledge.py +121 -2
- package/graph-provider/fuli_graph/provider_values.py +24 -0
- package/graph-provider/fuli_graph/runtime.py +78 -0
- package/graph-provider/fuli_graph/search_projection.py +14 -1
- package/graph-provider/fuli_graph/store_knowledge.py +116 -2
- package/graph-provider/fuli_graph/workflow_candidate_models.py +168 -0
- package/graph-provider/fuli_graph/workflow_candidates.py +996 -0
- package/graph-provider/fuli_graph/workflow_observation_models.py +30 -0
- package/graph-provider/fuli_graph/workflow_observations.py +84 -0
- package/graph-provider/pyproject.toml +1 -1
- package/npm-shrinkwrap.json +18 -18
- package/package.json +4 -4
- package/skills/capturing-session-knowledge/SKILL.md +32 -5
- package/skills/flreview/SKILL.md +4 -3
- package/src/agent-tools/graph-definitions.js +136 -20
- package/src/agent-tools/graph-handlers.js +10 -4
- package/src/cli/command-registry.js +1 -1
- package/src/cli/local-runtime-command.js +69 -2
- package/src/graphiti/agent-knowledge-workflows.js +497 -16
- package/src/graphiti/collaboration-preference-projection.js +162 -0
- package/src/graphiti/federated-application.js +155 -161
- package/src/graphiti/federated-search-support.js +102 -0
- package/src/graphiti/knowledge-provider-mapping.js +15 -0
- package/src/graphiti/provider-client.js +162 -10
- package/src/graphiti/related-project-suggestions.js +109 -0
- package/src/graphiti/runtime-config.js +14 -0
- package/src/graphiti/source-marker.js +2 -2
- package/src/graphiti/user-taste-skill.js +241 -0
- package/src/graphiti/workflow-candidate-mapping.js +87 -0
- package/src/graphiti/workflow-observation.js +82 -0
- package/src/graphiti/writing-taste-profile-workflow.js +25 -0
- package/src/graphiti/writing-taste-profile.js +490 -0
- package/src/http/graph-api-router.js +15 -2
- package/src/mcp/context-resources.js +158 -0
- package/src/mcp/create-mcp-server.js +39 -5
- package/src/mcp/instructions.js +5 -7
- package/src/mcp/session-id.js +60 -0
- package/src/mcp/task-context-registry.js +15 -3
- package/src/mcp/tool-annotations.js +8 -1
- package/src/mcp/tool-result.js +28 -4
- package/src/server.js +0 -1
- package/src/setup/claude-code-config.js +17 -6
- package/src/setup/graph-runtime.js +20 -2
- package/src/system/runtime-settings.js +121 -2
- package/src/system/system-service.js +5 -1
- package/dist/web/assets/KnowledgeInspector-DEW5lA1m.js +0 -1
- package/dist/web/assets/KnowledgeOrganizerPage-BkM568Ld.js +0 -1
- package/dist/web/assets/KnowledgePage-BAT4aZIm.js +0 -1
- package/dist/web/assets/KnowledgeWorkspace-DyGtBz--.js +0 -4
- package/dist/web/assets/PersonalProfilePage-C42mwKN8.js +0 -1
- package/dist/web/assets/PersonalProfilePage-CdmEzyvZ.css +0 -1
- package/dist/web/assets/PersonalProjectsPage-BwaXR62K.js +0 -1
- package/dist/web/assets/PublicProjectsPage-egKBulOq.js +0 -1
- package/dist/web/assets/SettingsPage-C6ouU0sR.css +0 -1
- package/dist/web/assets/SettingsPage-oLE8mNs5.js +0 -1
- package/dist/web/assets/index-BzhNfzU_.js +0 -2
- package/dist/web/assets/paths-DwHeiGMT.js +0 -1
- package/dist/web/assets/useMinimumLoadingDisplay-HdI7XAmu.js +0 -1
- package/dist/web/assets/vue-router-Drfl1F8i.js +0 -1
|
@@ -0,0 +1,1247 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import json
|
|
3
|
+
import secrets
|
|
4
|
+
from datetime import datetime, timedelta, timezone
|
|
5
|
+
|
|
6
|
+
from fastapi import HTTPException
|
|
7
|
+
|
|
8
|
+
from .models import ConfirmationBasis
|
|
9
|
+
from .personal_global_preference_models import (
|
|
10
|
+
PersonalGlobalPreferenceDecisionApply,
|
|
11
|
+
PersonalGlobalPreferenceDecisionInspection,
|
|
12
|
+
PersonalGlobalPreferenceDecisionIntent,
|
|
13
|
+
PersonalGlobalPreferenceDecisionPreview,
|
|
14
|
+
PersonalGlobalPreferenceDecisionRecord,
|
|
15
|
+
PersonalGlobalPreferenceDecisionRevision,
|
|
16
|
+
PersonalGlobalPreferenceDecisionStatusRequest,
|
|
17
|
+
PersonalGlobalPreferenceDecisionStatusResult,
|
|
18
|
+
PersonalGlobalPreferenceEligibleTargetScope,
|
|
19
|
+
PersonalGlobalPreferenceScopeOptions,
|
|
20
|
+
PersonalGlobalPreferenceScopeOptionsRequest,
|
|
21
|
+
PersonalGlobalPreferenceSourceSnapshot,
|
|
22
|
+
)
|
|
23
|
+
from .provider_values import (
|
|
24
|
+
json_object,
|
|
25
|
+
native_datetime,
|
|
26
|
+
now_utc,
|
|
27
|
+
preference_qualifiers,
|
|
28
|
+
stable_uuid,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
DECISION_PREVIEW_TTL = timedelta(minutes=10)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
async def personal_global_preference_scope_options(
|
|
36
|
+
store,
|
|
37
|
+
actor: dict,
|
|
38
|
+
candidate_id: str,
|
|
39
|
+
request: PersonalGlobalPreferenceScopeOptionsRequest,
|
|
40
|
+
) -> PersonalGlobalPreferenceScopeOptions:
|
|
41
|
+
space = await _personal_space(
|
|
42
|
+
store,
|
|
43
|
+
actor,
|
|
44
|
+
request.personal_space_id,
|
|
45
|
+
'reader',
|
|
46
|
+
)
|
|
47
|
+
snapshots = await _candidate_source_snapshots(
|
|
48
|
+
store,
|
|
49
|
+
space,
|
|
50
|
+
candidate_id,
|
|
51
|
+
request.source_items,
|
|
52
|
+
request.preference_key,
|
|
53
|
+
)
|
|
54
|
+
eligible_target_scopes = await _eligible_target_scopes(
|
|
55
|
+
store,
|
|
56
|
+
space,
|
|
57
|
+
snapshots,
|
|
58
|
+
)
|
|
59
|
+
return PersonalGlobalPreferenceScopeOptions(
|
|
60
|
+
personal_space_id=space['id'],
|
|
61
|
+
candidate_id=candidate_id,
|
|
62
|
+
candidate_version=_candidate_version(
|
|
63
|
+
snapshots,
|
|
64
|
+
eligible_target_scopes,
|
|
65
|
+
),
|
|
66
|
+
preference_key=request.preference_key,
|
|
67
|
+
source_snapshots=snapshots,
|
|
68
|
+
eligible_target_scopes=eligible_target_scopes,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
async def inspect_personal_global_preference_decision(
|
|
73
|
+
store,
|
|
74
|
+
actor: dict,
|
|
75
|
+
candidate_id: str,
|
|
76
|
+
request: PersonalGlobalPreferenceDecisionIntent,
|
|
77
|
+
) -> PersonalGlobalPreferenceDecisionInspection:
|
|
78
|
+
space = await _personal_space(
|
|
79
|
+
store,
|
|
80
|
+
actor,
|
|
81
|
+
request.personal_space_id,
|
|
82
|
+
'reader',
|
|
83
|
+
)
|
|
84
|
+
snapshots, eligible_target_scopes = await _validated_source_snapshots(
|
|
85
|
+
store,
|
|
86
|
+
space,
|
|
87
|
+
candidate_id,
|
|
88
|
+
request,
|
|
89
|
+
)
|
|
90
|
+
current_revision = await _decision_revision(
|
|
91
|
+
store,
|
|
92
|
+
space['id'],
|
|
93
|
+
candidate_id,
|
|
94
|
+
)
|
|
95
|
+
if current_revision != request.decision_revision:
|
|
96
|
+
raise HTTPException(
|
|
97
|
+
status_code=409,
|
|
98
|
+
detail='personal-global decision changed; refresh before reviewing',
|
|
99
|
+
)
|
|
100
|
+
return PersonalGlobalPreferenceDecisionInspection(
|
|
101
|
+
status='human_review_required',
|
|
102
|
+
personal_space_id=space['id'],
|
|
103
|
+
candidate_id=candidate_id,
|
|
104
|
+
candidate_version=request.candidate_version,
|
|
105
|
+
decision_revision=request.decision_revision,
|
|
106
|
+
decision=request.decision,
|
|
107
|
+
preference_key=request.preference_key,
|
|
108
|
+
target_scope=request.target_scope,
|
|
109
|
+
target_project_id=request.target_project_id,
|
|
110
|
+
eligible_target_scopes=eligible_target_scopes,
|
|
111
|
+
payload_fingerprint=_decision_payload_fingerprint(
|
|
112
|
+
candidate_id,
|
|
113
|
+
request,
|
|
114
|
+
),
|
|
115
|
+
source_snapshots=snapshots,
|
|
116
|
+
required_action=(
|
|
117
|
+
'An independent human-review channel must mint the one-time '
|
|
118
|
+
'approval token for this exact fingerprint.'
|
|
119
|
+
),
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
async def preview_personal_global_preference_decision(
|
|
124
|
+
store,
|
|
125
|
+
actor: dict,
|
|
126
|
+
candidate_id: str,
|
|
127
|
+
request: PersonalGlobalPreferenceDecisionIntent,
|
|
128
|
+
*,
|
|
129
|
+
created_at: datetime | None = None,
|
|
130
|
+
token_factory=secrets.token_urlsafe,
|
|
131
|
+
) -> PersonalGlobalPreferenceDecisionPreview:
|
|
132
|
+
space = await _personal_space(
|
|
133
|
+
store,
|
|
134
|
+
actor,
|
|
135
|
+
request.personal_space_id,
|
|
136
|
+
'maintainer',
|
|
137
|
+
)
|
|
138
|
+
snapshots, eligible_target_scopes = await _validated_source_snapshots(
|
|
139
|
+
store,
|
|
140
|
+
space,
|
|
141
|
+
candidate_id,
|
|
142
|
+
request,
|
|
143
|
+
)
|
|
144
|
+
issued_at = created_at or now_utc()
|
|
145
|
+
expires_at = issued_at + DECISION_PREVIEW_TTL
|
|
146
|
+
approval_token = token_factory(32)
|
|
147
|
+
token_hash = _sha256(approval_token)
|
|
148
|
+
payload_fingerprint = _decision_payload_fingerprint(candidate_id, request)
|
|
149
|
+
preview_id = stable_uuid(
|
|
150
|
+
space['id'],
|
|
151
|
+
'personal-global-preference-preview',
|
|
152
|
+
token_hash,
|
|
153
|
+
)
|
|
154
|
+
records, _, _ = await store.runtime.driver.execute_query(
|
|
155
|
+
_CREATE_DECISION_PREVIEW_QUERY,
|
|
156
|
+
space_id=space['id'],
|
|
157
|
+
candidate_id=candidate_id,
|
|
158
|
+
candidate_version=request.candidate_version,
|
|
159
|
+
decision_revision=request.decision_revision,
|
|
160
|
+
target_scope=request.target_scope,
|
|
161
|
+
target_project_id=request.target_project_id,
|
|
162
|
+
preview_id=preview_id,
|
|
163
|
+
token_hash=token_hash,
|
|
164
|
+
payload_fingerprint=payload_fingerprint,
|
|
165
|
+
issued_to_actor_id=actor['id'],
|
|
166
|
+
issued_channel='independent_human_review',
|
|
167
|
+
created_at=issued_at,
|
|
168
|
+
expires_at=expires_at,
|
|
169
|
+
)
|
|
170
|
+
if not records:
|
|
171
|
+
raise HTTPException(
|
|
172
|
+
status_code=409,
|
|
173
|
+
detail='personal-global decision changed before preview was created',
|
|
174
|
+
)
|
|
175
|
+
return PersonalGlobalPreferenceDecisionPreview(
|
|
176
|
+
preview_id=preview_id,
|
|
177
|
+
candidate_id=candidate_id,
|
|
178
|
+
candidate_version=request.candidate_version,
|
|
179
|
+
decision_revision=request.decision_revision,
|
|
180
|
+
target_scope=request.target_scope,
|
|
181
|
+
target_project_id=request.target_project_id,
|
|
182
|
+
eligible_target_scopes=eligible_target_scopes,
|
|
183
|
+
payload_fingerprint=payload_fingerprint,
|
|
184
|
+
approval_token=approval_token,
|
|
185
|
+
expires_at=expires_at,
|
|
186
|
+
source_snapshots=snapshots,
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
async def apply_personal_global_preference_decision(
|
|
191
|
+
store,
|
|
192
|
+
actor: dict,
|
|
193
|
+
candidate_id: str,
|
|
194
|
+
request: PersonalGlobalPreferenceDecisionApply,
|
|
195
|
+
*,
|
|
196
|
+
applied_at: datetime | None = None,
|
|
197
|
+
) -> PersonalGlobalPreferenceDecisionRecord:
|
|
198
|
+
space = await _personal_space(
|
|
199
|
+
store,
|
|
200
|
+
actor,
|
|
201
|
+
request.personal_space_id,
|
|
202
|
+
'maintainer',
|
|
203
|
+
)
|
|
204
|
+
payload_fingerprint = _decision_payload_fingerprint(candidate_id, request)
|
|
205
|
+
event_id = stable_uuid(
|
|
206
|
+
space['id'],
|
|
207
|
+
'personal-global-preference-decision-event',
|
|
208
|
+
candidate_id,
|
|
209
|
+
request.candidate_version,
|
|
210
|
+
request.idempotency_key,
|
|
211
|
+
)
|
|
212
|
+
existing_rows, _, _ = await store.runtime.driver.execute_query(
|
|
213
|
+
_DECISION_EVENT_QUERY,
|
|
214
|
+
space_id=space['id'],
|
|
215
|
+
event_id=event_id,
|
|
216
|
+
routing_='r',
|
|
217
|
+
)
|
|
218
|
+
if existing_rows:
|
|
219
|
+
if existing_rows[0].get('payload_fingerprint') != payload_fingerprint:
|
|
220
|
+
raise HTTPException(
|
|
221
|
+
status_code=409,
|
|
222
|
+
detail=(
|
|
223
|
+
'personal-global decision idempotency key was used for '
|
|
224
|
+
'another candidate version or payload'
|
|
225
|
+
),
|
|
226
|
+
)
|
|
227
|
+
return _decision_record(existing_rows[0])
|
|
228
|
+
|
|
229
|
+
snapshots, eligible_target_scopes = await _validated_source_snapshots(
|
|
230
|
+
store,
|
|
231
|
+
space,
|
|
232
|
+
candidate_id,
|
|
233
|
+
request,
|
|
234
|
+
)
|
|
235
|
+
changed_at = applied_at or now_utc()
|
|
236
|
+
decision_status = (
|
|
237
|
+
'approved' if request.decision == 'approve' else 'rejected'
|
|
238
|
+
)
|
|
239
|
+
global_assertion_id = (
|
|
240
|
+
stable_uuid(
|
|
241
|
+
space['group_id'],
|
|
242
|
+
'personal-global-preference-assertion',
|
|
243
|
+
event_id,
|
|
244
|
+
payload_fingerprint,
|
|
245
|
+
)
|
|
246
|
+
if request.decision == 'approve'
|
|
247
|
+
else None
|
|
248
|
+
)
|
|
249
|
+
episode_id = (
|
|
250
|
+
stable_uuid(
|
|
251
|
+
space['group_id'],
|
|
252
|
+
'personal-global-preference-episode',
|
|
253
|
+
event_id,
|
|
254
|
+
)
|
|
255
|
+
if global_assertion_id
|
|
256
|
+
else None
|
|
257
|
+
)
|
|
258
|
+
source_uris = sorted({
|
|
259
|
+
uri
|
|
260
|
+
for snapshot in snapshots
|
|
261
|
+
for uri in snapshot.source_uris
|
|
262
|
+
})
|
|
263
|
+
attributes = {
|
|
264
|
+
'candidateId': candidate_id,
|
|
265
|
+
'candidateVersion': request.candidate_version,
|
|
266
|
+
'decisionRevision': request.decision_revision + 1,
|
|
267
|
+
'preferenceKey': request.preference_key,
|
|
268
|
+
'targetScope': request.target_scope,
|
|
269
|
+
'targetProjectId': request.target_project_id,
|
|
270
|
+
'eligibleTargetScopes': [
|
|
271
|
+
scope.model_dump(mode='json')
|
|
272
|
+
for scope in eligible_target_scopes
|
|
273
|
+
],
|
|
274
|
+
'scopeDecision': decision_status,
|
|
275
|
+
'humanConfirmationReason': request.human_confirmation_reason,
|
|
276
|
+
'sourceItemIds': [item.item_id for item in snapshots],
|
|
277
|
+
'sourceProjectIds': [item.project_id for item in snapshots],
|
|
278
|
+
'sourceUris': source_uris,
|
|
279
|
+
'sourcePreferenceSnapshots': [
|
|
280
|
+
snapshot.model_dump(mode='json') for snapshot in snapshots
|
|
281
|
+
],
|
|
282
|
+
'searchTerms': [candidate_id, request.candidate_version],
|
|
283
|
+
}
|
|
284
|
+
confirmation_basis = ConfirmationBasis.model_validate({
|
|
285
|
+
'existence_reason': request.human_confirmation_reason,
|
|
286
|
+
'quadrant_reason': request.human_confirmation_reason,
|
|
287
|
+
'proposed_by': {
|
|
288
|
+
'kind': 'user',
|
|
289
|
+
'label': 'Independent human scope review',
|
|
290
|
+
},
|
|
291
|
+
'confirmed_by': {
|
|
292
|
+
'kind': 'user',
|
|
293
|
+
'label': 'Authenticated personal-space maintainer',
|
|
294
|
+
},
|
|
295
|
+
'confirmed_at': request.confirmed_at,
|
|
296
|
+
})
|
|
297
|
+
embedding = (
|
|
298
|
+
await store.runtime.embedder.create(request.global_title)
|
|
299
|
+
if global_assertion_id
|
|
300
|
+
else None
|
|
301
|
+
)
|
|
302
|
+
token_hash = _sha256(request.approval_token)
|
|
303
|
+
records, _, _ = await store.runtime.driver.execute_query(
|
|
304
|
+
_APPLY_DECISION_QUERY,
|
|
305
|
+
space_id=space['id'],
|
|
306
|
+
group_id=space['group_id'],
|
|
307
|
+
candidate_id=candidate_id,
|
|
308
|
+
candidate_version=request.candidate_version,
|
|
309
|
+
decision_revision=request.decision_revision,
|
|
310
|
+
decision_status=decision_status,
|
|
311
|
+
target_scope=request.target_scope,
|
|
312
|
+
target_project_id=request.target_project_id,
|
|
313
|
+
source_project_ids=sorted({
|
|
314
|
+
snapshot.project_id for snapshot in snapshots
|
|
315
|
+
}),
|
|
316
|
+
expected_parent_scopes=[
|
|
317
|
+
{
|
|
318
|
+
'target_project_id': scope.target_project_id,
|
|
319
|
+
'max_distance': scope.max_distance,
|
|
320
|
+
}
|
|
321
|
+
for scope in eligible_target_scopes
|
|
322
|
+
if scope.target_scope == 'parent_project'
|
|
323
|
+
],
|
|
324
|
+
token_hash=token_hash,
|
|
325
|
+
payload_fingerprint=payload_fingerprint,
|
|
326
|
+
event_id=event_id,
|
|
327
|
+
actor_id=actor['id'],
|
|
328
|
+
global_assertion_id=global_assertion_id,
|
|
329
|
+
episode_id=episode_id,
|
|
330
|
+
global_key=(
|
|
331
|
+
'personal-preference-convergence:'
|
|
332
|
+
f'{candidate_id}:{request.target_scope}:'
|
|
333
|
+
f'{request.target_project_id or "personal-global"}'
|
|
334
|
+
),
|
|
335
|
+
global_title=request.global_title,
|
|
336
|
+
global_instruction=request.global_instruction,
|
|
337
|
+
profile_aspect=request.profile_aspect,
|
|
338
|
+
preference_scope=(
|
|
339
|
+
'project'
|
|
340
|
+
if request.target_scope == 'parent_project'
|
|
341
|
+
else 'global'
|
|
342
|
+
),
|
|
343
|
+
preference_project_id=request.target_project_id,
|
|
344
|
+
inheritance_mode=(
|
|
345
|
+
'descendants'
|
|
346
|
+
if request.target_scope == 'parent_project'
|
|
347
|
+
else 'local_only'
|
|
348
|
+
),
|
|
349
|
+
confirmation_basis_json=json.dumps(
|
|
350
|
+
confirmation_basis.model_dump(mode='json'),
|
|
351
|
+
ensure_ascii=False,
|
|
352
|
+
sort_keys=True,
|
|
353
|
+
),
|
|
354
|
+
human_confirmation_reason=request.human_confirmation_reason,
|
|
355
|
+
attributes_json=json.dumps(
|
|
356
|
+
attributes,
|
|
357
|
+
ensure_ascii=False,
|
|
358
|
+
sort_keys=True,
|
|
359
|
+
),
|
|
360
|
+
source_uri=source_uris[0] if source_uris else None,
|
|
361
|
+
session_id=request.session_id,
|
|
362
|
+
source_items=[
|
|
363
|
+
item.model_dump(mode='json') for item in request.source_items
|
|
364
|
+
],
|
|
365
|
+
source_items_json=json.dumps(
|
|
366
|
+
[item.model_dump(mode='json') for item in request.source_items],
|
|
367
|
+
ensure_ascii=False,
|
|
368
|
+
sort_keys=True,
|
|
369
|
+
),
|
|
370
|
+
source_snapshots=[
|
|
371
|
+
_source_cas_row(snapshot) for snapshot in snapshots
|
|
372
|
+
],
|
|
373
|
+
source_snapshots_json=json.dumps(
|
|
374
|
+
[snapshot.model_dump(mode='json') for snapshot in snapshots],
|
|
375
|
+
ensure_ascii=False,
|
|
376
|
+
sort_keys=True,
|
|
377
|
+
),
|
|
378
|
+
embedding=embedding,
|
|
379
|
+
confirmed_at=request.confirmed_at,
|
|
380
|
+
changed_at=changed_at,
|
|
381
|
+
expires_after=changed_at,
|
|
382
|
+
)
|
|
383
|
+
if not records:
|
|
384
|
+
raise HTTPException(
|
|
385
|
+
status_code=409,
|
|
386
|
+
detail=(
|
|
387
|
+
'personal-global review token is invalid, expired, used, '
|
|
388
|
+
'stale, or another decision won this revision'
|
|
389
|
+
),
|
|
390
|
+
)
|
|
391
|
+
return _decision_record(records[0])
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
async def personal_global_preference_decision_status(
|
|
395
|
+
store,
|
|
396
|
+
actor: dict,
|
|
397
|
+
request: PersonalGlobalPreferenceDecisionStatusRequest,
|
|
398
|
+
) -> PersonalGlobalPreferenceDecisionStatusResult:
|
|
399
|
+
space = await _personal_space(
|
|
400
|
+
store,
|
|
401
|
+
actor,
|
|
402
|
+
request.personal_space_id,
|
|
403
|
+
'reader',
|
|
404
|
+
)
|
|
405
|
+
rows, _, _ = await store.runtime.driver.execute_query(
|
|
406
|
+
_DECISION_STATUS_QUERY,
|
|
407
|
+
space_id=space['id'],
|
|
408
|
+
candidates=[item.model_dump() for item in request.candidates],
|
|
409
|
+
routing_='r',
|
|
410
|
+
)
|
|
411
|
+
decisions = []
|
|
412
|
+
revisions = []
|
|
413
|
+
for row in rows:
|
|
414
|
+
value = dict(row)
|
|
415
|
+
revisions.append(PersonalGlobalPreferenceDecisionRevision(
|
|
416
|
+
candidate_id=value['candidate_id'],
|
|
417
|
+
decision_revision=int(value.get('decision_revision') or 0),
|
|
418
|
+
current_candidate_version=value.get('current_candidate_version'),
|
|
419
|
+
))
|
|
420
|
+
if (
|
|
421
|
+
value.get('decision_event_id')
|
|
422
|
+
and value.get('current_candidate_version')
|
|
423
|
+
== value.get('requested_candidate_version')
|
|
424
|
+
):
|
|
425
|
+
decisions.append(_decision_record(value))
|
|
426
|
+
return PersonalGlobalPreferenceDecisionStatusResult(
|
|
427
|
+
personal_space_id=space['id'],
|
|
428
|
+
decisions=decisions,
|
|
429
|
+
revisions=revisions,
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
async def _validated_source_snapshots(
|
|
434
|
+
store,
|
|
435
|
+
space: dict,
|
|
436
|
+
candidate_id: str,
|
|
437
|
+
request: PersonalGlobalPreferenceDecisionIntent,
|
|
438
|
+
) -> tuple[
|
|
439
|
+
list[PersonalGlobalPreferenceSourceSnapshot],
|
|
440
|
+
list[PersonalGlobalPreferenceEligibleTargetScope],
|
|
441
|
+
]:
|
|
442
|
+
snapshots = await _candidate_source_snapshots(
|
|
443
|
+
store,
|
|
444
|
+
space,
|
|
445
|
+
candidate_id,
|
|
446
|
+
request.source_items,
|
|
447
|
+
request.preference_key,
|
|
448
|
+
)
|
|
449
|
+
eligible_target_scopes = await _eligible_target_scopes(
|
|
450
|
+
store,
|
|
451
|
+
space,
|
|
452
|
+
snapshots,
|
|
453
|
+
)
|
|
454
|
+
if _candidate_version(
|
|
455
|
+
snapshots,
|
|
456
|
+
eligible_target_scopes,
|
|
457
|
+
) != request.candidate_version:
|
|
458
|
+
raise HTTPException(
|
|
459
|
+
status_code=409,
|
|
460
|
+
detail=(
|
|
461
|
+
'candidate source or eligible target scope version changed; '
|
|
462
|
+
'rediscover before reviewing'
|
|
463
|
+
),
|
|
464
|
+
)
|
|
465
|
+
requested_target = (
|
|
466
|
+
request.target_scope,
|
|
467
|
+
request.target_project_id,
|
|
468
|
+
)
|
|
469
|
+
eligible_targets = {
|
|
470
|
+
(scope.target_scope, scope.target_project_id)
|
|
471
|
+
for scope in eligible_target_scopes
|
|
472
|
+
}
|
|
473
|
+
if requested_target not in eligible_targets:
|
|
474
|
+
raise HTTPException(
|
|
475
|
+
status_code=409,
|
|
476
|
+
detail=(
|
|
477
|
+
'requested target scope is not an active, human-authorized '
|
|
478
|
+
'common scope for every source project'
|
|
479
|
+
),
|
|
480
|
+
)
|
|
481
|
+
return snapshots, eligible_target_scopes
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
async def _candidate_source_snapshots(
|
|
485
|
+
store,
|
|
486
|
+
space: dict,
|
|
487
|
+
candidate_id: str,
|
|
488
|
+
source_items,
|
|
489
|
+
requested_preference_key: str,
|
|
490
|
+
) -> list[PersonalGlobalPreferenceSourceSnapshot]:
|
|
491
|
+
expected_candidate_id = _candidate_id(
|
|
492
|
+
[item.item_id for item in source_items]
|
|
493
|
+
)
|
|
494
|
+
if candidate_id != expected_candidate_id:
|
|
495
|
+
raise HTTPException(
|
|
496
|
+
status_code=409,
|
|
497
|
+
detail='candidate id does not match the selected source preferences',
|
|
498
|
+
)
|
|
499
|
+
source_refs = [item.model_dump() for item in source_items]
|
|
500
|
+
records = []
|
|
501
|
+
entity_sources = [
|
|
502
|
+
item for item in source_refs if item['item_kind'] == 'entity'
|
|
503
|
+
]
|
|
504
|
+
relationship_sources = [
|
|
505
|
+
item for item in source_refs if item['item_kind'] == 'relationship'
|
|
506
|
+
]
|
|
507
|
+
if entity_sources:
|
|
508
|
+
rows, _, _ = await store.runtime.driver.execute_query(
|
|
509
|
+
_SOURCE_ENTITY_QUERY,
|
|
510
|
+
group_id=space['group_id'],
|
|
511
|
+
sources=entity_sources,
|
|
512
|
+
routing_='r',
|
|
513
|
+
)
|
|
514
|
+
records.extend(rows)
|
|
515
|
+
if relationship_sources:
|
|
516
|
+
rows, _, _ = await store.runtime.driver.execute_query(
|
|
517
|
+
_SOURCE_RELATIONSHIP_QUERY,
|
|
518
|
+
group_id=space['group_id'],
|
|
519
|
+
sources=relationship_sources,
|
|
520
|
+
routing_='r',
|
|
521
|
+
)
|
|
522
|
+
records.extend(rows)
|
|
523
|
+
if len(records) != len(source_refs):
|
|
524
|
+
raise HTTPException(
|
|
525
|
+
status_code=409,
|
|
526
|
+
detail='one or more candidate source preferences are missing',
|
|
527
|
+
)
|
|
528
|
+
snapshot_by_key = {}
|
|
529
|
+
for record in records:
|
|
530
|
+
value = dict(record)
|
|
531
|
+
attributes = json_object(value.get('attributes_json'))
|
|
532
|
+
preference_key = (
|
|
533
|
+
attributes.get('preferenceKey')
|
|
534
|
+
or attributes.get('preference_key')
|
|
535
|
+
or value.get('key')
|
|
536
|
+
or value.get('item_id')
|
|
537
|
+
)
|
|
538
|
+
basis = json_object(value.get('confirmation_basis_json'))
|
|
539
|
+
try:
|
|
540
|
+
snapshot = PersonalGlobalPreferenceSourceSnapshot(
|
|
541
|
+
item_id=value['item_id'],
|
|
542
|
+
item_kind=value['item_kind'],
|
|
543
|
+
project_id=value['project_id'],
|
|
544
|
+
key=value.get('key') or value['item_id'],
|
|
545
|
+
preference_key=preference_key,
|
|
546
|
+
preference_qualifiers=preference_qualifiers(attributes),
|
|
547
|
+
title=value.get('title') or value['item_id'],
|
|
548
|
+
instruction=value.get('instruction') or '',
|
|
549
|
+
profile_aspect=value.get('profile_aspect'),
|
|
550
|
+
confirmation_status=value.get('confirmation_status'),
|
|
551
|
+
confirmation_basis=ConfirmationBasis.model_validate(basis),
|
|
552
|
+
human_change_version=int(
|
|
553
|
+
value.get('human_change_version') or 0
|
|
554
|
+
),
|
|
555
|
+
usage_generation=int(value.get('usage_generation') or 1),
|
|
556
|
+
last_human_changed_at=native_datetime(
|
|
557
|
+
value.get('last_human_changed_at')
|
|
558
|
+
),
|
|
559
|
+
negative_evidence_count=int(
|
|
560
|
+
value.get('negative_evidence_count') or 0
|
|
561
|
+
),
|
|
562
|
+
requires_attention=value.get('requires_attention') is True,
|
|
563
|
+
last_feedback_at=native_datetime(
|
|
564
|
+
value.get('last_feedback_at')
|
|
565
|
+
),
|
|
566
|
+
source_uris=sorted(set(value.get('source_uris') or [])),
|
|
567
|
+
stored_confirmation_basis_json=(
|
|
568
|
+
value.get('confirmation_basis_json') or '{}'
|
|
569
|
+
),
|
|
570
|
+
stored_attributes_json=(
|
|
571
|
+
value.get('attributes_json') or '{}'
|
|
572
|
+
),
|
|
573
|
+
)
|
|
574
|
+
except (KeyError, TypeError, ValueError) as error:
|
|
575
|
+
raise HTTPException(
|
|
576
|
+
status_code=409,
|
|
577
|
+
detail='candidate source preference is no longer eligible',
|
|
578
|
+
) from error
|
|
579
|
+
snapshot_by_key[(snapshot.item_kind, snapshot.item_id)] = snapshot
|
|
580
|
+
snapshots = []
|
|
581
|
+
for reference in source_items:
|
|
582
|
+
snapshot = snapshot_by_key.get(
|
|
583
|
+
(reference.item_kind, reference.item_id)
|
|
584
|
+
)
|
|
585
|
+
if (
|
|
586
|
+
not snapshot
|
|
587
|
+
or snapshot.project_id != reference.project_id
|
|
588
|
+
or snapshot.preference_key != requested_preference_key
|
|
589
|
+
or snapshot.requires_attention
|
|
590
|
+
):
|
|
591
|
+
raise HTTPException(
|
|
592
|
+
status_code=409,
|
|
593
|
+
detail='candidate source preference is no longer eligible',
|
|
594
|
+
)
|
|
595
|
+
snapshots.append(snapshot)
|
|
596
|
+
snapshots.sort(key=lambda item: item.item_id)
|
|
597
|
+
return snapshots
|
|
598
|
+
|
|
599
|
+
|
|
600
|
+
async def _eligible_target_scopes(
|
|
601
|
+
store,
|
|
602
|
+
space: dict,
|
|
603
|
+
snapshots: list[PersonalGlobalPreferenceSourceSnapshot],
|
|
604
|
+
) -> list[PersonalGlobalPreferenceEligibleTargetScope]:
|
|
605
|
+
source_project_ids = sorted({item.project_id for item in snapshots})
|
|
606
|
+
rows, _, _ = await store.runtime.driver.execute_query(
|
|
607
|
+
_ELIGIBLE_PARENT_SCOPE_QUERY,
|
|
608
|
+
space_id=space['id'],
|
|
609
|
+
source_project_ids=source_project_ids,
|
|
610
|
+
source_project_count=len(source_project_ids),
|
|
611
|
+
routing_='r',
|
|
612
|
+
)
|
|
613
|
+
result = [
|
|
614
|
+
PersonalGlobalPreferenceEligibleTargetScope(
|
|
615
|
+
target_scope='parent_project',
|
|
616
|
+
target_project_id=row['target_project_id'],
|
|
617
|
+
max_distance=int(row['max_distance']),
|
|
618
|
+
)
|
|
619
|
+
for row in rows
|
|
620
|
+
if row.get('target_project_id') not in source_project_ids
|
|
621
|
+
]
|
|
622
|
+
result.sort(key=lambda item: (
|
|
623
|
+
item.max_distance or 0,
|
|
624
|
+
item.target_project_id or '',
|
|
625
|
+
))
|
|
626
|
+
result.append(PersonalGlobalPreferenceEligibleTargetScope(
|
|
627
|
+
target_scope='personal_global',
|
|
628
|
+
))
|
|
629
|
+
return result
|
|
630
|
+
|
|
631
|
+
|
|
632
|
+
async def _decision_revision(store, space_id: str, candidate_id: str) -> int:
|
|
633
|
+
rows, _, _ = await store.runtime.driver.execute_query(
|
|
634
|
+
_DECISION_REVISION_QUERY,
|
|
635
|
+
space_id=space_id,
|
|
636
|
+
candidate_id=candidate_id,
|
|
637
|
+
routing_='r',
|
|
638
|
+
)
|
|
639
|
+
return int(rows[0]['decision_revision']) if rows else 0
|
|
640
|
+
|
|
641
|
+
|
|
642
|
+
async def _personal_space(store, actor, space_id: str, role: str) -> dict:
|
|
643
|
+
store._require_personal()
|
|
644
|
+
space = await store.authorize(actor, space_id, role)
|
|
645
|
+
if space['kind'] != 'personal':
|
|
646
|
+
raise HTTPException(
|
|
647
|
+
status_code=422,
|
|
648
|
+
detail='personal-global preference decisions are personal-only',
|
|
649
|
+
)
|
|
650
|
+
return space
|
|
651
|
+
|
|
652
|
+
|
|
653
|
+
def _candidate_id(item_ids: list[str]) -> str:
|
|
654
|
+
digest = _sha256('\n'.join(sorted(item_ids)))[:20]
|
|
655
|
+
return f'personal-global-{digest}'
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
def _candidate_version(
|
|
659
|
+
snapshots: list[PersonalGlobalPreferenceSourceSnapshot],
|
|
660
|
+
eligible_target_scopes: list[
|
|
661
|
+
PersonalGlobalPreferenceEligibleTargetScope
|
|
662
|
+
],
|
|
663
|
+
) -> str:
|
|
664
|
+
source_state = [{
|
|
665
|
+
'item_id': item.item_id,
|
|
666
|
+
'item_kind': item.item_kind,
|
|
667
|
+
'project_id': item.project_id,
|
|
668
|
+
'key': item.key,
|
|
669
|
+
'preference_key': item.preference_key,
|
|
670
|
+
'preference_qualifiers': item.preference_qualifiers,
|
|
671
|
+
'title': item.title,
|
|
672
|
+
'instruction': item.instruction,
|
|
673
|
+
'profile_aspect': item.profile_aspect,
|
|
674
|
+
'confirmation_status': item.confirmation_status,
|
|
675
|
+
'confirmation_basis': item.confirmation_basis.model_dump(mode='json'),
|
|
676
|
+
'stored_confirmation_basis_json': (
|
|
677
|
+
item.stored_confirmation_basis_json
|
|
678
|
+
),
|
|
679
|
+
'stored_attributes_json': item.stored_attributes_json,
|
|
680
|
+
'human_change_version': item.human_change_version,
|
|
681
|
+
'usage_generation': item.usage_generation,
|
|
682
|
+
'last_human_changed_at': _json_datetime(item.last_human_changed_at),
|
|
683
|
+
'negative_evidence_count': item.negative_evidence_count,
|
|
684
|
+
'requires_attention': item.requires_attention,
|
|
685
|
+
'last_feedback_at': _json_datetime(item.last_feedback_at),
|
|
686
|
+
'source_uris': sorted(set(item.source_uris)),
|
|
687
|
+
} for item in sorted(snapshots, key=lambda value: value.item_id)]
|
|
688
|
+
candidate_state = {
|
|
689
|
+
'eligible_target_scopes': [
|
|
690
|
+
item.model_dump(mode='json')
|
|
691
|
+
for item in eligible_target_scopes
|
|
692
|
+
],
|
|
693
|
+
'sources': source_state,
|
|
694
|
+
}
|
|
695
|
+
canonical = json.dumps(
|
|
696
|
+
candidate_state,
|
|
697
|
+
ensure_ascii=False,
|
|
698
|
+
sort_keys=True,
|
|
699
|
+
separators=(',', ':'),
|
|
700
|
+
)
|
|
701
|
+
return f'v1:{_sha256(canonical)[:24]}'
|
|
702
|
+
|
|
703
|
+
|
|
704
|
+
def _decision_payload_fingerprint(
|
|
705
|
+
candidate_id: str,
|
|
706
|
+
request: PersonalGlobalPreferenceDecisionIntent,
|
|
707
|
+
) -> str:
|
|
708
|
+
payload = {
|
|
709
|
+
'candidate_id': candidate_id,
|
|
710
|
+
**request.model_dump(
|
|
711
|
+
mode='json',
|
|
712
|
+
exclude={'approval_token'},
|
|
713
|
+
),
|
|
714
|
+
}
|
|
715
|
+
return _sha256(json.dumps(
|
|
716
|
+
payload,
|
|
717
|
+
ensure_ascii=False,
|
|
718
|
+
sort_keys=True,
|
|
719
|
+
separators=(',', ':'),
|
|
720
|
+
))
|
|
721
|
+
|
|
722
|
+
|
|
723
|
+
def _source_cas_row(
|
|
724
|
+
snapshot: PersonalGlobalPreferenceSourceSnapshot,
|
|
725
|
+
) -> dict:
|
|
726
|
+
return {
|
|
727
|
+
'item_id': snapshot.item_id,
|
|
728
|
+
'item_kind': snapshot.item_kind,
|
|
729
|
+
'project_id': snapshot.project_id,
|
|
730
|
+
'key': snapshot.key,
|
|
731
|
+
'preference_key': snapshot.preference_key,
|
|
732
|
+
'title': snapshot.title,
|
|
733
|
+
'instruction': snapshot.instruction,
|
|
734
|
+
'profile_aspect': snapshot.profile_aspect,
|
|
735
|
+
'confirmation_status': snapshot.confirmation_status,
|
|
736
|
+
'confirmation_basis_json': (
|
|
737
|
+
snapshot.stored_confirmation_basis_json
|
|
738
|
+
),
|
|
739
|
+
'human_change_version': snapshot.human_change_version,
|
|
740
|
+
'usage_generation': snapshot.usage_generation,
|
|
741
|
+
'last_human_changed_at': snapshot.last_human_changed_at,
|
|
742
|
+
'negative_evidence_count': snapshot.negative_evidence_count,
|
|
743
|
+
'requires_attention': snapshot.requires_attention,
|
|
744
|
+
'last_feedback_at': snapshot.last_feedback_at,
|
|
745
|
+
'source_uris': snapshot.source_uris,
|
|
746
|
+
'attributes_json': snapshot.stored_attributes_json,
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
|
|
750
|
+
def _decision_record(record) -> PersonalGlobalPreferenceDecisionRecord:
|
|
751
|
+
value = dict(record)
|
|
752
|
+
return PersonalGlobalPreferenceDecisionRecord(
|
|
753
|
+
decision_event_id=value['decision_event_id'],
|
|
754
|
+
candidate_id=value['candidate_id'],
|
|
755
|
+
candidate_version=value['candidate_version'],
|
|
756
|
+
decision_revision=int(value['decision_revision']),
|
|
757
|
+
decision=value['decision'],
|
|
758
|
+
target_scope=value.get('target_scope') or 'personal_global',
|
|
759
|
+
target_project_id=value.get('target_project_id'),
|
|
760
|
+
global_assertion_id=value.get('global_assertion_id'),
|
|
761
|
+
global_assertion_active=value.get('global_assertion_active') is True,
|
|
762
|
+
decision_sequence=int(
|
|
763
|
+
value.get('decision_sequence') or value['decision_revision']
|
|
764
|
+
),
|
|
765
|
+
decided_at=native_datetime(value['decided_at']),
|
|
766
|
+
human_confirmation_reason=value['human_confirmation_reason'],
|
|
767
|
+
)
|
|
768
|
+
|
|
769
|
+
|
|
770
|
+
def _json_datetime(value: datetime | None) -> str | None:
|
|
771
|
+
if value is None:
|
|
772
|
+
return None
|
|
773
|
+
native = native_datetime(value)
|
|
774
|
+
if native.tzinfo is None:
|
|
775
|
+
native = native.replace(tzinfo=timezone.utc)
|
|
776
|
+
return native.astimezone(timezone.utc).isoformat().replace('+00:00', 'Z')
|
|
777
|
+
|
|
778
|
+
|
|
779
|
+
def _sha256(value: str) -> str:
|
|
780
|
+
return hashlib.sha256(value.encode('utf-8')).hexdigest()
|
|
781
|
+
|
|
782
|
+
|
|
783
|
+
_SOURCE_ENTITY_QUERY = '''
|
|
784
|
+
/* fuli:personal-global-source-entities */
|
|
785
|
+
UNWIND $sources AS source
|
|
786
|
+
MATCH (item:Entity {
|
|
787
|
+
group_id: $group_id,
|
|
788
|
+
uuid: source.item_id
|
|
789
|
+
})
|
|
790
|
+
WHERE item.fuli_invalid_at IS NULL
|
|
791
|
+
AND item.fuli_profile_aspect IS NOT NULL
|
|
792
|
+
AND item.fuli_preference_scope = 'project'
|
|
793
|
+
AND item.fuli_preference_project_id = source.project_id
|
|
794
|
+
AND coalesce(item.fuli_confirmation_status, 'pending')
|
|
795
|
+
IN ['confirmed', 'agent_confirmed']
|
|
796
|
+
OPTIONAL MATCH (episode:Episodic {group_id: $group_id})-[:MENTIONS]->(item)
|
|
797
|
+
WITH source, item,
|
|
798
|
+
[uri IN collect(DISTINCT episode.fuli_source_uri)
|
|
799
|
+
WHERE uri IS NOT NULL] AS source_uris
|
|
800
|
+
RETURN item.uuid AS item_id,
|
|
801
|
+
'entity' AS item_kind,
|
|
802
|
+
item.fuli_preference_project_id AS project_id,
|
|
803
|
+
coalesce(item.fuli_key, item.uuid) AS key,
|
|
804
|
+
item.name AS title,
|
|
805
|
+
coalesce(item.summary, '') AS instruction,
|
|
806
|
+
item.fuli_profile_aspect AS profile_aspect,
|
|
807
|
+
item.fuli_confirmation_status AS confirmation_status,
|
|
808
|
+
item.fuli_confirmation_basis_json AS confirmation_basis_json,
|
|
809
|
+
coalesce(item.fuli_human_change_version, 0) AS human_change_version,
|
|
810
|
+
coalesce(item.fuli_usage_generation, 1) AS usage_generation,
|
|
811
|
+
item.fuli_last_human_changed_at AS last_human_changed_at,
|
|
812
|
+
coalesce(item.fuli_negative_evidence_count, 0)
|
|
813
|
+
AS negative_evidence_count,
|
|
814
|
+
coalesce(item.fuli_requires_attention, false) AS requires_attention,
|
|
815
|
+
item.fuli_last_feedback_at AS last_feedback_at,
|
|
816
|
+
item.fuli_attributes_json AS attributes_json,
|
|
817
|
+
source_uris
|
|
818
|
+
'''
|
|
819
|
+
|
|
820
|
+
|
|
821
|
+
_SOURCE_RELATIONSHIP_QUERY = '''
|
|
822
|
+
/* fuli:personal-global-source-relationships */
|
|
823
|
+
UNWIND $sources AS source
|
|
824
|
+
MATCH ()-[item:RELATES_TO {
|
|
825
|
+
group_id: $group_id,
|
|
826
|
+
uuid: source.item_id
|
|
827
|
+
}]->()
|
|
828
|
+
WHERE item.invalid_at IS NULL
|
|
829
|
+
AND item.fuli_profile_aspect IS NOT NULL
|
|
830
|
+
AND item.fuli_preference_scope = 'project'
|
|
831
|
+
AND item.fuli_preference_project_id = source.project_id
|
|
832
|
+
AND coalesce(item.fuli_confirmation_status, 'pending')
|
|
833
|
+
IN ['confirmed', 'agent_confirmed']
|
|
834
|
+
OPTIONAL MATCH (episode:Episodic {group_id: $group_id})
|
|
835
|
+
WHERE episode.uuid IN coalesce(item.episodes, [])
|
|
836
|
+
WITH source, item,
|
|
837
|
+
[uri IN collect(DISTINCT episode.fuli_source_uri)
|
|
838
|
+
WHERE uri IS NOT NULL] AS source_uris
|
|
839
|
+
RETURN item.uuid AS item_id,
|
|
840
|
+
'relationship' AS item_kind,
|
|
841
|
+
item.fuli_preference_project_id AS project_id,
|
|
842
|
+
coalesce(item.fuli_key, item.uuid) AS key,
|
|
843
|
+
coalesce(item.name, 'Preference relationship') AS title,
|
|
844
|
+
coalesce(item.fact, '') AS instruction,
|
|
845
|
+
item.fuli_profile_aspect AS profile_aspect,
|
|
846
|
+
item.fuli_confirmation_status AS confirmation_status,
|
|
847
|
+
item.fuli_confirmation_basis_json AS confirmation_basis_json,
|
|
848
|
+
coalesce(item.fuli_human_change_version, 0) AS human_change_version,
|
|
849
|
+
coalesce(item.fuli_usage_generation, 1) AS usage_generation,
|
|
850
|
+
item.fuli_last_human_changed_at AS last_human_changed_at,
|
|
851
|
+
coalesce(item.fuli_negative_evidence_count, 0)
|
|
852
|
+
AS negative_evidence_count,
|
|
853
|
+
coalesce(item.fuli_requires_attention, false) AS requires_attention,
|
|
854
|
+
item.fuli_last_feedback_at AS last_feedback_at,
|
|
855
|
+
item.fuli_attributes_json AS attributes_json,
|
|
856
|
+
source_uris
|
|
857
|
+
'''
|
|
858
|
+
|
|
859
|
+
|
|
860
|
+
_ELIGIBLE_PARENT_SCOPE_QUERY = '''
|
|
861
|
+
/* fuli:personal-global-eligible-parent-scopes */
|
|
862
|
+
UNWIND $source_project_ids AS source_project_id
|
|
863
|
+
MATCH (space:FuliSpace {id: $space_id, kind: 'personal'})
|
|
864
|
+
-[:CONTAINS_PROJECT]->
|
|
865
|
+
(source:FuliPersonalProject {project_id: source_project_id})
|
|
866
|
+
MATCH path=(source)-[:PERSONAL_PROJECT_RELATION*1..2]->
|
|
867
|
+
(target:FuliPersonalProject)
|
|
868
|
+
WHERE all(relation IN relationships(path)
|
|
869
|
+
WHERE relation.relation_type = 'PART_OF'
|
|
870
|
+
AND relation.status = 'active'
|
|
871
|
+
AND relation.confirmation_authority = 'human_review')
|
|
872
|
+
AND EXISTS {
|
|
873
|
+
MATCH (space)-[:CONTAINS_PROJECT]->(target)
|
|
874
|
+
}
|
|
875
|
+
AND NOT target.project_id IN $source_project_ids
|
|
876
|
+
WITH target, source_project_id, min(length(path)) AS distance
|
|
877
|
+
WITH target,
|
|
878
|
+
collect(DISTINCT source_project_id) AS covered_source_project_ids,
|
|
879
|
+
max(distance) AS max_distance
|
|
880
|
+
WHERE size(covered_source_project_ids) = $source_project_count
|
|
881
|
+
RETURN target.project_id AS target_project_id,
|
|
882
|
+
max_distance
|
|
883
|
+
ORDER BY max_distance ASC, target_project_id ASC
|
|
884
|
+
'''
|
|
885
|
+
|
|
886
|
+
|
|
887
|
+
_DECISION_REVISION_QUERY = '''
|
|
888
|
+
/* fuli:personal-global-decision-revision */
|
|
889
|
+
MATCH (decision:FuliPersonalGlobalPreferenceDecision {
|
|
890
|
+
space_id: $space_id,
|
|
891
|
+
candidate_id: $candidate_id
|
|
892
|
+
})
|
|
893
|
+
RETURN coalesce(decision.decision_revision, 0) AS decision_revision
|
|
894
|
+
'''
|
|
895
|
+
|
|
896
|
+
|
|
897
|
+
_CREATE_DECISION_PREVIEW_QUERY = '''
|
|
898
|
+
/* fuli:create-personal-global-decision-preview */
|
|
899
|
+
MATCH (space:FuliSpace {id: $space_id, kind: 'personal'})
|
|
900
|
+
MERGE (decision:FuliPersonalGlobalPreferenceDecision {
|
|
901
|
+
space_id: $space_id,
|
|
902
|
+
candidate_id: $candidate_id
|
|
903
|
+
})
|
|
904
|
+
ON CREATE SET decision.decision_revision = 0,
|
|
905
|
+
decision.created_at = $created_at
|
|
906
|
+
WITH space, decision
|
|
907
|
+
WHERE decision.decision_revision = $decision_revision
|
|
908
|
+
CREATE (preview:FuliPersonalGlobalPreferenceDecisionPreview {
|
|
909
|
+
id: $preview_id,
|
|
910
|
+
space_id: $space_id,
|
|
911
|
+
candidate_id: $candidate_id,
|
|
912
|
+
candidate_version: $candidate_version,
|
|
913
|
+
decision_revision: $decision_revision,
|
|
914
|
+
target_scope: $target_scope,
|
|
915
|
+
target_project_id: $target_project_id,
|
|
916
|
+
token_hash: $token_hash,
|
|
917
|
+
payload_fingerprint: $payload_fingerprint,
|
|
918
|
+
issued_to_actor_id: $issued_to_actor_id,
|
|
919
|
+
issued_channel: $issued_channel,
|
|
920
|
+
created_at: $created_at,
|
|
921
|
+
expires_at: $expires_at,
|
|
922
|
+
used_at: null
|
|
923
|
+
})
|
|
924
|
+
RETURN preview.id AS preview_id
|
|
925
|
+
'''
|
|
926
|
+
|
|
927
|
+
|
|
928
|
+
_DECISION_EVENT_QUERY = '''
|
|
929
|
+
/* fuli:personal-global-decision-event */
|
|
930
|
+
MATCH (event:FuliPersonalGlobalPreferenceDecisionEvent {
|
|
931
|
+
space_id: $space_id,
|
|
932
|
+
id: $event_id
|
|
933
|
+
})
|
|
934
|
+
OPTIONAL MATCH (current:FuliPersonalGlobalPreferenceDecision {
|
|
935
|
+
space_id: $space_id,
|
|
936
|
+
candidate_id: event.candidate_id,
|
|
937
|
+
current_event_id: event.id
|
|
938
|
+
})
|
|
939
|
+
OPTIONAL MATCH (assertion:Entity {uuid: event.global_assertion_id})
|
|
940
|
+
RETURN event.id AS decision_event_id,
|
|
941
|
+
event.payload_fingerprint AS payload_fingerprint,
|
|
942
|
+
event.candidate_id AS candidate_id,
|
|
943
|
+
event.candidate_version AS candidate_version,
|
|
944
|
+
event.decision_revision AS decision_revision,
|
|
945
|
+
event.decision AS decision,
|
|
946
|
+
coalesce(event.target_scope, 'personal_global') AS target_scope,
|
|
947
|
+
event.target_project_id AS target_project_id,
|
|
948
|
+
event.global_assertion_id AS global_assertion_id,
|
|
949
|
+
current IS NOT NULL
|
|
950
|
+
AND event.global_assertion_id IS NOT NULL
|
|
951
|
+
AND assertion IS NOT NULL
|
|
952
|
+
AND assertion.fuli_invalid_at IS NULL AS global_assertion_active,
|
|
953
|
+
event.decision_revision AS decision_sequence,
|
|
954
|
+
event.decided_at AS decided_at,
|
|
955
|
+
event.human_confirmation_reason AS human_confirmation_reason
|
|
956
|
+
'''
|
|
957
|
+
|
|
958
|
+
|
|
959
|
+
_APPLY_DECISION_QUERY = '''
|
|
960
|
+
/* fuli:apply-personal-global-decision */
|
|
961
|
+
MATCH (space:FuliSpace {id: $space_id, kind: 'personal'})
|
|
962
|
+
MATCH (current:FuliPersonalGlobalPreferenceDecision {
|
|
963
|
+
space_id: $space_id,
|
|
964
|
+
candidate_id: $candidate_id
|
|
965
|
+
})
|
|
966
|
+
SET current.cas_lock = coalesce(current.cas_lock, 0) + 1
|
|
967
|
+
WITH space, current
|
|
968
|
+
WHERE current.decision_revision = $decision_revision
|
|
969
|
+
MATCH (preview:FuliPersonalGlobalPreferenceDecisionPreview {
|
|
970
|
+
space_id: $space_id,
|
|
971
|
+
candidate_id: $candidate_id,
|
|
972
|
+
candidate_version: $candidate_version,
|
|
973
|
+
decision_revision: $decision_revision,
|
|
974
|
+
token_hash: $token_hash,
|
|
975
|
+
payload_fingerprint: $payload_fingerprint
|
|
976
|
+
})
|
|
977
|
+
WHERE preview.used_at IS NULL
|
|
978
|
+
AND preview.expires_at > $expires_after
|
|
979
|
+
AND preview.issued_to_actor_id = $actor_id
|
|
980
|
+
CALL {
|
|
981
|
+
WITH space
|
|
982
|
+
UNWIND [source IN $source_snapshots
|
|
983
|
+
WHERE source.item_kind = 'entity'] AS source
|
|
984
|
+
OPTIONAL MATCH (item:Entity {
|
|
985
|
+
group_id: $group_id,
|
|
986
|
+
uuid: source.item_id
|
|
987
|
+
})
|
|
988
|
+
OPTIONAL MATCH (episode:Episodic {group_id: $group_id})-[:MENTIONS]->(item)
|
|
989
|
+
WITH source, item,
|
|
990
|
+
[uri IN collect(DISTINCT episode.fuli_source_uri)
|
|
991
|
+
WHERE uri IS NOT NULL] AS current_source_uris
|
|
992
|
+
WHERE item.fuli_invalid_at IS NULL
|
|
993
|
+
AND item.fuli_preference_scope = 'project'
|
|
994
|
+
AND item.fuli_preference_project_id = source.project_id
|
|
995
|
+
AND coalesce(item.fuli_key, item.uuid) = source.key
|
|
996
|
+
AND item.name = source.title
|
|
997
|
+
AND coalesce(item.summary, '') = source.instruction
|
|
998
|
+
AND item.fuli_profile_aspect = source.profile_aspect
|
|
999
|
+
AND item.fuli_confirmation_status = source.confirmation_status
|
|
1000
|
+
AND coalesce(item.fuli_confirmation_basis_json, '{}') =
|
|
1001
|
+
source.confirmation_basis_json
|
|
1002
|
+
AND coalesce(item.fuli_human_change_version, 0) =
|
|
1003
|
+
source.human_change_version
|
|
1004
|
+
AND coalesce(item.fuli_usage_generation, 1) = source.usage_generation
|
|
1005
|
+
AND (
|
|
1006
|
+
item.fuli_last_human_changed_at = source.last_human_changed_at
|
|
1007
|
+
OR (
|
|
1008
|
+
item.fuli_last_human_changed_at IS NULL
|
|
1009
|
+
AND source.last_human_changed_at IS NULL
|
|
1010
|
+
)
|
|
1011
|
+
)
|
|
1012
|
+
AND coalesce(item.fuli_negative_evidence_count, 0) =
|
|
1013
|
+
source.negative_evidence_count
|
|
1014
|
+
AND coalesce(item.fuli_requires_attention, false) =
|
|
1015
|
+
source.requires_attention
|
|
1016
|
+
AND (
|
|
1017
|
+
item.fuli_last_feedback_at = source.last_feedback_at
|
|
1018
|
+
OR (
|
|
1019
|
+
item.fuli_last_feedback_at IS NULL
|
|
1020
|
+
AND source.last_feedback_at IS NULL
|
|
1021
|
+
)
|
|
1022
|
+
)
|
|
1023
|
+
AND coalesce(item.fuli_attributes_json, '{}') = source.attributes_json
|
|
1024
|
+
AND size(current_source_uris) = size(source.source_uris)
|
|
1025
|
+
AND all(uri IN current_source_uris WHERE uri IN source.source_uris)
|
|
1026
|
+
RETURN count(item) AS valid_entity_count
|
|
1027
|
+
}
|
|
1028
|
+
CALL {
|
|
1029
|
+
WITH space
|
|
1030
|
+
UNWIND [source IN $source_snapshots
|
|
1031
|
+
WHERE source.item_kind = 'relationship'] AS source
|
|
1032
|
+
OPTIONAL MATCH ()-[item:RELATES_TO {
|
|
1033
|
+
group_id: $group_id,
|
|
1034
|
+
uuid: source.item_id
|
|
1035
|
+
}]->()
|
|
1036
|
+
OPTIONAL MATCH (episode:Episodic {group_id: $group_id})
|
|
1037
|
+
WHERE episode.uuid IN coalesce(item.episodes, [])
|
|
1038
|
+
WITH source, item,
|
|
1039
|
+
[uri IN collect(DISTINCT episode.fuli_source_uri)
|
|
1040
|
+
WHERE uri IS NOT NULL] AS current_source_uris
|
|
1041
|
+
WHERE item.invalid_at IS NULL
|
|
1042
|
+
AND item.fuli_preference_scope = 'project'
|
|
1043
|
+
AND item.fuli_preference_project_id = source.project_id
|
|
1044
|
+
AND coalesce(item.fuli_key, item.uuid) = source.key
|
|
1045
|
+
AND coalesce(item.name, 'Preference relationship') = source.title
|
|
1046
|
+
AND coalesce(item.fact, '') = source.instruction
|
|
1047
|
+
AND item.fuli_profile_aspect = source.profile_aspect
|
|
1048
|
+
AND item.fuli_confirmation_status = source.confirmation_status
|
|
1049
|
+
AND coalesce(item.fuli_confirmation_basis_json, '{}') =
|
|
1050
|
+
source.confirmation_basis_json
|
|
1051
|
+
AND coalesce(item.fuli_human_change_version, 0) =
|
|
1052
|
+
source.human_change_version
|
|
1053
|
+
AND coalesce(item.fuli_usage_generation, 1) = source.usage_generation
|
|
1054
|
+
AND (
|
|
1055
|
+
item.fuli_last_human_changed_at = source.last_human_changed_at
|
|
1056
|
+
OR (
|
|
1057
|
+
item.fuli_last_human_changed_at IS NULL
|
|
1058
|
+
AND source.last_human_changed_at IS NULL
|
|
1059
|
+
)
|
|
1060
|
+
)
|
|
1061
|
+
AND coalesce(item.fuli_negative_evidence_count, 0) =
|
|
1062
|
+
source.negative_evidence_count
|
|
1063
|
+
AND coalesce(item.fuli_requires_attention, false) =
|
|
1064
|
+
source.requires_attention
|
|
1065
|
+
AND (
|
|
1066
|
+
item.fuli_last_feedback_at = source.last_feedback_at
|
|
1067
|
+
OR (
|
|
1068
|
+
item.fuli_last_feedback_at IS NULL
|
|
1069
|
+
AND source.last_feedback_at IS NULL
|
|
1070
|
+
)
|
|
1071
|
+
)
|
|
1072
|
+
AND coalesce(item.fuli_attributes_json, '{}') = source.attributes_json
|
|
1073
|
+
AND size(current_source_uris) = size(source.source_uris)
|
|
1074
|
+
AND all(uri IN current_source_uris WHERE uri IN source.source_uris)
|
|
1075
|
+
RETURN count(item) AS valid_relationship_count
|
|
1076
|
+
}
|
|
1077
|
+
CALL {
|
|
1078
|
+
WITH space
|
|
1079
|
+
UNWIND $source_project_ids AS source_project_id
|
|
1080
|
+
MATCH (space)-[:CONTAINS_PROJECT]->
|
|
1081
|
+
(source:FuliPersonalProject {project_id: source_project_id})
|
|
1082
|
+
MATCH path=(source)-[:PERSONAL_PROJECT_RELATION*1..2]->
|
|
1083
|
+
(target:FuliPersonalProject)
|
|
1084
|
+
WHERE all(relation IN relationships(path)
|
|
1085
|
+
WHERE relation.relation_type = 'PART_OF'
|
|
1086
|
+
AND relation.status = 'active'
|
|
1087
|
+
AND relation.confirmation_authority = 'human_review')
|
|
1088
|
+
AND EXISTS {
|
|
1089
|
+
MATCH (space)-[:CONTAINS_PROJECT]->(target)
|
|
1090
|
+
}
|
|
1091
|
+
AND NOT target.project_id IN $source_project_ids
|
|
1092
|
+
WITH target, source_project_id, min(length(path)) AS distance
|
|
1093
|
+
WITH target,
|
|
1094
|
+
collect(DISTINCT source_project_id) AS covered_source_project_ids,
|
|
1095
|
+
max(distance) AS max_distance
|
|
1096
|
+
WHERE size(covered_source_project_ids) = size($source_project_ids)
|
|
1097
|
+
WITH {
|
|
1098
|
+
target_project_id: target.project_id,
|
|
1099
|
+
max_distance: max_distance
|
|
1100
|
+
} AS option
|
|
1101
|
+
ORDER BY option.max_distance ASC, option.target_project_id ASC
|
|
1102
|
+
RETURN collect(option) AS current_parent_scopes
|
|
1103
|
+
}
|
|
1104
|
+
WITH space, preview, current, valid_entity_count,
|
|
1105
|
+
valid_relationship_count, current_parent_scopes
|
|
1106
|
+
WHERE valid_entity_count = size([
|
|
1107
|
+
source IN $source_snapshots WHERE source.item_kind = 'entity'
|
|
1108
|
+
])
|
|
1109
|
+
AND valid_relationship_count = size([
|
|
1110
|
+
source IN $source_snapshots WHERE source.item_kind = 'relationship'
|
|
1111
|
+
])
|
|
1112
|
+
AND current_parent_scopes = $expected_parent_scopes
|
|
1113
|
+
OPTIONAL MATCH (old_assertion:Entity {
|
|
1114
|
+
uuid: current.global_assertion_id,
|
|
1115
|
+
group_id: $group_id
|
|
1116
|
+
})
|
|
1117
|
+
SET preview.used_at = $changed_at
|
|
1118
|
+
FOREACH (_ IN CASE WHEN old_assertion IS NULL THEN [] ELSE [1] END |
|
|
1119
|
+
SET old_assertion.fuli_invalid_at = $changed_at,
|
|
1120
|
+
old_assertion.expired_at = $changed_at
|
|
1121
|
+
)
|
|
1122
|
+
SET current.candidate_version = $candidate_version,
|
|
1123
|
+
current.decision_revision = $decision_revision + 1,
|
|
1124
|
+
current.decision = $decision_status,
|
|
1125
|
+
current.target_scope = $target_scope,
|
|
1126
|
+
current.target_project_id = $target_project_id,
|
|
1127
|
+
current.global_assertion_id = $global_assertion_id,
|
|
1128
|
+
current.current_event_id = $event_id,
|
|
1129
|
+
current.updated_at = $changed_at
|
|
1130
|
+
CREATE (event:FuliPersonalGlobalPreferenceDecisionEvent {
|
|
1131
|
+
id: $event_id,
|
|
1132
|
+
space_id: $space_id,
|
|
1133
|
+
candidate_id: $candidate_id,
|
|
1134
|
+
candidate_version: $candidate_version,
|
|
1135
|
+
decision_revision: $decision_revision + 1,
|
|
1136
|
+
decision: $decision_status,
|
|
1137
|
+
target_scope: $target_scope,
|
|
1138
|
+
target_project_id: $target_project_id,
|
|
1139
|
+
payload_fingerprint: $payload_fingerprint,
|
|
1140
|
+
global_assertion_id: $global_assertion_id,
|
|
1141
|
+
decided_by_actor_id: $actor_id,
|
|
1142
|
+
decision_channel: 'independent_human_review_token',
|
|
1143
|
+
human_confirmation_reason: $human_confirmation_reason,
|
|
1144
|
+
source_items_json: $source_items_json,
|
|
1145
|
+
source_snapshots_json: $source_snapshots_json,
|
|
1146
|
+
decided_at: $confirmed_at,
|
|
1147
|
+
recorded_at: $changed_at
|
|
1148
|
+
})
|
|
1149
|
+
FOREACH (_ IN CASE WHEN $global_assertion_id IS NULL THEN [] ELSE [1] END |
|
|
1150
|
+
MERGE (assertion:Entity {uuid: $global_assertion_id})
|
|
1151
|
+
ON CREATE SET assertion.group_id = $group_id,
|
|
1152
|
+
assertion.name = $global_title,
|
|
1153
|
+
assertion.name_embedding = $embedding,
|
|
1154
|
+
assertion.summary = $global_instruction,
|
|
1155
|
+
assertion.created_at = $changed_at,
|
|
1156
|
+
assertion.fuli_key = $global_key,
|
|
1157
|
+
assertion.fuli_type = 'PersonalPreference',
|
|
1158
|
+
assertion.fuli_origin_quadrant = 'known_known',
|
|
1159
|
+
assertion.fuli_current_quadrant = 'known_known',
|
|
1160
|
+
assertion.fuli_epistemic_status = 'confirmed',
|
|
1161
|
+
assertion.fuli_confirmation_status = 'confirmed',
|
|
1162
|
+
assertion.fuli_confirmation_basis_json =
|
|
1163
|
+
$confirmation_basis_json,
|
|
1164
|
+
assertion.fuli_reasoning_summary =
|
|
1165
|
+
$human_confirmation_reason,
|
|
1166
|
+
assertion.fuli_profile_aspect = $profile_aspect,
|
|
1167
|
+
assertion.fuli_preference_scope = $preference_scope,
|
|
1168
|
+
assertion.fuli_preference_project_id =
|
|
1169
|
+
$preference_project_id,
|
|
1170
|
+
assertion.fuli_inheritance_mode = $inheritance_mode,
|
|
1171
|
+
assertion.fuli_inherited_project_ids = [],
|
|
1172
|
+
assertion.fuli_attributes_json = $attributes_json,
|
|
1173
|
+
assertion.fuli_utility_score = 0.0,
|
|
1174
|
+
assertion.fuli_confidence_score = 1.0,
|
|
1175
|
+
assertion.fuli_qualified_use_count = 0,
|
|
1176
|
+
assertion.fuli_distinct_task_count = 0,
|
|
1177
|
+
assertion.fuli_usage_generation = 1,
|
|
1178
|
+
assertion.fuli_invalid_at = null
|
|
1179
|
+
MERGE (episode:Episodic {uuid: $episode_id})
|
|
1180
|
+
ON CREATE SET episode.group_id = $group_id,
|
|
1181
|
+
episode.name = 'Personal-global preference human review',
|
|
1182
|
+
episode.source = 'json',
|
|
1183
|
+
episode.source_description = $human_confirmation_reason,
|
|
1184
|
+
episode.content = '',
|
|
1185
|
+
episode.valid_at = $confirmed_at,
|
|
1186
|
+
episode.created_at = $changed_at,
|
|
1187
|
+
episode.entity_edges = [],
|
|
1188
|
+
episode.fuli_session_id = $session_id,
|
|
1189
|
+
episode.fuli_source_kind = 'human_scope_decision',
|
|
1190
|
+
episode.fuli_source_uri = $source_uri,
|
|
1191
|
+
episode.fuli_summary = $global_instruction,
|
|
1192
|
+
episode.fuli_sensitivity = 'private',
|
|
1193
|
+
episode.fuli_personal_project_id =
|
|
1194
|
+
$preference_project_id,
|
|
1195
|
+
episode.fuli_idempotency_key = $event_id
|
|
1196
|
+
MERGE (episode)-[mention:MENTIONS]->(assertion)
|
|
1197
|
+
ON CREATE SET mention.uuid = $event_id + ':mention',
|
|
1198
|
+
mention.group_id = $group_id,
|
|
1199
|
+
mention.created_at = $changed_at
|
|
1200
|
+
)
|
|
1201
|
+
RETURN event.id AS decision_event_id,
|
|
1202
|
+
event.candidate_id AS candidate_id,
|
|
1203
|
+
event.candidate_version AS candidate_version,
|
|
1204
|
+
event.decision_revision AS decision_revision,
|
|
1205
|
+
event.decision AS decision,
|
|
1206
|
+
event.target_scope AS target_scope,
|
|
1207
|
+
event.target_project_id AS target_project_id,
|
|
1208
|
+
event.global_assertion_id AS global_assertion_id,
|
|
1209
|
+
event.global_assertion_id IS NOT NULL AS global_assertion_active,
|
|
1210
|
+
event.decision_revision AS decision_sequence,
|
|
1211
|
+
event.decided_at AS decided_at,
|
|
1212
|
+
event.human_confirmation_reason AS human_confirmation_reason
|
|
1213
|
+
'''
|
|
1214
|
+
|
|
1215
|
+
|
|
1216
|
+
_DECISION_STATUS_QUERY = '''
|
|
1217
|
+
/* fuli:personal-global-decision-status */
|
|
1218
|
+
UNWIND $candidates AS requested
|
|
1219
|
+
OPTIONAL MATCH (current:FuliPersonalGlobalPreferenceDecision {
|
|
1220
|
+
space_id: $space_id,
|
|
1221
|
+
candidate_id: requested.candidate_id
|
|
1222
|
+
})
|
|
1223
|
+
OPTIONAL MATCH (event:FuliPersonalGlobalPreferenceDecisionEvent {
|
|
1224
|
+
space_id: $space_id,
|
|
1225
|
+
id: current.current_event_id
|
|
1226
|
+
})
|
|
1227
|
+
OPTIONAL MATCH (assertion:Entity {uuid: current.global_assertion_id})
|
|
1228
|
+
RETURN event.id AS decision_event_id,
|
|
1229
|
+
requested.candidate_id AS candidate_id,
|
|
1230
|
+
requested.candidate_version AS requested_candidate_version,
|
|
1231
|
+
current.candidate_version AS current_candidate_version,
|
|
1232
|
+
requested.candidate_version AS candidate_version,
|
|
1233
|
+
coalesce(current.decision_revision, 0) AS decision_revision,
|
|
1234
|
+
current.decision AS decision,
|
|
1235
|
+
coalesce(event.target_scope, current.target_scope,
|
|
1236
|
+
'personal_global') AS target_scope,
|
|
1237
|
+
coalesce(event.target_project_id,
|
|
1238
|
+
current.target_project_id) AS target_project_id,
|
|
1239
|
+
current.global_assertion_id AS global_assertion_id,
|
|
1240
|
+
current.decision = 'approved'
|
|
1241
|
+
AND assertion IS NOT NULL
|
|
1242
|
+
AND assertion.fuli_invalid_at IS NULL AS global_assertion_active,
|
|
1243
|
+
current.decision_revision AS decision_sequence,
|
|
1244
|
+
event.decided_at AS decided_at,
|
|
1245
|
+
event.human_confirmation_reason AS human_confirmation_reason
|
|
1246
|
+
ORDER BY current.updated_at DESC
|
|
1247
|
+
'''
|