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.
Files changed (220) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +178 -0
  3. package/compose.graphiti.yml +86 -0
  4. package/dist/web/assets/ConnectionsPage-CY6H_mJJ.js +1 -0
  5. package/dist/web/assets/KnowledgeInspector-BSpz5Sfm.js +1 -0
  6. package/dist/web/assets/KnowledgeInspector-CHbB_0rC.css +1 -0
  7. package/dist/web/assets/KnowledgeOrganizerPage-BlEvhPrv.css +1 -0
  8. package/dist/web/assets/KnowledgeOrganizerPage-CvjGPlUc.js +1 -0
  9. package/dist/web/assets/KnowledgePage-Khuo3cNs.js +1 -0
  10. package/dist/web/assets/KnowledgeWorkspace-DVZusvem.js +4 -0
  11. package/dist/web/assets/OverviewPage-DC4C4Fhn.js +1 -0
  12. package/dist/web/assets/PersonalProfilePage-CSV9Mtgz.js +1 -0
  13. package/dist/web/assets/PersonalProfilePage-CVJ3TcRi.css +1 -0
  14. package/dist/web/assets/PersonalProjectsPage-A50EaseQ.js +1 -0
  15. package/dist/web/assets/PublicProjectsPage-BAdO_bae.js +1 -0
  16. package/dist/web/assets/ReviewPage-Dztk2jDG.js +1 -0
  17. package/dist/web/assets/console-tbvTUi6p.js +1 -0
  18. package/dist/web/assets/fuli-logo-DGuyqchQ.png +0 -0
  19. package/dist/web/assets/identity-B3s5CXhv.js +1 -0
  20. package/dist/web/assets/index-Devf7glZ.css +1 -0
  21. package/dist/web/assets/index-DwUDhQKc.js +6 -0
  22. package/dist/web/assets/model-DVT8sjy9.js +1 -0
  23. package/dist/web/assets/paths-B790iniO.js +1 -0
  24. package/dist/web/assets/runtime-dom.esm-bundler-DttsK21F.js +1 -0
  25. package/dist/web/assets/vue-router-BYTAoWT3.js +1 -0
  26. package/dist/web/index.html +22 -0
  27. package/graph-provider/Dockerfile +16 -0
  28. package/graph-provider/fuli_graph/__init__.py +5 -0
  29. package/graph-provider/fuli_graph/app.py +547 -0
  30. package/graph-provider/fuli_graph/auth.py +17 -0
  31. package/graph-provider/fuli_graph/collaboration_context.py +290 -0
  32. package/graph-provider/fuli_graph/config.py +34 -0
  33. package/graph-provider/fuli_graph/graph_models.py +123 -0
  34. package/graph-provider/fuli_graph/graph_projection.py +405 -0
  35. package/graph-provider/fuli_graph/graph_query.py +561 -0
  36. package/graph-provider/fuli_graph/knowledge_audit.py +765 -0
  37. package/graph-provider/fuli_graph/knowledge_batch_confirmation.py +246 -0
  38. package/graph-provider/fuli_graph/knowledge_management.py +816 -0
  39. package/graph-provider/fuli_graph/knowledge_records.py +65 -0
  40. package/graph-provider/fuli_graph/knowledge_search.py +723 -0
  41. package/graph-provider/fuli_graph/knowledge_usage_models.py +52 -0
  42. package/graph-provider/fuli_graph/model_validation.py +58 -0
  43. package/graph-provider/fuli_graph/models.py +1179 -0
  44. package/graph-provider/fuli_graph/personal_project_access.py +29 -0
  45. package/graph-provider/fuli_graph/preference_conflicts.py +420 -0
  46. package/graph-provider/fuli_graph/project_action_models.py +142 -0
  47. package/graph-provider/fuli_graph/project_knowledge.py +707 -0
  48. package/graph-provider/fuli_graph/project_management.py +251 -0
  49. package/graph-provider/fuli_graph/provider_values.py +40 -0
  50. package/graph-provider/fuli_graph/runtime.py +350 -0
  51. package/graph-provider/fuli_graph/search_projection.py +156 -0
  52. package/graph-provider/fuli_graph/store.py +24 -0
  53. package/graph-provider/fuli_graph/store_identity.py +237 -0
  54. package/graph-provider/fuli_graph/store_knowledge.py +447 -0
  55. package/graph-provider/fuli_graph/store_projects.py +242 -0
  56. package/graph-provider/fuli_graph/store_publication.py +370 -0
  57. package/graph-provider/fuli_graph/store_records.py +134 -0
  58. package/graph-provider/pyproject.toml +35 -0
  59. package/npm-shrinkwrap.json +6045 -0
  60. package/package.json +99 -0
  61. package/skills/capturing-session-knowledge/SKILL.md +226 -0
  62. package/skills/capturing-session-knowledge/agents/openai.yaml +13 -0
  63. package/skills/grilling-project/SKILL.md +129 -0
  64. package/skills/grilling-project/agents/openai.yaml +4 -0
  65. package/skills/grilling-project/references/project-profile.md +105 -0
  66. package/src/agent/candidate-read-service.js +53 -0
  67. package/src/agent/context-pack-service.js +83 -0
  68. package/src/agent/public-read-policy.js +39 -0
  69. package/src/agent/public-read-service.js +69 -0
  70. package/src/agent/read-projection.js +107 -0
  71. package/src/agent/search-service.js +61 -0
  72. package/src/agent/space-boundary.js +33 -0
  73. package/src/agent-tools/graph-definitions.js +479 -0
  74. package/src/agent-tools/graph-handlers.js +96 -0
  75. package/src/agent-tools/schema.js +37 -0
  76. package/src/agent-tools.js +16 -0
  77. package/src/app/application-error.js +13 -0
  78. package/src/app/bootstrap.js +8 -0
  79. package/src/app/create-application.js +143 -0
  80. package/src/app/state-service.js +38 -0
  81. package/src/candidates.js +78 -0
  82. package/src/classifier.js +63 -0
  83. package/src/cli/command-arguments.js +14 -0
  84. package/src/cli/command-registry.js +78 -0
  85. package/src/cli/commands/candidates.js +17 -0
  86. package/src/cli/commands/ingestion.js +32 -0
  87. package/src/cli/commands/reads.js +50 -0
  88. package/src/cli/commands/spaces.js +20 -0
  89. package/src/cli/invocation.js +47 -0
  90. package/src/cli/local-runtime-command.js +94 -0
  91. package/src/cli/local-runtime-options.js +64 -0
  92. package/src/cli/migrate-command.js +71 -0
  93. package/src/cli/migration-destination.js +117 -0
  94. package/src/cli/setup-agent-selection.js +195 -0
  95. package/src/cli/setup-command.js +85 -0
  96. package/src/cli/uninstall-command.js +67 -0
  97. package/src/cli.js +90 -0
  98. package/src/context-pack.js +206 -0
  99. package/src/defaults.js +1 -0
  100. package/src/extractor.js +77 -0
  101. package/src/fact-history.js +39 -0
  102. package/src/facts/fact-service.js +89 -0
  103. package/src/graphiti/agent-access-policy.js +88 -0
  104. package/src/graphiti/capture-policy.js +84 -0
  105. package/src/graphiti/federated-application.js +1292 -0
  106. package/src/graphiti/personal-context.js +29 -0
  107. package/src/graphiti/project-path-context.js +159 -0
  108. package/src/graphiti/provider-client.js +288 -0
  109. package/src/graphiti/runtime-config.js +85 -0
  110. package/src/graphiti/setup.js +43 -0
  111. package/src/graphiti/source-marker.js +142 -0
  112. package/src/http/api-router.js +133 -0
  113. package/src/http/bounded-json-array.js +19 -0
  114. package/src/http/error-mapping.js +36 -0
  115. package/src/http/graph-api-router.js +257 -0
  116. package/src/http/lens-route.js +50 -0
  117. package/src/http/request-policy.js +41 -0
  118. package/src/http/response.js +29 -0
  119. package/src/http/static-handler.js +55 -0
  120. package/src/ingestion.js +161 -0
  121. package/src/lens/lens-correction-service.js +81 -0
  122. package/src/lens/lens-fact-writer.js +85 -0
  123. package/src/lens/lens-input.js +62 -0
  124. package/src/lens/lens-query-projection.js +87 -0
  125. package/src/lens/lens-query-ranking.js +62 -0
  126. package/src/lens/lens-query.js +143 -0
  127. package/src/lens/lens-resource-projection.js +93 -0
  128. package/src/lens/lens-resource-selection.js +45 -0
  129. package/src/lens/lens-resource-service.js +103 -0
  130. package/src/lens/lens-search-query.js +122 -0
  131. package/src/lens/lens-service.js +183 -0
  132. package/src/local-runtime/lifecycle.js +254 -0
  133. package/src/mcp/create-mcp-server.js +85 -0
  134. package/src/mcp/instructions.js +10 -0
  135. package/src/mcp/project-action-preview-tokens.js +72 -0
  136. package/src/mcp/runtime.js +71 -0
  137. package/src/mcp/tool-annotations.js +63 -0
  138. package/src/mcp/tool-result.js +167 -0
  139. package/src/mcp/tool-schema.js +60 -0
  140. package/src/mcp-server.js +76 -0
  141. package/src/models.js +44 -0
  142. package/src/observer.js +83 -0
  143. package/src/package-metadata.js +6 -0
  144. package/src/project-rules.js +33 -0
  145. package/src/publication/canonical-json.js +39 -0
  146. package/src/publication/outbox-service.js +46 -0
  147. package/src/publication/publication-envelope-schema.js +75 -0
  148. package/src/publication/publication-envelope.js +37 -0
  149. package/src/publication/publication-policy.js +82 -0
  150. package/src/publication/publication-service.js +48 -0
  151. package/src/router.js +58 -0
  152. package/src/runtime-options.js +139 -0
  153. package/src/security/episode-visibility.js +14 -0
  154. package/src/security/fact-visibility.js +15 -0
  155. package/src/security/sensitive-content.js +18 -0
  156. package/src/security/source-metadata-policy.js +8 -0
  157. package/src/server/application-lifecycle.js +34 -0
  158. package/src/server/listen.js +24 -0
  159. package/src/server.js +135 -0
  160. package/src/setup/agent-installation-status.js +97 -0
  161. package/src/setup/agents.js +123 -0
  162. package/src/setup/codex-bootstrap.js +118 -0
  163. package/src/setup/codex-config.js +122 -0
  164. package/src/setup/config-backup.js +22 -0
  165. package/src/setup/container-runtime.js +388 -0
  166. package/src/setup/cursor-config.js +61 -0
  167. package/src/setup/graph-runtime.js +360 -0
  168. package/src/setup/node-runtime.js +24 -0
  169. package/src/setup/options.js +91 -0
  170. package/src/setup/paths.js +47 -0
  171. package/src/setup/session-skill.js +91 -0
  172. package/src/setup/setup.js +85 -0
  173. package/src/setup/uninstall.js +90 -0
  174. package/src/storage/episode-evidence-preview.js +100 -0
  175. package/src/storage/fact-search-order.js +18 -0
  176. package/src/storage/file/episode-evidence.js +37 -0
  177. package/src/storage/file/episode-index.js +59 -0
  178. package/src/storage/file/fact-search.js +60 -0
  179. package/src/storage/file-store.js +437 -0
  180. package/src/storage/import-json.js +70 -0
  181. package/src/storage/json-file.js +41 -0
  182. package/src/storage/legacy-record-validation.js +152 -0
  183. package/src/storage/legacy-snapshot-validation.js +32 -0
  184. package/src/storage/migrate.js +128 -0
  185. package/src/storage/migrations/001-initial.sql +83 -0
  186. package/src/storage/migrations/002-storage-integrity.sql +113 -0
  187. package/src/storage/record-validation.js +46 -0
  188. package/src/storage/snapshot-defaults.js +54 -0
  189. package/src/storage/sqlite/candidates-repository.js +84 -0
  190. package/src/storage/sqlite/episode-evidence-repository.js +154 -0
  191. package/src/storage/sqlite/episode-privacy-sql.js +33 -0
  192. package/src/storage/sqlite/episodes-repository.js +52 -0
  193. package/src/storage/sqlite/fact-search-repository.js +162 -0
  194. package/src/storage/sqlite/facts-repository.js +146 -0
  195. package/src/storage/sqlite/imports-repository.js +39 -0
  196. package/src/storage/sqlite/mapper.js +84 -0
  197. package/src/storage/sqlite/open-database.js +15 -0
  198. package/src/storage/sqlite/outbox-repository.js +103 -0
  199. package/src/storage/sqlite/snapshot-repository.js +69 -0
  200. package/src/storage/sqlite/spaces-repository.js +52 -0
  201. package/src/storage/sqlite/subscriptions-repository.js +54 -0
  202. package/src/storage/sqlite-store.js +159 -0
  203. package/src/storage/stable-json.js +24 -0
  204. package/src/storage/store-port.js +20 -0
  205. package/src/store.js +2 -0
  206. package/src/workspace-protocol/array-validation.js +19 -0
  207. package/src/workspace-protocol/base64url-contract.js +27 -0
  208. package/src/workspace-protocol/constants.js +37 -0
  209. package/src/workspace-protocol/error-contract.js +24 -0
  210. package/src/workspace-protocol/fact-contract.js +110 -0
  211. package/src/workspace-protocol/governance-contract.js +47 -0
  212. package/src/workspace-protocol/index.js +53 -0
  213. package/src/workspace-protocol/json-budget.js +19 -0
  214. package/src/workspace-protocol/manifest-contract.js +186 -0
  215. package/src/workspace-protocol/publication-contract.js +212 -0
  216. package/src/workspace-protocol/query-contract.js +149 -0
  217. package/src/workspace-protocol/scalar-contract.js +192 -0
  218. package/src/workspace-protocol/sync-contract.js +184 -0
  219. package/src/workspace-protocol/text-contract.js +39 -0
  220. package/src/workspace-protocol/validation.js +66 -0
