Commit Graph
359 Commits
Author SHA1 Message Date
github-actions[bot] 90d2192347 bump: version 0.108.0 → 0.108.1 2026-06-07 16:55:16 +00:00
github-actions[bot] 792c536802 bump: version 0.107.0 → 0.108.0 2026-06-07 15:22:22 +00:00
github-actions[bot] 42505f8f87 bump: version 0.106.0 → 0.107.0 2026-06-06 12:25:28 +00:00
github-actions[bot] 74b864354f bump: version 0.105.0 → 0.106.0 2026-06-06 07:33:10 +00:00
github-actions[bot] 78f3f284a6 bump: version 0.104.1 → 0.105.0 2026-06-05 03:22:24 +00:00
github-actions[bot] d421bf6953 bump: version 0.104.0 → 0.104.1 2026-06-05 01:28:03 +00:00
github-actions[bot] 3e681ef2ee bump: version 0.103.0 → 0.104.0 2026-06-05 00:47:32 +00:00
Chris CoutinhoandGitHub c67b998784 Merge pull request #858 from cbcoutinho/feat/tiered-doc-processor-b2-tier1
feat: tiered PDF processor — pypdfium2 fast path (deprecate pymupdf4llm)
2026-06-05 02:43:11 +02:00
github-actions[bot] ccff5389b0 bump: version 0.102.0 → 0.103.0 2026-06-04 23:43:48 +00:00
Chris CoutinhoandClaude Opus 4.8 c48a797896 feat: tiered PDF processor with pypdfium2 fast path (deprecate pymupdf4llm)
Replaces single-engine pymupdf4llm extraction with a tiered pipeline (Deck #205,
follows the tier-0 classifier #855). pypdfium2 becomes the default and only
hot-path PDF extractor; pymupdf4llm is deprecated to a rollback toggle.

Why: pymupdf4llm's O(n^2) find_tables drove the OOM (#852) and the form-PDF
parse timeouts (#856), carries AGPL/commercial licensing liability, and -- per
the benchmarks -- recovers near-zero usable tables on the real corpus. pypdfium2
(Apache/BSD) extracts the same text far faster (Student 1a.pdf: 120s timeout ->
0.2s) with no table-detection bomb.

- document_processors/pypdfium2_fast.py: tier-1 "fast" processor emitting text +
  exact page_boundaries (the pdf_highlighter contract). pymupdf processor is now
  tier "structured" (the rollback engine), registered but not default.
- registry: tiered routing in ProcessorRegistry. tier-1 fast extracts, then
  classification is DERIVED from that text (classifier.classify_from_text -- no
  PDF re-open), records the classification metrics, and escalates scanned /
  no-text-layer docs to the "ocr" tier when document_ocr_enabled (default off;
  no provider yet, so fast is terminal). Wires record_document_escalation + the
  real "escalated" span attribute (was hardcoded False).
- Removes the separate _shadow_classify pass from vector/processor.py -- it
  re-opened every PDF and re-extracted text (~0.5-1.3s/doc of pure duplicated
  CPU that lowered throughput); classification now rides the tier-1 extraction.
- Settings: document_tier1_engine ("pypdfium2" default | "pymupdf" rollback,
  enum-validated), document_ocr_enabled (default false).

Tests: pypdfium2 extractor, registry tiering (fast routing, rollback, classify
recording, OCR escalation on/off), classify_from_text. Full unit suite green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 01:32:14 +02:00
github-actions[bot] 967298ddbe bump: version 0.101.4 → 0.102.0 2026-06-04 22:47:40 +00:00
github-actions[bot] dd335275ac bump: version 0.101.3 → 0.101.4 2026-06-04 22:09:33 +00:00
github-actions[bot] dc01e55b60 bump: version 0.101.2 → 0.101.3 2026-06-04 22:06:48 +00:00
github-actions[bot] eb5f59f085 bump: version 0.101.1 → 0.101.2 2026-06-04 20:43:01 +00:00
github-actions[bot] 09e84783e5 bump: version 0.101.0 → 0.101.1 2026-06-04 20:32:07 +00:00
github-actions[bot] af4a264aa3 bump: version 0.100.0 → 0.101.0 2026-06-04 19:27:41 +00:00
github-actions[bot] b84ac24d10 bump: version 0.99.0 → 0.100.0 2026-06-04 19:22:47 +00:00
github-actions[bot] 55ea8dd358 bump: version 0.98.1 → 0.99.0 2026-06-04 16:00:02 +00:00
github-actions[bot] 0919513f21 bump: version 0.98.0 → 0.98.1 2026-06-04 00:01:09 +00:00
github-actions[bot] 08318c8ea6 bump: version 0.97.0 → 0.98.0 2026-06-03 21:44:27 +00:00
Chris CoutinhoandGitHub 4e983e98f6 Merge pull request #836 from cbcoutinho/feat/183-procrastinate-ingest-queue
feat: replace NATS ingest with procrastinate Postgres queue (#183)
2026-06-03 23:44:06 +02:00
github-actions[bot] 1b37089beb bump: version 0.96.0 → 0.97.0 2026-06-03 12:12:24 +00:00
Chris CoutinhoandClaude Opus 4.8 63e073c224 fix(ci): install procrastinate in the dev group so ty + unit tests resolve it
CI runs `uv run --frozen ty check -- nextcloud_mcp_server` and `uv run pytest -m
unit`, which install the default + dev groups but not the `[postgres]` optional
extra. vector/queue/procrastinate.py imports procrastinate at module scope (the
task registration needs App/Blueprint), so without it installed ty fails on
unresolved imports and the procrastinate unit tests fail to collect.

Add procrastinate + psycopg to the dev group (kept in the [postgres] extra for
production opt-in) so dev/CI always type-check and test against them, while
SQLite/personal installs stay free of the Postgres deps. Matches the repo's
optional-DB-driver philosophy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 04:25:30 +02:00
Chris CoutinhoandClaude Opus 4.8 21b7922bac feat: replace NATS ingest with procrastinate Postgres queue (#183)
Re-architect document ingest from the shared NATS-glued document-processor to a
per-tenant, in-process model owned by nextcloud-mcp-server (Deck #183). The MCP
server now owns both sides of ingest:

- Producer (api role): the scanner defers one job per changed document into the
  app's Postgres via procrastinate (queueing_lock dedup; no execution lock, so a
  crashed worker can't deadlock a doc — Qdrant upserts are idempotent).
- Consumer (worker role): `nextcloud-mcp-server worker` drains the queue and runs
  the existing process_document pipeline; a periodic task reclaims jobs orphaned
  in `doing` by a crash.

INGEST_QUEUE selects the transport (auto: postgres when DATABASE_URL is Postgres,
else the in-process anyio queue for SQLite/dev). procrastinate manages its own
tables (applied on a fresh DB at startup and by `db upgrade`). The vector-sync
status surface reads job counts from Postgres in postgres mode. procrastinate +
psycopg3 ship in the [postgres] extra; the app's own engine still uses asyncpg
(driver unification is a follow-up handled in the rendered Helm chart).

NATS JetStream, the Postgres-queue stub, the bus status subscriber, and nats-py
are removed.

BREAKING CHANGE: the external-NATS-ingest env vars are removed
(INGEST_MODE, STATUS_BACKEND, INGEST_BUS_URL, INGEST_BUS_NUM_REPLICAS,
FACT_EVENT_EMITTER). Use INGEST_QUEUE (memory|postgres) and the `worker`
command instead. TENANT_ID is retained (no longer NATS-subject-charset-validated).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 04:11:11 +02:00
github-actions[bot] db04ae3bcc bump: version 0.95.0 → 0.96.0 2026-06-03 00:09:08 +00:00
github-actions[bot] e39574e5d6 bump: version 0.94.1 → 0.95.0 2026-06-03 00:04:46 +00:00
github-actions[bot] 4c024b7f97 bump: version 0.94.0 → 0.94.1 2026-06-03 00:00:55 +00:00
github-actions[bot] f8afde0751 bump: version 0.93.0 → 0.94.0 2026-06-02 23:46:05 +00:00
github-actions[bot] 56ae9aeb0e bump: version 0.92.1 → 0.93.0 2026-06-01 15:57:21 +00:00
github-actions[bot] 4f1a42f171 bump: version 0.92.0 → 0.92.1 2026-06-01 15:56:17 +00:00
github-actions[bot] 2d191b4055 bump: version 0.91.3 → 0.92.0 2026-06-01 14:10:34 +00:00
github-actions[bot] 875f756f6e bump: version 0.91.2 → 0.91.3 2026-06-01 14:06:34 +00:00
github-actions[bot] c662d57c4f bump: version 0.91.1 → 0.91.2 2026-05-31 20:17:56 +00:00
github-actions[bot] 425ea5e0e8 bump: version 0.91.0 → 0.91.1 2026-05-31 20:16:46 +00:00
github-actions[bot] f65ebfc65b bump: version 0.90.2 → 0.91.0 2026-05-31 19:48:13 +00:00
Chris Coutinho 1528a1248d Merge remote-tracking branch 'origin/master' into feat/decomp-hook-points
# Conflicts:
#	nextcloud_mcp_server/vector/scanner.py
2026-05-31 20:10:37 +02:00
github-actions[bot] 0caf2cef8e bump: version 0.90.1 → 0.90.2 2026-05-30 12:41:59 +00:00
github-actions[bot] 541abe19f6 bump: version 0.90.0 → 0.90.1 2026-05-30 09:55:32 +00:00
Chris Coutinho a92f6260fb Merge remote-tracking branch 'origin/master' into feat/decomp-hook-points
# Conflicts:
#	nextcloud_mcp_server/vector/scanner.py
2026-05-29 18:31:05 +02:00
github-actions[bot] 76a8313717 bump: version 0.89.0 → 0.90.0 2026-05-29 16:01:53 +00:00
Chris CoutinhoandClaude Opus 4.8 d883052fb8 feat: add opt-in MCP decomposition hook points (design §10)
Adds the seven §10.2 hook-point modules + five env vars so Astrolabe Cloud can
offload document processing to the external document-processor / embedding
gateway. Purely additive: with every setting unset the server behaves exactly
as today, so self-hosters are unaffected (Deck #92).

Hook points (all default to current monolith behavior):
- config: EMBEDDING_PROVIDER, INGEST_MODE, STATUS_BACKEND,
  COLLECTION_METADATA_SOURCE, FACT_EVENT_EMITTER (+ supporting settings),
  validated in Settings.__post_init__ (fail-fast STATUS_BACKEND=local with
  INGEST_MODE=external); shared canonical.py.
- vector/payload_keys.py + acl_hash.py: cross-impl NAMESPACE/point_id (§2.2)
  and BLAKE2b-128 ACL hash (§11), pinned by fixtures shared with the
  document-processor repo.
- embedding/gateway_client.py: OpenAI-compatible GatewayProvider authenticating
  via M2M OIDC client-credentials (separate realm); manual-only registry entry.
- vector/collection_metadata.py: sentinel-point / API metadata source with env
  fallback.
- vector/queue/: hexagonal ingest producer ports + memory/NATS adapters
  (Postgres seam); INGEST_MODE=external publishes mcp.ingest.requested.{tenant}
  instead of the in-memory stream and skips the in-process processor pool. The
  lifespan becomes a composition root across both deployment branches.
- vector/queue/status.py: STATUS_BACKEND=bus subscriber feeding a StatusStore
  the vector-sync status endpoint reads.
- admin/payload_backfill.py: POST /api/v1/admin/payload-backfill (admin scope);
  processor writes the new payload keys; query-side ACL pre-filter gated behind
  ACL_PREFILTER_ENABLED (default off).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 13:13:25 +02:00
github-actions[bot] e4497c48c9 bump: version 0.88.3 → 0.89.0 2026-05-24 10:42:38 +00:00
github-actions[bot] 303d6a53a3 bump: version 0.88.2 → 0.88.3 2026-05-22 20:12:23 +00:00
github-actions[bot] 32fc03cf67 bump: version 0.88.1 → 0.88.2 2026-05-21 09:28:27 +00:00
github-actions[bot] 04a6294cc5 bump: version 0.88.0 → 0.88.1 2026-05-20 09:48:56 +00:00
github-actions[bot] 7cea24eb40 bump: version 0.87.2 → 0.88.0 2026-05-20 06:10:16 +00:00
github-actions[bot] 0c499de6ab bump: version 0.87.1 → 0.87.2 2026-05-17 17:34:35 +00:00
github-actions[bot] 63c1ff0bb4 bump: version 0.87.0 → 0.87.1 2026-05-17 16:48:02 +00:00
github-actions[bot] fb3973ae0d bump: version 0.86.4 → 0.87.0 2026-05-17 07:35:15 +00:00
Chris CoutinhoandClaude Opus 4.7 f2b7bf132f fix(storage): address PR #798 review feedback (credentials, asyncpg extra, TLS, pool)
Round-2 fixes after the bot review on PR #798 plus two user follow-ups
(self-signed Postgres support; asyncpg should be a PyPI extra). Folded
into the same PR rather than a follow-up since the work is still
unmerged.

Security
--------
- Mask database credentials in all 5 log call sites (storage.py × 4,
  migrations.py × 1) via a new `mask_db_password()` helper in config.py.
  Uses SQLAlchemy's `make_url(...).render_as_string(hide_password=True)`
  with a regex fallback so the masking path never raises.
- New `tests/unit/test_storage_logging.py` asserts a sentinel password
  never appears in `caplog` during `RefreshTokenStorage.initialize()`.

Distribution
------------
- `asyncpg` moved to `[project.optional-dependencies] postgres` so a
  vanilla `pip install nextcloud-mcp-server` no longer pulls in the
  ~5 MB C extension. The Docker image runs `uv sync --extra postgres`,
  so containerized deployments are unchanged.
- When `DATABASE_URL=postgresql+asyncpg://...` is set on a venv missing
  the extra, `RefreshTokenStorage.initialize()` raises a friendly
  RuntimeError pointing at `[postgres]` rather than the generic
  ModuleNotFoundError.

TLS for the Postgres backend
----------------------------
- New `DATABASE_VERIFY_SSL` + `DATABASE_CA_BUNDLE` env vars mirror the
  existing `NEXTCLOUD_VERIFY_SSL` / `NEXTCLOUD_CA_BUNDLE` pattern
  (validators in Settings.__post_init__, `get_database_ssl()` helper
  alongside `get_nextcloud_ssl_verify()`). `DATABASE_VERIFY_SSL=false`
  wins over `DATABASE_CA_BUNDLE` for incident-response convenience.
- Default is **None** rather than True — keeps PR #798's behavior
  intact for cluster-internal Postgres that runs without TLS. Operators
  opt into verify-full or supply a private CA. ADR-026 records the
  reasoning vs the Nextcloud HTTPS default.
- Engine factory in `storage.py` passes `ssl` via `connect_args` only
  when `get_database_ssl()` returns non-None; otherwise asyncpg's
  default (`prefer`) applies.
- Storage logs which TLS mode is active at INFO (no secret material).

Configurable connection pool
----------------------------
- `DATABASE_POOL_SIZE` (default 10) and `DATABASE_MAX_OVERFLOW`
  (default 20) replace the hardcoded engine values. With many replicas
  this can blow past managed-Postgres `max_connections=100`; tune down
  for large fleets.
- gte-1 / gte-0 validators in __post_init__ reject 0/negative pool
  sizes at startup with the offending value in the error.

Consistency polish
------------------
- Migration 006: convert raw `op.execute("ALTER TABLE ... ADD COLUMN")`
  to `op.batch_alter_table(...).add_column(sa.Column("nonce", sa.Text))`
  for stylistic consistency with the rewritten 001-005. Downgrade now
  drops the column instead of being a no-op.
- `registered_webhooks.created_at` standardized from `sa.Float` to
  `sa.BigInteger` (all other `*_at` columns); `store_webhook()` casts
  `time.time()` → `int`.
- `is_sqlite_url()` made case-insensitive.

Testing
-------
- New `tests/integration/test_storage_postgres.py::test_cleanup_expired_roundtrip`
  exercises `cleanup_expired_tokens`, `cleanup_expired_sessions`, and
  `cleanup_expired_browser_sessions` — relies on DELETE rowcount,
  historically dialect-tricky.
- `tests/unit/test_ssl_config.py` extended with `TestDatabaseSSLSettings`
  + `TestGetDatabaseSSL` classes (9 new tests) mirroring the existing
  Nextcloud SSL tests one-for-one.

Docs
----
- `docs/configuration.md` Centralized-Storage section grew the four new
  env vars + a homelab example with a private CA.
- `docs/ADR-026` grew Distribution, TLS, and `alembic/env.py` async-pattern
  subsections explaining the non-obvious design choices.

Helm chart counterpart in cbcoutinho/helm-charts PR #34 (separate
commit on `feat/nextcloud-mcp-server-database-url`).

Verification
------------
- `uv run pytest tests/unit/` — 1025 passed.
- `TEST_DATABASE_URL=... uv run pytest tests/integration/test_storage_postgres.py -m postgres` — 6 passed (including new cleanup test).
- `uv run ruff check && uv run ruff format --check && uv run ty check -- nextcloud_mcp_server` — clean.

Tracked on Astrolabe Cloud POC board, card #99.

---

_This PR was generated with the help of AI, and reviewed by a Human_

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 18:53:45 +02:00