@promptowl/contextnest-community 0.1.0-alpha.1 → 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/CONFIGURATION.md +118 -118
- package/LICENSE.md +142 -142
- package/README.md +105 -105
- package/dist/{chunk-Q2DCOS7V.js → chunk-2FXVMVZJ.js} +53 -4
- package/dist/{chunk-USIDOGVJ.js → chunk-2TW25QEA.js} +79 -3
- package/dist/{chunk-P6NG56CO.js → chunk-BLOPZDPL.js} +25 -2
- package/dist/{chunk-DJFEV4ET.js → chunk-XDCW4HTW.js} +2 -2
- package/dist/index.js +1019 -327
- package/dist/{review-service-5CLVZKAR.js → review-service-2JHZHZWJ.js} +3 -3
- package/dist/{stewardship-service-NC67XBYO.js → stewardship-service-ZJATH6OM.js} +2 -2
- package/dist/{version-service-Z6FYJRAG.js → version-service-2MZJGE3H.js} +4 -2
- package/dist/web3/assets/index-BlGzOlFt.css +1 -0
- package/dist/web3/assets/index-C3W5d7fT.js +591 -0
- package/dist/web3/index.html +2 -2
- package/package.json +21 -4
- package/dist/web3/assets/index-CemroDXg.css +0 -1
- package/dist/web3/assets/index-xLLf4lHJ.js +0 -332
package/README.md
CHANGED
|
@@ -1,105 +1,105 @@
|
|
|
1
|
-
# ContextNest Community Edition
|
|
2
|
-
|
|
3
|
-
**Self-hosted context governance server for AI workflows.** Part of the [PromptOwl](https://promptowl.ai) platform.
|
|
4
|
-
|
|
5
|
-
> ⚠️ **Commercial Software.** ContextNest Community Edition is proprietary software licensed by Promptowl LLC. A free [PromptOwl account](https://app.promptowl.ai) is required to use it. Redistribution, hosting-as-a-service, and competitive use are prohibited. See [LICENSE.md](./LICENSE.md) for full terms.
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## What it is
|
|
10
|
-
|
|
11
|
-
ContextNest Community Edition is a self-hosted server that lets you:
|
|
12
|
-
|
|
13
|
-
- Store, version, and govern markdown-based context documents ("nests")
|
|
14
|
-
- Apply stewardship workflows — draft, pending review, approved
|
|
15
|
-
- Serve approved context to AI agents via MCP, HTTP, or CLI
|
|
16
|
-
- Sync with the PromptOwl hosted platform for multi-user collaboration
|
|
17
|
-
|
|
18
|
-
The server runs locally or on your own infrastructure. Your PromptOwl account handles authentication, entitlement, and governance metadata.
|
|
19
|
-
|
|
20
|
-
## Quickstart
|
|
21
|
-
|
|
22
|
-
```bash
|
|
23
|
-
# 1. Get a license key — sign up free at https://app.promptowl.ai
|
|
24
|
-
# Settings → License Keys → Create a Community Server key
|
|
25
|
-
|
|
26
|
-
# 2. (Optional) Scaffold a local nest with the open-source CLI
|
|
27
|
-
npx @promptowl/contextnest-cli init
|
|
28
|
-
|
|
29
|
-
# 3. Run the community server
|
|
30
|
-
PROMPTOWL_KEY=pk_... npx @promptowl/contextnest-community
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
The server listens on `http://localhost:3000` by default. Without a valid `PROMPTOWL_KEY` the server still runs but some features are limited. See [CONFIGURATION.md](./CONFIGURATION.md) for all environment variables (port, auth mode, storage, telemetry).
|
|
34
|
-
|
|
35
|
-
## System requirements
|
|
36
|
-
|
|
37
|
-
- **Node.js** 20.x or later
|
|
38
|
-
- **PromptOwl account** — free signup at <https://app.promptowl.ai/signup>
|
|
39
|
-
- **OS:** Windows, macOS, or Linux
|
|
40
|
-
- **Disk:** ~200 MB for the server, plus storage for your nests
|
|
41
|
-
|
|
42
|
-
## What you get
|
|
43
|
-
|
|
44
|
-
| Feature | Community Edition | Enterprise |
|
|
45
|
-
|---|:---:|:---:|
|
|
46
|
-
| Self-hosted context server | ✅ | ✅ |
|
|
47
|
-
| Markdown + YAML frontmatter vaults | ✅ | ✅ |
|
|
48
|
-
| Stewardship workflow (draft/review/approve) | ✅ | ✅ |
|
|
49
|
-
| MCP server for AI agents | ✅ | ✅ |
|
|
50
|
-
| Multi-user governance UI | — | ✅ |
|
|
51
|
-
| SSO / SAML / SCIM | — | ✅ |
|
|
52
|
-
| Audit log streaming | — | ✅ |
|
|
53
|
-
| Policy transforms (redaction, summarization) | — | ✅ |
|
|
54
|
-
| Priority support and SLA | — | ✅ |
|
|
55
|
-
|
|
56
|
-
For Enterprise pricing and features, contact **hoot@promptowl.ai** or visit <https://promptowl.ai/contextnest/>.
|
|
57
|
-
|
|
58
|
-
## Licensing
|
|
59
|
-
|
|
60
|
-
ContextNest Community Edition is **commercial software**. It is **not open source**.
|
|
61
|
-
|
|
62
|
-
**You may:**
|
|
63
|
-
- Install and run the Software on devices You own or control
|
|
64
|
-
- Use the Software for internal business purposes, tied to a valid PromptOwl account
|
|
65
|
-
- Make backup and archival copies
|
|
66
|
-
|
|
67
|
-
**You may not:**
|
|
68
|
-
- Redistribute, resell, rent, lease, or sublicense the Software
|
|
69
|
-
- Offer the Software as a hosted, managed, or software-as-a-service product to third parties
|
|
70
|
-
- Reverse engineer, decompile, or create derivative works
|
|
71
|
-
- Use the Software to build a competing product or service
|
|
72
|
-
- Remove copyright, trademark, or license notices
|
|
73
|
-
|
|
74
|
-
Full license text: [LICENSE.md](./LICENSE.md)
|
|
75
|
-
|
|
76
|
-
**For redistribution, hosted-service, OEM, or regulated-industry use,** contact **hoot@promptowl.ai** for a commercial license agreement.
|
|
77
|
-
|
|
78
|
-
## Platform terms
|
|
79
|
-
|
|
80
|
-
Because the Software requires a PromptOwl account, the following terms also apply to Your use:
|
|
81
|
-
|
|
82
|
-
- **End User License Agreement** — <https://promptowl.ai/eula/>
|
|
83
|
-
- **Terms of Service** — <https://promptowl.ai/terms-of-service/>
|
|
84
|
-
- **Privacy Policy** — <https://promptowl.ai/privacy-policy/>
|
|
85
|
-
- **Acceptable Use Policy** — <https://promptowl.ai/acceptable-use/>
|
|
86
|
-
- **Disclaimer** — <https://promptowl.ai/disclaimer/>
|
|
87
|
-
- **Cookie Policy** — <https://promptowl.ai/cookies/>
|
|
88
|
-
|
|
89
|
-
## Support
|
|
90
|
-
|
|
91
|
-
- **Documentation:** <https://promptowl.ai/contextnest/>
|
|
92
|
-
- **Product questions:** <https://promptowl.ai/contact-us/>
|
|
93
|
-
- **Support & bugs:** `hoot@promptowl.ai`
|
|
94
|
-
- **Commercial licensing:** `hoot@promptowl.ai` (subject: *ContextNest Commercial License*)
|
|
95
|
-
|
|
96
|
-
## AI output disclaimer
|
|
97
|
-
|
|
98
|
-
The Software injects content into large language models. AI output may be inaccurate, incomplete, or inappropriate for your use case. You are responsible for reviewing and validating any AI-generated content before relying on it, particularly in business-critical or regulated contexts. Do not deploy the Software in medical, legal, financial-advisory, or safety-critical environments without appropriate human oversight.
|
|
99
|
-
|
|
100
|
-
---
|
|
101
|
-
|
|
102
|
-
**Copyright © 2026 Promptowl LLC.** All rights reserved.
|
|
103
|
-
"ContextNest" and "PromptOwl" are trademarks of Promptowl LLC.
|
|
104
|
-
|
|
105
|
-
Promptowl LLC · 3060 Mercer University Dr Ste 110 · Atlanta, GA 30341 · USA
|
|
1
|
+
# ContextNest Community Edition
|
|
2
|
+
|
|
3
|
+
**Self-hosted context governance server for AI workflows.** Part of the [PromptOwl](https://promptowl.ai) platform.
|
|
4
|
+
|
|
5
|
+
> ⚠️ **Commercial Software.** ContextNest Community Edition is proprietary software licensed by Promptowl LLC. A free [PromptOwl account](https://app.promptowl.ai) is required to use it. Redistribution, hosting-as-a-service, and competitive use are prohibited. See [LICENSE.md](./LICENSE.md) for full terms.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## What it is
|
|
10
|
+
|
|
11
|
+
ContextNest Community Edition is a self-hosted server that lets you:
|
|
12
|
+
|
|
13
|
+
- Store, version, and govern markdown-based context documents ("nests")
|
|
14
|
+
- Apply stewardship workflows — draft, pending review, approved
|
|
15
|
+
- Serve approved context to AI agents via MCP, HTTP, or CLI
|
|
16
|
+
- Sync with the PromptOwl hosted platform for multi-user collaboration
|
|
17
|
+
|
|
18
|
+
The server runs locally or on your own infrastructure. Your PromptOwl account handles authentication, entitlement, and governance metadata.
|
|
19
|
+
|
|
20
|
+
## Quickstart
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# 1. Get a license key — sign up free at https://app.promptowl.ai
|
|
24
|
+
# Settings → License Keys → Create a Community Server key
|
|
25
|
+
|
|
26
|
+
# 2. (Optional) Scaffold a local nest with the open-source CLI
|
|
27
|
+
npx @promptowl/contextnest-cli init
|
|
28
|
+
|
|
29
|
+
# 3. Run the community server
|
|
30
|
+
PROMPTOWL_KEY=pk_... npx @promptowl/contextnest-community
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The server listens on `http://localhost:3000` by default. Without a valid `PROMPTOWL_KEY` the server still runs but some features are limited. See [CONFIGURATION.md](./CONFIGURATION.md) for all environment variables (port, auth mode, storage, telemetry).
|
|
34
|
+
|
|
35
|
+
## System requirements
|
|
36
|
+
|
|
37
|
+
- **Node.js** 20.x or later
|
|
38
|
+
- **PromptOwl account** — free signup at <https://app.promptowl.ai/signup>
|
|
39
|
+
- **OS:** Windows, macOS, or Linux
|
|
40
|
+
- **Disk:** ~200 MB for the server, plus storage for your nests
|
|
41
|
+
|
|
42
|
+
## What you get
|
|
43
|
+
|
|
44
|
+
| Feature | Community Edition | Enterprise |
|
|
45
|
+
|---|:---:|:---:|
|
|
46
|
+
| Self-hosted context server | ✅ | ✅ |
|
|
47
|
+
| Markdown + YAML frontmatter vaults | ✅ | ✅ |
|
|
48
|
+
| Stewardship workflow (draft/review/approve) | ✅ | ✅ |
|
|
49
|
+
| MCP server for AI agents | ✅ | ✅ |
|
|
50
|
+
| Multi-user governance UI | — | ✅ |
|
|
51
|
+
| SSO / SAML / SCIM | — | ✅ |
|
|
52
|
+
| Audit log streaming | — | ✅ |
|
|
53
|
+
| Policy transforms (redaction, summarization) | — | ✅ |
|
|
54
|
+
| Priority support and SLA | — | ✅ |
|
|
55
|
+
|
|
56
|
+
For Enterprise pricing and features, contact **hoot@promptowl.ai** or visit <https://promptowl.ai/contextnest/>.
|
|
57
|
+
|
|
58
|
+
## Licensing
|
|
59
|
+
|
|
60
|
+
ContextNest Community Edition is **commercial software**. It is **not open source**.
|
|
61
|
+
|
|
62
|
+
**You may:**
|
|
63
|
+
- Install and run the Software on devices You own or control
|
|
64
|
+
- Use the Software for internal business purposes, tied to a valid PromptOwl account
|
|
65
|
+
- Make backup and archival copies
|
|
66
|
+
|
|
67
|
+
**You may not:**
|
|
68
|
+
- Redistribute, resell, rent, lease, or sublicense the Software
|
|
69
|
+
- Offer the Software as a hosted, managed, or software-as-a-service product to third parties
|
|
70
|
+
- Reverse engineer, decompile, or create derivative works
|
|
71
|
+
- Use the Software to build a competing product or service
|
|
72
|
+
- Remove copyright, trademark, or license notices
|
|
73
|
+
|
|
74
|
+
Full license text: [LICENSE.md](./LICENSE.md)
|
|
75
|
+
|
|
76
|
+
**For redistribution, hosted-service, OEM, or regulated-industry use,** contact **hoot@promptowl.ai** for a commercial license agreement.
|
|
77
|
+
|
|
78
|
+
## Platform terms
|
|
79
|
+
|
|
80
|
+
Because the Software requires a PromptOwl account, the following terms also apply to Your use:
|
|
81
|
+
|
|
82
|
+
- **End User License Agreement** — <https://promptowl.ai/eula/>
|
|
83
|
+
- **Terms of Service** — <https://promptowl.ai/terms-of-service/>
|
|
84
|
+
- **Privacy Policy** — <https://promptowl.ai/privacy-policy/>
|
|
85
|
+
- **Acceptable Use Policy** — <https://promptowl.ai/acceptable-use/>
|
|
86
|
+
- **Disclaimer** — <https://promptowl.ai/disclaimer/>
|
|
87
|
+
- **Cookie Policy** — <https://promptowl.ai/cookies/>
|
|
88
|
+
|
|
89
|
+
## Support
|
|
90
|
+
|
|
91
|
+
- **Documentation:** <https://promptowl.ai/contextnest/>
|
|
92
|
+
- **Product questions:** <https://promptowl.ai/contact-us/>
|
|
93
|
+
- **Support & bugs:** `hoot@promptowl.ai`
|
|
94
|
+
- **Commercial licensing:** `hoot@promptowl.ai` (subject: *ContextNest Commercial License*)
|
|
95
|
+
|
|
96
|
+
## AI output disclaimer
|
|
97
|
+
|
|
98
|
+
The Software injects content into large language models. AI output may be inaccurate, incomplete, or inappropriate for your use case. You are responsible for reviewing and validating any AI-generated content before relying on it, particularly in business-critical or regulated contexts. Do not deploy the Software in medical, legal, financial-advisory, or safety-critical environments without appropriate human oversight.
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
**Copyright © 2026 Promptowl LLC.** All rights reserved.
|
|
103
|
+
"ContextNest" and "PromptOwl" are trademarks of Promptowl LLC.
|
|
104
|
+
|
|
105
|
+
Promptowl LLC · 3060 Mercer University Dr Ste 110 · Atlanta, GA 30341 · USA
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
config,
|
|
3
3
|
getDb
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-2TW25QEA.js";
|
|
5
5
|
|
|
6
6
|
// src/governance/stewardship-service.ts
|
|
7
7
|
import { v4 as uuid } from "uuid";
|
|
@@ -170,7 +170,34 @@ function listStewards(params) {
|
|
|
170
170
|
sql += " ORDER BY scope, COALESCE(node_pattern, tag_name, ''), user_email";
|
|
171
171
|
return db.prepare(sql).all(...args).map(rowToSteward);
|
|
172
172
|
}
|
|
173
|
-
function
|
|
173
|
+
function rolePermission(role) {
|
|
174
|
+
return role === "editor" ? "write" : "read";
|
|
175
|
+
}
|
|
176
|
+
async function ensureCollaborator(nestId, email, permission, grantedBy) {
|
|
177
|
+
const db = getDb();
|
|
178
|
+
let userRow = db.prepare("SELECT id FROM users WHERE email = ?").get(email);
|
|
179
|
+
if (!userRow) {
|
|
180
|
+
const { hashPassword } = await import("./keys-YV33AJK3.js");
|
|
181
|
+
const newId = uuid();
|
|
182
|
+
db.prepare(
|
|
183
|
+
"INSERT INTO users (id, email, name, password_hash, is_invited) VALUES (?, ?, ?, ?, 1)"
|
|
184
|
+
).run(newId, email, null, await hashPassword(uuid()));
|
|
185
|
+
userRow = { id: newId };
|
|
186
|
+
}
|
|
187
|
+
const nestRow = db.prepare("SELECT user_id FROM nests WHERE id = ?").get(nestId);
|
|
188
|
+
if (nestRow && nestRow.user_id === userRow.id) return;
|
|
189
|
+
const existing = db.prepare(
|
|
190
|
+
"SELECT id FROM nest_collaborators WHERE nest_id = ? AND user_id = ?"
|
|
191
|
+
).get(nestId, userRow.id);
|
|
192
|
+
if (existing) return;
|
|
193
|
+
const granterRow = db.prepare("SELECT id FROM users WHERE email = ?").get(grantedBy);
|
|
194
|
+
const granterId = granterRow?.id ?? nestRow?.user_id;
|
|
195
|
+
if (!granterId) return;
|
|
196
|
+
db.prepare(
|
|
197
|
+
"INSERT INTO nest_collaborators (id, nest_id, user_id, permission, granted_by) VALUES (?, ?, ?, ?, ?)"
|
|
198
|
+
).run(uuid(), nestId, userRow.id, permission, granterId);
|
|
199
|
+
}
|
|
200
|
+
async function createStewardRecord(params) {
|
|
174
201
|
if (params.users.length === 0) {
|
|
175
202
|
throw new Error("At least one user is required");
|
|
176
203
|
}
|
|
@@ -229,6 +256,12 @@ function createStewardRecord(params) {
|
|
|
229
256
|
isActive: true
|
|
230
257
|
});
|
|
231
258
|
results.push(created);
|
|
259
|
+
await ensureCollaborator(
|
|
260
|
+
params.nestId,
|
|
261
|
+
email,
|
|
262
|
+
rolePermission(user.role),
|
|
263
|
+
params.assignedBy
|
|
264
|
+
);
|
|
232
265
|
}
|
|
233
266
|
db.prepare(
|
|
234
267
|
"UPDATE nests SET stewardship_enabled = 1 WHERE id = ? AND stewardship_enabled = 0"
|
|
@@ -255,7 +288,10 @@ function resolve(nestId, nodeId) {
|
|
|
255
288
|
WHERE s.nest_id = ? AND s.is_active = 1 AND s.scope = 'folder'
|
|
256
289
|
AND s.node_pattern IS NOT NULL
|
|
257
290
|
AND instr(s.node_pattern, '*') = 0
|
|
258
|
-
AND
|
|
291
|
+
AND (
|
|
292
|
+
? LIKE s.node_pattern || '/%'
|
|
293
|
+
OR s.node_pattern = ?
|
|
294
|
+
)
|
|
259
295
|
UNION ALL
|
|
260
296
|
SELECT s.*, 3 AS priority, ('tag: ' || s.tag_name) AS match_source
|
|
261
297
|
FROM stewards s
|
|
@@ -270,7 +306,20 @@ function resolve(nestId, nodeId) {
|
|
|
270
306
|
WHERE s.nest_id = ? AND s.is_active = 1 AND s.scope = 'nest'
|
|
271
307
|
ORDER BY priority ASC, user_email ASC
|
|
272
308
|
`
|
|
273
|
-
).all(
|
|
309
|
+
).all(
|
|
310
|
+
nestId,
|
|
311
|
+
nodeId,
|
|
312
|
+
// document branch
|
|
313
|
+
nestId,
|
|
314
|
+
nodeId,
|
|
315
|
+
nestId,
|
|
316
|
+
// folder branch (path-prefix OR whole-nest folder)
|
|
317
|
+
nestId,
|
|
318
|
+
nodeId,
|
|
319
|
+
// tag branch
|
|
320
|
+
nestId
|
|
321
|
+
// nest branch
|
|
322
|
+
);
|
|
274
323
|
const resolved = rows.map((row) => ({
|
|
275
324
|
steward: rowToSteward(row),
|
|
276
325
|
priority: row.priority,
|
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
// src/config.ts
|
|
2
|
-
import { join } from "path";
|
|
2
|
+
import { join, dirname } from "path";
|
|
3
|
+
import { existsSync } from "fs";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
import dotenv from "dotenv";
|
|
6
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
var __dirname = dirname(__filename);
|
|
8
|
+
var envCandidates = [
|
|
9
|
+
join(process.cwd(), ".env"),
|
|
10
|
+
join(__dirname, "..", ".env")
|
|
11
|
+
];
|
|
12
|
+
var envFileLoaded = envCandidates.find((p) => existsSync(p)) || null;
|
|
13
|
+
if (envFileLoaded) {
|
|
14
|
+
dotenv.config({ path: envFileLoaded, override: true });
|
|
15
|
+
}
|
|
16
|
+
console.log(
|
|
17
|
+
`[config] dotenv: ${envFileLoaded ? `loaded ${envFileLoaded}` : "no .env file found"}`
|
|
18
|
+
);
|
|
3
19
|
function dataRoot() {
|
|
4
20
|
return process.env.DATA_ROOT || join(process.cwd(), "data");
|
|
5
21
|
}
|
|
@@ -19,6 +35,14 @@ var config = {
|
|
|
19
35
|
get PROMPTOWL_KEY() {
|
|
20
36
|
return process.env.PROMPTOWL_KEY || "";
|
|
21
37
|
},
|
|
38
|
+
/**
|
|
39
|
+
* Path to the .env file the server reads its config from. Used by
|
|
40
|
+
* the license install flow to persist PROMPTOWL_KEY alongside any
|
|
41
|
+
* existing env vars, instead of a separate sidecar file.
|
|
42
|
+
*/
|
|
43
|
+
get ENV_FILE_PATH() {
|
|
44
|
+
return process.env.ENV_FILE_PATH || join(process.cwd(), ".env");
|
|
45
|
+
},
|
|
22
46
|
get TELEMETRY_ENABLED() {
|
|
23
47
|
return process.env.TELEMETRY_ENABLED !== "false";
|
|
24
48
|
},
|
|
@@ -52,7 +76,7 @@ var config = {
|
|
|
52
76
|
// src/db/client.ts
|
|
53
77
|
import Database from "better-sqlite3";
|
|
54
78
|
import { mkdirSync } from "fs";
|
|
55
|
-
import { dirname } from "path";
|
|
79
|
+
import { dirname as dirname2 } from "path";
|
|
56
80
|
|
|
57
81
|
// src/db/migrations.ts
|
|
58
82
|
function runMigrations(db2) {
|
|
@@ -204,6 +228,9 @@ function runMigrations(db2) {
|
|
|
204
228
|
if (!userCols.includes("is_admin")) {
|
|
205
229
|
db2.exec("ALTER TABLE users ADD COLUMN is_admin INTEGER NOT NULL DEFAULT 0");
|
|
206
230
|
}
|
|
231
|
+
if (!userCols.includes("is_invited")) {
|
|
232
|
+
db2.exec("ALTER TABLE users ADD COLUMN is_invited INTEGER NOT NULL DEFAULT 0");
|
|
233
|
+
}
|
|
207
234
|
db2.exec(`
|
|
208
235
|
CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
209
236
|
id TEXT PRIMARY KEY,
|
|
@@ -326,16 +353,65 @@ function runMigrations(db2) {
|
|
|
326
353
|
recordMigration("002_steward_parity");
|
|
327
354
|
})();
|
|
328
355
|
}
|
|
356
|
+
if (!hasMigration("003_sessions_and_single_api_key")) {
|
|
357
|
+
db2.transaction(() => {
|
|
358
|
+
db2.exec(`
|
|
359
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
360
|
+
id TEXT PRIMARY KEY,
|
|
361
|
+
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
362
|
+
expires_at TEXT NOT NULL,
|
|
363
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
364
|
+
last_seen_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
365
|
+
user_agent TEXT
|
|
366
|
+
);
|
|
367
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_user ON sessions(user_id);
|
|
368
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_expires ON sessions(expires_at);
|
|
369
|
+
`);
|
|
370
|
+
db2.exec(`
|
|
371
|
+
DELETE FROM api_keys
|
|
372
|
+
WHERE id IN (
|
|
373
|
+
SELECT id FROM (
|
|
374
|
+
SELECT
|
|
375
|
+
id,
|
|
376
|
+
ROW_NUMBER() OVER (
|
|
377
|
+
PARTITION BY user_id
|
|
378
|
+
ORDER BY
|
|
379
|
+
COALESCE(last_used_at, '') DESC,
|
|
380
|
+
created_at DESC,
|
|
381
|
+
id DESC
|
|
382
|
+
) AS rn
|
|
383
|
+
FROM api_keys
|
|
384
|
+
)
|
|
385
|
+
WHERE rn > 1
|
|
386
|
+
);
|
|
387
|
+
`);
|
|
388
|
+
db2.exec(`
|
|
389
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_api_keys_user_unique
|
|
390
|
+
ON api_keys(user_id);
|
|
391
|
+
`);
|
|
392
|
+
recordMigration("003_sessions_and_single_api_key");
|
|
393
|
+
})();
|
|
394
|
+
}
|
|
395
|
+
if (!hasMigration("004_license_cache_owner_email")) {
|
|
396
|
+
db2.transaction(() => {
|
|
397
|
+
const cols = db2.prepare("PRAGMA table_info(license_cache)").all().map((c) => c.name);
|
|
398
|
+
if (!cols.includes("owner_email")) {
|
|
399
|
+
db2.exec("ALTER TABLE license_cache ADD COLUMN owner_email TEXT");
|
|
400
|
+
}
|
|
401
|
+
recordMigration("004_license_cache_owner_email");
|
|
402
|
+
})();
|
|
403
|
+
}
|
|
329
404
|
}
|
|
330
405
|
|
|
331
406
|
// src/db/client.ts
|
|
332
407
|
var db = null;
|
|
333
408
|
function getDb() {
|
|
334
409
|
if (!db) {
|
|
335
|
-
mkdirSync(
|
|
410
|
+
mkdirSync(dirname2(config.DATABASE_PATH), { recursive: true });
|
|
336
411
|
db = new Database(config.DATABASE_PATH);
|
|
337
412
|
db.pragma("journal_mode = WAL");
|
|
338
413
|
db.pragma("foreign_keys = ON");
|
|
414
|
+
db.pragma("busy_timeout = 5000");
|
|
339
415
|
runMigrations(db);
|
|
340
416
|
}
|
|
341
417
|
return db;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getDb
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-2TW25QEA.js";
|
|
4
4
|
|
|
5
5
|
// src/governance/version-service.ts
|
|
6
6
|
import { createHash } from "crypto";
|
|
@@ -102,6 +102,28 @@ function getNodeTags(nestId, nodeId) {
|
|
|
102
102
|
return [];
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
|
+
function getDisplayStatus(nestId, nodeId) {
|
|
106
|
+
const db = getDb();
|
|
107
|
+
const pending = db.prepare(
|
|
108
|
+
"SELECT 1 FROM review_requests WHERE nest_id = ? AND node_id = ? AND status = 'pending' LIMIT 1"
|
|
109
|
+
).get(nestId, nodeId);
|
|
110
|
+
if (pending) return "pending_review";
|
|
111
|
+
const current = db.prepare(
|
|
112
|
+
`SELECT version, status FROM node_versions
|
|
113
|
+
WHERE nest_id = ? AND node_id = ?
|
|
114
|
+
ORDER BY version DESC LIMIT 1`
|
|
115
|
+
).get(nestId, nodeId);
|
|
116
|
+
if (!current) return "draft";
|
|
117
|
+
if (current.status === "approved") {
|
|
118
|
+
const approved = db.prepare(
|
|
119
|
+
"SELECT approved_version FROM approved_versions WHERE nest_id = ? AND node_id = ?"
|
|
120
|
+
).get(nestId, nodeId);
|
|
121
|
+
if (approved?.approved_version === current.version) return "approved";
|
|
122
|
+
return "draft";
|
|
123
|
+
}
|
|
124
|
+
if (current.status === "rejected") return "rejected";
|
|
125
|
+
return "draft";
|
|
126
|
+
}
|
|
105
127
|
function rowToVersion(row) {
|
|
106
128
|
return {
|
|
107
129
|
version: row.version,
|
|
@@ -123,5 +145,6 @@ export {
|
|
|
123
145
|
getApprovedVersion,
|
|
124
146
|
setApprovedVersion,
|
|
125
147
|
checkConflict,
|
|
126
|
-
getNodeTags
|
|
148
|
+
getNodeTags,
|
|
149
|
+
getDisplayStatus
|
|
127
150
|
};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
canUserApprove,
|
|
3
3
|
resolveStewardsForNode
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-2FXVMVZJ.js";
|
|
5
5
|
import {
|
|
6
6
|
getDb
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-2TW25QEA.js";
|
|
8
8
|
|
|
9
9
|
// src/governance/review-service.ts
|
|
10
10
|
import { v4 as uuid } from "uuid";
|