alvis 1.0.0rc1__tar.gz

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 (151) hide show
  1. alvis-1.0.0rc1/.dockerignore +13 -0
  2. alvis-1.0.0rc1/.github/workflows/ci.yml +99 -0
  3. alvis-1.0.0rc1/.github/workflows/publish.yml +79 -0
  4. alvis-1.0.0rc1/.gitignore +22 -0
  5. alvis-1.0.0rc1/CHANGELOG.md +40 -0
  6. alvis-1.0.0rc1/CONSTITUTION.md +110 -0
  7. alvis-1.0.0rc1/CONTRIBUTING.md +244 -0
  8. alvis-1.0.0rc1/Dockerfile +39 -0
  9. alvis-1.0.0rc1/LICENSE +201 -0
  10. alvis-1.0.0rc1/PKG-INFO +539 -0
  11. alvis-1.0.0rc1/README.md +488 -0
  12. alvis-1.0.0rc1/ROADMAP.md +118 -0
  13. alvis-1.0.0rc1/docker/mock-confluence/Dockerfile +9 -0
  14. alvis-1.0.0rc1/docker/mock-confluence/main.py +109 -0
  15. alvis-1.0.0rc1/docker/observability/docker-compose.yml +57 -0
  16. alvis-1.0.0rc1/docker/observability/grafana/dashboards/alvis.json +148 -0
  17. alvis-1.0.0rc1/docker/observability/grafana/provisioning/dashboards/dashboards.yml +10 -0
  18. alvis-1.0.0rc1/docker/observability/grafana/provisioning/datasources/datasource.yml +7 -0
  19. alvis-1.0.0rc1/docker/observability/pipeline.yaml +26 -0
  20. alvis-1.0.0rc1/docker/observability/prometheus.yml +9 -0
  21. alvis-1.0.0rc1/docker-compose.yml +67 -0
  22. alvis-1.0.0rc1/docs/README.md +23 -0
  23. alvis-1.0.0rc1/docs/dsl.md +84 -0
  24. alvis-1.0.0rc1/docs/faq.md +99 -0
  25. alvis-1.0.0rc1/docs/getting-started.md +69 -0
  26. alvis-1.0.0rc1/docs/observability.md +132 -0
  27. alvis-1.0.0rc1/docs/plugins.md +64 -0
  28. alvis-1.0.0rc1/docs/release-1.0.md +130 -0
  29. alvis-1.0.0rc1/docs/schema.md +60 -0
  30. alvis-1.0.0rc1/docs/status.md +141 -0
  31. alvis-1.0.0rc1/docs/versioning.md +57 -0
  32. alvis-1.0.0rc1/examples/confluence-qdrant.yaml +27 -0
  33. alvis-1.0.0rc1/examples/docs/alvis-guide.md +27 -0
  34. alvis-1.0.0rc1/examples/docs/infra.md +19 -0
  35. alvis-1.0.0rc1/examples/documents/intro.md +11 -0
  36. alvis-1.0.0rc1/examples/hello-pipeline.yaml +21 -0
  37. alvis-1.0.0rc1/examples/kb-plugin/catalog.yaml +10 -0
  38. alvis-1.0.0rc1/examples/kb-plugin/data/incremental-ingestion.txt +3 -0
  39. alvis-1.0.0rc1/examples/kb-plugin/data/scalable-vector-search.txt +3 -0
  40. alvis-1.0.0rc1/examples/kb-plugin/kb_plugin/__init__.py +79 -0
  41. alvis-1.0.0rc1/examples/kb-plugin/pyproject.toml +14 -0
  42. alvis-1.0.0rc1/examples/pgvector.yaml +26 -0
  43. alvis-1.0.0rc1/examples/pipeline.yaml +27 -0
  44. alvis-1.0.0rc1/examples/python_dsl.py +35 -0
  45. alvis-1.0.0rc1/examples/s3-qdrant.yaml +18 -0
  46. alvis-1.0.0rc1/examples/s3-seed/docs/bucket-notes.md +8 -0
  47. alvis-1.0.0rc1/examples/s3-seed/manuals/quickstart.txt +8 -0
  48. alvis-1.0.0rc1/examples/s3-seed/videos/demo.mp4 +0 -0
  49. alvis-1.0.0rc1/examples/url-qdrant.yaml +29 -0
  50. alvis-1.0.0rc1/pyproject.toml +95 -0
  51. alvis-1.0.0rc1/scripts/check_golden_coverage.py +58 -0
  52. alvis-1.0.0rc1/scripts/check_release.py +30 -0
  53. alvis-1.0.0rc1/src/alvis/__init__.py +39 -0
  54. alvis-1.0.0rc1/src/alvis/__main__.py +6 -0
  55. alvis-1.0.0rc1/src/alvis/answer.py +160 -0
  56. alvis-1.0.0rc1/src/alvis/chunk/__init__.py +7 -0
  57. alvis-1.0.0rc1/src/alvis/chunk/auto.py +109 -0
  58. alvis-1.0.0rc1/src/alvis/chunk/sections.py +50 -0
  59. alvis-1.0.0rc1/src/alvis/chunk/size.py +68 -0
  60. alvis-1.0.0rc1/src/alvis/cli.py +866 -0
  61. alvis-1.0.0rc1/src/alvis/config/__init__.py +22 -0
  62. alvis-1.0.0rc1/src/alvis/config/loader.py +47 -0
  63. alvis-1.0.0rc1/src/alvis/config/models.py +175 -0
  64. alvis-1.0.0rc1/src/alvis/core/__init__.py +5 -0
  65. alvis-1.0.0rc1/src/alvis/core/ids.py +11 -0
  66. alvis-1.0.0rc1/src/alvis/core/models.py +96 -0
  67. alvis-1.0.0rc1/src/alvis/deprecated.py +72 -0
  68. alvis-1.0.0rc1/src/alvis/docstore.py +247 -0
  69. alvis-1.0.0rc1/src/alvis/dsl.py +325 -0
  70. alvis-1.0.0rc1/src/alvis/embed/__init__.py +23 -0
  71. alvis-1.0.0rc1/src/alvis/embed/base.py +36 -0
  72. alvis-1.0.0rc1/src/alvis/embed/cache.py +173 -0
  73. alvis-1.0.0rc1/src/alvis/embed/hash.py +52 -0
  74. alvis-1.0.0rc1/src/alvis/embed/openai.py +73 -0
  75. alvis-1.0.0rc1/src/alvis/env.py +78 -0
  76. alvis-1.0.0rc1/src/alvis/errors.py +7 -0
  77. alvis-1.0.0rc1/src/alvis/extract/__init__.py +18 -0
  78. alvis-1.0.0rc1/src/alvis/extract/base.py +85 -0
  79. alvis-1.0.0rc1/src/alvis/extract/csv.py +66 -0
  80. alvis-1.0.0rc1/src/alvis/extract/documents.py +164 -0
  81. alvis-1.0.0rc1/src/alvis/extract/json.py +51 -0
  82. alvis-1.0.0rc1/src/alvis/extract/markdown.py +62 -0
  83. alvis-1.0.0rc1/src/alvis/factories.py +154 -0
  84. alvis-1.0.0rc1/src/alvis/index/__init__.py +8 -0
  85. alvis-1.0.0rc1/src/alvis/index/base.py +52 -0
  86. alvis-1.0.0rc1/src/alvis/index/memory.py +99 -0
  87. alvis-1.0.0rc1/src/alvis/index/pgvector.py +196 -0
  88. alvis-1.0.0rc1/src/alvis/index/qdrant.py +210 -0
  89. alvis-1.0.0rc1/src/alvis/observability.py +446 -0
  90. alvis-1.0.0rc1/src/alvis/pipeline/__init__.py +5 -0
  91. alvis-1.0.0rc1/src/alvis/pipeline/engine.py +323 -0
  92. alvis-1.0.0rc1/src/alvis/pipeline/runner.py +285 -0
  93. alvis-1.0.0rc1/src/alvis/pipeline/stages.py +272 -0
  94. alvis-1.0.0rc1/src/alvis/plugin.py +277 -0
  95. alvis-1.0.0rc1/src/alvis/registry.py +76 -0
  96. alvis-1.0.0rc1/src/alvis/sources/__init__.py +20 -0
  97. alvis-1.0.0rc1/src/alvis/sources/base.py +42 -0
  98. alvis-1.0.0rc1/src/alvis/sources/confluence.py +100 -0
  99. alvis-1.0.0rc1/src/alvis/sources/content_types.py +87 -0
  100. alvis-1.0.0rc1/src/alvis/sources/fs.py +96 -0
  101. alvis-1.0.0rc1/src/alvis/sources/github.py +134 -0
  102. alvis-1.0.0rc1/src/alvis/sources/gitlab.py +157 -0
  103. alvis-1.0.0rc1/src/alvis/sources/http.py +193 -0
  104. alvis-1.0.0rc1/src/alvis/sources/s3.py +264 -0
  105. alvis-1.0.0rc1/src/alvis/sources/url.py +273 -0
  106. alvis-1.0.0rc1/src/alvis/testing.py +157 -0
  107. alvis-1.0.0rc1/src/alvis/transport.py +13 -0
  108. alvis-1.0.0rc1/tests/fixtures/corpus/data.csv +3 -0
  109. alvis-1.0.0rc1/tests/fixtures/corpus/guide.md +12 -0
  110. alvis-1.0.0rc1/tests/fixtures/corpus/notes.txt +3 -0
  111. alvis-1.0.0rc1/tests/golden/confluence.golden.json +26 -0
  112. alvis-1.0.0rc1/tests/golden/fs.golden.json +32 -0
  113. alvis-1.0.0rc1/tests/golden/github.golden.json +14 -0
  114. alvis-1.0.0rc1/tests/golden/gitlab.golden.json +13 -0
  115. alvis-1.0.0rc1/tests/golden/manifest.json +10 -0
  116. alvis-1.0.0rc1/tests/golden/s3.golden.json +13 -0
  117. alvis-1.0.0rc1/tests/golden/static_url.golden.json +24 -0
  118. alvis-1.0.0rc1/tests/test_answer.py +116 -0
  119. alvis-1.0.0rc1/tests/test_chunk.py +33 -0
  120. alvis-1.0.0rc1/tests/test_chunk_sections.py +56 -0
  121. alvis-1.0.0rc1/tests/test_chunk_size.py +50 -0
  122. alvis-1.0.0rc1/tests/test_cli.py +513 -0
  123. alvis-1.0.0rc1/tests/test_config.py +178 -0
  124. alvis-1.0.0rc1/tests/test_deprecated.py +46 -0
  125. alvis-1.0.0rc1/tests/test_documents.py +153 -0
  126. alvis-1.0.0rc1/tests/test_dsl.py +107 -0
  127. alvis-1.0.0rc1/tests/test_embed_cache.py +137 -0
  128. alvis-1.0.0rc1/tests/test_embed_index.py +55 -0
  129. alvis-1.0.0rc1/tests/test_embed_openai.py +111 -0
  130. alvis-1.0.0rc1/tests/test_env.py +103 -0
  131. alvis-1.0.0rc1/tests/test_extract.py +52 -0
  132. alvis-1.0.0rc1/tests/test_extract_csv.py +63 -0
  133. alvis-1.0.0rc1/tests/test_extract_json.py +85 -0
  134. alvis-1.0.0rc1/tests/test_golden_sources.py +209 -0
  135. alvis-1.0.0rc1/tests/test_http.py +120 -0
  136. alvis-1.0.0rc1/tests/test_idempotency.py +84 -0
  137. alvis-1.0.0rc1/tests/test_incremental.py +282 -0
  138. alvis-1.0.0rc1/tests/test_listing_sources.py +162 -0
  139. alvis-1.0.0rc1/tests/test_memory_safe.py +114 -0
  140. alvis-1.0.0rc1/tests/test_observability.py +259 -0
  141. alvis-1.0.0rc1/tests/test_pgvector.py +127 -0
  142. alvis-1.0.0rc1/tests/test_pipeline.py +160 -0
  143. alvis-1.0.0rc1/tests/test_plugin_sdk.py +255 -0
  144. alvis-1.0.0rc1/tests/test_qdrant.py +223 -0
  145. alvis-1.0.0rc1/tests/test_qdrant_integration.py +49 -0
  146. alvis-1.0.0rc1/tests/test_query.py +262 -0
  147. alvis-1.0.0rc1/tests/test_remote_sources.py +297 -0
  148. alvis-1.0.0rc1/tests/test_runner.py +85 -0
  149. alvis-1.0.0rc1/tests/test_stages.py +72 -0
  150. alvis-1.0.0rc1/tests/test_url_source.py +189 -0
  151. alvis-1.0.0rc1/tests/test_version.py +5 -0