@@ -0,0 +1,547 @@
1
+ from contextlib import asynccontextmanager
2
+ from typing import Annotated
3
+
4
+ from fastapi import Depends, FastAPI, Header, Query
5
+
6
+ from .auth import matches_bootstrap_token
7
+ from .config import Settings, get_settings
8
+ from .graph_models import GraphResult
9
+ from .knowledge_usage_models import KnowledgeUsageCreate, KnowledgeUsageResult
10
+ from .project_action_models import (
11
+ KnowledgeProjectActionRequest,
12
+ KnowledgeProjectActionResult,
13
+ KnowledgeProjectPreviewRecord,
14
+ KnowledgeProjectPreviewRequest,
15
+ )
16
+ from .models import (
17
+ BootstrapRequest,
18
+ BootstrapResult,
19
+ CollaborationContextResult,
20
+ CommitResult,
21
+ KnowledgeAgentReviewCreate,
22
+ KnowledgeAgentViewCreate,
23
+ KnowledgeAgentViewResult,
24
+ KnowledgeAuditRecord,
25
+ KnowledgeCommit,
26
+ KnowledgeAssignmentChange,
27
+ KnowledgeAssignmentRecord,
28
+ KnowledgeBatchConfirmationCreate,
29
+ KnowledgeBatchConfirmationResult,
30
+ PreferenceScopeChange,
31
+ PreferenceConflictCompleteCreate,
32
+ PreferenceConflictDeferCreate,
33
+ PreferenceConflictRecord,
34
+ PreferenceConflictResolveCreate,
35
+ KnowledgeRevisionCreate,
36
+ KnowledgeRevisionRecord,
37
+ KnowledgeHumanChangeSearchRequest,
38
+ KnowledgeHumanChangeSearchResult,
39
+ MembershipCreate,
40
+ MembershipRecord,
41
+ PersonalProjectRecord,
42
+ PersonalProjectUpsert,
43
+ PrincipalCreate,
44
+ PrincipalResult,
45
+ ProjectRelationCreate,
46
+ ProjectRelationDecision,
47
+ ProjectRelationRecord,
48
+ ProjectDeleteResult,
49
+ ProjectReleaseRecord,
50
+ PublicationDraftCreate,
51
+ PublicationDraftDecision,
52
+ PublicationDraftRecord,
53
+ ProposalCreate,
54
+ ProposalDecision,
55
+ ProposalRecord,
56
+ SearchRequest,
57
+ SearchResult,
58
+ SpaceCreate,
59
+ SpaceRecord,
60
+ SubscriptionCreate,
61
+ SubscriptionDeleteResult,
62
+ SubscriptionRecord,
63
+ )
64
+ from .knowledge_audit import (
65
+ record_agent_views,
66
+ record_knowledge_usage,
67
+ review_human_change,
68
+ search_human_changes,
69
+ )
70
+ from .knowledge_batch_confirmation import confirm_knowledge_batch
71
+ from .knowledge_management import (
72
+ reassign_knowledge_item,
73
+ revise_knowledge_item,
74
+ set_preference_scope,
75
+ )
76
+ from .project_knowledge import (
77
+ apply_knowledge_project_action,
78
+ preview_knowledge_project_action,
79
+ )
80
+ from .preference_conflicts import (
81
+ complete_preference_conflict,
82
+ defer_preference_conflict,
83
+ list_preference_conflicts,
84
+ resolve_preference_conflict,
85
+ )
86
+ from .runtime import GraphitiRuntime
87
+ from .store import GraphStore
88
+
89
+
90
+ def create_app(settings: Settings | None = None) -> FastAPI:
91
+ resolved_settings = settings or get_settings()
92
+ runtime = GraphitiRuntime(resolved_settings)
93
+ store = GraphStore(runtime, resolved_settings)
94
+
95
+ @asynccontextmanager
96
+ async def lifespan(_: FastAPI):
97
+ await runtime.initialize()
98
+ yield
99
+ await runtime.close()
100
+
101
+ application = FastAPI(
102
+ title='Fuli Graph Provider',
103
+ version='0.2.0',
104
+ lifespan=lifespan,
105
+ )
106
+ application.state.store = store
107
+ application.state.settings = resolved_settings
108
+
109
+ async def current_actor(
110
+ authorization: Annotated[str | None, Header()] = None,
111
+ ) -> dict:
112
+ prefix = 'Bearer '
113
+ if not authorization or not authorization.startswith(prefix):
114
+ from fastapi import HTTPException
115
+
116
+ raise HTTPException(status_code=401, detail='bearer access token required')
117
+ return await store.authenticate(authorization[len(prefix) :])
118
+
119
+ Actor = Annotated[dict, Depends(current_actor)]
120
+
121
+ @application.get('/health')
122
+ async def health() -> dict:
123
+ return await store.health()
124
+
125
+ @application.post('/v1/bootstrap', response_model=BootstrapResult)
126
+ async def bootstrap(
127
+ request: BootstrapRequest,
128
+ x_fuli_bootstrap_token: Annotated[str | None, Header()] = None,
129
+ ) -> BootstrapResult:
130
+ if not matches_bootstrap_token(
131
+ x_fuli_bootstrap_token,
132
+ resolved_settings.bootstrap_token,
133
+ ):
134
+ from fastapi import HTTPException
135
+
136
+ raise HTTPException(status_code=401, detail='invalid bootstrap token')
137
+ principal_id, access_token = await store.bootstrap(request.principal_name)
138
+ return BootstrapResult(principal_id=principal_id, access_token=access_token)
139
+
140
+ @application.post('/v1/principals', response_model=PrincipalResult)
141
+ async def create_principal(request: PrincipalCreate, actor: Actor) -> PrincipalResult:
142
+ return await store.create_principal(actor, request.name)
143
+
144
+ @application.post('/v1/spaces', response_model=SpaceRecord)
145
+ async def create_space(request: SpaceCreate, actor: Actor) -> SpaceRecord:
146
+ return await store.create_space(actor, request)
147
+
148
+ @application.get('/v1/spaces', response_model=list[SpaceRecord])
149
+ async def list_spaces(actor: Actor) -> list[SpaceRecord]:
150
+ return await store.list_spaces(actor)
151
+
152
+ @application.get(
153
+ '/v1/projects/{project_id}/releases',
154
+ response_model=list[ProjectReleaseRecord],
155
+ )
156
+ async def list_project_releases(
157
+ project_id: str,
158
+ actor: Actor,
159
+ ) -> list[ProjectReleaseRecord]:
160
+ return await store.list_project_releases(actor, project_id)
161
+
162
+ @application.delete(
163
+ '/v1/projects/{project_id}',
164
+ response_model=ProjectDeleteResult,
165
+ )
166
+ async def delete_project(
167
+ project_id: str,
168
+ actor: Actor,
169
+ ) -> ProjectDeleteResult:
170
+ return await store.delete_project(actor, project_id)
171
+
172
+ @application.put('/v1/personal-projects', response_model=PersonalProjectRecord)
173
+ async def upsert_personal_project(
174
+ request: PersonalProjectUpsert,
175
+ actor: Actor,
176
+ ) -> PersonalProjectRecord:
177
+ return await store.upsert_personal_project(actor, request)
178
+
179
+ @application.get('/v1/personal-projects', response_model=list[PersonalProjectRecord])
180
+ async def list_personal_projects(
181
+ actor: Actor,
182
+ personal_space_id: Annotated[str, Query(min_length=1, max_length=128)],
183
+ ) -> list[PersonalProjectRecord]:
184
+ return await store.list_personal_projects(actor, personal_space_id)
185
+
186
+ @application.get(
187
+ '/v1/personal-projects/{project_id}',
188
+ response_model=PersonalProjectRecord,
189
+ )
190
+ async def get_personal_project(
191
+ project_id: str,
192
+ actor: Actor,
193
+ personal_space_id: Annotated[str, Query(min_length=1, max_length=128)],
194
+ ) -> PersonalProjectRecord:
195
+ return await store.get_personal_project(actor, personal_space_id, project_id)
196
+
197
+ @application.post('/v1/publication-drafts', response_model=PublicationDraftRecord)
198
+ async def create_publication_draft(
199
+ request: PublicationDraftCreate,
200
+ actor: Actor,
201
+ ) -> PublicationDraftRecord:
202
+ return await store.create_publication_draft(actor, request)
203
+
204
+ @application.get('/v1/publication-drafts', response_model=list[PublicationDraftRecord])
205
+ async def list_publication_drafts(
206
+ actor: Actor,
207
+ personal_space_id: Annotated[str, Query(min_length=1, max_length=128)],
208
+ draft_status: Annotated[str, Query(alias='status')] = 'pending',
209
+ ) -> list[PublicationDraftRecord]:
210
+ return await store.list_publication_drafts(actor, personal_space_id, draft_status)
211
+
212
+ @application.get(
213
+ '/v1/publication-drafts/{draft_id}',
214
+ response_model=PublicationDraftRecord,
215
+ )
216
+ async def get_publication_draft(
217
+ draft_id: str,
218
+ actor: Actor,
219
+ personal_space_id: Annotated[str, Query(min_length=1, max_length=128)],
220
+ ) -> PublicationDraftRecord:
221
+ return await store.get_publication_draft(actor, personal_space_id, draft_id)
222
+
223
+ @application.post(
224
+ '/v1/publication-drafts/{draft_id}/decision',
225
+ response_model=PublicationDraftRecord,
226
+ )
227
+ async def decide_publication_draft(
228
+ draft_id: str,
229
+ request: PublicationDraftDecision,
230
+ actor: Actor,
231
+ personal_space_id: Annotated[str, Query(min_length=1, max_length=128)],
232
+ ) -> PublicationDraftRecord:
233
+ return await store.decide_publication_draft(
234
+ actor,
235
+ personal_space_id,
236
+ draft_id,
237
+ request,
238
+ )
239
+
240
+ @application.post(
241
+ '/v1/projects/{project_id}/members',
242
+ response_model=MembershipRecord,
243
+ )
244
+ async def add_member(
245
+ project_id: str,
246
+ request: MembershipCreate,
247
+ actor: Actor,
248
+ ) -> MembershipRecord:
249
+ return await store.add_membership(actor, project_id, request)
250
+
251
+ @application.post(
252
+ '/v1/projects/{project_id}/relations',
253
+ response_model=ProjectRelationRecord,
254
+ )
255
+ async def create_project_relation(
256
+ project_id: str,
257
+ request: ProjectRelationCreate,
258
+ actor: Actor,
259
+ ) -> ProjectRelationRecord:
260
+ return await store.create_project_relation(actor, project_id, request)
261
+
262
+ @application.get(
263
+ '/v1/projects/{project_id}/relations',
264
+ response_model=list[ProjectRelationRecord],
265
+ )
266
+ async def list_project_relations(
267
+ project_id: str,
268
+ actor: Actor,
269
+ ) -> list[ProjectRelationRecord]:
270
+ return await store.list_project_relations(actor, project_id)
271
+
272
+ @application.post(
273
+ '/v1/projects/{project_id}/relations/{relation_id}/decision',
274
+ response_model=ProjectRelationRecord,
275
+ )
276
+ async def decide_project_relation(
277
+ project_id: str,
278
+ relation_id: str,
279
+ request: ProjectRelationDecision,
280
+ actor: Actor,
281
+ ) -> ProjectRelationRecord:
282
+ return await store.decide_project_relation(actor, project_id, relation_id, request)
283
+
284
+ @application.post('/v1/subscriptions', response_model=SubscriptionRecord)
285
+ async def subscribe(request: SubscriptionCreate, actor: Actor) -> SubscriptionRecord:
286
+ return await store.subscribe(actor, request)
287
+
288
+ @application.get('/v1/subscriptions', response_model=list[SubscriptionRecord])
289
+ async def list_subscriptions(
290
+ actor: Actor,
291
+ personal_space_id: Annotated[str, Query(min_length=1, max_length=128)],
292
+ ) -> list[SubscriptionRecord]:
293
+ return await store.list_subscriptions(actor, personal_space_id)
294
+
295
+ @application.delete(
296
+ '/v1/subscriptions/{project_id}',
297
+ response_model=SubscriptionDeleteResult,
298
+ )
299
+ async def unsubscribe(
300
+ project_id: str,
301
+ actor: Actor,
302
+ personal_space_id: Annotated[str, Query(min_length=1, max_length=128)],
303
+ provider_url: Annotated[str, Query(min_length=8, max_length=2048)],
304
+ ) -> SubscriptionDeleteResult:
305
+ return await store.unsubscribe(actor, personal_space_id, project_id, provider_url)
306
+
307
+ @application.post('/v1/knowledge/commits', response_model=CommitResult)
308
+ async def commit_personal(request: KnowledgeCommit, actor: Actor) -> CommitResult:
309
+ return await store.commit_personal(actor, request)
310
+
311
+ @application.patch(
312
+ '/v1/knowledge/items/{item_id}',
313
+ response_model=KnowledgeRevisionRecord,
314
+ )
315
+ async def revise_item(
316
+ item_id: str,
317
+ request: KnowledgeRevisionCreate,
318
+ actor: Actor,
319
+ ) -> KnowledgeRevisionRecord:
320
+ return await revise_knowledge_item(store, actor, item_id, request)
321
+
322
+ @application.post(
323
+ '/v1/knowledge/agent-views',
324
+ response_model=KnowledgeAgentViewResult,
325
+ )
326
+ async def record_agent_knowledge_views(
327
+ request: KnowledgeAgentViewCreate,
328
+ actor: Actor,
329
+ ) -> KnowledgeAgentViewResult:
330
+ return await record_agent_views(store, actor, request)
331
+
332
+ @application.post(
333
+ '/v1/knowledge/usage',
334
+ response_model=KnowledgeUsageResult,
335
+ )
336
+ async def record_agent_knowledge_usage(
337
+ request: KnowledgeUsageCreate,
338
+ actor: Actor,
339
+ ) -> KnowledgeUsageResult:
340
+ return await record_knowledge_usage(store, actor, request)
341
+
342
+ @application.post(
343
+ '/v1/knowledge/items/{item_id}/agent-review',
344
+ response_model=KnowledgeAuditRecord,
345
+ )
346
+ async def review_human_knowledge_change(
347
+ item_id: str,
348
+ request: KnowledgeAgentReviewCreate,
349
+ actor: Actor,
350
+ ) -> KnowledgeAuditRecord:
351
+ return await review_human_change(store, actor, item_id, request)
352
+
353
+ @application.post(
354
+ '/v1/knowledge/human-changes/search',
355
+ response_model=KnowledgeHumanChangeSearchResult,
356
+ )
357
+ async def search_human_knowledge_changes(
358
+ request: KnowledgeHumanChangeSearchRequest,
359
+ actor: Actor,
360
+ ) -> KnowledgeHumanChangeSearchResult:
361
+ return await search_human_changes(store, actor, request)
362
+
363
+ @application.post(
364
+ '/v1/knowledge/batch-confirmations',
365
+ response_model=KnowledgeBatchConfirmationResult,
366
+ )
367
+ async def confirm_batch(
368
+ request: KnowledgeBatchConfirmationCreate,
369
+ actor: Actor,
370
+ ) -> KnowledgeBatchConfirmationResult:
371
+ return await confirm_knowledge_batch(store, actor, request)
372
+
373
+ @application.post(
374
+ '/v1/knowledge/items/{item_id}/assignment',
375
+ response_model=KnowledgeAssignmentRecord,
376
+ )
377
+ async def reassign_item(
378
+ item_id: str,
379
+ request: KnowledgeAssignmentChange,
380
+ actor: Actor,
381
+ ) -> KnowledgeAssignmentRecord:
382
+ return await reassign_knowledge_item(store, actor, item_id, request)
383
+
384
+ @application.post(
385
+ '/v1/knowledge/items/{item_id}/preference-scope',
386
+ response_model=KnowledgeRevisionRecord,
387
+ )
388
+ async def change_preference_scope(
389
+ item_id: str,
390
+ request: PreferenceScopeChange,
391
+ actor: Actor,
392
+ ) -> KnowledgeRevisionRecord:
393
+ return await set_preference_scope(store, actor, item_id, request)
394
+
395
+ @application.post(
396
+ '/v1/preference-conflicts/defer',
397
+ response_model=PreferenceConflictRecord,
398
+ )
399
+ async def defer_conflict(
400
+ request: PreferenceConflictDeferCreate,
401
+ actor: Actor,
402
+ ) -> PreferenceConflictRecord:
403
+ return await defer_preference_conflict(store, actor, request)
404
+
405
+ @application.get(
406
+ '/v1/preference-conflicts',
407
+ response_model=list[PreferenceConflictRecord],
408
+ )
409
+ async def preference_conflicts(
410
+ actor: Actor,
411
+ personal_space_id: Annotated[str, Query(min_length=1, max_length=128)],
412
+ status: Annotated[str | None, Query(pattern='^(ai_pending|resolved)$')] = None,
413
+ limit: Annotated[int, Query(ge=1, le=1000)] = 500,
414
+ ) -> list[PreferenceConflictRecord]:
415
+ return await list_preference_conflicts(
416
+ store,
417
+ actor,
418
+ personal_space_id,
419
+ status=status,
420
+ limit=limit,
421
+ )
422
+
423
+ @application.post(
424
+ '/v1/preference-conflicts/{conflict_id}/resolve',
425
+ response_model=PreferenceConflictRecord,
426
+ )
427
+ async def resolve_conflict(
428
+ conflict_id: str,
429
+ request: PreferenceConflictResolveCreate,
430
+ actor: Actor,
431
+ ) -> PreferenceConflictRecord:
432
+ return await resolve_preference_conflict(
433
+ store,
434
+ actor,
435
+ conflict_id,
436
+ request,
437
+ )
438
+
439
+ @application.post(
440
+ '/v1/preference-conflicts/{conflict_id}/complete',
441
+ response_model=PreferenceConflictRecord,
442
+ )
443
+ async def complete_conflict(
444
+ conflict_id: str,
445
+ request: PreferenceConflictCompleteCreate,
446
+ actor: Actor,
447
+ ) -> PreferenceConflictRecord:
448
+ return await complete_preference_conflict(
449
+ store,
450
+ actor,
451
+ conflict_id,
452
+ request,
453
+ )
454
+
455
+ @application.post(
456
+ '/v1/knowledge/items/{item_id}/project-action/preview',
457
+ response_model=KnowledgeProjectPreviewRecord,
458
+ )
459
+ async def preview_project_action(
460
+ item_id: str,
461
+ request: KnowledgeProjectPreviewRequest,
462
+ actor: Actor,
463
+ ) -> KnowledgeProjectPreviewRecord:
464
+ return await preview_knowledge_project_action(store, actor, item_id, request)
465
+
466
+ @application.post(
467
+ '/v1/knowledge/items/{item_id}/project-action',
468
+ response_model=KnowledgeProjectActionResult,
469
+ )
470
+ async def apply_project_action(
471
+ item_id: str,
472
+ request: KnowledgeProjectActionRequest,
473
+ actor: Actor,
474
+ ) -> KnowledgeProjectActionResult:
475
+ return await apply_knowledge_project_action(store, actor, item_id, request)
476
+
477
+ @application.post(
478
+ '/v1/projects/{project_id}/proposals',
479
+ response_model=ProposalRecord,
480
+ )
481
+ async def create_proposal(
482
+ project_id: str,
483
+ request: ProposalCreate,
484
+ actor: Actor,
485
+ ) -> ProposalRecord:
486
+ return await store.create_proposal(actor, project_id, request)
487
+
488
+ @application.get(
489
+ '/v1/projects/{project_id}/proposals',
490
+ response_model=list[ProposalRecord],
491
+ )
492
+ async def list_proposals(
493
+ project_id: str,
494
+ actor: Actor,
495
+ proposal_status: Annotated[str, Query(alias='status')] = 'pending',
496
+ ) -> list[ProposalRecord]:
497
+ return await store.list_proposals(actor, project_id, proposal_status)
498
+
499
+ @application.post(
500
+ '/v1/projects/{project_id}/proposals/{proposal_id}/decision',
501
+ response_model=ProposalRecord,
502
+ )
503
+ async def decide_proposal(
504
+ project_id: str,
505
+ proposal_id: str,
506
+ decision: ProposalDecision,
507
+ actor: Actor,
508
+ ) -> ProposalRecord:
509
+ return await store.decide_proposal(actor, project_id, proposal_id, decision)
510
+
511
+ @application.post('/v1/search', response_model=SearchResult)
512
+ async def search(request: SearchRequest, actor: Actor) -> SearchResult:
513
+ return await store.search(actor, request)
514
+
515
+ @application.get(
516
+ '/v1/collaboration-preferences',
517
+ response_model=CollaborationContextResult,
518
+ )
519
+ async def collaboration_preferences(
520
+ actor: Actor,
521
+ personal_space_id: Annotated[str, Query(min_length=1, max_length=128)],
522
+ personal_project_id: Annotated[
523
+ str | None,
524
+ Query(min_length=1, max_length=128),
525
+ ] = None,
526
+ limit: Annotated[int, Query(ge=1, le=200)] = 100,
527
+ ) -> CollaborationContextResult:
528
+ return await store.collaboration_context(
529
+ actor,
530
+ personal_space_id,
531
+ personal_project_id,
532
+ limit,
533
+ )
534
+
535
+ @application.get('/v1/spaces/{space_id}/graph', response_model=GraphResult)
536
+ async def graph(
537
+ space_id: str,
538
+ actor: Actor,
539
+ limit: Annotated[int | None, Query(ge=1, le=2000)] = None,
540
+ personal_project_id: Annotated[str | None, Query(min_length=1, max_length=128)] = None,
541
+ ) -> GraphResult:
542
+ return await store.graph(actor, space_id, limit, personal_project_id)
543
+
544
+ return application
545
+
546
+
547
+ app = create_app()
@@ -0,0 +1,17 @@
1
+ import hashlib
2
+ import hmac
3
+ import secrets
4
+
5
+
6
+ def issue_access_token() -> str:
7
+ return secrets.token_urlsafe(48)
8
+
9
+
10
+ def token_hash(token: str) -> str:
11
+ return hashlib.sha256(token.encode('utf-8')).hexdigest()
12
+
13
+
14
+ def matches_bootstrap_token(provided: str | None, expected: str) -> bool:
15
+ if not provided:
16
+ return False
17
+ return hmac.compare_digest(provided, expected)