Files
poimen-memory/tasks/M7.5-s3-connector.md
T

110 lines
4.1 KiB
Markdown
Raw Normal View History

# M7.5 — S3-compatible storage connector
| Field | Value |
|---|---|
| Phase | M7 — Source connectors |
| Size | M — 13 days |
| Status | ⬜ Not started |
| Flags | — |
| Spec | inlined below |
| Blocks | M7.10 |
| Depends | M7.1 |
## Goal
Implement a `SourceConnector` for S3-compatible object storage (MinIO, AWS S3,
R2, etc.) so documents stored in buckets become searchable through the memory
service.
## Facts (inlined — no spec read needed)
S3 is the universal storage protocol. MinIO runs in many homelabs, and cloud
providers expose the same API. This connector makes any S3 bucket a knowledge
source.
**Configuration:**
```yaml
connectors:
- kind: s3
name: knowledge-bucket
config:
endpoint: https://minio.riotpiao.com
bucket: knowledge-base
prefix: docs/ # only this prefix
extensions: [md, txt, pdf] # filter by key suffix
access_key_secret: minio-creds # k8s secret with access/secret keys
region: us-east-1 # for AWS; ignored by MinIO
```
**ETag for change detection.** S3 objects have ETags (usually MD5 of content).
Use this as `content_hash` in `SourceDocument` — the sync framework skips objects
whose ETag hasn't changed.
**Text extraction.** S3 stores raw files. Markdown and text files are read
directly. PDF/DOCX support is out of scope for M7.5 — those MIME types are
skipped with a warning. Future: add a text extraction layer or require pre-processed
text.
**Pagination via continuation tokens.** S3 ListObjectsV2 returns max 1000 keys
per request. Use `ContinuationToken` for subsequent pages.
## Steps
1. Implement `S3Connector` in `mem-ingest/src/connectors/s3.rs`.
2. `list_documents()``ListObjectsV2` with `Prefix`, paginate, filter by
extension, return `SourceDocument` per object.
3. `fetch_document()``GetObject`, read body as text (UTF-8), return with
metadata (key, size, last_modified, ETag).
4. `health_check()``HeadBucket` to verify access.
5. Auth via access key + secret key from k8s secret.
6. Use `aws-sdk-s3` or `rust-s3` crate for S3 API.
7. Register `"s3"` kind in connector registry factory.
8. `source_type()` returns `Reference`.
## Acceptance
- `S3Connector` implements `SourceConnector` fully.
- Prefix filtering limits to configured path.
- Extension filtering skips non-text objects.
- ETag is used as `content_hash` for change detection.
- Pagination handles > 1000 objects.
- Auth works with MinIO and AWS-style credentials.
## Verify
**Harness:** mock S3 server (localstack or in-process mock) with fixture objects.
**Integration test**`tests/it_s3_connector.rs`:
1. `a1_list_with_prefix` — mock bucket with objects under `docs/` and `images/`;
assert only `docs/` objects listed.
2. `a2_extension_filtering` — mock with .md, .png, .txt; assert .png excluded.
3. `a3_fetch_returns_content` — fetch a .md object; assert content matches.
4. `a4_etag_as_content_hash` — assert `SourceDocument.content_hash` equals
the object's ETag.
5. `a5_pagination` — mock 1500 objects; assert all listed via continuation tokens.
6. `a6_health_check_valid_bucket` — mock HeadBucket 200; assert reachable.
7. `a7_health_check_no_access` — mock HeadBucket 403; assert not reachable with
error message.
8. `a8_non_utf8_skipped` — mock object with binary content; assert skipped with
warning, not crash.
9. `a9_config_from_yaml` — parse connector from YAML; assert fields match.
**Command:** `cargo test --test it_s3_connector`
**False pass:**
- Testing with a local filesystem mock instead of S3 API mock. The pagination
and ETag handling are S3-specific.
## Traps
- Assuming ETags are always MD5. Multipart uploads produce composite ETags
(`hash-N`). These are still unique per version — use as-is for change detection.
- Not handling `NoSuchBucket` vs `AccessDenied`. Both are errors but mean
different things for health reporting.
- Reading binary files as UTF-8. A JPEG read as text produces garbage. Check
content-type header and skip non-text MIME types.
---
Background: [DESIGN.md](../DESIGN.md) — source connectors section