mcp-scraper 0.34.1 → 0.36.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/README.md +10 -9
- package/dist/bin/api-server.cjs +24342 -17931
- package/dist/bin/api-server.cjs.map +1 -1
- package/dist/bin/api-server.js +3 -3
- package/dist/bin/mcp-scraper-cli.cjs +51 -7
- package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
- package/dist/bin/mcp-scraper-cli.js +48 -5
- package/dist/bin/mcp-scraper-cli.js.map +1 -1
- package/dist/bin/mcp-scraper-install.cjs +2 -2
- package/dist/bin/mcp-scraper-install.cjs.map +1 -1
- package/dist/bin/mcp-scraper-install.js +2 -2
- package/dist/bin/mcp-stdio-server.cjs +2997 -2070
- package/dist/bin/mcp-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-stdio-server.js +8 -8
- package/dist/bin/paa-harvest.cjs +125 -70
- package/dist/bin/paa-harvest.cjs.map +1 -1
- package/dist/bin/paa-harvest.js +4 -4
- package/dist/chunk-345BQXZH.js +712 -0
- package/dist/chunk-345BQXZH.js.map +1 -0
- package/dist/{chunk-M2S27J6Z.js → chunk-44HZLHDV.js} +10 -1
- package/dist/chunk-44HZLHDV.js.map +1 -0
- package/dist/chunk-4HO66323.js +7 -0
- package/dist/chunk-4HO66323.js.map +1 -0
- package/dist/{chunk-BWXLTWF7.js → chunk-4ZIJ3BKZ.js} +6 -4
- package/dist/chunk-4ZIJ3BKZ.js.map +1 -0
- package/dist/{chunk-6OPHG76G.js → chunk-5RULXBJ7.js} +3166 -2322
- package/dist/chunk-5RULXBJ7.js.map +1 -0
- package/dist/chunk-AN3VQARU.js +684 -0
- package/dist/chunk-AN3VQARU.js.map +1 -0
- package/dist/{chunk-XVVNKASZ.js → chunk-ANCGXUQJ.js} +118 -73
- package/dist/chunk-ANCGXUQJ.js.map +1 -0
- package/dist/{chunk-3HBPKR5G.js → chunk-D7LM5QZN.js} +3 -3
- package/dist/{chunk-4ZB3X6BQ.js → chunk-E5UEELA7.js} +16 -2
- package/dist/{chunk-4ZB3X6BQ.js.map → chunk-E5UEELA7.js.map} +1 -1
- package/dist/{chunk-ZID3WQID.js → chunk-FQI5PFE7.js} +9 -71
- package/dist/chunk-FQI5PFE7.js.map +1 -0
- package/dist/chunk-G3P3ZDB4.js +69 -0
- package/dist/chunk-G3P3ZDB4.js.map +1 -0
- package/dist/{chunk-7AYRWAEK.js → chunk-G7KAVJ3F.js} +2 -2
- package/dist/{chunk-7AYRWAEK.js.map → chunk-G7KAVJ3F.js.map} +1 -1
- package/dist/{chunk-62DQAWPF.js → chunk-IFYER7O4.js} +367 -42
- package/dist/chunk-IFYER7O4.js.map +1 -0
- package/dist/chunk-O2MCWFXQ.js +499 -0
- package/dist/chunk-O2MCWFXQ.js.map +1 -0
- package/dist/{chunk-JWIE5NCR.js → chunk-QZXKQB7Y.js} +140 -10
- package/dist/chunk-QZXKQB7Y.js.map +1 -0
- package/dist/{db-YAI5AQOI.js → db-N6MPVMEF.js} +8 -2
- package/dist/extract-bundle-M4SDJG3V.js +568 -0
- package/dist/extract-bundle-M4SDJG3V.js.map +1 -0
- package/dist/index.cjs +129 -70
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +4 -4
- package/dist/location-data-repository-Z4NQOU5Y.js +35 -0
- package/dist/{server-IWDHTES2.js → server-ZIGAFKOL.js} +10844 -7480
- package/dist/server-ZIGAFKOL.js.map +1 -0
- package/dist/site-extract-repository-PGQZNW6V.js +62 -0
- package/dist/site-extract-repository-PGQZNW6V.js.map +1 -0
- package/dist/{worker-645BZPEK.js → worker-EBB6CTGW.js} +7 -7
- package/docs/hosted-location-data.md +108 -0
- package/docs/mcp-tool-craft-lint.generated.md +6 -3
- package/docs/mcp-tool-manifest.generated.json +2073 -551
- package/docs/mcp-tool-quality-spec.md +1 -1
- package/docs/specs/connected-services-control-plane-decoupling-spec.md +1044 -0
- package/docs/specs/kernel-stealth-captcha-test-matrix.md +278 -0
- package/docs/specs/multimodal-image-memory-architecture-spec.md +1022 -0
- package/docs/specs/query-fanout-transport-contract-fix.md +9 -1
- package/docs/specs/unified-credit-and-scheduled-execution-billing-spec.md +36 -27
- package/package.json +6 -5
- package/dist/chunk-5PZ6N2QM.js +0 -130
- package/dist/chunk-5PZ6N2QM.js.map +0 -1
- package/dist/chunk-62DQAWPF.js.map +0 -1
- package/dist/chunk-6OPHG76G.js.map +0 -1
- package/dist/chunk-7N2KYL4U.js +0 -7
- package/dist/chunk-7N2KYL4U.js.map +0 -1
- package/dist/chunk-BWXLTWF7.js.map +0 -1
- package/dist/chunk-JWIE5NCR.js.map +0 -1
- package/dist/chunk-M2S27J6Z.js.map +0 -1
- package/dist/chunk-R7EETU7Z.js +0 -419
- package/dist/chunk-R7EETU7Z.js.map +0 -1
- package/dist/chunk-XVVNKASZ.js.map +0 -1
- package/dist/chunk-ZID3WQID.js.map +0 -1
- package/dist/extract-bundle-GPZRLBVY.js +0 -331
- package/dist/extract-bundle-GPZRLBVY.js.map +0 -1
- package/dist/server-IWDHTES2.js.map +0 -1
- package/dist/site-extract-repository-OLVWMOU2.js +0 -30
- /package/dist/{chunk-3HBPKR5G.js.map → chunk-D7LM5QZN.js.map} +0 -0
- /package/dist/{db-YAI5AQOI.js.map → db-N6MPVMEF.js.map} +0 -0
- /package/dist/{site-extract-repository-OLVWMOU2.js.map → location-data-repository-Z4NQOU5Y.js.map} +0 -0
- /package/dist/{worker-645BZPEK.js.map → worker-EBB6CTGW.js.map} +0 -0
|
@@ -0,0 +1,1022 @@
|
|
|
1
|
+
# Multimodal Image Memory Architecture and Technical Specification
|
|
2
|
+
|
|
3
|
+
- **Status:** Proposed
|
|
4
|
+
- **Date:** 2026-07-17
|
|
5
|
+
- **Target release:** Incremental, behind feature flags
|
|
6
|
+
- **Primary systems:** MCP Scraper, `mcpscraper-memory-tools`, `mcpscraper-memory-db`, memory Neon, private S3-compatible storage, Jina Embeddings API
|
|
7
|
+
- **Related decision:** `docs/decisions/2026-07-17-private-object-storage-for-multimodal-memory.md`
|
|
8
|
+
|
|
9
|
+
## 1. Executive summary
|
|
10
|
+
|
|
11
|
+
MCP Scraper will gain governed image memory: users can save an image discovered by a scraper or supplied by URL/upload, keep the original bytes, organize it by project and folder, embed it with Jina Omni v5, and retrieve it using text or another image.
|
|
12
|
+
|
|
13
|
+
The architecture preserves the current product boundaries:
|
|
14
|
+
|
|
15
|
+
- A private S3-compatible bucket stores immutable original and derived image bytes.
|
|
16
|
+
- The memory Neon database is the authoritative source for ownership, vault access, projects, folders, asset state, checksums, quotas, jobs, provenance, and embedding metadata.
|
|
17
|
+
- `mcpscraper-memory-db` owns schema, repositories, object-store contracts, and embedding/index primitives.
|
|
18
|
+
- `mcpscraper-memory-tools` owns authenticated workflows and public tool behavior.
|
|
19
|
+
- MCP Scraper remains the canonical aggregate hosted MCP execution surface and supplies scraper-to-memory handoffs.
|
|
20
|
+
- Image vectors use a new versioned PgVector index. Existing note vectors remain untouched.
|
|
21
|
+
|
|
22
|
+
The current system is closer to this capability than it appears. MCP Scraper already has blob storage and image-download paths, while the memory database already defaults to `jina-embeddings-v5-omni-small` with 1,024-dimensional vectors. The required work is to add a private governed asset plane and make the Jina adapter genuinely multimodal.
|
|
23
|
+
|
|
24
|
+
## 2. Goals
|
|
25
|
+
|
|
26
|
+
1. Persist original image bytes independently from their source URL.
|
|
27
|
+
2. Produce non-reversible image and optional fused image-plus-context embeddings.
|
|
28
|
+
3. Support text-to-image and image-to-image semantic retrieval.
|
|
29
|
+
4. Partition every asset by its owning identity and entitled vault.
|
|
30
|
+
5. Add stable project and folder organization without coupling logical moves to object copies.
|
|
31
|
+
6. Reuse existing memory authentication, sharing, quota, provenance, and aggregate MCP boundaries.
|
|
32
|
+
7. Keep all original objects private and issue only short-lived authorized reads.
|
|
33
|
+
8. Make ingest, embedding, retry, delete, and cleanup durable and observable.
|
|
34
|
+
9. Preserve current text memory behavior and vector indexes during rollout.
|
|
35
|
+
10. Leave an internal path to audio, video, and PDF assets without exposing those modalities in the first public contract.
|
|
36
|
+
|
|
37
|
+
## 3. Non-goals
|
|
38
|
+
|
|
39
|
+
- Reconstructing images from embeddings.
|
|
40
|
+
- Automatically saving every image encountered during every scrape.
|
|
41
|
+
- Replacing the current note Smart RAG implementation.
|
|
42
|
+
- Re-embedding all existing notes in the first release.
|
|
43
|
+
- Public image hosting or a public CDN library.
|
|
44
|
+
- Cross-tenant binary deduplication.
|
|
45
|
+
- Arbitrary file storage in the first release.
|
|
46
|
+
- Image generation or editing.
|
|
47
|
+
- Note-level sharing of individual assets in the first release; assets inherit vault access.
|
|
48
|
+
- Self-hosting Jina Omni v5 in the first release.
|
|
49
|
+
|
|
50
|
+
## 4. Verified current-state constraints
|
|
51
|
+
|
|
52
|
+
### 4.1 MCP Scraper aggregate service
|
|
53
|
+
|
|
54
|
+
- `src/api/blob-store.ts` provides local and Vercel Blob implementations.
|
|
55
|
+
- The current Vercel implementation writes with `access: 'public'`; it is appropriate for existing public/temporary artifacts, not private memory assets.
|
|
56
|
+
- `src/api/instagram-routes.ts` already downloads image bytes and writes them through `getBlobStore()`.
|
|
57
|
+
- `src/mcp/report-artifact-offload.ts` already uses owner-prefixed object keys for temporary report artifacts.
|
|
58
|
+
- Public MCP memory execution is routed through the hosted aggregate service; shared memory packages remain implementation sources of truth.
|
|
59
|
+
|
|
60
|
+
### 4.2 Memory service
|
|
61
|
+
|
|
62
|
+
- Durable notes, vault entitlements, usage, and provenance live in the memory Neon database.
|
|
63
|
+
- `mcpscraper-memory-db` uses `@mastra/pg` `PgVector` with the current index name `memory`.
|
|
64
|
+
- The configured default embed model is `jina-embeddings-v5-omni-small` with a default dimension of 1,024.
|
|
65
|
+
- The current AI SDK adapter is typed as `EmbeddingModelV2<string>` and sends every request with `task: 'retrieval.passage'`.
|
|
66
|
+
- Current note search embeds queries through that same adapter, so the task mode is not explicitly query-side.
|
|
67
|
+
- Existing vault resolution translates caller-visible handles into owner-qualified physical vaults before data access.
|
|
68
|
+
|
|
69
|
+
### 4.3 Thorbit Sites reuse pattern
|
|
70
|
+
|
|
71
|
+
- Thorbit Sites uses `@mastra/s3` as a mounted workspace filesystem.
|
|
72
|
+
- Its Postgres records carry `resource_id` and `project_id` independently of the object filesystem.
|
|
73
|
+
- Its current capture key convention is domain-oriented and must not be copied as a tenant-isolation scheme.
|
|
74
|
+
|
|
75
|
+
### 4.4 Jina v5 API contract
|
|
76
|
+
|
|
77
|
+
The current Jina OpenAPI describes `jina-embeddings-v5-omni-small` as accepting:
|
|
78
|
+
|
|
79
|
+
- text strings or `{ "text": "..." }`;
|
|
80
|
+
- `{ "image": "https://..." }` or `{ "image": "<base64>" }`;
|
|
81
|
+
- fused `{ "content": [{ "text": "..." }, { "image": "..." }] }` groups;
|
|
82
|
+
- `retrieval.query` and `retrieval.passage` task modes;
|
|
83
|
+
- normalized vectors with dimensions up to 1,024;
|
|
84
|
+
- multimodal usage fields including `image_tokens`.
|
|
85
|
+
|
|
86
|
+
Primary provider references:
|
|
87
|
+
|
|
88
|
+
- `https://api.jina.ai/openapi.json`
|
|
89
|
+
- `https://jina.ai/models/jina-embeddings-v5-omni-small/`
|
|
90
|
+
- `https://huggingface.co/jinaai/jina-embeddings-v5-omni-small`
|
|
91
|
+
|
|
92
|
+
### 4.5 Evidence baseline
|
|
93
|
+
|
|
94
|
+
The design was checked against these authenticated repository snapshots:
|
|
95
|
+
|
|
96
|
+
| Repository | Commit |
|
|
97
|
+
|---|---|
|
|
98
|
+
| `VilovietaSEO/mcp-scraper` | `b199e838d7ca2b9cf4945b18e84c3607edce925a` |
|
|
99
|
+
| `VilovietaSEO/mcpscraper-memory-tools` | `3acfff6d844290d14af06621578b855d3328a84c` |
|
|
100
|
+
| `VilovietaSEO/mcpscraper-memory-db` | `e22cae478f40b27995cef43357b2e03711b78559` |
|
|
101
|
+
|
|
102
|
+
Implementation must recheck the active local and deployed revisions before applying migrations because these repositories release independently.
|
|
103
|
+
|
|
104
|
+
## 5. System ownership
|
|
105
|
+
|
|
106
|
+
| Concern | Owning system | Reason |
|
|
107
|
+
|---|---|---|
|
|
108
|
+
| User account, billing plan, aggregate API key | MCP Scraper | Existing product control plane |
|
|
109
|
+
| Memory identity, key scopes, vault entitlement, sharing | Memory Neon / memory DB | Existing authorization source of truth |
|
|
110
|
+
| Project, folder, asset, object, job, quota reservation | Memory Neon / memory DB | Must transact with memory authorization and vector lifecycle |
|
|
111
|
+
| Original and derived bytes | Private S3-compatible bucket | Large immutable object workload |
|
|
112
|
+
| Asset embeddings | Versioned PgVector index in memory Neon | Existing semantic retrieval infrastructure |
|
|
113
|
+
| Authenticated asset workflows | `mcpscraper-memory-tools` | Shared by aggregate and standalone memory surfaces |
|
|
114
|
+
| Public aggregate MCP registration and policy | MCP Scraper | Existing canonical execution boundary |
|
|
115
|
+
| Scraper result to asset handoff | MCP Scraper | Has authenticated scraper context and safe-fetch utilities |
|
|
116
|
+
| Standalone memory deployment | Memory service | Direct memory clients must retain parity |
|
|
117
|
+
|
|
118
|
+
No caller may supply an owner identity, physical vault name, S3 namespace, or object key. Those values are derived from authenticated context.
|
|
119
|
+
|
|
120
|
+
## 6. High-level architecture
|
|
121
|
+
|
|
122
|
+
```text
|
|
123
|
+
MCP / web caller
|
|
124
|
+
|
|
|
125
|
+
v
|
|
126
|
+
MCP Scraper aggregate auth, billing, tool contract
|
|
127
|
+
|
|
|
128
|
+
v
|
|
129
|
+
mcpscraper-memory-tools
|
|
130
|
+
|
|
|
131
|
+
+---- authorize identity + logical vault ----------+
|
|
132
|
+
| |
|
|
133
|
+
v v
|
|
134
|
+
memory Neon private S3-compatible bucket
|
|
135
|
+
projects / folders / assets / objects / jobs original + sanitized preview
|
|
136
|
+
|
|
|
137
|
+
v
|
|
138
|
+
durable embedding worker
|
|
139
|
+
|
|
|
140
|
+
+---- signed object read or bounded base64 ----> Jina v5 Omni
|
|
141
|
+
| |
|
|
142
|
+
+<---------------- 1024-d normalized vector --------+
|
|
143
|
+
|
|
|
144
|
+
v
|
|
145
|
+
PgVector index: memory_assets_v1
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## 7. Tenant and authorization model
|
|
149
|
+
|
|
150
|
+
### 7.1 Tenant boundary
|
|
151
|
+
|
|
152
|
+
The canonical tenant is the memory `owner_identity`, not a caller-provided user ID. The existing vault entitlement model remains the authorization boundary:
|
|
153
|
+
|
|
154
|
+
1. Authenticate the memory key or hosted MCP session.
|
|
155
|
+
2. Resolve the caller-visible vault handle to `{ ownerIdentity, physicalVault }`.
|
|
156
|
+
3. Check the requested operation through the existing FGA/scope implementation.
|
|
157
|
+
4. Load projects, folders, and assets only through predicates containing both `owner_identity` and `physical_vault`.
|
|
158
|
+
5. Build object keys only from the stored opaque namespace and server-generated IDs.
|
|
159
|
+
|
|
160
|
+
Shared-vault readers may search and read assets in the shared vault. Shared-vault writers may create or mutate assets only if their entitlement grants write. Deletion requires the same destructive/admin rules used by governed memory deletion and must not be inferred from read access.
|
|
161
|
+
|
|
162
|
+
### 7.2 Opaque object namespace
|
|
163
|
+
|
|
164
|
+
Raw email addresses or other identity strings must never appear in object keys. Each owner receives an opaque namespace row generated server-side.
|
|
165
|
+
|
|
166
|
+
```text
|
|
167
|
+
{environment}/tenants/{namespace_id}/objects/{object_id}/original.{ext}
|
|
168
|
+
{environment}/tenants/{namespace_id}/objects/{object_id}/preview.webp
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Projects and folders are intentionally absent from physical keys. Renaming or moving a logical asset therefore updates Neon only.
|
|
172
|
+
|
|
173
|
+
### 7.3 Database enforcement
|
|
174
|
+
|
|
175
|
+
Repositories must use composite owner/vault predicates on every read and write. Composite foreign keys or equivalent transactional validation must prevent linking:
|
|
176
|
+
|
|
177
|
+
- a folder to another owner's project;
|
|
178
|
+
- an asset to another owner's folder;
|
|
179
|
+
- an asset to an object in another namespace;
|
|
180
|
+
- an embedding job to an asset outside the same owner.
|
|
181
|
+
|
|
182
|
+
Application authorization is mandatory even if database RLS is later added. RLS is defense in depth, not a replacement for vault entitlement resolution.
|
|
183
|
+
|
|
184
|
+
## 8. Object storage design
|
|
185
|
+
|
|
186
|
+
### 8.1 Bucket policy
|
|
187
|
+
|
|
188
|
+
- One private bucket per environment is preferred: development, staging, production.
|
|
189
|
+
- Block all public access and ACLs.
|
|
190
|
+
- Use TLS for all requests.
|
|
191
|
+
- Enable provider-managed encryption at rest; use KMS only when compliance or customer contracts justify its operating cost.
|
|
192
|
+
- Enable versioning only if recovery requirements justify retaining overwritten derivatives. Originals are immutable and do not require overwrite-based versioning.
|
|
193
|
+
- Abort incomplete multipart uploads after one day.
|
|
194
|
+
- Lifecycle-delete tombstoned objects after the configured recovery window, default seven days.
|
|
195
|
+
- Do not expose bucket listing to clients.
|
|
196
|
+
|
|
197
|
+
### 8.2 Asset object-store contract
|
|
198
|
+
|
|
199
|
+
Add a private asset-specific contract rather than weakening the current public artifact behavior:
|
|
200
|
+
|
|
201
|
+
```ts
|
|
202
|
+
interface AssetObjectStore {
|
|
203
|
+
put(input: {
|
|
204
|
+
key: string
|
|
205
|
+
body: ReadableStream | Buffer
|
|
206
|
+
contentType: string
|
|
207
|
+
contentLength?: number
|
|
208
|
+
checksumSha256?: string
|
|
209
|
+
}): Promise<{ key: string; etag?: string; versionId?: string; bytes: number }>
|
|
210
|
+
|
|
211
|
+
head(key: string): Promise<{
|
|
212
|
+
exists: boolean
|
|
213
|
+
bytes?: number
|
|
214
|
+
contentType?: string
|
|
215
|
+
checksumSha256?: string
|
|
216
|
+
etag?: string
|
|
217
|
+
}>
|
|
218
|
+
|
|
219
|
+
getStream(key: string): Promise<ReadableStream | null>
|
|
220
|
+
createSignedGetUrl(key: string, expiresInSeconds: number): Promise<string>
|
|
221
|
+
createSignedPutUrl?(input: SignedPutInput): Promise<SignedPutResult>
|
|
222
|
+
delete(key: string): Promise<void>
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Implementations:
|
|
227
|
+
|
|
228
|
+
- `S3AssetObjectStore` for production and S3-compatible providers;
|
|
229
|
+
- `LocalAssetObjectStore` for local development and tests;
|
|
230
|
+
- no public Vercel Blob implementation unless it supports private reads and equivalent deletion/head semantics.
|
|
231
|
+
|
|
232
|
+
### 8.3 Logical versus physical records
|
|
233
|
+
|
|
234
|
+
Physical objects and logical assets are separate:
|
|
235
|
+
|
|
236
|
+
- an object is immutable bytes plus checksum and derived variants;
|
|
237
|
+
- an asset is the user's governed record, title, source, vault, project, folder, tags, and lifecycle;
|
|
238
|
+
- deduplication happens within one owner namespace only;
|
|
239
|
+
- multiple logical assets may reference one owner-scoped object.
|
|
240
|
+
|
|
241
|
+
This avoids cross-project surprises while preventing repeated storage of the same bytes for one owner.
|
|
242
|
+
|
|
243
|
+
## 9. Neon schema
|
|
244
|
+
|
|
245
|
+
IDs are generated by the application using opaque prefixed IDs such as `ans_`, `apr_`, `afl_`, `aob_`, `ast_`, and `aej_`. Timestamps are UTC.
|
|
246
|
+
|
|
247
|
+
### 9.1 `mem_asset_namespaces`
|
|
248
|
+
|
|
249
|
+
| Column | Type | Rules |
|
|
250
|
+
|---|---|---|
|
|
251
|
+
| `owner_identity` | text | primary key |
|
|
252
|
+
| `namespace_id` | text | unique, opaque, immutable |
|
|
253
|
+
| `used_bytes` | bigint | nonnegative cached counter |
|
|
254
|
+
| `reserved_bytes` | bigint | nonnegative active upload reservations |
|
|
255
|
+
| `created_at` | timestamptz | default now |
|
|
256
|
+
| `updated_at` | timestamptz | default now |
|
|
257
|
+
|
|
258
|
+
The byte counters support atomic quota admission and are periodically reconciled from authoritative object rows.
|
|
259
|
+
|
|
260
|
+
### 9.2 `mem_asset_projects`
|
|
261
|
+
|
|
262
|
+
| Column | Type | Rules |
|
|
263
|
+
|---|---|---|
|
|
264
|
+
| `id` | text | primary key |
|
|
265
|
+
| `owner_identity` | text | required |
|
|
266
|
+
| `vault` | text | owner-qualified physical vault, required |
|
|
267
|
+
| `name` | text | 1-120 characters |
|
|
268
|
+
| `description` | text | nullable, max 2,000 |
|
|
269
|
+
| `status` | text | `active`, `archived`, `deleted` |
|
|
270
|
+
| `created_by` | text | authenticated identity |
|
|
271
|
+
| `created_at`, `updated_at` | timestamptz | required |
|
|
272
|
+
| `deleted_at` | timestamptz | nullable |
|
|
273
|
+
|
|
274
|
+
Unique active project name per `(owner_identity, vault, lower(name))`.
|
|
275
|
+
|
|
276
|
+
### 9.3 `mem_asset_folders`
|
|
277
|
+
|
|
278
|
+
| Column | Type | Rules |
|
|
279
|
+
|---|---|---|
|
|
280
|
+
| `id` | text | primary key |
|
|
281
|
+
| `owner_identity` | text | required |
|
|
282
|
+
| `vault` | text | required |
|
|
283
|
+
| `project_id` | text | required, owner/vault scoped |
|
|
284
|
+
| `parent_id` | text | nullable self-reference |
|
|
285
|
+
| `name` | text | 1-120 characters |
|
|
286
|
+
| `created_at`, `updated_at` | timestamptz | required |
|
|
287
|
+
| `deleted_at` | timestamptz | nullable |
|
|
288
|
+
|
|
289
|
+
Folder depth is capped at eight. Folder cycles are rejected transactionally. Names are unique among active siblings. Folder paths are computed for presentation and never used as object keys.
|
|
290
|
+
|
|
291
|
+
### 9.4 `mem_asset_objects`
|
|
292
|
+
|
|
293
|
+
| Column | Type | Rules |
|
|
294
|
+
|---|---|---|
|
|
295
|
+
| `id` | text | primary key |
|
|
296
|
+
| `owner_identity` | text | required |
|
|
297
|
+
| `namespace_id` | text | required |
|
|
298
|
+
| `sha256` | text | lowercase hex, required |
|
|
299
|
+
| `storage_key` | text | unique, server-generated |
|
|
300
|
+
| `preview_storage_key` | text | nullable |
|
|
301
|
+
| `mime_type` | text | validated MIME |
|
|
302
|
+
| `extension` | text | normalized extension |
|
|
303
|
+
| `bytes` | bigint | positive |
|
|
304
|
+
| `width`, `height` | integer | positive |
|
|
305
|
+
| `etag`, `version_id` | text | nullable provider receipts |
|
|
306
|
+
| `status` | text | `pending`, `available`, `quarantined`, `deleting`, `deleted` |
|
|
307
|
+
| `created_at`, `updated_at`, `deleted_at` | timestamptz | lifecycle timestamps |
|
|
308
|
+
|
|
309
|
+
Unique `(owner_identity, sha256)` for active/available objects. Never deduplicate across owners.
|
|
310
|
+
|
|
311
|
+
### 9.5 `mem_assets`
|
|
312
|
+
|
|
313
|
+
| Column | Type | Rules |
|
|
314
|
+
|---|---|---|
|
|
315
|
+
| `id` | text | primary key |
|
|
316
|
+
| `owner_identity` | text | required |
|
|
317
|
+
| `vault` | text | owner-qualified physical vault |
|
|
318
|
+
| `project_id` | text | nullable for Unfiled |
|
|
319
|
+
| `folder_id` | text | nullable |
|
|
320
|
+
| `object_id` | text | required |
|
|
321
|
+
| `media_kind` | text | `image` in v1 |
|
|
322
|
+
| `title` | text | 1-240 characters |
|
|
323
|
+
| `description` | text | nullable |
|
|
324
|
+
| `alt_text` | text | nullable |
|
|
325
|
+
| `source_url` | text | nullable, normalized |
|
|
326
|
+
| `source_kind` | text | `upload`, `scrape`, `screenshot`, `instagram`, `external_url`, `import` |
|
|
327
|
+
| `source_ref` | jsonb | bounded provenance, no secrets |
|
|
328
|
+
| `tags` | jsonb | canonical string array |
|
|
329
|
+
| `metadata` | jsonb | bounded non-secret metadata |
|
|
330
|
+
| `status` | text | `processing`, `ready`, `failed`, `deleting`, `deleted` |
|
|
331
|
+
| `embedding_status` | text | `pending`, `running`, `ready`, `partial`, `failed` |
|
|
332
|
+
| `failure_code`, `failure_message` | text | bounded, nullable |
|
|
333
|
+
| `created_by`, `updated_by` | text | authenticated identity |
|
|
334
|
+
| `created_at`, `updated_at`, `deleted_at` | timestamptz | lifecycle timestamps |
|
|
335
|
+
|
|
336
|
+
Indexes:
|
|
337
|
+
|
|
338
|
+
- `(owner_identity, vault, status, created_at desc)`;
|
|
339
|
+
- `(owner_identity, project_id, folder_id, created_at desc)`;
|
|
340
|
+
- GIN on `tags` only if observed query volume justifies it;
|
|
341
|
+
- source URL hash for idempotent scraper handoffs.
|
|
342
|
+
|
|
343
|
+
### 9.6 `mem_asset_embedding_jobs`
|
|
344
|
+
|
|
345
|
+
| Column | Type | Rules |
|
|
346
|
+
|---|---|---|
|
|
347
|
+
| `id` | text | primary key |
|
|
348
|
+
| `asset_id` | text | required |
|
|
349
|
+
| `owner_identity` | text | required |
|
|
350
|
+
| `representation` | text | `visual` or `fused` |
|
|
351
|
+
| `model` | text | required |
|
|
352
|
+
| `dimensions` | integer | required |
|
|
353
|
+
| `status` | text | `queued`, `running`, `retry`, `done`, `failed`, `cancelled` |
|
|
354
|
+
| `attempts` | integer | nonnegative |
|
|
355
|
+
| `max_attempts` | integer | default 5 |
|
|
356
|
+
| `run_after` | timestamptz | retry scheduling |
|
|
357
|
+
| `lease_owner` | text | nullable |
|
|
358
|
+
| `lease_until` | timestamptz | nullable |
|
|
359
|
+
| `last_error_code`, `last_error` | text | bounded |
|
|
360
|
+
| `usage` | jsonb | text/image/total token counts |
|
|
361
|
+
| `created_at`, `updated_at`, `completed_at` | timestamptz | lifecycle timestamps |
|
|
362
|
+
|
|
363
|
+
Unique active job per `(asset_id, representation, model, dimensions)`.
|
|
364
|
+
|
|
365
|
+
### 9.7 `mem_asset_upload_reservations`
|
|
366
|
+
|
|
367
|
+
Records bounded upload reservations with owner, expected bytes, expiry, idempotency key, and status. Finalization locks the owner namespace, validates quota against `used_bytes + reserved_bytes`, converts the reservation into used bytes, and creates or reuses the object row.
|
|
368
|
+
|
|
369
|
+
Expired reservations release bytes automatically.
|
|
370
|
+
|
|
371
|
+
## 10. Vector index
|
|
372
|
+
|
|
373
|
+
### 10.1 Index contract
|
|
374
|
+
|
|
375
|
+
```text
|
|
376
|
+
index name: memory_assets_v1
|
|
377
|
+
dimension: 1024
|
|
378
|
+
distance: cosine
|
|
379
|
+
normalization: true
|
|
380
|
+
model: jina-embeddings-v5-omni-small
|
|
381
|
+
document task: retrieval.passage
|
|
382
|
+
query task: retrieval.query
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
The index name is versioned because model, dimensions, normalization, representations, or metadata contracts may change.
|
|
386
|
+
|
|
387
|
+
### 10.2 Vector IDs and metadata
|
|
388
|
+
|
|
389
|
+
Vector ID:
|
|
390
|
+
|
|
391
|
+
```text
|
|
392
|
+
sha256("asset-v1::{asset_id}::{representation}::{model}::{dimensions}")
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
Required metadata:
|
|
396
|
+
|
|
397
|
+
```json
|
|
398
|
+
{
|
|
399
|
+
"assetId": "ast_...",
|
|
400
|
+
"objectId": "aob_...",
|
|
401
|
+
"ownerIdentity": "owner-qualified internal identity",
|
|
402
|
+
"vault": "owner-qualified physical vault",
|
|
403
|
+
"projectId": "apr_... or null",
|
|
404
|
+
"folderId": "afl_... or null",
|
|
405
|
+
"representation": "visual",
|
|
406
|
+
"model": "jina-embeddings-v5-omni-small",
|
|
407
|
+
"dimensions": 1024,
|
|
408
|
+
"mimeType": "image/jpeg",
|
|
409
|
+
"sourceKind": "scrape",
|
|
410
|
+
"createdAt": "ISO-8601"
|
|
411
|
+
}
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
Search authorization is derived before querying. Vector filters receive only entitled physical vaults and optional authorized project/folder IDs. Results are reloaded from Neon by asset ID under the same owner/vault predicates before they are returned. Vector metadata alone is never accepted as authorization proof.
|
|
415
|
+
|
|
416
|
+
### 10.3 Representations
|
|
417
|
+
|
|
418
|
+
V1 supports at most two vectors per asset:
|
|
419
|
+
|
|
420
|
+
1. `visual`: the sanitized image only.
|
|
421
|
+
2. `fused`: ordered image plus bounded title, alt text, description, and source context when meaningful text exists.
|
|
422
|
+
|
|
423
|
+
The visual vector preserves image-to-image similarity. The fused vector improves text retrieval for screenshots, products, charts, and page imagery. Results from both representations are fused by asset ID so one asset is returned once.
|
|
424
|
+
|
|
425
|
+
## 11. Jina embedding adapter
|
|
426
|
+
|
|
427
|
+
Do not overload the current string-only AI SDK adapter with ambiguous task selection. Add a direct typed multimodal client:
|
|
428
|
+
|
|
429
|
+
```ts
|
|
430
|
+
type JinaInput =
|
|
431
|
+
| string
|
|
432
|
+
| { text: string }
|
|
433
|
+
| { image: string }
|
|
434
|
+
| { content: Array<{ text: string } | { image: string }> }
|
|
435
|
+
|
|
436
|
+
embedJina(input: {
|
|
437
|
+
values: JinaInput[]
|
|
438
|
+
task: 'retrieval.query' | 'retrieval.passage'
|
|
439
|
+
model?: string
|
|
440
|
+
dimensions?: number
|
|
441
|
+
signal?: AbortSignal
|
|
442
|
+
}): Promise<{
|
|
443
|
+
embeddings: number[][]
|
|
444
|
+
usage: {
|
|
445
|
+
totalTokens: number
|
|
446
|
+
promptTokens: number
|
|
447
|
+
imageTokens: number
|
|
448
|
+
}
|
|
449
|
+
}>
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
Requirements:
|
|
453
|
+
|
|
454
|
+
- validate exactly one 1,024-dimensional finite vector per input;
|
|
455
|
+
- require L2 normalization from the provider and reject malformed vectors;
|
|
456
|
+
- rotate existing Jina key pool on retryable errors;
|
|
457
|
+
- retry 429, 524, and transient 5xx with capped exponential backoff and jitter;
|
|
458
|
+
- do not retry validation, authentication, or unsupported-media errors;
|
|
459
|
+
- record model, dimension, task, representation, usage, latency, and request ID without logging image bytes or signed URLs;
|
|
460
|
+
- use a signed object URL with a five-minute lifetime by default;
|
|
461
|
+
- support bounded base64 as a fallback when provider URL fetch fails;
|
|
462
|
+
- never send the original source URL when a durable private object exists.
|
|
463
|
+
|
|
464
|
+
The existing note adapter remains unchanged in the first asset release. Correcting query-side task selection for note Smart RAG is a separate measured migration because it can change ranking behavior.
|
|
465
|
+
|
|
466
|
+
## 12. Ingestion pipeline
|
|
467
|
+
|
|
468
|
+
### 12.1 Accepted inputs
|
|
469
|
+
|
|
470
|
+
- HTTPS public image URL;
|
|
471
|
+
- image URL returned by an MCP Scraper tool;
|
|
472
|
+
- existing authorized MCP Scraper artifact reference;
|
|
473
|
+
- direct web upload through presigned upload initiation/finalization;
|
|
474
|
+
- bounded base64 only for MCP clients without an upload transport.
|
|
475
|
+
|
|
476
|
+
Saving images is opt-in. Existing scrape tools must not automatically persist every discovered image.
|
|
477
|
+
|
|
478
|
+
### 12.2 Validation limits
|
|
479
|
+
|
|
480
|
+
Defaults are configuration constants, not caller-controlled values:
|
|
481
|
+
|
|
482
|
+
- maximum original size: 20 MiB;
|
|
483
|
+
- maximum redirect count: 5;
|
|
484
|
+
- fetch timeout: 60 seconds;
|
|
485
|
+
- supported v1 MIME: JPEG, PNG, WebP, and GIF;
|
|
486
|
+
- GIF embeds the sanitized first frame while retaining the original;
|
|
487
|
+
- maximum decoded pixels: 40 million;
|
|
488
|
+
- maximum preview edge: 2,048 pixels;
|
|
489
|
+
- reject SVG, HEIC, TIFF, archives, HTML, polyglots, and unknown MIME in v1;
|
|
490
|
+
- sniff bytes and decode the image; do not trust filename or `Content-Type` alone;
|
|
491
|
+
- revalidate every redirect against the public-URL/SSRF policy;
|
|
492
|
+
- reject loopback, link-local, private, metadata-service, and non-HTTP(S) destinations.
|
|
493
|
+
|
|
494
|
+
Use `sharp` or an equivalent bounded decoder to auto-orient, strip metadata, flatten unsafe animation behavior, and produce a WebP preview. Store the original unchanged and embed the sanitized derivative.
|
|
495
|
+
|
|
496
|
+
### 12.3 Save flow
|
|
497
|
+
|
|
498
|
+
1. Authenticate and resolve writable physical vault.
|
|
499
|
+
2. Validate project and folder under the same owner/vault.
|
|
500
|
+
3. Create an idempotent upload reservation.
|
|
501
|
+
4. Fetch or receive bytes with streaming size enforcement.
|
|
502
|
+
5. Sniff MIME, decode safely, compute SHA-256, dimensions, and sanitized preview.
|
|
503
|
+
6. Lock the owner namespace and enforce quota.
|
|
504
|
+
7. Reuse an existing owner-scoped object with the same SHA-256 or upload new immutable objects.
|
|
505
|
+
8. Create the logical asset row with `status=processing`.
|
|
506
|
+
9. Commit the database transaction and enqueue visual and optional fused jobs.
|
|
507
|
+
10. Return immediately with asset ID and `embeddingStatus=pending`.
|
|
508
|
+
11. Worker embeds representations and upserts vectors.
|
|
509
|
+
12. Mark the asset `ready`, `partial`, or `failed` according to representation outcomes.
|
|
510
|
+
|
|
511
|
+
Object upload and database commit cannot be one distributed transaction. Any upload that succeeds before a failed DB commit is tagged as an orphan candidate and removed by reconciliation after a safety window.
|
|
512
|
+
|
|
513
|
+
### 12.4 Idempotency
|
|
514
|
+
|
|
515
|
+
`image_asset_save` accepts an optional idempotency key. The unique key is scoped to authenticated owner plus operation. Repeating a completed request returns the same asset. Concurrent requests for identical bytes may converge on one owner-scoped object but may create distinct logical assets only when their idempotency keys differ.
|
|
516
|
+
|
|
517
|
+
## 13. Embedding worker
|
|
518
|
+
|
|
519
|
+
Use the existing durable Postgres worker pattern rather than an in-memory queue.
|
|
520
|
+
|
|
521
|
+
### 13.1 Claiming
|
|
522
|
+
|
|
523
|
+
- atomically claim eligible queued/retry jobs;
|
|
524
|
+
- set a unique lease owner and expiration;
|
|
525
|
+
- heartbeat long operations;
|
|
526
|
+
- allow expired leases to be reclaimed;
|
|
527
|
+
- cap attempts at five;
|
|
528
|
+
- cancel jobs when the asset enters deletion.
|
|
529
|
+
|
|
530
|
+
### 13.2 Execution
|
|
531
|
+
|
|
532
|
+
1. Reload asset and object with owner predicates.
|
|
533
|
+
2. Refuse deleted, quarantined, or unavailable objects.
|
|
534
|
+
3. Produce a signed preview URL or bounded base64 payload.
|
|
535
|
+
4. Build visual or fused Jina input.
|
|
536
|
+
5. Call `embedJina` with `retrieval.passage`.
|
|
537
|
+
6. Validate the vector.
|
|
538
|
+
7. Upsert the vector with versioned metadata.
|
|
539
|
+
8. Record Jina usage in `mem_usage_ledger` with source `embed_image`.
|
|
540
|
+
9. Mark the job complete and reconcile aggregate asset embedding status.
|
|
541
|
+
|
|
542
|
+
### 13.3 Failure policy
|
|
543
|
+
|
|
544
|
+
| Failure | Action |
|
|
545
|
+
|---|---|
|
|
546
|
+
| 429, 524, transient 5xx, timeout | retry with backoff |
|
|
547
|
+
| expired signed URL | refresh once within the same attempt |
|
|
548
|
+
| provider cannot fetch URL | retry once using bounded base64 |
|
|
549
|
+
| invalid or unsupported image | permanent failure, quarantine if validation missed it |
|
|
550
|
+
| invalid vector length/non-finite values | permanent provider-contract failure and alert |
|
|
551
|
+
| asset deleted during work | cancel and remove any newly written vectors |
|
|
552
|
+
|
|
553
|
+
One failed optional fused representation yields `partial`; a failed visual representation yields `failed` because visual is required.
|
|
554
|
+
|
|
555
|
+
## 14. Search and retrieval
|
|
556
|
+
|
|
557
|
+
### 14.1 Query modes
|
|
558
|
+
|
|
559
|
+
- text-to-image: embed query text with `retrieval.query`;
|
|
560
|
+
- image-to-image: embed an authorized stored asset or validated temporary image with `retrieval.query`;
|
|
561
|
+
- fused query: optional text plus image encoded as one query vector;
|
|
562
|
+
- exact filtering without embedding: project, folder, tag, source kind, source host, MIME, and date.
|
|
563
|
+
|
|
564
|
+
### 14.2 Retrieval flow
|
|
565
|
+
|
|
566
|
+
1. Authenticate and resolve readable vaults.
|
|
567
|
+
2. Validate project/folder filters against those vaults.
|
|
568
|
+
3. Embed the query with `retrieval.query` unless exact-list mode is requested.
|
|
569
|
+
4. Query `memory_assets_v1` with entitled physical-vault filters.
|
|
570
|
+
5. Overfetch up to 50 representation hits.
|
|
571
|
+
6. Fuse visual and fused hits by asset ID using reciprocal-rank fusion plus vector score.
|
|
572
|
+
7. Reload candidate assets from Neon under authorization predicates.
|
|
573
|
+
8. Apply exact metadata filters and remove deleted/not-ready rows.
|
|
574
|
+
9. Optionally rerank using bounded textual metadata; multimodal reranking with `jina-reranker-m0` is a later gated enhancement.
|
|
575
|
+
10. Return at most 30 compact asset results.
|
|
576
|
+
|
|
577
|
+
### 14.3 Unified memory search
|
|
578
|
+
|
|
579
|
+
Do not change `memory-search` defaults in the first release. Introduce asset search separately. After relevance evaluation:
|
|
580
|
+
|
|
581
|
+
- add `includeAssets?: boolean`, default `false`;
|
|
582
|
+
- query notes and assets independently;
|
|
583
|
+
- fuse only at the final result layer;
|
|
584
|
+
- preserve result modality and provenance;
|
|
585
|
+
- never pretend an asset is a note or place binary data in note text.
|
|
586
|
+
|
|
587
|
+
## 15. Public MCP contract
|
|
588
|
+
|
|
589
|
+
Canonical public IDs use the product's generated tool naming convention; generated stdio/MCPB schemas must remain in parity with hosted execution.
|
|
590
|
+
|
|
591
|
+
### 15.1 Initial tools
|
|
592
|
+
|
|
593
|
+
| Tool | Purpose | Side effect |
|
|
594
|
+
|---|---|---|
|
|
595
|
+
| `image_project_create` | Create a stable image project in a writable vault | creates project |
|
|
596
|
+
| `image_project_list` | List accessible projects | read-only |
|
|
597
|
+
| `image_folder_create` | Create a nested folder in a project | creates folder |
|
|
598
|
+
| `image_folder_list` | List project folder tree | read-only |
|
|
599
|
+
| `image_asset_save` | Save URL/artifact/base64 image and queue embedding | stores bytes, consumes quota/provider usage |
|
|
600
|
+
| `image_asset_get` | Read authorized metadata and optional short-lived preview link | read-only except access log |
|
|
601
|
+
| `image_asset_list` | Exact project/folder/tag/date listing | read-only |
|
|
602
|
+
| `image_asset_search` | Text/image semantic retrieval with filters | consumes embedding usage |
|
|
603
|
+
| `image_asset_move` | Change project/folder placement without copying bytes | updates metadata |
|
|
604
|
+
| `image_asset_delete` | Soft-delete, then purge vectors/object when unreferenced | destructive |
|
|
605
|
+
|
|
606
|
+
Project and folder deletion are separate destructive tools only if product UI needs them in the first release. A project with active assets cannot be hard-deleted; archive is the default.
|
|
607
|
+
|
|
608
|
+
### 15.2 `image_asset_save` input
|
|
609
|
+
|
|
610
|
+
Exactly one source:
|
|
611
|
+
|
|
612
|
+
```json
|
|
613
|
+
{
|
|
614
|
+
"vault": "Library",
|
|
615
|
+
"projectId": "apr_...",
|
|
616
|
+
"folderId": "afl_...",
|
|
617
|
+
"sourceUrl": "https://example.com/image.jpg",
|
|
618
|
+
"artifactId": null,
|
|
619
|
+
"imageBase64": null,
|
|
620
|
+
"title": "Pricing table screenshot",
|
|
621
|
+
"description": "Optional retrieval context",
|
|
622
|
+
"altText": "Optional source alt text",
|
|
623
|
+
"tags": ["pricing", "competitor"],
|
|
624
|
+
"sourceRef": { "pageUrl": "https://example.com/pricing" },
|
|
625
|
+
"idempotencyKey": "caller-stable-key"
|
|
626
|
+
}
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
The schema must use a discriminated union or a strict post-parse refinement so multiple sources are rejected.
|
|
630
|
+
|
|
631
|
+
### 15.3 Search result
|
|
632
|
+
|
|
633
|
+
```json
|
|
634
|
+
{
|
|
635
|
+
"assetId": "ast_...",
|
|
636
|
+
"title": "Pricing table screenshot",
|
|
637
|
+
"mimeType": "image/webp",
|
|
638
|
+
"width": 1440,
|
|
639
|
+
"height": 900,
|
|
640
|
+
"projectId": "apr_...",
|
|
641
|
+
"folderId": "afl_...",
|
|
642
|
+
"sourceUrl": "https://example.com/pricing",
|
|
643
|
+
"score": 0.84,
|
|
644
|
+
"matchRepresentations": ["visual", "fused"],
|
|
645
|
+
"embeddingModel": "jina-embeddings-v5-omni-small",
|
|
646
|
+
"preview": {
|
|
647
|
+
"url": "short-lived authorized URL",
|
|
648
|
+
"expiresAt": "ISO-8601"
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
Preview URLs are optional and short-lived. Persistent records store object keys, never signed URLs.
|
|
654
|
+
|
|
655
|
+
## 16. Existing scraper integrations
|
|
656
|
+
|
|
657
|
+
Add explicit save handoffs after the core asset tools are stable:
|
|
658
|
+
|
|
659
|
+
- screenshots;
|
|
660
|
+
- Instagram image downloads;
|
|
661
|
+
- Facebook and Google ad image URLs;
|
|
662
|
+
- site-audit image inventory;
|
|
663
|
+
- browser screenshots;
|
|
664
|
+
- extracted page image links.
|
|
665
|
+
|
|
666
|
+
Default behavior remains unchanged. Callers either invoke `image_asset_save` with a returned URL/artifact or set an explicit save option where a compound workflow justifies it.
|
|
667
|
+
|
|
668
|
+
Existing public/temporary blobs may be imported by server-side artifact reference. The importer must authenticate artifact ownership before reading it; it must not trust a caller-supplied blob URL.
|
|
669
|
+
|
|
670
|
+
## 17. Quota, usage, and billing
|
|
671
|
+
|
|
672
|
+
### 17.1 Storage quota
|
|
673
|
+
|
|
674
|
+
Storage usage equals:
|
|
675
|
+
|
|
676
|
+
```text
|
|
677
|
+
note content bytes
|
|
678
|
+
+ note vector bytes
|
|
679
|
+
+ owner-scoped asset object bytes
|
|
680
|
+
+ derived preview bytes
|
|
681
|
+
+ asset vector bytes
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
Shared assets count against the owner, not each grantee. Deduplicated objects are counted once. Soft-deleted assets continue to count until the object is purged or no active references remain.
|
|
685
|
+
|
|
686
|
+
### 17.2 Admission control
|
|
687
|
+
|
|
688
|
+
- Reserve declared bytes before accepting an upload.
|
|
689
|
+
- Reject when `used + reserved + requested > plan quota`.
|
|
690
|
+
- Finalize against actual bytes.
|
|
691
|
+
- Release unused reservation bytes.
|
|
692
|
+
- Reconcile namespace counters from object rows daily and on operator request.
|
|
693
|
+
|
|
694
|
+
### 17.3 Provider metering
|
|
695
|
+
|
|
696
|
+
Record Jina `total_tokens`, `prompt_tokens`, and `image_tokens`. Use source names `embed_image_document`, `embed_image_query`, and later `rerank_image`. Pricing must be derived from the current rate table, not hard-coded in tools.
|
|
697
|
+
|
|
698
|
+
## 18. Deletion and retention
|
|
699
|
+
|
|
700
|
+
### 18.1 Asset deletion
|
|
701
|
+
|
|
702
|
+
1. Require destructive scope and vault write/delete authority.
|
|
703
|
+
2. Mark the logical asset `deleting` and cancel queued jobs.
|
|
704
|
+
3. Delete all asset vectors.
|
|
705
|
+
4. Mark the asset `deleted` with audit provenance.
|
|
706
|
+
5. If no active logical asset references the object, mark the object `deleting`.
|
|
707
|
+
6. Delete original and preview objects asynchronously.
|
|
708
|
+
7. Decrement namespace used bytes and mark the object `deleted`.
|
|
709
|
+
|
|
710
|
+
Retries are idempotent. A failed object deletion remains visible to the cleanup worker but never restores caller access.
|
|
711
|
+
|
|
712
|
+
### 18.2 Project/folder deletion
|
|
713
|
+
|
|
714
|
+
- Folders with assets must be moved or recursively deleted explicitly.
|
|
715
|
+
- Project archive is non-destructive.
|
|
716
|
+
- Project deletion requires an explicit recursive flag and a dry-run count/byte summary or a separate confirmation contract.
|
|
717
|
+
|
|
718
|
+
### 18.3 Orphan reconciliation
|
|
719
|
+
|
|
720
|
+
Scheduled reconciliation finds:
|
|
721
|
+
|
|
722
|
+
- S3 objects with no available object row after the orphan safety window;
|
|
723
|
+
- object rows whose S3 object is missing;
|
|
724
|
+
- ready assets without required visual vectors;
|
|
725
|
+
- vectors whose asset row is deleted or missing;
|
|
726
|
+
- expired upload reservations;
|
|
727
|
+
- stale leased jobs.
|
|
728
|
+
|
|
729
|
+
## 19. Security requirements
|
|
730
|
+
|
|
731
|
+
- Private bucket with no anonymous reads.
|
|
732
|
+
- Short-lived signed GET URLs, default five minutes.
|
|
733
|
+
- Signed PUTs are bound to exact key, content type, maximum length where supported, and short expiry.
|
|
734
|
+
- All keys are server-generated and contain opaque tenant namespaces.
|
|
735
|
+
- SSRF-safe URL fetch with redirect revalidation and DNS/IP checks.
|
|
736
|
+
- MIME sniffing and bounded image decode before availability.
|
|
737
|
+
- No SVG in v1; no active content served inline.
|
|
738
|
+
- Response headers for previews use safe content types and `Content-Disposition` where appropriate.
|
|
739
|
+
- Never log object bytes, base64, signed URLs, credentials, or private customer metadata.
|
|
740
|
+
- Jina requests use the durable sanitized object, not an arbitrary unverified source URL.
|
|
741
|
+
- Provider processing and retention terms require a production/legal check before launch.
|
|
742
|
+
- The Hugging Face open-weight license must not be treated as permission for commercial self-hosting; managed API terms govern the initial deployment.
|
|
743
|
+
|
|
744
|
+
## 20. Reliability and consistency
|
|
745
|
+
|
|
746
|
+
### 20.1 Source of truth
|
|
747
|
+
|
|
748
|
+
Neon rows are authoritative for existence and authorization. S3 is authoritative for bytes. PgVector is a rebuildable derived index.
|
|
749
|
+
|
|
750
|
+
### 20.2 State invariants
|
|
751
|
+
|
|
752
|
+
- `ready` asset implies an available object and a completed visual embedding.
|
|
753
|
+
- `embedding_status=ready` implies all requested representations exist.
|
|
754
|
+
- search never returns non-ready or deleted assets.
|
|
755
|
+
- object deletion happens only after the last active logical reference is gone.
|
|
756
|
+
- signed URLs are never persisted.
|
|
757
|
+
- vectors can be deleted/rebuilt without losing the original asset.
|
|
758
|
+
|
|
759
|
+
### 20.3 Recovery
|
|
760
|
+
|
|
761
|
+
- Re-embedding is driven from asset/object rows, not source URLs.
|
|
762
|
+
- Index rebuild writes to a new versioned index, verifies counts/recall, then switches an environment alias.
|
|
763
|
+
- Object-store outage blocks new ingest and signed reads but does not corrupt metadata.
|
|
764
|
+
- Jina outage leaves assets in retryable processing state; originals remain durable.
|
|
765
|
+
|
|
766
|
+
## 21. Observability
|
|
767
|
+
|
|
768
|
+
Metrics:
|
|
769
|
+
|
|
770
|
+
- assets saved/ready/failed/deleted by source kind;
|
|
771
|
+
- bytes uploaded, deduplicated, and purged;
|
|
772
|
+
- upload reservation failures and quota rejections;
|
|
773
|
+
- validation failures by code;
|
|
774
|
+
- embedding latency, retries, token usage, and failure rate;
|
|
775
|
+
- job queue depth, oldest age, stale leases, and terminal failures;
|
|
776
|
+
- search latency, candidate counts, vector hit rate, and zero-result rate;
|
|
777
|
+
- orphan and drift reconciliation counts;
|
|
778
|
+
- signed-read issuance and denied access counts.
|
|
779
|
+
|
|
780
|
+
Structured logs include request ID, asset ID, owner namespace ID, job ID, model, task, representation, and bounded error code. Do not log raw owner identity where a stable opaque namespace suffices.
|
|
781
|
+
|
|
782
|
+
Provenance operations:
|
|
783
|
+
|
|
784
|
+
```text
|
|
785
|
+
asset_project_create
|
|
786
|
+
asset_folder_create
|
|
787
|
+
asset_save
|
|
788
|
+
asset_embed
|
|
789
|
+
asset_search
|
|
790
|
+
asset_move
|
|
791
|
+
asset_delete
|
|
792
|
+
asset_object_purge
|
|
793
|
+
asset_reconcile
|
|
794
|
+
```
|
|
795
|
+
|
|
796
|
+
## 22. Configuration
|
|
797
|
+
|
|
798
|
+
```text
|
|
799
|
+
ASSET_STORAGE_PROVIDER=s3|local
|
|
800
|
+
ASSET_S3_BUCKET=
|
|
801
|
+
ASSET_S3_REGION=
|
|
802
|
+
ASSET_S3_ENDPOINT= # optional for S3-compatible providers
|
|
803
|
+
ASSET_S3_ACCESS_KEY_ID=
|
|
804
|
+
ASSET_S3_SECRET_ACCESS_KEY=
|
|
805
|
+
ASSET_S3_FORCE_PATH_STYLE=false
|
|
806
|
+
ASSET_STORAGE_ENV_PREFIX=production
|
|
807
|
+
ASSET_SIGNED_GET_TTL_SECONDS=300
|
|
808
|
+
ASSET_SIGNED_PUT_TTL_SECONDS=900
|
|
809
|
+
ASSET_MAX_BYTES=20971520
|
|
810
|
+
ASSET_MAX_DECODED_PIXELS=40000000
|
|
811
|
+
ASSET_PREVIEW_MAX_EDGE=2048
|
|
812
|
+
ASSET_DELETE_GRACE_HOURS=168
|
|
813
|
+
ASSET_EMBED_INDEX=memory_assets_v1
|
|
814
|
+
ASSET_EMBED_MODEL=jina-embeddings-v5-omni-small
|
|
815
|
+
ASSET_EMBED_DIM=1024
|
|
816
|
+
ASSET_EMBED_WORKER_CONCURRENCY=
|
|
817
|
+
ASSET_FEATURE_ENABLED=false
|
|
818
|
+
ASSET_SEARCH_ENABLED=false
|
|
819
|
+
```
|
|
820
|
+
|
|
821
|
+
Prefer workload identity or platform-managed credentials over long-lived access keys when the deployment provider supports it. Never expose these variables to browser bundles.
|
|
822
|
+
|
|
823
|
+
## 23. Release and compatibility plan
|
|
824
|
+
|
|
825
|
+
### Phase 0: provider and storage proof
|
|
826
|
+
|
|
827
|
+
- Verify Jina v5 text-query, image-document, image-query, and fused inputs with non-sensitive fixtures.
|
|
828
|
+
- Verify 1,024 dimensions, normalization, usage fields, latency, and retry semantics.
|
|
829
|
+
- Verify private S3 put/head/signed-get/delete and local parity.
|
|
830
|
+
- Confirm managed API commercial and retention terms.
|
|
831
|
+
|
|
832
|
+
Exit: repeatable integration tests and no public object exposure.
|
|
833
|
+
|
|
834
|
+
### Phase 1: schema and internal asset plane
|
|
835
|
+
|
|
836
|
+
- Release memory DB schema/repositories and object-store contract.
|
|
837
|
+
- Add project/folder/object/asset/job state machines.
|
|
838
|
+
- Add quota reservations and reconciliation.
|
|
839
|
+
- Keep public feature flag off.
|
|
840
|
+
|
|
841
|
+
Exit: migration, repository tests, and destructive cleanup tests pass.
|
|
842
|
+
|
|
843
|
+
### Phase 2: embedding and standalone tools
|
|
844
|
+
|
|
845
|
+
- Add multimodal Jina client and worker.
|
|
846
|
+
- Add asset tools to memory tools.
|
|
847
|
+
- Add separate asset vector index and search.
|
|
848
|
+
- Deploy and verify standalone memory service.
|
|
849
|
+
|
|
850
|
+
Exit: authorized text-to-image and image-to-image live smoke pass; cross-tenant tests deny access.
|
|
851
|
+
|
|
852
|
+
### Phase 3: aggregate MCP cutover
|
|
853
|
+
|
|
854
|
+
- Update MCP Scraper package dependencies.
|
|
855
|
+
- Register generated schemas and hosted cutovers.
|
|
856
|
+
- Regenerate manifests, MCPB, SDK contracts, and docs.
|
|
857
|
+
- Verify hosted, stdio, and MCPB parity.
|
|
858
|
+
|
|
859
|
+
Exit: aggregate hosted MCP and direct memory MCP return the same contract.
|
|
860
|
+
|
|
861
|
+
### Phase 4: scraper handoffs and product UI
|
|
862
|
+
|
|
863
|
+
- Add opt-in save actions to selected image-producing tools.
|
|
864
|
+
- Add project/folder browser and image search UI.
|
|
865
|
+
- Add storage/usage visibility.
|
|
866
|
+
|
|
867
|
+
Exit: end-to-end save from a real scraper result, async embed, search, signed preview, move, and delete.
|
|
868
|
+
|
|
869
|
+
### Phase 5: optional unified Smart RAG
|
|
870
|
+
|
|
871
|
+
- Evaluate `memory-search includeAssets` relevance.
|
|
872
|
+
- Evaluate multimodal reranking.
|
|
873
|
+
- Consider a fully re-embedded unified note/asset index only with migration and rollback proof.
|
|
874
|
+
|
|
875
|
+
## 24. Test specification
|
|
876
|
+
|
|
877
|
+
### 24.1 Unit
|
|
878
|
+
|
|
879
|
+
- object-key generation never contains identity, filename, project name, or folder name;
|
|
880
|
+
- source union rejects zero or multiple sources;
|
|
881
|
+
- URL validation blocks private/metadata hosts and unsafe redirects;
|
|
882
|
+
- MIME sniffing rejects mismatches and decompression bombs;
|
|
883
|
+
- quota reservation/finalization/release arithmetic;
|
|
884
|
+
- folder depth, cycles, sibling uniqueness, and cross-owner links;
|
|
885
|
+
- Jina request shape for visual, fused, query, and passage tasks;
|
|
886
|
+
- vector validation rejects wrong dimensions and non-finite values;
|
|
887
|
+
- search fusion deduplicates representations by asset;
|
|
888
|
+
- signed URLs are omitted from persisted records and logs.
|
|
889
|
+
|
|
890
|
+
### 24.2 Database integration
|
|
891
|
+
|
|
892
|
+
- owner A cannot read/write/move/delete owner B assets;
|
|
893
|
+
- shared-vault read and write entitlements behave correctly;
|
|
894
|
+
- project/folder/object composite ownership is enforced;
|
|
895
|
+
- identical owner checksum reuses object; different owners do not;
|
|
896
|
+
- concurrent quota reservations cannot oversubscribe;
|
|
897
|
+
- lease expiry and job reclaim are safe;
|
|
898
|
+
- deletion removes vectors and purges only unreferenced objects;
|
|
899
|
+
- storage usage counts objects and vectors once.
|
|
900
|
+
|
|
901
|
+
### 24.3 Provider integration
|
|
902
|
+
|
|
903
|
+
- text query retrieves relevant fixture image;
|
|
904
|
+
- fixture image retrieves itself and a visually similar fixture;
|
|
905
|
+
- fused context improves a text-specific retrieval fixture without destroying visual recall;
|
|
906
|
+
- signed URL and base64 fallback produce compatible normalized vectors;
|
|
907
|
+
- recorded token usage matches response fields.
|
|
908
|
+
|
|
909
|
+
### 24.4 MCP contract
|
|
910
|
+
|
|
911
|
+
- tool schemas are strict and generated outputs validate;
|
|
912
|
+
- destructive annotations are correct;
|
|
913
|
+
- hosted, stdio, MCPB, standalone memory, and generated SDK schemas match;
|
|
914
|
+
- responses stay bounded and never inline original image bytes by default;
|
|
915
|
+
- expired preview links can be refreshed through an authorized read.
|
|
916
|
+
|
|
917
|
+
### 24.5 Live end-to-end
|
|
918
|
+
|
|
919
|
+
1. Save a public test image into a project/folder.
|
|
920
|
+
2. Poll until embedding is ready.
|
|
921
|
+
3. Retrieve it with a natural-language query.
|
|
922
|
+
4. Retrieve it using a second similar image.
|
|
923
|
+
5. Open the signed preview and verify private-bucket behavior after expiry.
|
|
924
|
+
6. Move it to another folder without changing object key.
|
|
925
|
+
7. Verify another user cannot discover metadata, vector hit, or object.
|
|
926
|
+
8. Delete it and verify vector removal and eventual object purge.
|
|
927
|
+
|
|
928
|
+
## 25. Acceptance criteria
|
|
929
|
+
|
|
930
|
+
- Original bytes survive source URL removal.
|
|
931
|
+
- No production asset object is anonymously readable.
|
|
932
|
+
- Every asset access is authorized through existing memory identity/vault rules.
|
|
933
|
+
- Object keys use opaque namespaces and server-generated IDs.
|
|
934
|
+
- A project/folder move performs no S3 copy.
|
|
935
|
+
- Text and image queries return relevant stored images from `memory_assets_v1`.
|
|
936
|
+
- Existing note search behavior and the `memory` index remain unchanged in the first release.
|
|
937
|
+
- Failed embeddings are retryable without re-uploading originals.
|
|
938
|
+
- Quota accounting includes original, preview, and vector bytes.
|
|
939
|
+
- Delete is idempotent and removes vectors before object purge.
|
|
940
|
+
- Hosted MCP Scraper and standalone memory contracts remain in parity.
|
|
941
|
+
- Cross-tenant, SSRF, MIME, quota-race, stale-job, and orphan-cleanup tests pass.
|
|
942
|
+
|
|
943
|
+
## 26. Implementation slices
|
|
944
|
+
|
|
945
|
+
1. **Asset storage contract:** S3/local private implementations, object-key builder, signed reads, delete/head operations.
|
|
946
|
+
2. **Memory DB schema:** namespaces, projects, folders, objects, assets, reservations, jobs, repositories, migrations.
|
|
947
|
+
3. **Multimodal Jina client:** typed request shapes, task separation, usage, retry, validation.
|
|
948
|
+
4. **Ingest pipeline:** safe fetch/upload, decode, checksum, preview, dedupe, quota, idempotency.
|
|
949
|
+
5. **Embedding worker:** leasing, visual/fused representations, versioned vector index, recovery.
|
|
950
|
+
6. **Asset retrieval:** authorized list/get/search, fusion, signed previews, provenance.
|
|
951
|
+
7. **Memory tools:** strict public schemas, project/folder/asset tools, standalone registration.
|
|
952
|
+
8. **Aggregate MCP integration:** dependency updates, cutovers, generated manifests/SDK/MCPB parity.
|
|
953
|
+
9. **Scraper handoffs:** explicit import from screenshots, Instagram, ad images, and audit inventories.
|
|
954
|
+
10. **Operations:** metrics, reconciliation, quota display, cleanup, runbooks, live proof.
|
|
955
|
+
|
|
956
|
+
Each slice requires paired tests. Public feature flags remain off until slices 1-8 and the cross-tenant live proof are complete.
|
|
957
|
+
|
|
958
|
+
## 27. Open decisions requiring implementation-time confirmation
|
|
959
|
+
|
|
960
|
+
These do not block the architecture but must be resolved with evidence before launch:
|
|
961
|
+
|
|
962
|
+
1. Which S3-compatible provider and region will be the production object store.
|
|
963
|
+
2. Whether provider workload identity is available or scoped access keys are required.
|
|
964
|
+
3. Whether a five-minute Jina-fetchable signed URL is reliable enough or base64 should be the primary transport.
|
|
965
|
+
4. Exact image-token pricing and plan margin thresholds.
|
|
966
|
+
5. Whether projects are always vault-local or the UI needs one project spanning multiple vaults. This specification chooses vault-local projects.
|
|
967
|
+
6. Whether `jina-reranker-m0` materially improves image retrieval enough to justify a second provider call.
|
|
968
|
+
7. Whether soft-delete recovery is exposed to users or remains an internal seven-day cleanup window.
|
|
969
|
+
|
|
970
|
+
Until changed by an accepted decision, the defaults in this specification govern implementation.
|
|
971
|
+
|
|
972
|
+
## 28. Expected file-level implementation map
|
|
973
|
+
|
|
974
|
+
Exact filenames may follow repository conventions, but ownership must remain as follows.
|
|
975
|
+
|
|
976
|
+
### `mcpscraper-memory-db`
|
|
977
|
+
|
|
978
|
+
```text
|
|
979
|
+
src/db/schema.ts additive asset tables and indexes
|
|
980
|
+
src/db/assets.ts owner/vault-scoped asset repositories
|
|
981
|
+
src/db/asset-projects.ts project and folder repositories
|
|
982
|
+
src/db/asset-jobs.ts leasing, retry, and reconciliation
|
|
983
|
+
src/storage/asset-object-store.ts provider-neutral private contract
|
|
984
|
+
src/storage/s3-asset-object-store.ts production S3-compatible adapter
|
|
985
|
+
src/storage/local-asset-object-store.ts local/test adapter
|
|
986
|
+
src/rag/multimodal-embedder.ts typed Jina v5 client
|
|
987
|
+
src/rag/asset-vector-store.ts memory_assets_v1 index operations
|
|
988
|
+
src/rag/asset-search.ts authorized vector query primitives
|
|
989
|
+
```
|
|
990
|
+
|
|
991
|
+
### `mcpscraper-memory-tools`
|
|
992
|
+
|
|
993
|
+
```text
|
|
994
|
+
src/tools/assets/project-create.ts
|
|
995
|
+
src/tools/assets/project-list.ts
|
|
996
|
+
src/tools/assets/folder-create.ts
|
|
997
|
+
src/tools/assets/folder-list.ts
|
|
998
|
+
src/tools/assets/asset-save.ts
|
|
999
|
+
src/tools/assets/asset-get.ts
|
|
1000
|
+
src/tools/assets/asset-list.ts
|
|
1001
|
+
src/tools/assets/asset-search.ts
|
|
1002
|
+
src/tools/assets/asset-move.ts
|
|
1003
|
+
src/tools/assets/asset-delete.ts
|
|
1004
|
+
src/lib/asset-ingest.ts validation and save orchestration
|
|
1005
|
+
src/lib/asset-access.ts entitlement-to-owner/vault resolution
|
|
1006
|
+
```
|
|
1007
|
+
|
|
1008
|
+
The standalone memory server registers these tools from the shared package and runs the durable embedding/cleanup worker or invokes a dedicated worker process using the same DB package.
|
|
1009
|
+
|
|
1010
|
+
### `mcp-scraper`
|
|
1011
|
+
|
|
1012
|
+
```text
|
|
1013
|
+
src/mcp/memory-tool-schemas.ts generated aggregate schemas
|
|
1014
|
+
src/mcp/memory-cutover/reads.ts read/search cutovers
|
|
1015
|
+
src/mcp/memory-cutover/writes.ts save/move/delete cutovers
|
|
1016
|
+
src/mcp/mcp-tool-schemas.ts aggregate registration as generated
|
|
1017
|
+
src/api/asset-import.ts owned scraper-artifact handoff
|
|
1018
|
+
scripts/generate-mcp-tool-manifest.ts regenerated contracts
|
|
1019
|
+
docs/mcp-tool-manifest.generated.json generated output
|
|
1020
|
+
```
|
|
1021
|
+
|
|
1022
|
+
Do not place private asset storage inside `src/api/blob-store.ts` by merely changing its current public behavior. Existing report and social-media artifact callers depend on that abstraction. Either leave it intact or refactor behind explicit public-artifact and private-asset interfaces with compatibility tests.
|