@@ -0,0 +1,13 @@
1
+ .git
2
+ .github
3
+ .venv
4
+ .mypy_cache
5
+ .pytest_cache
6
+ .ruff_cache
7
+ __pycache__
8
+ *.py[cod]
9
+ docker
10
+ docs
11
+ examples
12
+ scripts
13
+ tests
@@ -0,0 +1,99 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ tags: ["v*"]
7
+ pull_request:
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ python-version: ["3.10", "3.11", "3.12"]
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+
18
+ - uses: actions/setup-python@v5
19
+ with:
20
+ python-version: ${{ matrix.python-version }}
21
+
22
+ - name: Install
23
+ run: pip install -e ".[dev]"
24
+
25
+ - name: Lint (ruff)
26
+ run: ruff check src tests
27
+
28
+ - name: Typecheck (mypy)
29
+ run: mypy src/alvis
30
+
31
+ - name: Tests (pytest)
32
+ run: pytest --cov=alvis --cov-report=term-missing --cov-fail-under=90
33
+
34
+ golden:
35
+ runs-on: ubuntu-latest
36
+ steps:
37
+ - uses: actions/checkout@v4
38
+
39
+ - uses: actions/setup-python@v5
40
+ with:
41
+ python-version: "3.12"
42
+
43
+ - name: Install
44
+ run: pip install -e ".[dev]"
45
+
46
+ - name: Golden tests (read-only snapshots)
47
+ run: pytest -m golden
48
+
49
+ - name: Golden coverage (every connector ships fixture + snapshot)
50
+ run: python scripts/check_golden_coverage.py
51
+
52
+ audit:
53
+ runs-on: ubuntu-latest
54
+ steps:
55
+ - uses: actions/checkout@v4
56
+
57
+ - uses: actions/setup-python@v5
58
+ with:
59
+ python-version: "3.12"
60
+
61
+ - name: Install (runtime deps + extras)
62
+ run: pip install -e ".[documents,pgindex,observability]"
63
+
64
+ - name: Audit dependencies (pip-audit)
65
+ run: pip install pip-audit && pip-audit
66
+
67
+ integration:
68
+ name: Qdrant integration
69
+ runs-on: ubuntu-latest
70
+ services:
71
+ qdrant:
72
+ image: qdrant/qdrant:v1.13.6
73
+ ports:
74
+ - 6333:6333
75
+ steps:
76
+ - uses: actions/checkout@v4
77
+
78
+ - uses: actions/setup-python@v5
79
+ with:
80
+ python-version: "3.12"
81
+
82
+ - name: Install
83
+ run: pip install -e ".[dev]"
84
+
85
+ - name: Wait for Qdrant
86
+ run: |
87
+ for attempt in {1..30}; do
88
+ if curl --fail --silent http://localhost:6333/readyz >/dev/null; then
89
+ exit 0
90
+ fi
91
+ sleep 2
92
+ done
93
+ docker logs "${{ job.services.qdrant.id }}"
94
+ exit 1
95
+
96
+ - name: Filesystem -> Qdrant -> query -> incremental -> reconcile
97
+ env:
98
+ ALVIS_QDRANT_URL: http://localhost:6333
99
+ run: pytest -m integration
@@ -0,0 +1,79 @@
1
+ name: Publish
2
+
3
+ on:
4
+ push:
5
+ tags: ["v*"]
6
+
7
+ permissions:
8
+ contents: read
9
+ id-token: write
10
+
11
+ jobs:
12
+ package:
13
+ name: Build and publish to PyPI
14
+ runs-on: ubuntu-latest
15
+ environment:
16
+ name: pypi
17
+ url: https://pypi.org/project/alvis/
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+
21
+ - uses: actions/setup-python@v5
22
+ with:
23
+ python-version: "3.12"
24
+
25
+ - name: Validate release tag
26
+ env:
27
+ GITHUB_REF_NAME: ${{ github.ref_name }}
28
+ run: python scripts/check_release.py
29
+
30
+ - name: Install build tools
31
+ run: python -m pip install --upgrade build twine
32
+
33
+ - name: Build distributions
34
+ run: python -m build
35
+
36
+ - name: Validate distributions
37
+ run: python -m twine check dist/*
38
+
39
+ - name: Publish to PyPI
40
+ uses: pypa/gh-action-pypi-publish@release/v1
41
+
42
+ docker:
43
+ name: Build and publish Docker image
44
+ runs-on: ubuntu-latest
45
+ needs: package
46
+ steps:
47
+ - uses: actions/checkout@v4
48
+
49
+ - name: Set up Docker Buildx
50
+ uses: docker/setup-buildx-action@v3
51
+
52
+ - name: Set up QEMU (multi-arch builds)
53
+ uses: docker/setup-qemu-action@v3
54
+
55
+ - name: Extract metadata (tags, labels)
56
+ id: meta
57
+ uses: docker/metadata-action@v5
58
+ with:
59
+ images: bzdvdn/alvis
60
+ tags: |
61
+ type=ref,event=tag
62
+ type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/') }}
63
+
64
+ - name: Log in to Docker Hub
65
+ uses: docker/login-action@v3
66
+ with:
67
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
68
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
69
+
70
+ - name: Build and push multi-platform image
71
+ uses: docker/build-push-action@v6
72
+ with:
73
+ context: .
74
+ push: true
75
+ tags: ${{ steps.meta.outputs.tags }}
76
+ labels: ${{ steps.meta.outputs.labels }}
77
+ cache-from: type=gha
78
+ cache-to: type=gha,mode=max
79
+ platforms: linux/amd64,linux/arm64
@@ -0,0 +1,22 @@
1
+ # Local (never committed) — RU constitution copy
2
+ CONSTITUTION.ru.md
3
+
4
+ # Python
5
+ __pycache__/
6
+ *.py[cod]
7
+ *.egg-info/
8
+ .eggs/
9
+ build/
10
+ dist/
11
+ .venv/
12
+ venv/
13
+ .mypy_cache/
14
+ .ruff_cache/
15
+ .pytest_cache/
16
+ .coverage
17
+ htmlcov/
18
+ # incremental ingestion state
19
+ .alvis/
20
+
21
+ # port pilot (workspace, not repo content)
22
+ pilot/
@@ -0,0 +1,40 @@
1
+ # Changelog
2
+
3
+ All notable changes to Alvis are documented here.
4
+
5
+ ## [1.0.0rc1] - 2026-08-20
6
+
7
+ First release candidate for the stable Alvis contract.
8
+
9
+ ### Added
10
+
11
+ - Versioned YAML pipeline schema (`schema_version: 1`) and Canonical Content Tree v1.
12
+ - Typed Python DSL with parity to the YAML contract.
13
+ - Filesystem, Confluence, GitHub, GitLab, S3, and static URL sources.
14
+ - Memory, Qdrant, and optional pgvector indexes.
15
+ - Incremental ingestion, cheap remote listing fingerprints, reconcile, and watch mode.
16
+ - OpenAI-compatible embeddings with batching, retries, and optional caching.
17
+ - Retrieval APIs (`alvis.query`, `alvis.answer`) and CLI commands.
18
+ - Plugin SDK with discovery, validation, and a documented deprecation policy.
19
+ - Structured logging, metrics, optional Prometheus/OpenTelemetry support, and status reporting.
20
+ - Versioned Qdrant payload fields with reserved `__` system namespace and `__document_id`.
21
+ - Multi-platform Docker image build configuration for `bzdvdn/alvis`.
22
+
23
+ ### Breaking Changes
24
+
25
+ - Product, import package, CLI, and distribution were renamed from `winnow` to `alvis`.
26
+ - The default incremental state directory is now `.alvis/`.
27
+ - The default CLI command is now `alvis`.
28
+
29
+ ### Known Limitations
30
+
31
+ - Qdrant point identity and reconcile currently use source URI; document-id-ledger
32
+ ownership is planned for a later release.
33
+ - Metadata filters, hybrid search, reranking, multi-turn history, and LLM-based
34
+ evaluation remain v2.x scope.
35
+ - The `default` embedder is deterministic and intended for development/testing;
36
+ production deployments should configure an OpenAI-compatible embedder.
37
+
38
+ ## [0.6.0]
39
+
40
+ See the repository history for pre-Alvis development releases.
@@ -0,0 +1,110 @@
1
+ # Alvis — Knowledge Ingestion Engine
2
+
3
+ **Project Constitution**
4
+
5
+ **Version:** 1.2.0
6
+ **Status:** Architecture baseline
7
+ **Language:** Python 3.10+
8
+ **Primary backends:** Qdrant, PostgreSQL/pgvector
9
+ **Primary sources:** GitLab, Confluence, S3
10
+ **Primary formats:** Markdown, HTML, PDF, DOCX, XLSX, CSV, source code
11
+
12
+ ---
13
+
14
+ # 1. Purpose
15
+
16
+ Alvis is an internal framework for building corporate knowledge bases from heterogeneous sources.
17
+
18
+ The core pipeline:
19
+
20
+ Source
21
+
22
+ Artifact
23
+
24
+ Extraction
25
+
26
+ Canonical Content Tree
27
+
28
+ Transformation
29
+
30
+ Chunking
31
+
32
+ Embedding
33
+
34
+ Index
35
+
36
+ The framework must allow users to assemble an ingestion pipeline declaratively,
37
+ without writing Python code — a YAML descriptor must be sufficient for any
38
+ pipeline (no-code by default). The typed Python DSL (`alvis.dsl`) is a
39
+ first-class alternative that produces the exact same config contract, and is
40
+ verified to run identically to its YAML twin.
41
+
42
+ Example:
43
+
44
+ ```yaml
45
+ pipeline:
46
+ source:
47
+ type: confluence
48
+
49
+ extract:
50
+ strategy: auto
51
+
52
+ chunk:
53
+ strategy: auto
54
+ config:
55
+ max_tokens: 500
56
+ overlap: 50
57
+
58
+ embed:
59
+ type: default
60
+
61
+ index:
62
+ type: qdrant
63
+ ```
64
+
65
+ # 2. Scope
66
+
67
+ ## In scope
68
+ - Pluggable source adapters (GitLab, Confluence, S3) and format parsers (Markdown, HTML, PDF, DOCX, XLSX, CSV, source code).
69
+ - Configurable extraction, chunking, and transformation strategies exposed via YAML.
70
+ - Canonical Content Tree as the intermediate representation between extraction and indexing.
71
+ - Embedding and indexing into a pluggable vector store.
72
+ - Idempotent, deduplicated ingestion with retry and error-tolerance semantics.
73
+
74
+ ## Out of scope
75
+ - Building end-user applications (chatbots, search UIs) on top of the indexed data.
76
+ - Managing source-of-truth systems (GitLab, Confluence, S3) themselves.
77
+ - Embedding model training.
78
+
79
+ # 3. Open Source Strategy
80
+
81
+ Alvis is distributed and developed as an open-source project.
82
+
83
+ ## Principles
84
+ - **Framework, not app.** The core must stay source/format-agnostic; all integration lives behind pluggable adapters.
85
+ - **Declarative by default.** Users assemble pipelines via YAML; the typed Python DSL is a first-class alternative with the same contract. Python remains the language for plugin SDK extensions.
86
+ - **First impression wins.** Time-to-hello-world is a hard quality gate: `pip install alvis` + one YAML must produce a running index.
87
+
88
+ ## License
89
+ - **Apache 2.0.** Permissive for corporate adopters, allows proprietary integration without forcing contribution back.
90
+ - CLA required for external contributors to keep the licensing path clean.
91
+ - A separate managed/hosted offering (future, optional) is a distinct distribution, never a fork of the core.
92
+
93
+ ## Repository & governance
94
+ - Single main repo (`alvis/alvis`) for core + CLI.
95
+ - Adapters/bundles may live in the same repo under `plugins/` until they prove stability, then graduate to the mono-repo or their own repos as the community demands.
96
+ - `CONTRIBUTING.md` contract: "how to write a connector" is the cover-page of the contributor guide, not an appendix.
97
+ - Discussions/issues on GitHub; RFCs for anything that changes the YAML contract or Canonical Content Tree.
98
+
99
+ ## Contributor model
100
+ - Core team: pipeline core, Canonical Content Tree, embedding/index abstraction.
101
+ - Community: connectors, format parsers, chunking strategies.
102
+ - Review rule: any plugin lands only with a hello-world example + test fixture + CI golden test.
103
+
104
+ # 4. Roadmap
105
+
106
+ - v0.1 — CLI (`alvis init/run/validate`), YAML schema validation, hello-world on Confluence → Qdrant
107
+ - v0.2 — docs site, contributor guide, first external connector PRs
108
+ - v1.0 — core pipeline stable contract (Canonical Content Tree v1), pgvector + GitLab/S3 sources
109
+ - v1.1 — plugin SDK for third-party source/strategy extensions
110
+ - v2.0 — stable plugin API, community-managed connector registry
@@ -0,0 +1,244 @@
1
+ # Contributing to Alvis
2
+
3
+ Thanks for contributing. Alvis is a no-code knowledge ingestion engine, and
4
+ most of its value is in breadth: the more sources, formats, and stores it
5
+ speaks, the more useful it is. This guide is the cover page for **how to write
6
+ a connector** — the single most common contribution.
7
+
8
+ Read [CONSTITUTION.md](CONSTITUTION.md) first: the roadmap and cross-cutting
9
+ rules live there and in [ROADMAP.md](ROADMAP.md).
10
+
11
+ ---
12
+
13
+ ## Development setup
14
+
15
+ ```bash
16
+ python -m venv .venv
17
+ .venv/bin/pip install -e ".[dev]"
18
+ .venv/bin/ruff check src tests
19
+ .venv/bin/mypy src/alvis
20
+ .venv/bin/python -m pytest
21
+ ```
22
+
23
+ - Python ≥ 3.10, ruff (lint), mypy `--strict`, pytest (asyncio auto mode).
24
+ - A feature ships only with its documentation (cross-cutting rule) and its
25
+ tests — see "Connector fixture + golden test" below, which is enforced in CI.
26
+ - YAML contract and Canonical Content Tree changes are RFC-first: open a
27
+ proposal before touching `config/models.py` or `core/models.py`.
28
+
29
+ ---
30
+
31
+ ## How to write a connector
32
+
33
+ A connector turns a source of documents into Alvis `Artifact`s so the rest of
34
+ the pipeline (extract → chunk → embed → index) can process them. Alvis never
35
+ reads the raw bytes of a source itself — it always goes through a connector's
36
+ `fetch()`.
37
+
38
+ ### 1. The contract
39
+
40
+ `src/alvis/sources/base.py` defines the interface:
41
+
42
+ ```python
43
+ class Source(Protocol):
44
+ async def fetch(self) -> list[Artifact]: ...
45
+ ```
46
+
47
+ `Artifact` (`alvis.core.models`) carries exactly:
48
+
49
+ | field | meaning |
50
+ | -------------- | ------------------------------------------------------------------ |
51
+ | `step_id` | stable, unique id for this document (page id, blob sha, ...) |
52
+ | `uri` | canonical URL/path the document lives at (used for retrieval links) |
53
+ | `content_type` | MIME type Alvis's extract stage understands (see step 3) |
54
+ | `data` | the raw bytes to be ingested |
55
+ | `metadata` | extra fields surfaced to users; keep it small and serialisable |
56
+
57
+ `fetch()` either returns artifacts or raises `SourceError`
58
+ (`alvis.sources.base`) with an optional `status_code`.
59
+
60
+ **Optional: cheap listing (recommended).** If your source can fingerprint a
61
+ document without downloading its body — an S3 ETag, a GitLab/GitHub blob sha, a
62
+ Confluence page version — also implement `ListingSource`
63
+ (`alvis.sources.base`):
64
+
65
+ ```python
66
+ class ListingSource(Source, Protocol):
67
+ async def list_documents(self) -> list[DocumentMeta]: ...
68
+ async def fetch(self, *, uris: set[str] | None = None) -> list[Artifact]: ...
69
+ ```
70
+
71
+ - `list_documents()` returns a `DocumentMeta` (`uri`, `step_id`, `content_type`,
72
+ `fingerprint`) per document, doing **only** the cheap scan call(s).
73
+ - `fetch(uris=...)` must download **only** the requested URIs — `uris=None`
74
+ means "everything". The engine's `--incremental` mode lists first, skips
75
+ documents whose stored fingerprint already matches, and calls
76
+ `fetch(uris=...)` with just the wanted URIs, so unchanged remote objects are
77
+ never fetched. A connector that skips this interface falls back to
78
+ content-hash fingerprinting (still skips processing, not downloads).
79
+ - `gitlab.py` / `github.py` / `s3.py` / `confluence.py` are complete
80
+ `ListingSource` examples.
81
+
82
+ ### 2. Layout and naming
83
+
84
+ Look at how the built-ins are organised — each adapter is one small module:
85
+
86
+ - `src/alvis/sources/confluence.py` — a REST API adapter, the best template
87
+ for an HTTP connector.
88
+ - `src/alvis/sources/fs.py` — the local-filesystem adapter.
89
+ - `src/alvis/sources/github.py`, `gitlab.py`, `s3.py` — REST adapters that
90
+ accept a `transport=` kwarg for testing.
91
+
92
+ For an HTTP connector that talks to a REST API, use `HttpClient`
93
+ (`alvis.sources.http`): it gives you retries/backoff on 429/5xx/network
94
+ errors, `Retry-After` honoring, token/basic auth from env vars
95
+ (`api_token_env`, `username`), optional `verify=False` for self-signed TLS, and
96
+ a `max_bytes` cap so oversized responses are aborted mid-read instead of
97
+ buffered. Confluence is a 70-line example of a full adapter.
98
+
99
+ Reach for `transport=` kwargs / `header_hook=` when the service needs special
100
+ signing (see `s3.py` for a SigV4 example).
101
+
102
+ ### 3. Wire it in — the five touch points
103
+
104
+ 1. **Module** — `src/alvis/sources/<name>.py` implementing `fetch()`.
105
+ 2. **Export** — add it to `__all__` in `src/alvis/sources/__init__.py`.
106
+ 3. **Registry** — `src/alvis/registry.py`: add the type string to
107
+ `KNOWN_SOURCES` so `alvis validate` recognises it.
108
+ 4. **Factory** — `src/alvis/factories.py`:
109
+ - `build_source(...)` — map `config.type == "<name>"` to your class.
110
+ - `source_identity(config)` — a stable `"<name>:<key>@<host>"` string; the
111
+ index uses it to namespace points so `reconcile` prunes only your source's
112
+ stale documents. Pick discriminator keys that uniquely identify a data
113
+ scope (e.g. GitLab: `project@host`).
114
+ 5. **DSL** — `src/alvis/dsl.py`: add a `dsl.<name>(...)` builder mirroring the
115
+ YAML config keys, so the Python DSL and the YAML schema stay in parity.
116
+
117
+ Config validation lives in `src/alvis/config/models.py` (`SourceConfig` is
118
+ permissive `dict` today; type-specific keys are validated in the adapter).
119
+ Document the YAML keys in the README source table.
120
+
121
+ ### 4. Content types and formats
122
+
123
+ `alvis.sources.content_types` maps extensions to MIME types. Your connector
124
+ should produce content types the `auto` extractor understands — Markdown
125
+ (`text/markdown`), HTML (`text/html`), plain text / code (`text/plain`), or
126
+ PDF/DOCX/XLSX for the `alvis[documents]` extra. If a remote format needs an
127
+ extension (e.g. GitLab raw blobs), derive it from the object name and pass it
128
+ through `content_type()`.
129
+
130
+ ### 5. Every connector ships two things — enforced in CI
131
+
132
+ No connector lands without:
133
+
134
+ 1. **A fixture** — a `tests/fixtures/` corpus of realistic sample data the
135
+ adapter ingests (a couple of small files is plenty). Fixtures must not live
136
+ under a directory Alvis itself would ingest in tests.
137
+ 2. **A golden test** — a test marked `@pytest.mark.golden` that runs the
138
+ connector against canned service responses and diffs the `Artifact` output
139
+ against a committed snapshot.
140
+
141
+ `src/alvis/testing.py` provides the harness (used by every built-in in
142
+ `tests/test_golden_sources.py`):
143
+
144
+ ```python
145
+ from alvis.testing import MockServer, artifacts_snapshot, assert_golden
146
+
147
+ @pytest.mark.golden
148
+ async def test_golden_my_source() -> None:
149
+ server = MockServer()
150
+ server.on("GET", "/api/v1/items", json_payload={"items": [{"id": "a", "body": "# Hi"}]})
151
+
152
+ source = MySource(url="http://example.com", transport=server.transport)
153
+ artifacts = await source.fetch()
154
+
155
+ assert_golden("my_source", artifacts_snapshot(artifacts))
156
+ ```
157
+
158
+ - `MockServer` is an `httpx.MockTransport` under the hood — no network. Point
159
+ your `HttpClient` at `server.transport` (via a `transport=` kwarg, or
160
+ `source.client.transport = server.transport` when the constructor builds its
161
+ own client, as Confluence does). **Every request must have a route** —
162
+ unimapped routes raise, so the fixture doubles as a contract check on which
163
+ endpoints your connector calls. `server.request_log()` lets you assert on
164
+ the calls themselves.
165
+ - `artifacts_snapshot(...)` renders the `fetch()` result as a stable,
166
+ serialisable list so the snapshot is diff-able in review. Pass `root=` to
167
+ scrub machine-specific absolute paths (see the `fs` golden test).
168
+ - Snapshots live in `tests/golden/<name>.golden.json`. Run
169
+ `ALVIS_ACCEPT=1 pytest -m golden` to (re)write them, review the diff, and
170
+ commit. CI runs `pytest -m golden` **read-only** (no `ALVIS_ACCEPT`), so
171
+ drift and missing snapshots fail loudly.
172
+
173
+ **Register the golden test in `tests/golden/manifest.json`** under the
174
+ connector's type string pointing at the test file. The CI script
175
+ `scripts/check_golden_coverage.py` fails if a connector has a manifest entry
176
+ but no test/snapshot — keep the three in a single commit.
177
+
178
+ ### 6. Beyond the connector
179
+
180
+ If you add a new parser, chunker, embedder, or indexer, follow the same shape:
181
+ module → export → `KNOWN_*` in `registry.py` → `build_*` in `factories.py` →
182
+ `dsl.*` builder → docs. Indexers that hold state (e.g. a new vector store)
183
+ must implement idempotent upsert + a `reconcile` pass for the per-source prune
184
+ to work.
185
+
186
+ ## Write a plugin, not a fork (v1.1)
187
+
188
+ You do **not** need to modify this repository to ship an adapter. A plugin is
189
+ an ordinary installed Python package that registers itself under the
190
+ `alvis.plugins` entry-point group and declares a `alvis.plugin.Plugin`:
191
+
192
+ ```toml
193
+ # pyproject.toml
194
+ [project.entry-points."alvis.plugins"]
195
+ kb-catalog = "kb_plugin:plugin"
196
+ ```
197
+
198
+ ```python
199
+ # kb_plugin/__init__.py
200
+ from alvis.plugin import Plugin
201
+
202
+ def _catalog(*, config, max_bytes=None): # factory contract (see alvis/plugin.py)
203
+ ...
204
+
205
+ plugin = Plugin(
206
+ name="kb-catalog",
207
+ version="0.1.0",
208
+ sources={"catalog": _catalog}, # also: extractors, chunkers, embedders, indexers
209
+ )
210
+ ```
211
+
212
+ After `pip install .`, the adapter is accepted by `alvis validate`, listed by
213
+ `alvis plugins`, and resolved by the stage factories — zero core changes.
214
+ Plugins are validated their own adapter (mirroring built-ins), so plugin types
215
+ may read any `config:` keys. The reference implementation is
216
+ `examples/kb-plugin` (a `catalog` source with cheap-listing incremental
217
+ support); copy it as your template and keep the tutorial contract: hello-world
218
+ example + test + docs. External plugins are tracked in [docs/plugins.md]
219
+ (docs/plugins.md).
220
+
221
+ ---
222
+
223
+ ## Contribution process
224
+
225
+ 1. Fork, create a branch (`add-magnolia-connector`).
226
+ 2. Implement the connector + fixture + golden test + docs + README table row
227
+ as one commit — the golden manifest, snapshot, and test never split.
228
+ 3. Run the full gate: `ruff check src tests`, `mypy src/alvis`,
229
+ `pytest` (which includes `-m golden` read-only), and
230
+ `python scripts/check_golden_coverage.py`.
231
+ 4. Open the pull request. CI runs lint, typecheck, the full suite on
232
+ Python 3.10/3.11/3.12, and a dedicated `golden` job that re-runs snapshots
233
+ read-only and enforces coverage.
234
+
235
+ ## Cross-cutting rules (from ROADMAP)
236
+
237
+ - Every plugin lands only with: hello-world example + test fixture + CI golden
238
+ test.
239
+ - Any change to the YAML contract or Canonical Content Tree is an RFC-first
240
+ change.
241
+ - No feature ships without the chapter it documents.
242
+ - Breaking a stable-surface name (see [docs/versioning.md](docs/versioning.md))
243
+ needs a semver `MAJOR` (or a `MINOR` before `1.0.0`), a `deprecated()`
244
+ warning cycle, and a changelog line — never a silent break.
@@ -0,0 +1,39 @@
1
+ # syntax=docker/dockerfile:1
2
+
3
+ # Alvis as a container. The product surface is YAML, so the image is a
4
+ # fixed `alvis` CLI with every optional extra preinstalled — users configure
5
+ # it purely by mounting pipeline files and a state volume, never by rebuilding:
6
+ #
7
+ # docker build -t alvis:dev .
8
+ # docker run --rm \
9
+ # -v "$PWD/pipeline.yaml:/workspace/pipeline.yaml:ro" \
10
+ # -v "$PWD/.alvis:/workspace/.alvis" \
11
+ # alvis:dev run --incremental pipeline.yaml
12
+ #
13
+ # State defaults to `.alvis/state.json` under the workdir (`/workspace`),
14
+ # which is exactly the mounted volume above.
15
+
16
+ FROM python:3.12-slim AS builder
17
+
18
+ COPY pyproject.toml README.md LICENSE ./
19
+ COPY src ./src
20
+
21
+ RUN pip install --no-cache-dir .[documents,pgindex,observability]
22
+
23
+ FROM python:3.12-slim
24
+
25
+ COPY --from=builder /usr/local /usr/local
26
+
27
+ ENV PYTHONUNBUFFERED=1 \
28
+ PYTHONDONTWRITEBYTECODE=1
29
+
30
+ RUN adduser --disabled-password --gecos "" --home /workspace alvis \
31
+ && chown -R alvis:alvis /workspace
32
+
33
+ USER alvis
34
+ WORKDIR /workspace
35
+
36
+ VOLUME ["/workspace"]
37
+
38
+ ENTRYPOINT ["alvis"]
39
+ CMD ["--help"]