fuli-context 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +178 -0
- package/compose.graphiti.yml +86 -0
- package/dist/web/assets/ConnectionsPage-CY6H_mJJ.js +1 -0
- package/dist/web/assets/KnowledgeInspector-BSpz5Sfm.js +1 -0
- package/dist/web/assets/KnowledgeInspector-CHbB_0rC.css +1 -0
- package/dist/web/assets/KnowledgeOrganizerPage-BlEvhPrv.css +1 -0
- package/dist/web/assets/KnowledgeOrganizerPage-CvjGPlUc.js +1 -0
- package/dist/web/assets/KnowledgePage-Khuo3cNs.js +1 -0
- package/dist/web/assets/KnowledgeWorkspace-DVZusvem.js +4 -0
- package/dist/web/assets/OverviewPage-DC4C4Fhn.js +1 -0
- package/dist/web/assets/PersonalProfilePage-CSV9Mtgz.js +1 -0
- package/dist/web/assets/PersonalProfilePage-CVJ3TcRi.css +1 -0
- package/dist/web/assets/PersonalProjectsPage-A50EaseQ.js +1 -0
- package/dist/web/assets/PublicProjectsPage-BAdO_bae.js +1 -0
- package/dist/web/assets/ReviewPage-Dztk2jDG.js +1 -0
- package/dist/web/assets/console-tbvTUi6p.js +1 -0
- package/dist/web/assets/fuli-logo-DGuyqchQ.png +0 -0
- package/dist/web/assets/identity-B3s5CXhv.js +1 -0
- package/dist/web/assets/index-Devf7glZ.css +1 -0
- package/dist/web/assets/index-DwUDhQKc.js +6 -0
- package/dist/web/assets/model-DVT8sjy9.js +1 -0
- package/dist/web/assets/paths-B790iniO.js +1 -0
- package/dist/web/assets/runtime-dom.esm-bundler-DttsK21F.js +1 -0
- package/dist/web/assets/vue-router-BYTAoWT3.js +1 -0
- package/dist/web/index.html +22 -0
- package/graph-provider/Dockerfile +16 -0
- package/graph-provider/fuli_graph/__init__.py +5 -0
- package/graph-provider/fuli_graph/app.py +547 -0
- package/graph-provider/fuli_graph/auth.py +17 -0
- package/graph-provider/fuli_graph/collaboration_context.py +290 -0
- package/graph-provider/fuli_graph/config.py +34 -0
- package/graph-provider/fuli_graph/graph_models.py +123 -0
- package/graph-provider/fuli_graph/graph_projection.py +405 -0
- package/graph-provider/fuli_graph/graph_query.py +561 -0
- package/graph-provider/fuli_graph/knowledge_audit.py +765 -0
- package/graph-provider/fuli_graph/knowledge_batch_confirmation.py +246 -0
- package/graph-provider/fuli_graph/knowledge_management.py +816 -0
- package/graph-provider/fuli_graph/knowledge_records.py +65 -0
- package/graph-provider/fuli_graph/knowledge_search.py +723 -0
- package/graph-provider/fuli_graph/knowledge_usage_models.py +52 -0
- package/graph-provider/fuli_graph/model_validation.py +58 -0
- package/graph-provider/fuli_graph/models.py +1179 -0
- package/graph-provider/fuli_graph/personal_project_access.py +29 -0
- package/graph-provider/fuli_graph/preference_conflicts.py +420 -0
- package/graph-provider/fuli_graph/project_action_models.py +142 -0
- package/graph-provider/fuli_graph/project_knowledge.py +707 -0
- package/graph-provider/fuli_graph/project_management.py +251 -0
- package/graph-provider/fuli_graph/provider_values.py +40 -0
- package/graph-provider/fuli_graph/runtime.py +350 -0
- package/graph-provider/fuli_graph/search_projection.py +156 -0
- package/graph-provider/fuli_graph/store.py +24 -0
- package/graph-provider/fuli_graph/store_identity.py +237 -0
- package/graph-provider/fuli_graph/store_knowledge.py +447 -0
- package/graph-provider/fuli_graph/store_projects.py +242 -0
- package/graph-provider/fuli_graph/store_publication.py +370 -0
- package/graph-provider/fuli_graph/store_records.py +134 -0
- package/graph-provider/pyproject.toml +35 -0
- package/npm-shrinkwrap.json +6045 -0
- package/package.json +99 -0
- package/skills/capturing-session-knowledge/SKILL.md +226 -0
- package/skills/capturing-session-knowledge/agents/openai.yaml +13 -0
- package/skills/grilling-project/SKILL.md +129 -0
- package/skills/grilling-project/agents/openai.yaml +4 -0
- package/skills/grilling-project/references/project-profile.md +105 -0
- package/src/agent/candidate-read-service.js +53 -0
- package/src/agent/context-pack-service.js +83 -0
- package/src/agent/public-read-policy.js +39 -0
- package/src/agent/public-read-service.js +69 -0
- package/src/agent/read-projection.js +107 -0
- package/src/agent/search-service.js +61 -0
- package/src/agent/space-boundary.js +33 -0
- package/src/agent-tools/graph-definitions.js +479 -0
- package/src/agent-tools/graph-handlers.js +96 -0
- package/src/agent-tools/schema.js +37 -0
- package/src/agent-tools.js +16 -0
- package/src/app/application-error.js +13 -0
- package/src/app/bootstrap.js +8 -0
- package/src/app/create-application.js +143 -0
- package/src/app/state-service.js +38 -0
- package/src/candidates.js +78 -0
- package/src/classifier.js +63 -0
- package/src/cli/command-arguments.js +14 -0
- package/src/cli/command-registry.js +78 -0
- package/src/cli/commands/candidates.js +17 -0
- package/src/cli/commands/ingestion.js +32 -0
- package/src/cli/commands/reads.js +50 -0
- package/src/cli/commands/spaces.js +20 -0
- package/src/cli/invocation.js +47 -0
- package/src/cli/local-runtime-command.js +94 -0
- package/src/cli/local-runtime-options.js +64 -0
- package/src/cli/migrate-command.js +71 -0
- package/src/cli/migration-destination.js +117 -0
- package/src/cli/setup-agent-selection.js +195 -0
- package/src/cli/setup-command.js +85 -0
- package/src/cli/uninstall-command.js +67 -0
- package/src/cli.js +90 -0
- package/src/context-pack.js +206 -0
- package/src/defaults.js +1 -0
- package/src/extractor.js +77 -0
- package/src/fact-history.js +39 -0
- package/src/facts/fact-service.js +89 -0
- package/src/graphiti/agent-access-policy.js +88 -0
- package/src/graphiti/capture-policy.js +84 -0
- package/src/graphiti/federated-application.js +1292 -0
- package/src/graphiti/personal-context.js +29 -0
- package/src/graphiti/project-path-context.js +159 -0
- package/src/graphiti/provider-client.js +288 -0
- package/src/graphiti/runtime-config.js +85 -0
- package/src/graphiti/setup.js +43 -0
- package/src/graphiti/source-marker.js +142 -0
- package/src/http/api-router.js +133 -0
- package/src/http/bounded-json-array.js +19 -0
- package/src/http/error-mapping.js +36 -0
- package/src/http/graph-api-router.js +257 -0
- package/src/http/lens-route.js +50 -0
- package/src/http/request-policy.js +41 -0
- package/src/http/response.js +29 -0
- package/src/http/static-handler.js +55 -0
- package/src/ingestion.js +161 -0
- package/src/lens/lens-correction-service.js +81 -0
- package/src/lens/lens-fact-writer.js +85 -0
- package/src/lens/lens-input.js +62 -0
- package/src/lens/lens-query-projection.js +87 -0
- package/src/lens/lens-query-ranking.js +62 -0
- package/src/lens/lens-query.js +143 -0
- package/src/lens/lens-resource-projection.js +93 -0
- package/src/lens/lens-resource-selection.js +45 -0
- package/src/lens/lens-resource-service.js +103 -0
- package/src/lens/lens-search-query.js +122 -0
- package/src/lens/lens-service.js +183 -0
- package/src/local-runtime/lifecycle.js +254 -0
- package/src/mcp/create-mcp-server.js +85 -0
- package/src/mcp/instructions.js +10 -0
- package/src/mcp/project-action-preview-tokens.js +72 -0
- package/src/mcp/runtime.js +71 -0
- package/src/mcp/tool-annotations.js +63 -0
- package/src/mcp/tool-result.js +167 -0
- package/src/mcp/tool-schema.js +60 -0
- package/src/mcp-server.js +76 -0
- package/src/models.js +44 -0
- package/src/observer.js +83 -0
- package/src/package-metadata.js +6 -0
- package/src/project-rules.js +33 -0
- package/src/publication/canonical-json.js +39 -0
- package/src/publication/outbox-service.js +46 -0
- package/src/publication/publication-envelope-schema.js +75 -0
- package/src/publication/publication-envelope.js +37 -0
- package/src/publication/publication-policy.js +82 -0
- package/src/publication/publication-service.js +48 -0
- package/src/router.js +58 -0
- package/src/runtime-options.js +139 -0
- package/src/security/episode-visibility.js +14 -0
- package/src/security/fact-visibility.js +15 -0
- package/src/security/sensitive-content.js +18 -0
- package/src/security/source-metadata-policy.js +8 -0
- package/src/server/application-lifecycle.js +34 -0
- package/src/server/listen.js +24 -0
- package/src/server.js +135 -0
- package/src/setup/agent-installation-status.js +97 -0
- package/src/setup/agents.js +123 -0
- package/src/setup/codex-bootstrap.js +118 -0
- package/src/setup/codex-config.js +122 -0
- package/src/setup/config-backup.js +22 -0
- package/src/setup/container-runtime.js +388 -0
- package/src/setup/cursor-config.js +61 -0
- package/src/setup/graph-runtime.js +360 -0
- package/src/setup/node-runtime.js +24 -0
- package/src/setup/options.js +91 -0
- package/src/setup/paths.js +47 -0
- package/src/setup/session-skill.js +91 -0
- package/src/setup/setup.js +85 -0
- package/src/setup/uninstall.js +90 -0
- package/src/storage/episode-evidence-preview.js +100 -0
- package/src/storage/fact-search-order.js +18 -0
- package/src/storage/file/episode-evidence.js +37 -0
- package/src/storage/file/episode-index.js +59 -0
- package/src/storage/file/fact-search.js +60 -0
- package/src/storage/file-store.js +437 -0
- package/src/storage/import-json.js +70 -0
- package/src/storage/json-file.js +41 -0
- package/src/storage/legacy-record-validation.js +152 -0
- package/src/storage/legacy-snapshot-validation.js +32 -0
- package/src/storage/migrate.js +128 -0
- package/src/storage/migrations/001-initial.sql +83 -0
- package/src/storage/migrations/002-storage-integrity.sql +113 -0
- package/src/storage/record-validation.js +46 -0
- package/src/storage/snapshot-defaults.js +54 -0
- package/src/storage/sqlite/candidates-repository.js +84 -0
- package/src/storage/sqlite/episode-evidence-repository.js +154 -0
- package/src/storage/sqlite/episode-privacy-sql.js +33 -0
- package/src/storage/sqlite/episodes-repository.js +52 -0
- package/src/storage/sqlite/fact-search-repository.js +162 -0
- package/src/storage/sqlite/facts-repository.js +146 -0
- package/src/storage/sqlite/imports-repository.js +39 -0
- package/src/storage/sqlite/mapper.js +84 -0
- package/src/storage/sqlite/open-database.js +15 -0
- package/src/storage/sqlite/outbox-repository.js +103 -0
- package/src/storage/sqlite/snapshot-repository.js +69 -0
- package/src/storage/sqlite/spaces-repository.js +52 -0
- package/src/storage/sqlite/subscriptions-repository.js +54 -0
- package/src/storage/sqlite-store.js +159 -0
- package/src/storage/stable-json.js +24 -0
- package/src/storage/store-port.js +20 -0
- package/src/store.js +2 -0
- package/src/workspace-protocol/array-validation.js +19 -0
- package/src/workspace-protocol/base64url-contract.js +27 -0
- package/src/workspace-protocol/constants.js +37 -0
- package/src/workspace-protocol/error-contract.js +24 -0
- package/src/workspace-protocol/fact-contract.js +110 -0
- package/src/workspace-protocol/governance-contract.js +47 -0
- package/src/workspace-protocol/index.js +53 -0
- package/src/workspace-protocol/json-budget.js +19 -0
- package/src/workspace-protocol/manifest-contract.js +186 -0
- package/src/workspace-protocol/publication-contract.js +212 -0
- package/src/workspace-protocol/query-contract.js +149 -0
- package/src/workspace-protocol/scalar-contract.js +192 -0
- package/src/workspace-protocol/sync-contract.js +184 -0
- package/src/workspace-protocol/text-contract.js +39 -0
- package/src/workspace-protocol/validation.js +66 -0
|
@@ -0,0 +1,765 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import json
|
|
3
|
+
from datetime import datetime, timezone
|
|
4
|
+
|
|
5
|
+
from fastapi import HTTPException
|
|
6
|
+
|
|
7
|
+
from .knowledge_usage_models import (
|
|
8
|
+
KnowledgeUsageCreate,
|
|
9
|
+
KnowledgeUsageItemResult,
|
|
10
|
+
KnowledgeUsageResult,
|
|
11
|
+
)
|
|
12
|
+
from .models import (
|
|
13
|
+
KnowledgeAgentReviewCreate,
|
|
14
|
+
KnowledgeAgentViewCreate,
|
|
15
|
+
KnowledgeAgentViewResult,
|
|
16
|
+
KnowledgeAuditRecord,
|
|
17
|
+
KnowledgeHumanChangeItem,
|
|
18
|
+
KnowledgeHumanChangeSearchRequest,
|
|
19
|
+
KnowledgeHumanChangeSearchResult,
|
|
20
|
+
)
|
|
21
|
+
from .provider_values import json_object, native_datetime, stable_uuid
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
AGENT_CONFIRMATION_POLICY_VERSION = 'agent-usage-v1'
|
|
25
|
+
AGENT_CONFIRMATION_MIN_USES = 5
|
|
26
|
+
AGENT_CONFIRMATION_MIN_TASKS = 3
|
|
27
|
+
AGENT_CONFIRMATION_CONFIDENCE_CAP = 0.85
|
|
28
|
+
USAGE_WEIGHTS = {
|
|
29
|
+
'cited': (0.12, 0.04),
|
|
30
|
+
'applied': (0.18, 0.06),
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
async def record_human_change(
|
|
35
|
+
store,
|
|
36
|
+
actor: dict,
|
|
37
|
+
space: dict,
|
|
38
|
+
item_id: str,
|
|
39
|
+
item_kind: str,
|
|
40
|
+
*,
|
|
41
|
+
reason: str,
|
|
42
|
+
operation: str,
|
|
43
|
+
) -> None:
|
|
44
|
+
changed_at = datetime.now(timezone.utc)
|
|
45
|
+
audit_id = stable_uuid(
|
|
46
|
+
space['id'],
|
|
47
|
+
'knowledge-audit',
|
|
48
|
+
item_kind,
|
|
49
|
+
item_id,
|
|
50
|
+
operation,
|
|
51
|
+
changed_at.isoformat(),
|
|
52
|
+
actor['id'],
|
|
53
|
+
)
|
|
54
|
+
records, _, _ = await store.runtime.driver.execute_query(
|
|
55
|
+
_human_change_query(item_kind),
|
|
56
|
+
space_id=space['id'],
|
|
57
|
+
group_id=space['group_id'],
|
|
58
|
+
item_id=item_id,
|
|
59
|
+
item_kind=item_kind,
|
|
60
|
+
audit_id=audit_id,
|
|
61
|
+
reason=reason,
|
|
62
|
+
operation=operation,
|
|
63
|
+
created_by=actor['id'],
|
|
64
|
+
changed_at=changed_at,
|
|
65
|
+
)
|
|
66
|
+
if not records:
|
|
67
|
+
raise HTTPException(status_code=404, detail='knowledge item not found')
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
async def record_agent_views(
|
|
71
|
+
store,
|
|
72
|
+
actor: dict,
|
|
73
|
+
request: KnowledgeAgentViewCreate,
|
|
74
|
+
) -> KnowledgeAgentViewResult:
|
|
75
|
+
store._require_personal()
|
|
76
|
+
space = await store.authorize(actor, request.personal_space_id, 'reader')
|
|
77
|
+
if space['kind'] != 'personal':
|
|
78
|
+
raise HTTPException(status_code=422, detail='Agent view audit is personal-only')
|
|
79
|
+
|
|
80
|
+
recorded = []
|
|
81
|
+
viewed_at = datetime.now(timezone.utc)
|
|
82
|
+
seen = set()
|
|
83
|
+
for item in request.items:
|
|
84
|
+
key = (item.item_kind, item.item_id)
|
|
85
|
+
if key in seen:
|
|
86
|
+
continue
|
|
87
|
+
seen.add(key)
|
|
88
|
+
audit_id = stable_uuid(
|
|
89
|
+
space['id'],
|
|
90
|
+
'knowledge-audit',
|
|
91
|
+
item.item_kind,
|
|
92
|
+
item.item_id,
|
|
93
|
+
'agent-view',
|
|
94
|
+
request.tool_name,
|
|
95
|
+
viewed_at.isoformat(),
|
|
96
|
+
actor['id'],
|
|
97
|
+
)
|
|
98
|
+
records, _, _ = await store.runtime.driver.execute_query(
|
|
99
|
+
_agent_view_query(item.item_kind),
|
|
100
|
+
space_id=space['id'],
|
|
101
|
+
group_id=space['group_id'],
|
|
102
|
+
item_id=item.item_id,
|
|
103
|
+
item_kind=item.item_kind,
|
|
104
|
+
audit_id=audit_id,
|
|
105
|
+
tool_name=request.tool_name,
|
|
106
|
+
reason=f'Agent 通过 {request.tool_name} 查看了这条人工变更。',
|
|
107
|
+
created_by=actor['id'],
|
|
108
|
+
viewed_at=viewed_at,
|
|
109
|
+
)
|
|
110
|
+
if records:
|
|
111
|
+
recorded.append(f'{item.item_kind}:{item.item_id}')
|
|
112
|
+
return KnowledgeAgentViewResult(
|
|
113
|
+
recorded_count=len(recorded),
|
|
114
|
+
item_keys=recorded,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
async def record_knowledge_usage(
|
|
119
|
+
store,
|
|
120
|
+
actor: dict,
|
|
121
|
+
request: KnowledgeUsageCreate,
|
|
122
|
+
) -> KnowledgeUsageResult:
|
|
123
|
+
"""Record material Agent use; retrieval and automatic preference injection do not call this."""
|
|
124
|
+
store._require_personal()
|
|
125
|
+
space = await store.authorize(actor, request.personal_space_id, 'maintainer')
|
|
126
|
+
if space['kind'] != 'personal':
|
|
127
|
+
raise HTTPException(status_code=422, detail='knowledge usage is personal-only')
|
|
128
|
+
|
|
129
|
+
lock = store._group_locks.setdefault(space['group_id'], asyncio.Lock())
|
|
130
|
+
results = []
|
|
131
|
+
async with lock:
|
|
132
|
+
for item in request.items:
|
|
133
|
+
results.append(
|
|
134
|
+
await _record_one_knowledge_use(
|
|
135
|
+
store,
|
|
136
|
+
actor,
|
|
137
|
+
space,
|
|
138
|
+
request,
|
|
139
|
+
item,
|
|
140
|
+
)
|
|
141
|
+
)
|
|
142
|
+
return KnowledgeUsageResult(
|
|
143
|
+
recorded_count=sum(result.recorded for result in results),
|
|
144
|
+
duplicate_count=sum(not result.recorded for result in results),
|
|
145
|
+
promoted_count=sum(result.promoted for result in results),
|
|
146
|
+
items=results,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
async def _record_one_knowledge_use(store, actor, space, request, item):
|
|
151
|
+
state = await _read_usage_state(
|
|
152
|
+
store,
|
|
153
|
+
space,
|
|
154
|
+
item.item_id,
|
|
155
|
+
item.item_kind,
|
|
156
|
+
)
|
|
157
|
+
if state is None:
|
|
158
|
+
raise HTTPException(status_code=404, detail='knowledge item not found')
|
|
159
|
+
if state.get('invalid_at') is not None:
|
|
160
|
+
raise HTTPException(
|
|
161
|
+
status_code=409,
|
|
162
|
+
detail='historical knowledge cannot receive usage evidence',
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
generation = int(state.get('usage_generation') or 1)
|
|
166
|
+
used_at = datetime.now(timezone.utc)
|
|
167
|
+
audit_id = stable_uuid(
|
|
168
|
+
space['id'],
|
|
169
|
+
'knowledge-usage',
|
|
170
|
+
item.item_kind,
|
|
171
|
+
item.item_id,
|
|
172
|
+
str(generation),
|
|
173
|
+
request.task_id,
|
|
174
|
+
item.use_kind,
|
|
175
|
+
)
|
|
176
|
+
records, _, _ = await store.runtime.driver.execute_query(
|
|
177
|
+
_knowledge_use_query(item.item_kind),
|
|
178
|
+
space_id=space['id'],
|
|
179
|
+
group_id=space['group_id'],
|
|
180
|
+
item_id=item.item_id,
|
|
181
|
+
item_kind=item.item_kind,
|
|
182
|
+
audit_id=audit_id,
|
|
183
|
+
task_id=request.task_id,
|
|
184
|
+
session_id=request.session_id,
|
|
185
|
+
use_kind=item.use_kind,
|
|
186
|
+
usage_generation=generation,
|
|
187
|
+
reason=(
|
|
188
|
+
f'Agent materially {item.use_kind} this knowledge in task '
|
|
189
|
+
f'{request.task_id}.'
|
|
190
|
+
),
|
|
191
|
+
tool_name=request.tool_name,
|
|
192
|
+
created_by=actor['id'],
|
|
193
|
+
used_at=used_at,
|
|
194
|
+
)
|
|
195
|
+
recorded = bool(records and records[0].get('recorded'))
|
|
196
|
+
|
|
197
|
+
aggregates, _, _ = await store.runtime.driver.execute_query(
|
|
198
|
+
'''
|
|
199
|
+
MATCH (:FuliSpace {id: $space_id, kind: 'personal'})-
|
|
200
|
+
[:HAS_KNOWLEDGE_AUDIT]->(audit:FuliKnowledgeAudit {
|
|
201
|
+
item_id: $item_id,
|
|
202
|
+
item_kind: $item_kind,
|
|
203
|
+
action: 'knowledge_used',
|
|
204
|
+
usage_generation: $usage_generation
|
|
205
|
+
})
|
|
206
|
+
RETURN count(audit) AS qualified_use_count,
|
|
207
|
+
count(DISTINCT audit.task_id) AS distinct_task_count,
|
|
208
|
+
sum(CASE audit.use_kind WHEN 'applied' THEN 1 ELSE 0 END)
|
|
209
|
+
AS applied_count,
|
|
210
|
+
sum(CASE audit.use_kind WHEN 'cited' THEN 1 ELSE 0 END)
|
|
211
|
+
AS cited_count,
|
|
212
|
+
max(audit.created_at) AS last_used_at
|
|
213
|
+
''',
|
|
214
|
+
space_id=space['id'],
|
|
215
|
+
item_id=item.item_id,
|
|
216
|
+
item_kind=item.item_kind,
|
|
217
|
+
usage_generation=generation,
|
|
218
|
+
routing_='r',
|
|
219
|
+
)
|
|
220
|
+
aggregate = dict(aggregates[0]) if aggregates else {}
|
|
221
|
+
qualified_use_count = int(aggregate.get('qualified_use_count') or 0)
|
|
222
|
+
distinct_task_count = int(aggregate.get('distinct_task_count') or 0)
|
|
223
|
+
cited_count = int(aggregate.get('cited_count') or 0)
|
|
224
|
+
applied_count = int(aggregate.get('applied_count') or 0)
|
|
225
|
+
utility_score = min(
|
|
226
|
+
1.0,
|
|
227
|
+
cited_count * USAGE_WEIGHTS['cited'][0]
|
|
228
|
+
+ applied_count * USAGE_WEIGHTS['applied'][0],
|
|
229
|
+
)
|
|
230
|
+
previous_status = state.get('confirmation_status') or 'pending'
|
|
231
|
+
if previous_status == 'confirmed':
|
|
232
|
+
confidence_score = 1.0
|
|
233
|
+
else:
|
|
234
|
+
confidence_score = min(
|
|
235
|
+
AGENT_CONFIRMATION_CONFIDENCE_CAP,
|
|
236
|
+
0.5
|
|
237
|
+
+ cited_count * USAGE_WEIGHTS['cited'][1]
|
|
238
|
+
+ applied_count * USAGE_WEIGHTS['applied'][1],
|
|
239
|
+
)
|
|
240
|
+
promoted = (
|
|
241
|
+
previous_status == 'pending'
|
|
242
|
+
and not state.get('has_open_conflict')
|
|
243
|
+
and qualified_use_count >= AGENT_CONFIRMATION_MIN_USES
|
|
244
|
+
and distinct_task_count >= AGENT_CONFIRMATION_MIN_TASKS
|
|
245
|
+
)
|
|
246
|
+
confirmation_status = (
|
|
247
|
+
'agent_confirmed' if promoted else previous_status
|
|
248
|
+
)
|
|
249
|
+
basis = json_object(state.get('confirmation_basis_json'))
|
|
250
|
+
if promoted:
|
|
251
|
+
basis = _agent_confirmation_basis(basis, used_at)
|
|
252
|
+
|
|
253
|
+
updated, _, _ = await store.runtime.driver.execute_query(
|
|
254
|
+
_usage_score_update_query(item.item_kind),
|
|
255
|
+
group_id=space['group_id'],
|
|
256
|
+
item_id=item.item_id,
|
|
257
|
+
usage_generation=generation,
|
|
258
|
+
utility_score=round(utility_score, 4),
|
|
259
|
+
confidence_score=round(confidence_score, 4),
|
|
260
|
+
qualified_use_count=qualified_use_count,
|
|
261
|
+
distinct_task_count=distinct_task_count,
|
|
262
|
+
last_used_at=native_datetime(aggregate.get('last_used_at')) or used_at,
|
|
263
|
+
confirmation_status=confirmation_status,
|
|
264
|
+
confirmation_basis_json=(
|
|
265
|
+
json.dumps(basis, ensure_ascii=False, sort_keys=True)
|
|
266
|
+
if basis else None
|
|
267
|
+
),
|
|
268
|
+
promoted=promoted,
|
|
269
|
+
audit_id=audit_id,
|
|
270
|
+
)
|
|
271
|
+
if not updated:
|
|
272
|
+
raise HTTPException(
|
|
273
|
+
status_code=409,
|
|
274
|
+
detail='knowledge changed while usage evidence was recorded; retry',
|
|
275
|
+
)
|
|
276
|
+
return KnowledgeUsageItemResult(
|
|
277
|
+
item_id=item.item_id,
|
|
278
|
+
item_kind=item.item_kind,
|
|
279
|
+
recorded=recorded,
|
|
280
|
+
promoted=promoted,
|
|
281
|
+
confirmation_status=confirmation_status,
|
|
282
|
+
utility_score=round(utility_score, 4),
|
|
283
|
+
confidence_score=round(confidence_score, 4),
|
|
284
|
+
qualified_use_count=qualified_use_count,
|
|
285
|
+
distinct_task_count=distinct_task_count,
|
|
286
|
+
usage_generation=generation,
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
async def review_human_change(
|
|
291
|
+
store,
|
|
292
|
+
actor: dict,
|
|
293
|
+
item_id: str,
|
|
294
|
+
request: KnowledgeAgentReviewCreate,
|
|
295
|
+
) -> KnowledgeAuditRecord:
|
|
296
|
+
store._require_personal()
|
|
297
|
+
space = await store.authorize(actor, request.personal_space_id, 'maintainer')
|
|
298
|
+
if space['kind'] != 'personal':
|
|
299
|
+
raise HTTPException(status_code=422, detail='Agent review is personal-only')
|
|
300
|
+
|
|
301
|
+
state = await _read_human_change_state(
|
|
302
|
+
store,
|
|
303
|
+
space,
|
|
304
|
+
item_id,
|
|
305
|
+
request.item_kind,
|
|
306
|
+
)
|
|
307
|
+
if state is None or not state.get('human_edited'):
|
|
308
|
+
raise HTTPException(status_code=404, detail='human-edited knowledge item not found')
|
|
309
|
+
version = int(state.get('human_change_version') or 0)
|
|
310
|
+
if version != request.human_change_version:
|
|
311
|
+
raise HTTPException(
|
|
312
|
+
status_code=409,
|
|
313
|
+
detail='human change version is stale; read the item again before reviewing',
|
|
314
|
+
)
|
|
315
|
+
if state.get('human_change_status') == 'unseen':
|
|
316
|
+
raise HTTPException(
|
|
317
|
+
status_code=409,
|
|
318
|
+
detail='Agent must read the current human change before reviewing it',
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
reviewed = (
|
|
322
|
+
request.conflict_check == 'no_conflict'
|
|
323
|
+
and request.classification_check == 'reasonable'
|
|
324
|
+
)
|
|
325
|
+
outcome = 'reviewed' if reviewed else 'requires_attention'
|
|
326
|
+
next_status = 'reviewed' if reviewed else 'viewed'
|
|
327
|
+
reviewed_at = datetime.now(timezone.utc)
|
|
328
|
+
audit_id = stable_uuid(
|
|
329
|
+
space['id'],
|
|
330
|
+
'knowledge-audit',
|
|
331
|
+
request.item_kind,
|
|
332
|
+
item_id,
|
|
333
|
+
'agent-review',
|
|
334
|
+
str(version),
|
|
335
|
+
reviewed_at.isoformat(),
|
|
336
|
+
actor['id'],
|
|
337
|
+
)
|
|
338
|
+
records, _, _ = await store.runtime.driver.execute_query(
|
|
339
|
+
_agent_review_query(request.item_kind),
|
|
340
|
+
space_id=space['id'],
|
|
341
|
+
group_id=space['group_id'],
|
|
342
|
+
item_id=item_id,
|
|
343
|
+
item_kind=request.item_kind,
|
|
344
|
+
audit_id=audit_id,
|
|
345
|
+
human_change_version=version,
|
|
346
|
+
conflict_check=request.conflict_check,
|
|
347
|
+
classification_check=request.classification_check,
|
|
348
|
+
outcome=outcome,
|
|
349
|
+
next_status=next_status,
|
|
350
|
+
reason=request.note,
|
|
351
|
+
created_by=actor['id'],
|
|
352
|
+
reviewed_at=reviewed_at,
|
|
353
|
+
)
|
|
354
|
+
if not records:
|
|
355
|
+
raise HTTPException(
|
|
356
|
+
status_code=409,
|
|
357
|
+
detail='human change moved while it was being reviewed; read it again',
|
|
358
|
+
)
|
|
359
|
+
return _audit_record(dict(records[0]['audit']))
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
async def search_human_changes(
|
|
363
|
+
store,
|
|
364
|
+
actor: dict,
|
|
365
|
+
request: KnowledgeHumanChangeSearchRequest,
|
|
366
|
+
) -> KnowledgeHumanChangeSearchResult:
|
|
367
|
+
store._require_personal()
|
|
368
|
+
space = await store.authorize(actor, request.personal_space_id, 'reader')
|
|
369
|
+
if space['kind'] != 'personal':
|
|
370
|
+
raise HTTPException(status_code=422, detail='human change search is personal-only')
|
|
371
|
+
|
|
372
|
+
needle = request.query.casefold().strip()
|
|
373
|
+
common = {
|
|
374
|
+
'space_id': space['id'],
|
|
375
|
+
'group_id': space['group_id'],
|
|
376
|
+
'status': request.status,
|
|
377
|
+
'search_query': needle,
|
|
378
|
+
'limit': request.limit,
|
|
379
|
+
'routing_': 'r',
|
|
380
|
+
}
|
|
381
|
+
entity_records, _, _ = await store.runtime.driver.execute_query(
|
|
382
|
+
_HUMAN_ENTITY_SEARCH,
|
|
383
|
+
**common,
|
|
384
|
+
)
|
|
385
|
+
relationship_records, _, _ = await store.runtime.driver.execute_query(
|
|
386
|
+
_HUMAN_RELATIONSHIP_SEARCH,
|
|
387
|
+
**common,
|
|
388
|
+
)
|
|
389
|
+
values = [
|
|
390
|
+
{**dict(record), 'item_kind': 'entity'}
|
|
391
|
+
for record in entity_records
|
|
392
|
+
] + [
|
|
393
|
+
{**dict(record), 'item_kind': 'relationship'}
|
|
394
|
+
for record in relationship_records
|
|
395
|
+
]
|
|
396
|
+
values.sort(
|
|
397
|
+
key=lambda value: native_datetime(value['last_human_changed_at']),
|
|
398
|
+
reverse=True,
|
|
399
|
+
)
|
|
400
|
+
values = values[: request.limit]
|
|
401
|
+
audits = await read_knowledge_audits(
|
|
402
|
+
store,
|
|
403
|
+
space['id'],
|
|
404
|
+
[value['item_id'] for value in values],
|
|
405
|
+
)
|
|
406
|
+
return KnowledgeHumanChangeSearchResult(items=[
|
|
407
|
+
KnowledgeHumanChangeItem(
|
|
408
|
+
**{
|
|
409
|
+
**value,
|
|
410
|
+
'last_human_changed_at': native_datetime(
|
|
411
|
+
value['last_human_changed_at']
|
|
412
|
+
),
|
|
413
|
+
'last_agent_viewed_at': native_datetime(
|
|
414
|
+
value.get('last_agent_viewed_at')
|
|
415
|
+
),
|
|
416
|
+
'last_agent_reviewed_at': native_datetime(
|
|
417
|
+
value.get('last_agent_reviewed_at')
|
|
418
|
+
),
|
|
419
|
+
'audit_events': audits.get(value['item_id'], []),
|
|
420
|
+
}
|
|
421
|
+
)
|
|
422
|
+
for value in values
|
|
423
|
+
])
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
async def read_knowledge_audits(
|
|
427
|
+
store,
|
|
428
|
+
space_id: str,
|
|
429
|
+
item_ids: list[str],
|
|
430
|
+
) -> dict[str, list[KnowledgeAuditRecord]]:
|
|
431
|
+
if not item_ids:
|
|
432
|
+
return {}
|
|
433
|
+
records, _, _ = await store.runtime.driver.execute_query(
|
|
434
|
+
'''
|
|
435
|
+
MATCH (:FuliSpace {id: $space_id, kind: 'personal'})-
|
|
436
|
+
[:HAS_KNOWLEDGE_AUDIT]->(audit:FuliKnowledgeAudit)
|
|
437
|
+
WHERE audit.item_id IN $item_ids
|
|
438
|
+
RETURN audit ORDER BY audit.created_at DESC
|
|
439
|
+
''',
|
|
440
|
+
space_id=space_id,
|
|
441
|
+
item_ids=item_ids,
|
|
442
|
+
routing_='r',
|
|
443
|
+
)
|
|
444
|
+
result = {}
|
|
445
|
+
for record in records:
|
|
446
|
+
value = _audit_record(dict(record['audit']))
|
|
447
|
+
result.setdefault(value.item_id, []).append(value)
|
|
448
|
+
return result
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
async def _read_human_change_state(store, space, item_id, item_kind):
|
|
452
|
+
records, _, _ = await store.runtime.driver.execute_query(
|
|
453
|
+
_state_query(item_kind),
|
|
454
|
+
group_id=space['group_id'],
|
|
455
|
+
item_id=item_id,
|
|
456
|
+
routing_='r',
|
|
457
|
+
)
|
|
458
|
+
return dict(records[0]) if records else None
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
def _audit_record(value: dict) -> KnowledgeAuditRecord:
|
|
462
|
+
return KnowledgeAuditRecord(
|
|
463
|
+
id=value['id'],
|
|
464
|
+
item_id=value['item_id'],
|
|
465
|
+
item_kind=value['item_kind'],
|
|
466
|
+
action=value['action'],
|
|
467
|
+
human_change_version=int(value.get('human_change_version') or 0),
|
|
468
|
+
reason=value['reason'],
|
|
469
|
+
tool_name=value.get('tool_name'),
|
|
470
|
+
task_id=value.get('task_id'),
|
|
471
|
+
session_id=value.get('session_id'),
|
|
472
|
+
use_kind=value.get('use_kind'),
|
|
473
|
+
usage_generation=(
|
|
474
|
+
int(value['usage_generation'])
|
|
475
|
+
if value.get('usage_generation') is not None else None
|
|
476
|
+
),
|
|
477
|
+
conflict_check=value.get('conflict_check'),
|
|
478
|
+
classification_check=value.get('classification_check'),
|
|
479
|
+
outcome=value.get('outcome'),
|
|
480
|
+
created_at=native_datetime(value['created_at']),
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
def _item_match(item_kind: str) -> str:
|
|
485
|
+
if item_kind == 'entity':
|
|
486
|
+
return 'MATCH (item:Entity {uuid: $item_id, group_id: $group_id})'
|
|
487
|
+
return 'MATCH ()-[item:RELATES_TO {uuid: $item_id, group_id: $group_id}]->()'
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
def _human_change_query(item_kind: str) -> str:
|
|
491
|
+
return f'''
|
|
492
|
+
MATCH (space:FuliSpace {{id: $space_id, kind: 'personal'}})
|
|
493
|
+
{_item_match(item_kind)}
|
|
494
|
+
SET item.fuli_human_edited = true,
|
|
495
|
+
item.fuli_human_change_version =
|
|
496
|
+
coalesce(item.fuli_human_change_version, 0) + 1,
|
|
497
|
+
item.fuli_human_change_status = 'unseen',
|
|
498
|
+
item.fuli_last_human_changed_at = $changed_at,
|
|
499
|
+
item.fuli_last_agent_viewed_at = NULL,
|
|
500
|
+
item.fuli_last_agent_reviewed_at = NULL
|
|
501
|
+
CREATE (audit:FuliKnowledgeAudit {{
|
|
502
|
+
id: $audit_id,
|
|
503
|
+
space_id: $space_id,
|
|
504
|
+
item_id: $item_id,
|
|
505
|
+
item_kind: $item_kind,
|
|
506
|
+
action: 'human_change',
|
|
507
|
+
human_change_version: item.fuli_human_change_version,
|
|
508
|
+
reason: $reason,
|
|
509
|
+
operation: $operation,
|
|
510
|
+
outcome: 'pending_review',
|
|
511
|
+
created_by: $created_by,
|
|
512
|
+
created_at: $changed_at
|
|
513
|
+
}})
|
|
514
|
+
MERGE (space)-[:HAS_KNOWLEDGE_AUDIT]->(audit)
|
|
515
|
+
RETURN item.fuli_human_change_version AS human_change_version
|
|
516
|
+
'''
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
def _agent_view_query(item_kind: str) -> str:
|
|
520
|
+
return f'''
|
|
521
|
+
MATCH (space:FuliSpace {{id: $space_id, kind: 'personal'}})
|
|
522
|
+
{_item_match(item_kind)}
|
|
523
|
+
WHERE item.fuli_human_edited = true
|
|
524
|
+
SET item.fuli_human_change_status =
|
|
525
|
+
CASE WHEN item.fuli_human_change_status = 'reviewed'
|
|
526
|
+
THEN 'reviewed' ELSE 'viewed' END,
|
|
527
|
+
item.fuli_last_agent_viewed_at = $viewed_at
|
|
528
|
+
CREATE (audit:FuliKnowledgeAudit {{
|
|
529
|
+
id: $audit_id,
|
|
530
|
+
space_id: $space_id,
|
|
531
|
+
item_id: $item_id,
|
|
532
|
+
item_kind: $item_kind,
|
|
533
|
+
action: 'agent_view',
|
|
534
|
+
human_change_version: item.fuli_human_change_version,
|
|
535
|
+
reason: $reason,
|
|
536
|
+
tool_name: $tool_name,
|
|
537
|
+
outcome: CASE WHEN item.fuli_human_change_status = 'reviewed'
|
|
538
|
+
THEN 'reviewed' ELSE 'pending_review' END,
|
|
539
|
+
created_by: $created_by,
|
|
540
|
+
created_at: $viewed_at
|
|
541
|
+
}})
|
|
542
|
+
MERGE (space)-[:HAS_KNOWLEDGE_AUDIT]->(audit)
|
|
543
|
+
RETURN audit.id AS audit_id
|
|
544
|
+
'''
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
def _agent_review_query(item_kind: str) -> str:
|
|
548
|
+
return f'''
|
|
549
|
+
MATCH (space:FuliSpace {{id: $space_id, kind: 'personal'}})
|
|
550
|
+
{_item_match(item_kind)}
|
|
551
|
+
WHERE item.fuli_human_edited = true
|
|
552
|
+
AND item.fuli_human_change_version = $human_change_version
|
|
553
|
+
AND item.fuli_human_change_status IN ['viewed', 'reviewed']
|
|
554
|
+
SET item.fuli_human_change_status = $next_status,
|
|
555
|
+
item.fuli_last_agent_reviewed_at = $reviewed_at
|
|
556
|
+
CREATE (audit:FuliKnowledgeAudit {{
|
|
557
|
+
id: $audit_id,
|
|
558
|
+
space_id: $space_id,
|
|
559
|
+
item_id: $item_id,
|
|
560
|
+
item_kind: $item_kind,
|
|
561
|
+
action: 'agent_review',
|
|
562
|
+
human_change_version: $human_change_version,
|
|
563
|
+
reason: $reason,
|
|
564
|
+
conflict_check: $conflict_check,
|
|
565
|
+
classification_check: $classification_check,
|
|
566
|
+
outcome: $outcome,
|
|
567
|
+
created_by: $created_by,
|
|
568
|
+
created_at: $reviewed_at
|
|
569
|
+
}})
|
|
570
|
+
MERGE (space)-[:HAS_KNOWLEDGE_AUDIT]->(audit)
|
|
571
|
+
RETURN audit
|
|
572
|
+
'''
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
def _state_query(item_kind: str) -> str:
|
|
576
|
+
return f'''
|
|
577
|
+
{_item_match(item_kind)}
|
|
578
|
+
RETURN coalesce(item.fuli_human_edited, false) AS human_edited,
|
|
579
|
+
coalesce(item.fuli_human_change_version, 0)
|
|
580
|
+
AS human_change_version,
|
|
581
|
+
coalesce(item.fuli_human_change_status, 'none')
|
|
582
|
+
AS human_change_status
|
|
583
|
+
'''
|
|
584
|
+
|
|
585
|
+
|
|
586
|
+
async def _read_usage_state(store, space, item_id, item_kind):
|
|
587
|
+
records, _, _ = await store.runtime.driver.execute_query(
|
|
588
|
+
f'''
|
|
589
|
+
MATCH (space:FuliSpace {{id: $space_id, kind: 'personal'}})
|
|
590
|
+
{_item_match(item_kind)}
|
|
591
|
+
RETURN coalesce(item.fuli_confirmation_status, 'pending')
|
|
592
|
+
AS confirmation_status,
|
|
593
|
+
item.fuli_confirmation_basis_json AS confirmation_basis_json,
|
|
594
|
+
coalesce(item.fuli_usage_generation, 1) AS usage_generation,
|
|
595
|
+
{
|
|
596
|
+
"item.fuli_invalid_at"
|
|
597
|
+
if item_kind == "entity" else "item.invalid_at"
|
|
598
|
+
} AS invalid_at,
|
|
599
|
+
(
|
|
600
|
+
EXISTS {{
|
|
601
|
+
MATCH (space)-[:HAS_KNOWLEDGE_CONFLICT]->
|
|
602
|
+
(knowledge_conflict:FuliKnowledgeConflict)
|
|
603
|
+
WHERE knowledge_conflict.status = 'pending'
|
|
604
|
+
AND (
|
|
605
|
+
knowledge_conflict.item_id = $item_id
|
|
606
|
+
OR knowledge_conflict.target_item_id = $item_id
|
|
607
|
+
)
|
|
608
|
+
}}
|
|
609
|
+
OR EXISTS {{
|
|
610
|
+
MATCH (space)-[:HAS_PREFERENCE_CONFLICT]->
|
|
611
|
+
(preference_conflict:FuliPreferenceConflict)
|
|
612
|
+
WHERE preference_conflict.status = 'ai_pending'
|
|
613
|
+
AND (
|
|
614
|
+
preference_conflict.left_item_id = $item_id
|
|
615
|
+
OR preference_conflict.right_item_id = $item_id
|
|
616
|
+
)
|
|
617
|
+
}}
|
|
618
|
+
) AS has_open_conflict
|
|
619
|
+
''',
|
|
620
|
+
space_id=space['id'],
|
|
621
|
+
group_id=space['group_id'],
|
|
622
|
+
item_id=item_id,
|
|
623
|
+
routing_='r',
|
|
624
|
+
)
|
|
625
|
+
return dict(records[0]) if records else None
|
|
626
|
+
|
|
627
|
+
|
|
628
|
+
def _knowledge_use_query(item_kind: str) -> str:
|
|
629
|
+
return f'''
|
|
630
|
+
MATCH (space:FuliSpace {{id: $space_id, kind: 'personal'}})
|
|
631
|
+
{_item_match(item_kind)}
|
|
632
|
+
WHERE coalesce(item.fuli_usage_generation, 1) = $usage_generation
|
|
633
|
+
MERGE (audit:FuliKnowledgeAudit {{id: $audit_id}})
|
|
634
|
+
ON CREATE SET audit.space_id = $space_id,
|
|
635
|
+
audit.item_id = $item_id,
|
|
636
|
+
audit.item_kind = $item_kind,
|
|
637
|
+
audit.action = 'knowledge_used',
|
|
638
|
+
audit.human_change_version = 0,
|
|
639
|
+
audit.task_id = $task_id,
|
|
640
|
+
audit.session_id = $session_id,
|
|
641
|
+
audit.use_kind = $use_kind,
|
|
642
|
+
audit.usage_generation = $usage_generation,
|
|
643
|
+
audit.reason = $reason,
|
|
644
|
+
audit.tool_name = $tool_name,
|
|
645
|
+
audit.created_by = $created_by,
|
|
646
|
+
audit.created_at = $used_at
|
|
647
|
+
MERGE (space)-[:HAS_KNOWLEDGE_AUDIT]->(audit)
|
|
648
|
+
RETURN audit.created_at = $used_at AS recorded
|
|
649
|
+
'''
|
|
650
|
+
|
|
651
|
+
|
|
652
|
+
def _usage_score_update_query(item_kind: str) -> str:
|
|
653
|
+
return f'''
|
|
654
|
+
{_item_match(item_kind)}
|
|
655
|
+
WHERE coalesce(item.fuli_usage_generation, 1) = $usage_generation
|
|
656
|
+
SET item.fuli_utility_score = $utility_score,
|
|
657
|
+
item.fuli_confidence_score = $confidence_score,
|
|
658
|
+
item.fuli_qualified_use_count = $qualified_use_count,
|
|
659
|
+
item.fuli_distinct_task_count = $distinct_task_count,
|
|
660
|
+
item.fuli_last_used_at = $last_used_at,
|
|
661
|
+
item.fuli_confirmation_status = $confirmation_status,
|
|
662
|
+
item.fuli_confirmation_basis_json = $confirmation_basis_json,
|
|
663
|
+
item.fuli_epistemic_status =
|
|
664
|
+
CASE WHEN $confirmation_status = 'confirmed' THEN 'confirmed'
|
|
665
|
+
WHEN item.fuli_origin_quadrant = 'unknown_unknown'
|
|
666
|
+
THEN 'exploratory'
|
|
667
|
+
ELSE 'observed' END
|
|
668
|
+
WITH item
|
|
669
|
+
OPTIONAL MATCH (audit:FuliKnowledgeAudit {{id: $audit_id}})
|
|
670
|
+
FOREACH (_ IN CASE WHEN $promoted AND audit IS NOT NULL THEN [1] ELSE [] END |
|
|
671
|
+
SET audit.outcome = 'agent_confirmed',
|
|
672
|
+
audit.promotion = true
|
|
673
|
+
)
|
|
674
|
+
RETURN item.fuli_confirmation_status AS confirmation_status
|
|
675
|
+
'''
|
|
676
|
+
|
|
677
|
+
|
|
678
|
+
def _agent_confirmation_basis(value: dict, confirmed_at: datetime) -> dict:
|
|
679
|
+
return {
|
|
680
|
+
'existence_reason': value.get(
|
|
681
|
+
'existence_reason',
|
|
682
|
+
'The knowledge was retained from an earlier Agent proposal.',
|
|
683
|
+
),
|
|
684
|
+
'quadrant_reason': value.get(
|
|
685
|
+
'quadrant_reason',
|
|
686
|
+
'The discovery quadrant remains the immutable capture-time label.',
|
|
687
|
+
),
|
|
688
|
+
'proposed_by': value.get(
|
|
689
|
+
'proposed_by',
|
|
690
|
+
{'kind': 'agent', 'label': 'Fuli'},
|
|
691
|
+
),
|
|
692
|
+
'confirmed_by': {
|
|
693
|
+
'kind': 'agent',
|
|
694
|
+
'label': 'Fuli usage evidence policy',
|
|
695
|
+
},
|
|
696
|
+
'confirmed_at': confirmed_at.isoformat(),
|
|
697
|
+
'agent_policy_version': AGENT_CONFIRMATION_POLICY_VERSION,
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
|
|
701
|
+
_HUMAN_ENTITY_SEARCH = '''
|
|
702
|
+
MATCH (item:Entity {group_id: $group_id})
|
|
703
|
+
WHERE item.fuli_human_edited = true
|
|
704
|
+
AND (
|
|
705
|
+
$status IN ['all', 'human_changed']
|
|
706
|
+
OR item.fuli_human_change_status = $status
|
|
707
|
+
)
|
|
708
|
+
AND (
|
|
709
|
+
$search_query = ''
|
|
710
|
+
OR toLower(item.name) CONTAINS $search_query
|
|
711
|
+
OR toLower(coalesce(item.summary, '')) CONTAINS $search_query
|
|
712
|
+
OR toLower(coalesce(item.fuli_type, 'Entity')) CONTAINS $search_query
|
|
713
|
+
OR EXISTS {
|
|
714
|
+
MATCH (audit:FuliKnowledgeAudit {
|
|
715
|
+
space_id: $space_id,
|
|
716
|
+
item_id: item.uuid
|
|
717
|
+
})
|
|
718
|
+
WHERE toLower(coalesce(audit.reason, '')) CONTAINS $search_query
|
|
719
|
+
}
|
|
720
|
+
)
|
|
721
|
+
RETURN item.uuid AS item_id,
|
|
722
|
+
item.name AS title,
|
|
723
|
+
coalesce(item.summary, '') AS body,
|
|
724
|
+
coalesce(item.fuli_type, 'Entity') AS type,
|
|
725
|
+
item.fuli_human_change_status AS human_change_status,
|
|
726
|
+
item.fuli_human_change_version AS human_change_version,
|
|
727
|
+
item.fuli_last_human_changed_at AS last_human_changed_at,
|
|
728
|
+
item.fuli_last_agent_viewed_at AS last_agent_viewed_at,
|
|
729
|
+
item.fuli_last_agent_reviewed_at AS last_agent_reviewed_at
|
|
730
|
+
ORDER BY item.fuli_last_human_changed_at DESC
|
|
731
|
+
LIMIT $limit
|
|
732
|
+
'''
|
|
733
|
+
|
|
734
|
+
|
|
735
|
+
_HUMAN_RELATIONSHIP_SEARCH = '''
|
|
736
|
+
MATCH ()-[item:RELATES_TO {group_id: $group_id}]->()
|
|
737
|
+
WHERE item.fuli_human_edited = true
|
|
738
|
+
AND (
|
|
739
|
+
$status IN ['all', 'human_changed']
|
|
740
|
+
OR item.fuli_human_change_status = $status
|
|
741
|
+
)
|
|
742
|
+
AND (
|
|
743
|
+
$search_query = ''
|
|
744
|
+
OR toLower(item.name) CONTAINS $search_query
|
|
745
|
+
OR toLower(coalesce(item.fact, '')) CONTAINS $search_query
|
|
746
|
+
OR EXISTS {
|
|
747
|
+
MATCH (audit:FuliKnowledgeAudit {
|
|
748
|
+
space_id: $space_id,
|
|
749
|
+
item_id: item.uuid
|
|
750
|
+
})
|
|
751
|
+
WHERE toLower(coalesce(audit.reason, '')) CONTAINS $search_query
|
|
752
|
+
}
|
|
753
|
+
)
|
|
754
|
+
RETURN item.uuid AS item_id,
|
|
755
|
+
item.name AS title,
|
|
756
|
+
coalesce(item.fact, '') AS body,
|
|
757
|
+
item.name AS type,
|
|
758
|
+
item.fuli_human_change_status AS human_change_status,
|
|
759
|
+
item.fuli_human_change_version AS human_change_version,
|
|
760
|
+
item.fuli_last_human_changed_at AS last_human_changed_at,
|
|
761
|
+
item.fuli_last_agent_viewed_at AS last_agent_viewed_at,
|
|
762
|
+
item.fuli_last_agent_reviewed_at AS last_agent_reviewed_at
|
|
763
|
+
ORDER BY item.fuli_last_human_changed_at DESC
|
|
764
|
+
LIMIT $limit
|
|
765
|
+
'''
|