daftari 1.0.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/CHANGELOG.md +37 -0
- package/LICENSE +21 -0
- package/README.md +259 -0
- package/dist/access/locks.d.ts +19 -0
- package/dist/access/locks.d.ts.map +1 -0
- package/dist/access/locks.js +112 -0
- package/dist/access/locks.js.map +1 -0
- package/dist/access/rbac.d.ts +18 -0
- package/dist/access/rbac.d.ts.map +1 -0
- package/dist/access/rbac.js +48 -0
- package/dist/access/rbac.js.map +1 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +216 -0
- package/dist/cli.js.map +1 -0
- package/dist/curation/lint.d.ts +20 -0
- package/dist/curation/lint.d.ts.map +1 -0
- package/dist/curation/lint.js +176 -0
- package/dist/curation/lint.js.map +1 -0
- package/dist/curation/provenance.d.ts +21 -0
- package/dist/curation/provenance.d.ts.map +1 -0
- package/dist/curation/provenance.js +80 -0
- package/dist/curation/provenance.js.map +1 -0
- package/dist/curation/staleness.d.ts +19 -0
- package/dist/curation/staleness.d.ts.map +1 -0
- package/dist/curation/staleness.js +67 -0
- package/dist/curation/staleness.js.map +1 -0
- package/dist/curation/tension.d.ts +20 -0
- package/dist/curation/tension.d.ts.map +1 -0
- package/dist/curation/tension.js +134 -0
- package/dist/curation/tension.js.map +1 -0
- package/dist/frontmatter/parser.d.ts +10 -0
- package/dist/frontmatter/parser.d.ts.map +1 -0
- package/dist/frontmatter/parser.js +29 -0
- package/dist/frontmatter/parser.js.map +1 -0
- package/dist/frontmatter/schema.d.ts +7 -0
- package/dist/frontmatter/schema.d.ts.map +1 -0
- package/dist/frontmatter/schema.js +115 -0
- package/dist/frontmatter/schema.js.map +1 -0
- package/dist/frontmatter/types.d.ts +41 -0
- package/dist/frontmatter/types.d.ts.map +1 -0
- package/dist/frontmatter/types.js +8 -0
- package/dist/frontmatter/types.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +94 -0
- package/dist/index.js.map +1 -0
- package/dist/search/bm25.d.ts +19 -0
- package/dist/search/bm25.d.ts.map +1 -0
- package/dist/search/bm25.js +115 -0
- package/dist/search/bm25.js.map +1 -0
- package/dist/search/hybrid.d.ts +38 -0
- package/dist/search/hybrid.d.ts.map +1 -0
- package/dist/search/hybrid.js +162 -0
- package/dist/search/hybrid.js.map +1 -0
- package/dist/search/reindex.d.ts +15 -0
- package/dist/search/reindex.d.ts.map +1 -0
- package/dist/search/reindex.js +189 -0
- package/dist/search/reindex.js.map +1 -0
- package/dist/search/vector.d.ts +9 -0
- package/dist/search/vector.d.ts.map +1 -0
- package/dist/search/vector.js +128 -0
- package/dist/search/vector.js.map +1 -0
- package/dist/server.d.ts +6 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +72 -0
- package/dist/server.js.map +1 -0
- package/dist/storage/index-db.d.ts +37 -0
- package/dist/storage/index-db.d.ts.map +1 -0
- package/dist/storage/index-db.js +145 -0
- package/dist/storage/index-db.js.map +1 -0
- package/dist/storage/local.d.ts +6 -0
- package/dist/storage/local.d.ts.map +1 -0
- package/dist/storage/local.js +57 -0
- package/dist/storage/local.js.map +1 -0
- package/dist/tools/curation.d.ts +22 -0
- package/dist/tools/curation.d.ts.map +1 -0
- package/dist/tools/curation.js +202 -0
- package/dist/tools/curation.js.map +1 -0
- package/dist/tools/read.d.ts +74 -0
- package/dist/tools/read.d.ts.map +1 -0
- package/dist/tools/read.js +254 -0
- package/dist/tools/read.js.map +1 -0
- package/dist/tools/search.d.ts +13 -0
- package/dist/tools/search.d.ts.map +1 -0
- package/dist/tools/search.js +190 -0
- package/dist/tools/search.js.map +1 -0
- package/dist/tools/write.d.ts +18 -0
- package/dist/tools/write.d.ts.map +1 -0
- package/dist/tools/write.js +465 -0
- package/dist/tools/write.js.map +1 -0
- package/dist/utils/config.d.ts +12 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +94 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/git.d.ts +23 -0
- package/dist/utils/git.d.ts.map +1 -0
- package/dist/utils/git.js +114 -0
- package/dist/utils/git.js.map +1 -0
- package/package.json +69 -0
- package/templates/config.yaml +31 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [1.0.0] - 2026-05-17
|
|
9
|
+
|
|
10
|
+
First public release. Daftari is an MCP server that exposes a curated markdown
|
|
11
|
+
vault to AI agents, exposing 13 tools over stdio.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **Read path** — `vault_read`, `vault_index`, and `vault_status` for reading
|
|
16
|
+
documents, listing them by collection/status/domain/tags, and reporting vault
|
|
17
|
+
health (file counts, invalid frontmatter, staleness distribution, unresolved
|
|
18
|
+
tensions, recent writes).
|
|
19
|
+
- **Hybrid search** — `vault_search`, `vault_search_related`, and
|
|
20
|
+
`vault_reindex`. BM25 lexical ranking fused with vector semantic similarity,
|
|
21
|
+
with tunable weights and graceful fallback to lexical-only when embeddings are
|
|
22
|
+
unavailable.
|
|
23
|
+
- **Write path** — `vault_write`, `vault_append`, `vault_promote`, and
|
|
24
|
+
`vault_deprecate`. File-level write locks (SQLite-backed, 60-second TTL),
|
|
25
|
+
every write auto-committed to git, and a provenance log of who wrote what.
|
|
26
|
+
- **Curation engine** — `vault_lint`, `vault_tension_log`, and
|
|
27
|
+
`vault_provenance`. Advisory TTL-based staleness detection, contradiction
|
|
28
|
+
(tension) logging, lint checks, and per-document write history. Reports
|
|
29
|
+
problems; does not auto-fix.
|
|
30
|
+
- **Config-driven RBAC** — roles and per-collection read/write/promote
|
|
31
|
+
permissions declared in `.daftari/config.yaml`; enforced across every tool.
|
|
32
|
+
Unknown or absent roles fall back to a deny-all guest.
|
|
33
|
+
- **CLI** — `daftari --init` scaffolds a new vault (collections, RBAC config,
|
|
34
|
+
example documents, git history, search index); `daftari --vault` serves it.
|
|
35
|
+
- 160 tests covering all 13 tools and their supporting modules.
|
|
36
|
+
|
|
37
|
+
[1.0.0]: https://github.com/mavaali/daftari/releases/tag/v1.0.0
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mihir Wagle (mavaali)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
# Daftari
|
|
2
|
+
|
|
3
|
+
[](https://github.com/mavaali/daftari/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/daftari)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
7
|
+
**An MCP server that exposes a curated markdown vault to AI agents.**
|
|
8
|
+
|
|
9
|
+
Daftari is not RAG. It is not a chatbot. It is a *living, agent-maintained
|
|
10
|
+
knowledge vault* — a directory of markdown files that an AI agent reads from,
|
|
11
|
+
writes to, and curates over time, so that knowledge **compounds** instead of
|
|
12
|
+
being re-derived on every query.
|
|
13
|
+
|
|
14
|
+
RAG retrieves chunks and hopes the model stitches them together. Daftari takes
|
|
15
|
+
the other path: the agent does the stitching *once*, writes the synthesized
|
|
16
|
+
result back as a durable document, and every later read starts from that
|
|
17
|
+
compiled answer. Karpathy's framing fits — **compilation over retrieval**. The
|
|
18
|
+
vault gets better the more it is used.
|
|
19
|
+
|
|
20
|
+
A vault is just markdown. You can read it, `git log` it, and edit it by hand.
|
|
21
|
+
Daftari adds the machinery an agent needs to treat it as a shared workspace:
|
|
22
|
+
access control, write arbitration, provenance, and curation.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## The four-layer model
|
|
27
|
+
|
|
28
|
+
Daftari is built in four layers. The first two are table stakes. **The moat is
|
|
29
|
+
layers 3 and 4** — anyone can store markdown and check a permission; arbitrating
|
|
30
|
+
concurrent agent writes and managing knowledge decay is the hard part.
|
|
31
|
+
|
|
32
|
+
| Layer | Concern | What Daftari provides |
|
|
33
|
+
|------:|---------|-----------------------|
|
|
34
|
+
| 1 | **Storage** | Markdown + YAML frontmatter on disk, a git history, a rebuildable SQLite index for hybrid BM25 + vector search. |
|
|
35
|
+
| 2 | **Multi-tenant ACL** | Config-driven RBAC. Roles and per-collection read/write/promote permissions declared in `.daftari/config.yaml`. |
|
|
36
|
+
| 3 | **Write arbitration** ⭐ | File-level write locks (SQLite-backed, 60s TTL), every write auto-committed to git, a provenance log of who wrote what and when. |
|
|
37
|
+
| 4 | **Curation decay** ⭐ | The draft → canonical → deprecated lifecycle, TTL-based staleness, tension logging for contradictions, and an advisory linter. Knowledge that stops being true is surfaced, not silently trusted. |
|
|
38
|
+
|
|
39
|
+
Layers 1–2 keep the vault *stored and scoped*. Layers 3–4 keep it *coherent as
|
|
40
|
+
it grows* — which is the entire point of a vault that compounds.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Quickstart
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# 1. Scaffold a new vault (collections, config, example documents, git, index)
|
|
48
|
+
npx daftari --init ./my-vault
|
|
49
|
+
|
|
50
|
+
# 2. Start the MCP server against it, as an identity with a role
|
|
51
|
+
npx daftari --vault ./my-vault --user me --role admin
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The server speaks the Model Context Protocol over stdio. Point any MCP client
|
|
55
|
+
(Claude Desktop, an agent SDK, your own harness) at it. See
|
|
56
|
+
[docs/getting-started.md](docs/getting-started.md) for the full walkthrough,
|
|
57
|
+
including a `claude_desktop_config.json` snippet.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## The MCP tools
|
|
62
|
+
|
|
63
|
+
Daftari exposes 13 tools, grouped by layer.
|
|
64
|
+
|
|
65
|
+
**Read path**
|
|
66
|
+
|
|
67
|
+
| Tool | Description |
|
|
68
|
+
|------|-------------|
|
|
69
|
+
| `vault_read` | Read one document: markdown body, parsed frontmatter, and an advisory validation report. |
|
|
70
|
+
| `vault_index` | List documents, filterable by collection, status, domain, or tags. |
|
|
71
|
+
| `vault_status` | Vault health dashboard: total file count, per-collection counts, count of documents with invalid frontmatter, a staleness distribution (fresh/aging/stale), unresolved tensions, and recent write history. |
|
|
72
|
+
|
|
73
|
+
**Search**
|
|
74
|
+
|
|
75
|
+
| Tool | Description |
|
|
76
|
+
|------|-------------|
|
|
77
|
+
| `vault_search` | Hybrid BM25 + vector search across the vault, with tunable ranking weights. |
|
|
78
|
+
| `vault_search_related` | Find documents thematically related to a given document. |
|
|
79
|
+
| `vault_reindex` | Rebuild the SQLite search index from the markdown files. |
|
|
80
|
+
|
|
81
|
+
**Write arbitration**
|
|
82
|
+
|
|
83
|
+
| Tool | Description |
|
|
84
|
+
|------|-------------|
|
|
85
|
+
| `vault_write` | Create or overwrite a document. Stamps `updated`/`updated_by`, preserves `created`, auto-commits. |
|
|
86
|
+
| `vault_append` | Append a markdown section to a document. Re-stamps metadata, auto-commits. |
|
|
87
|
+
| `vault_promote` | Promote a draft to canonical — refuses unless the draft's frontmatter is complete. |
|
|
88
|
+
| `vault_deprecate` | Mark a document deprecated with a required reason and an optional `superseded_by`. |
|
|
89
|
+
|
|
90
|
+
**Curation**
|
|
91
|
+
|
|
92
|
+
| Tool | Description |
|
|
93
|
+
|------|-------------|
|
|
94
|
+
| `vault_tension_log` | Record a contradiction between two documents to the advisory tension log. Records; does not resolve. |
|
|
95
|
+
| `vault_lint` | Run advisory curation checks: stale-past-TTL, orphans, old drafts, stagnant low-confidence files, deprecated-but-linked. |
|
|
96
|
+
| `vault_provenance` | Return a single document's full write history from the provenance log. |
|
|
97
|
+
|
|
98
|
+
The curation engine is **advisory**: `vault_lint` reports problems and
|
|
99
|
+
`vault_tension_log` records contradictions — neither auto-fixes anything. A
|
|
100
|
+
human or a deliberate agent decision drives every change.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## What an agent call looks like
|
|
105
|
+
|
|
106
|
+
Daftari speaks the Model Context Protocol over stdio. An agent invokes a tool
|
|
107
|
+
by name with JSON arguments; the server replies with a JSON text block. Here is
|
|
108
|
+
`vault_search` against a freshly scaffolded vault (`npx daftari --init`):
|
|
109
|
+
|
|
110
|
+
**Request**
|
|
111
|
+
|
|
112
|
+
```json
|
|
113
|
+
{ "method": "tools/call", "params": {
|
|
114
|
+
"name": "vault_search",
|
|
115
|
+
"arguments": { "query": "consumption pricing", "limit": 1 } } }
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**Response** — `content[0].text`, parsed:
|
|
119
|
+
|
|
120
|
+
```json
|
|
121
|
+
{
|
|
122
|
+
"query": "consumption pricing",
|
|
123
|
+
"count": 1,
|
|
124
|
+
"vectorUsed": true,
|
|
125
|
+
"weights": { "bm25": 0.5, "vector": 0.5 },
|
|
126
|
+
"hits": [
|
|
127
|
+
{
|
|
128
|
+
"path": "pricing/helios-consumption-pricing.md",
|
|
129
|
+
"title": "Helios Consumption Pricing (Compute Credit Model)",
|
|
130
|
+
"collection": "pricing", "status": "canonical",
|
|
131
|
+
"score": 1, "bm25Score": 1, "vectorScore": 1,
|
|
132
|
+
"snippet": "# Helios Consumption Pricing (Compute Credit Model) Helios is a fictional platform…"
|
|
133
|
+
}
|
|
134
|
+
]
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## RBAC
|
|
141
|
+
|
|
142
|
+
Access is config-driven. There is no user-management system — roles and their
|
|
143
|
+
per-collection permissions live in `.daftari/config.yaml`, and the server is
|
|
144
|
+
started with `--role <name>` to select one:
|
|
145
|
+
|
|
146
|
+
```yaml
|
|
147
|
+
version: 1
|
|
148
|
+
vault_name: my-vault
|
|
149
|
+
|
|
150
|
+
roles:
|
|
151
|
+
analyst:
|
|
152
|
+
read: [competitive-intel, pricing]
|
|
153
|
+
write: [competitive-intel, _drafts]
|
|
154
|
+
researcher:
|
|
155
|
+
read: ["*"] # "*" matches every collection
|
|
156
|
+
write: [moonshot, _drafts]
|
|
157
|
+
admin:
|
|
158
|
+
read: ["*"]
|
|
159
|
+
write: ["*"]
|
|
160
|
+
promote: true # only this role may promote drafts to canonical
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
- `read` — collections the role may read and search
|
|
164
|
+
- `write` — collections the role may create, append to, or deprecate in
|
|
165
|
+
- `promote` — whether the role may promote a draft to canonical (default `false`)
|
|
166
|
+
|
|
167
|
+
Starting the server with no `--role`, or with a name not in the config, falls
|
|
168
|
+
back to a deny-all **guest**: every tool is denied.
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## File format
|
|
173
|
+
|
|
174
|
+
Every document is a markdown file with a YAML frontmatter block. Frontmatter
|
|
175
|
+
*is* the metadata layer — there is no separate database of record.
|
|
176
|
+
|
|
177
|
+
```markdown
|
|
178
|
+
---
|
|
179
|
+
title: "Aurora Pipelines — Positioning Overview"
|
|
180
|
+
domain: accumulation
|
|
181
|
+
collection: competitive-intel
|
|
182
|
+
status: canonical
|
|
183
|
+
confidence: medium
|
|
184
|
+
created: 2026-05-17
|
|
185
|
+
updated: 2026-05-17
|
|
186
|
+
updated_by: agent:claude-code
|
|
187
|
+
provenance: direct
|
|
188
|
+
sources:
|
|
189
|
+
- aurora-product-page
|
|
190
|
+
superseded_by: null
|
|
191
|
+
ttl_days: 120
|
|
192
|
+
tags: [aurora, ingestion, competitive]
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
# Aurora Pipelines — Positioning Overview
|
|
196
|
+
|
|
197
|
+
Aurora Pipelines treats ingestion as an authored, version-controlled artifact
|
|
198
|
+
rather than a managed black box.
|
|
199
|
+
|
|
200
|
+
## Questions Answered
|
|
201
|
+
- How does Aurora frame the ingestion-vs-transformation boundary?
|
|
202
|
+
|
|
203
|
+
## Questions Raised
|
|
204
|
+
- Does an authored-pipeline model slow teams down at small scale?
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
The `## Questions Answered` / `## Questions Raised` pattern is a convention,
|
|
208
|
+
not a requirement: it makes a document's epistemic edges explicit so the next
|
|
209
|
+
agent knows what is settled and what is still open. Full field reference in
|
|
210
|
+
[docs/file-format.md](docs/file-format.md).
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## What's not in v1
|
|
215
|
+
|
|
216
|
+
A few capabilities were deliberately deferred so v1 ships with a tight,
|
|
217
|
+
defensible surface — a server that does its core job well rather than a wide
|
|
218
|
+
one that does many jobs partially. Not in this release:
|
|
219
|
+
|
|
220
|
+
- **Cloud-hosted multi-tenant server** — an S3/GCS storage backend with
|
|
221
|
+
auth-token identity. v1 runs against a local filesystem as a single process.
|
|
222
|
+
- **Conflict resolution beyond file-level locks** — CRDTs or semantic merge for
|
|
223
|
+
concurrent edits to the same document. v1 arbitrates with 60-second write locks.
|
|
224
|
+
- **Background curation agent** — a scheduler that runs `vault_lint` on a
|
|
225
|
+
cadence. v1's linter is advisory and invoked on demand.
|
|
226
|
+
- **LLM reranking of search results** — a model pass over the BM25 + vector
|
|
227
|
+
candidate set. v1 ships hybrid ranking without a rerank stage.
|
|
228
|
+
- **Enforced domain separation** — v1 *documents* the convention that
|
|
229
|
+
generative-domain documents are not cross-referenced into accumulation pages;
|
|
230
|
+
the write tools do not yet enforce it. v2 will.
|
|
231
|
+
|
|
232
|
+
Each of these is a clean increment on top of a surface that already works —
|
|
233
|
+
deliberately deferred, not forgotten.
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## Documentation
|
|
238
|
+
|
|
239
|
+
- [docs/getting-started.md](docs/getting-started.md) — end-to-end walkthrough: scaffold, write, search, lint, promote, deprecate, and connect from Claude Desktop.
|
|
240
|
+
- [docs/architecture.md](docs/architecture.md) — the layered architecture, the request path, and the accumulation-vs-generative domain split.
|
|
241
|
+
- [docs/file-format.md](docs/file-format.md) — the complete frontmatter reference and markdown body conventions.
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## Development
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
npm install
|
|
249
|
+
npm run build # compile TypeScript to dist/
|
|
250
|
+
npm test # run the vitest suite
|
|
251
|
+
npm run dev # run the server in watch mode against the sample vault
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Design tenets: functions and types, no classes; tool handlers return
|
|
255
|
+
`Result<T, Error>` rather than throwing; tests mirror the `src/` structure.
|
|
256
|
+
|
|
257
|
+
## License
|
|
258
|
+
|
|
259
|
+
MIT. Open source — `daftari` on npm, [`mavaali/daftari`](https://github.com/mavaali/daftari) on GitHub.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import Database from "better-sqlite3";
|
|
2
|
+
import { type Result } from "../frontmatter/types.js";
|
|
3
|
+
export type LockDb = Database.Database;
|
|
4
|
+
export declare const LOCK_TTL_MS = 60000;
|
|
5
|
+
export interface Lock {
|
|
6
|
+
path: string;
|
|
7
|
+
holder: string;
|
|
8
|
+
acquiredAt: number;
|
|
9
|
+
expiresAt: number;
|
|
10
|
+
}
|
|
11
|
+
export declare function lockDbPath(vaultRoot: string): string;
|
|
12
|
+
export declare function openLockDb(vaultRoot: string): Result<LockDb, Error>;
|
|
13
|
+
export declare function acquireLock(db: LockDb, path: string, holder: string, now?: number): Result<Lock, Error>;
|
|
14
|
+
export declare function releaseLock(db: LockDb, path: string, holder: string): Result<{
|
|
15
|
+
released: boolean;
|
|
16
|
+
}, Error>;
|
|
17
|
+
export declare function isLocked(db: LockDb, path: string, now?: number): boolean;
|
|
18
|
+
export declare function getLock(db: LockDb, path: string, now?: number): Lock | null;
|
|
19
|
+
//# sourceMappingURL=locks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"locks.d.ts","sourceRoot":"","sources":["../../src/access/locks.ts"],"names":[],"mappings":"AAcA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAW,KAAK,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAE/D,MAAM,MAAM,MAAM,GAAG,QAAQ,CAAC,QAAQ,CAAC;AAEvC,eAAO,MAAM,WAAW,QAAS,CAAC;AAElC,MAAM,WAAW,IAAI;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEpD;AAWD,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAWnE;AA4BD,wBAAgB,WAAW,CACzB,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,GAAG,GAAE,MAAmB,GACvB,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,CAmCrB;AAKD,wBAAgB,WAAW,CACzB,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,GACb,MAAM,CAAC;IAAE,QAAQ,EAAE,OAAO,CAAA;CAAE,EAAE,KAAK,CAAC,CAQtC;AAGD,wBAAgB,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,GAAE,MAAmB,GAAG,OAAO,CAKpF;AAGD,wBAAgB,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,GAAE,MAAmB,GAAG,IAAI,GAAG,IAAI,CAIvF"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// File-level write locks, SQLite-backed.
|
|
2
|
+
//
|
|
3
|
+
// A write to a vault document acquires an exclusive lock on its path for the
|
|
4
|
+
// duration of the operation. Locks carry a 60-second TTL: a lock whose
|
|
5
|
+
// expires_at has passed is treated as released, so a crashed or hung writer
|
|
6
|
+
// can never wedge a file permanently. There is no background reaper — TTL is
|
|
7
|
+
// enforced lazily, on every acquire/isLocked check.
|
|
8
|
+
//
|
|
9
|
+
// Locks live in their own .daftari/locks.db (separate from the search index)
|
|
10
|
+
// so a reindex never disturbs them. The file is still ephemeral: every lock
|
|
11
|
+
// expires within a minute, so a lost locks.db costs nothing.
|
|
12
|
+
import { mkdirSync } from "node:fs";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import Database from "better-sqlite3";
|
|
15
|
+
import { err, ok } from "../frontmatter/types.js";
|
|
16
|
+
export const LOCK_TTL_MS = 60_000;
|
|
17
|
+
export function lockDbPath(vaultRoot) {
|
|
18
|
+
return join(vaultRoot, ".daftari", "locks.db");
|
|
19
|
+
}
|
|
20
|
+
const SCHEMA = `
|
|
21
|
+
CREATE TABLE IF NOT EXISTS locks (
|
|
22
|
+
path TEXT PRIMARY KEY,
|
|
23
|
+
holder TEXT NOT NULL,
|
|
24
|
+
acquired_at INTEGER NOT NULL,
|
|
25
|
+
expires_at INTEGER NOT NULL
|
|
26
|
+
);
|
|
27
|
+
`;
|
|
28
|
+
export function openLockDb(vaultRoot) {
|
|
29
|
+
try {
|
|
30
|
+
mkdirSync(join(vaultRoot, ".daftari"), { recursive: true });
|
|
31
|
+
const db = new Database(lockDbPath(vaultRoot));
|
|
32
|
+
db.pragma("journal_mode = WAL");
|
|
33
|
+
db.exec(SCHEMA);
|
|
34
|
+
return ok(db);
|
|
35
|
+
}
|
|
36
|
+
catch (e) {
|
|
37
|
+
const reason = e instanceof Error ? e.message : String(e);
|
|
38
|
+
return err(new Error(`cannot open lock db: ${reason}`));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function rowToLock(row) {
|
|
42
|
+
return {
|
|
43
|
+
path: row.path,
|
|
44
|
+
holder: row.holder,
|
|
45
|
+
acquiredAt: row.acquired_at,
|
|
46
|
+
expiresAt: row.expires_at,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
// Drops every lock whose TTL has passed. Called before each acquire so an
|
|
50
|
+
// expired lock is auto-released without a separate reaper.
|
|
51
|
+
function purgeExpired(db, now) {
|
|
52
|
+
db.prepare("DELETE FROM locks WHERE expires_at <= ?").run(now);
|
|
53
|
+
}
|
|
54
|
+
// Acquires an exclusive lock on `path` for `holder`. Fails if the file is held
|
|
55
|
+
// by a *different* holder under a still-live TTL. Re-acquiring a lock the same
|
|
56
|
+
// holder already owns succeeds and refreshes the TTL. `now` is injectable for
|
|
57
|
+
// deterministic tests.
|
|
58
|
+
export function acquireLock(db, path, holder, now = Date.now()) {
|
|
59
|
+
if (typeof path !== "string" || path.length === 0) {
|
|
60
|
+
return err(new Error("acquireLock requires a non-empty path"));
|
|
61
|
+
}
|
|
62
|
+
if (typeof holder !== "string" || holder.length === 0) {
|
|
63
|
+
return err(new Error("acquireLock requires a non-empty holder"));
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
purgeExpired(db, now);
|
|
67
|
+
const existing = db.prepare("SELECT * FROM locks WHERE path = ?").get(path);
|
|
68
|
+
if (existing && existing.holder !== holder) {
|
|
69
|
+
return err(new Error(`file is locked by ${existing.holder}: ${path} ` +
|
|
70
|
+
`(expires in ${Math.max(0, existing.expires_at - now)}ms)`));
|
|
71
|
+
}
|
|
72
|
+
const lock = {
|
|
73
|
+
path,
|
|
74
|
+
holder,
|
|
75
|
+
acquiredAt: now,
|
|
76
|
+
expiresAt: now + LOCK_TTL_MS,
|
|
77
|
+
};
|
|
78
|
+
db.prepare(`INSERT OR REPLACE INTO locks (path, holder, acquired_at, expires_at)
|
|
79
|
+
VALUES (?, ?, ?, ?)`).run(lock.path, lock.holder, lock.acquiredAt, lock.expiresAt);
|
|
80
|
+
return ok(lock);
|
|
81
|
+
}
|
|
82
|
+
catch (e) {
|
|
83
|
+
const reason = e instanceof Error ? e.message : String(e);
|
|
84
|
+
return err(new Error(`cannot acquire lock: ${reason}`));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// Releases a lock. Only the holder may release its own lock; releasing a lock
|
|
88
|
+
// held by someone else (or one that no longer exists) is a no-op that reports
|
|
89
|
+
// `released: false` rather than an error.
|
|
90
|
+
export function releaseLock(db, path, holder) {
|
|
91
|
+
try {
|
|
92
|
+
const info = db.prepare("DELETE FROM locks WHERE path = ? AND holder = ?").run(path, holder);
|
|
93
|
+
return ok({ released: info.changes > 0 });
|
|
94
|
+
}
|
|
95
|
+
catch (e) {
|
|
96
|
+
const reason = e instanceof Error ? e.message : String(e);
|
|
97
|
+
return err(new Error(`cannot release lock: ${reason}`));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// True if `path` carries a lock whose TTL has not yet passed.
|
|
101
|
+
export function isLocked(db, path, now = Date.now()) {
|
|
102
|
+
const row = db.prepare("SELECT expires_at FROM locks WHERE path = ?").get(path);
|
|
103
|
+
return row !== undefined && row.expires_at > now;
|
|
104
|
+
}
|
|
105
|
+
// Returns the live lock on `path`, or null if none / expired.
|
|
106
|
+
export function getLock(db, path, now = Date.now()) {
|
|
107
|
+
const row = db.prepare("SELECT * FROM locks WHERE path = ?").get(path);
|
|
108
|
+
if (!row || row.expires_at <= now)
|
|
109
|
+
return null;
|
|
110
|
+
return rowToLock(row);
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=locks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"locks.js","sourceRoot":"","sources":["../../src/access/locks.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { DaftariConfig, RoleConfig } from "../utils/config.js";
|
|
2
|
+
export declare const GUEST_ROLE = "guest";
|
|
3
|
+
export declare const WILDCARD = "*";
|
|
4
|
+
export interface AccessContext {
|
|
5
|
+
user: string;
|
|
6
|
+
roleName: string;
|
|
7
|
+
role: RoleConfig | null;
|
|
8
|
+
}
|
|
9
|
+
export declare function resolveAccess(config: DaftariConfig, user: string, roleName: string): AccessContext;
|
|
10
|
+
export declare function guestAccess(user?: string): AccessContext;
|
|
11
|
+
export declare function canRead(role: RoleConfig | null, collection: string): boolean;
|
|
12
|
+
export declare function canWrite(role: RoleConfig | null, collection: string): boolean;
|
|
13
|
+
export declare function canPromote(role: RoleConfig | null): boolean;
|
|
14
|
+
export declare function hasAnyRead(role: RoleConfig | null): boolean;
|
|
15
|
+
export declare function filterByReadPermission<T extends {
|
|
16
|
+
collection: string;
|
|
17
|
+
}>(role: RoleConfig | null, items: T[]): T[];
|
|
18
|
+
//# sourceMappingURL=rbac.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rbac.d.ts","sourceRoot":"","sources":["../../src/access/rbac.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAEpE,eAAO,MAAM,UAAU,UAAU,CAAC;AAClC,eAAO,MAAM,QAAQ,MAAM,CAAC;AAI5B,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,UAAU,GAAG,IAAI,CAAC;CACzB;AAKD,wBAAgB,aAAa,CAC3B,MAAM,EAAE,aAAa,EACrB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GACf,aAAa,CAEf;AAID,wBAAgB,WAAW,CAAC,IAAI,SAAU,GAAG,aAAa,CAEzD;AAOD,wBAAgB,OAAO,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAE5E;AAGD,wBAAgB,QAAQ,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAE7E;AAGD,wBAAgB,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,GAAG,OAAO,CAE3D;AAID,wBAAgB,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,GAAG,OAAO,CAE3D;AAID,wBAAgB,sBAAsB,CAAC,CAAC,SAAS;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,EACrE,IAAI,EAAE,UAAU,GAAG,IAAI,EACvB,KAAK,EAAE,CAAC,EAAE,GACT,CAAC,EAAE,CAEL"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Role-based access control.
|
|
2
|
+
//
|
|
3
|
+
// Permissions are config-driven (.daftari/config.yaml) — Daftari has no user
|
|
4
|
+
// management system of its own. A running server holds one AccessContext: the
|
|
5
|
+
// --user / --role it was started with, resolved against the loaded config.
|
|
6
|
+
//
|
|
7
|
+
// The model fails safe. A role that does not exist in the config, or a server
|
|
8
|
+
// started without --role, resolves to a null role — the implicit "guest" —
|
|
9
|
+
// which is denied everything. Tools never grant access on a missing rule.
|
|
10
|
+
export const GUEST_ROLE = "guest";
|
|
11
|
+
export const WILDCARD = "*";
|
|
12
|
+
// Resolves a --user / --role pair against the config into an AccessContext. An
|
|
13
|
+
// unknown role name yields a null role rather than an error: unknown ⇒ guest
|
|
14
|
+
// ⇒ denied, never granted.
|
|
15
|
+
export function resolveAccess(config, user, roleName) {
|
|
16
|
+
return { user, roleName, role: config.roles[roleName] ?? null };
|
|
17
|
+
}
|
|
18
|
+
// A guest AccessContext — no role, no permissions. Used when the server is
|
|
19
|
+
// started without --role.
|
|
20
|
+
export function guestAccess(user = "guest") {
|
|
21
|
+
return { user, roleName: GUEST_ROLE, role: null };
|
|
22
|
+
}
|
|
23
|
+
function permits(list, collection) {
|
|
24
|
+
return list.includes(WILDCARD) || list.includes(collection);
|
|
25
|
+
}
|
|
26
|
+
// True if the role may read documents in `collection`.
|
|
27
|
+
export function canRead(role, collection) {
|
|
28
|
+
return role !== null && permits(role.read, collection);
|
|
29
|
+
}
|
|
30
|
+
// True if the role may create/modify documents in `collection`.
|
|
31
|
+
export function canWrite(role, collection) {
|
|
32
|
+
return role !== null && permits(role.write, collection);
|
|
33
|
+
}
|
|
34
|
+
// True if the role may promote a draft to canonical.
|
|
35
|
+
export function canPromote(role) {
|
|
36
|
+
return role?.promote ?? false;
|
|
37
|
+
}
|
|
38
|
+
// True if the role has read access to at least one collection. Curation tools
|
|
39
|
+
// (lint, tension log, provenance) are open to anyone with any read grant.
|
|
40
|
+
export function hasAnyRead(role) {
|
|
41
|
+
return role !== null && role.read.length > 0;
|
|
42
|
+
}
|
|
43
|
+
// Keeps only the items in collections the role may read. Each item must carry
|
|
44
|
+
// a `collection` field.
|
|
45
|
+
export function filterByReadPermission(role, items) {
|
|
46
|
+
return items.filter((item) => canRead(role, item.collection));
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=rbac.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rbac.js","sourceRoot":"","sources":["../../src/access/rbac.ts"],"names":[],"mappings":"AAAA,6BAA6B;AAC7B,EAAE;AACF,6EAA6E;AAC7E,8EAA8E;AAC9E,2EAA2E;AAC3E,EAAE;AACF,8EAA8E;AAC9E,2EAA2E;AAC3E,0EAA0E;AAI1E,MAAM,CAAC,MAAM,UAAU,GAAG,OAAO,CAAC;AAClC,MAAM,CAAC,MAAM,QAAQ,GAAG,GAAG,CAAC;AAU5B,+EAA+E;AAC/E,6EAA6E;AAC7E,2BAA2B;AAC3B,MAAM,UAAU,aAAa,CAC3B,MAAqB,EACrB,IAAY,EACZ,QAAgB;IAEhB,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,IAAI,EAAE,CAAC;AAClE,CAAC;AAED,2EAA2E;AAC3E,0BAA0B;AAC1B,MAAM,UAAU,WAAW,CAAC,IAAI,GAAG,OAAO;IACxC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;AACpD,CAAC;AAED,SAAS,OAAO,CAAC,IAAc,EAAE,UAAkB;IACjD,OAAO,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;AAC9D,CAAC;AAED,uDAAuD;AACvD,MAAM,UAAU,OAAO,CAAC,IAAuB,EAAE,UAAkB;IACjE,OAAO,IAAI,KAAK,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;AACzD,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,QAAQ,CAAC,IAAuB,EAAE,UAAkB;IAClE,OAAO,IAAI,KAAK,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;AAC1D,CAAC;AAED,qDAAqD;AACrD,MAAM,UAAU,UAAU,CAAC,IAAuB;IAChD,OAAO,IAAI,EAAE,OAAO,IAAI,KAAK,CAAC;AAChC,CAAC;AAED,8EAA8E;AAC9E,0EAA0E;AAC1E,MAAM,UAAU,UAAU,CAAC,IAAuB;IAChD,OAAO,IAAI,KAAK,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;AAC/C,CAAC;AAED,8EAA8E;AAC9E,wBAAwB;AACxB,MAAM,UAAU,sBAAsB,CACpC,IAAuB,EACvB,KAAU;IAEV,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;AAChE,CAAC"}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAsJA,wBAAsB,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA8DnE;AAED,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAqBvD"}
|