@promptowl/contextnest-community 0.1.0-alpha.2 → 1.0.1
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/dist/{chunk-DJFEV4ET.js → chunk-5VHKEIAW.js} +100 -4
- package/dist/{chunk-P6NG56CO.js → chunk-JMZ75ZCD.js} +44 -6
- package/dist/{chunk-Q2DCOS7V.js → chunk-K22GWPT4.js} +65 -58
- package/dist/{chunk-USIDOGVJ.js → chunk-KQCWNHDM.js} +218 -21
- package/dist/index.js +1738 -462
- package/dist/{review-service-5CLVZKAR.js → review-service-4WS3XL6K.js} +4 -3
- package/dist/{stewardship-service-NC67XBYO.js → stewardship-service-C5D2O7ZE.js} +2 -2
- package/dist/{version-service-Z6FYJRAG.js → version-service-TFEYNPH7.js} +10 -4
- package/dist/web3/assets/index-DkLevP7k.js +624 -0
- package/dist/web3/assets/index-DpoBdKrd.css +1 -0
- package/dist/web3/index.html +2 -2
- package/package.json +134 -108
- package/dist/web3/assets/index-CemroDXg.css +0 -1
- package/dist/web3/assets/index-xLLf4lHJ.js +0 -332
package/CONFIGURATION.md
CHANGED
|
@@ -1,118 +1,118 @@
|
|
|
1
|
-
# Configuration
|
|
2
|
-
|
|
3
|
-
All server configuration is driven by environment variables. Set them in your shell, in a `.env` file, in a Docker image, or via your process manager — however you deploy the rest of your stack.
|
|
4
|
-
|
|
5
|
-
## Auth modes
|
|
6
|
-
|
|
7
|
-
The server supports two authentication modes. You pick one per instance with `AUTH_MODE`.
|
|
8
|
-
|
|
9
|
-
### `AUTH_MODE=key` (default)
|
|
10
|
-
|
|
11
|
-
Every request requires an `Authorization: Bearer cnst_...` header. Use this for any multi-user or internet-facing deployment.
|
|
12
|
-
|
|
13
|
-
- Users register via `POST /auth/register` (email + password) or log in with PromptOwl via `POST /auth/device` → `POST /auth/promptowl`.
|
|
14
|
-
- API keys are per-user (`cnst_<64-hex>`) and can be scoped to a single nest for service-account use.
|
|
15
|
-
- Rate-limited login / register / device-auth endpoints (sliding window, per IP + per email).
|
|
16
|
-
- First PromptOwl-authenticated user becomes the server admin (atomic claim). Admin can invite teammates at `POST /auth/invite`.
|
|
17
|
-
|
|
18
|
-
Clients include the token on every request:
|
|
19
|
-
|
|
20
|
-
```bash
|
|
21
|
-
curl -H 'Authorization: Bearer cnst_<your-token>' http://your-server/nests
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
### `AUTH_MODE=open`
|
|
25
|
-
|
|
26
|
-
No authentication required. Every request is attributed to the anonymous admin user (`00000000-...`). Everyone effectively owns every anon-created nest.
|
|
27
|
-
|
|
28
|
-
**Use this only when:**
|
|
29
|
-
- You're running locally for yourself (`bind 127.0.0.1`), or
|
|
30
|
-
- You're on a trusted LAN and don't care about access controls, or
|
|
31
|
-
- You're behind an upstream reverse proxy that already authenticates.
|
|
32
|
-
|
|
33
|
-
**Don't use this when:**
|
|
34
|
-
- The port is reachable from the public internet
|
|
35
|
-
- Multiple people share the deployment and need isolated data
|
|
36
|
-
- You need audit trails (every write shows up under "anonymous admin")
|
|
37
|
-
|
|
38
|
-
The server prints a loud warning at startup when `AUTH_MODE=open` is active.
|
|
39
|
-
|
|
40
|
-
---
|
|
41
|
-
|
|
42
|
-
## Full env var reference
|
|
43
|
-
|
|
44
|
-
| Var | Default | Purpose |
|
|
45
|
-
|---|---|---|
|
|
46
|
-
| `PORT` | `3838` | HTTP port the server listens on. |
|
|
47
|
-
| `DATA_ROOT` | `./data` (relative to cwd) | Root directory for SQLite DB + nest filesystem vaults. Set to an absolute path in production so it's independent of where you `cd`'d from. |
|
|
48
|
-
| `DATABASE_PATH` | `$DATA_ROOT/community.db` | Override the SQLite file location explicitly. |
|
|
49
|
-
| `AUTH_MODE` | `key` | `key` or `open`. See above. |
|
|
50
|
-
| `PROMPTOWL_API_URL` | `https://app.promptowl.ai` | PromptOwl's API origin — used for device auth, license validation, telemetry. Override for air-gapped or test setups. |
|
|
51
|
-
| `PROMPTOWL_KEY` | `""` | Your PromptOwl Community Server license key (`pk_...`). Unlicensed instances still run but some features may be limited. |
|
|
52
|
-
| `TELEMETRY_ENABLED` | `"true"` (set to `"false"` to disable) | Batched, anonymized usage events sent to PromptOwl. Off disables the loop entirely. |
|
|
53
|
-
| `TELEMETRY_INTERVAL_MS` | `3600000` (1 hour) | How often buffered telemetry is flushed to PromptOwl. |
|
|
54
|
-
| `CORS_ORIGINS` | `*` in open mode; `http://localhost:5173,http://localhost:3838` in key mode | Comma-separated allowlist. Set to `*` to allow any origin (**only** safe in open mode — in key mode with Bearer tokens this enables CSRF). |
|
|
55
|
-
| `MAX_BODY_BYTES` | `10485760` (10 MB) | Reject requests whose `Content-Length` exceeds this. Prevents giant-payload DoS. |
|
|
56
|
-
|
|
57
|
-
---
|
|
58
|
-
|
|
59
|
-
## Typical deployments
|
|
60
|
-
|
|
61
|
-
### Local dev / single user
|
|
62
|
-
|
|
63
|
-
```bash
|
|
64
|
-
AUTH_MODE=open DATA_ROOT=./my-data pnpm dev
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
Or the default dev mode if `pnpm dev` already sets `AUTH_MODE=open` in your scripts.
|
|
68
|
-
|
|
69
|
-
### Team / multi-user behind a reverse proxy
|
|
70
|
-
|
|
71
|
-
```bash
|
|
72
|
-
AUTH_MODE=key \
|
|
73
|
-
CORS_ORIGINS="https://team.example.com,https://admin.example.com" \
|
|
74
|
-
DATA_ROOT=/var/lib/contextnest \
|
|
75
|
-
PROMPTOWL_KEY=pk_... \
|
|
76
|
-
pnpm start
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
Terminate TLS at the proxy, forward `X-Forwarded-For` and `X-Real-IP` headers (the rate limiter reads them), and bind the server to `127.0.0.1` so only the proxy can reach it.
|
|
80
|
-
|
|
81
|
-
### Hosted / commercial SaaS
|
|
82
|
-
|
|
83
|
-
Same as team, plus:
|
|
84
|
-
- Run N instances behind a load balancer sharing a network-attached `DATA_ROOT` (note: SQLite + shared filesystem is not a great long-term story — migrate to the MongoDB storage adapter when scaling beyond one box).
|
|
85
|
-
- Set `TELEMETRY_ENABLED=true` and a valid `PROMPTOWL_KEY` so usage rolls up to PromptOwl.
|
|
86
|
-
- Rotate keys regularly via `DELETE /auth/keys/:id` + `POST /auth/keys`.
|
|
87
|
-
|
|
88
|
-
---
|
|
89
|
-
|
|
90
|
-
## access.yaml (optional, open + key mode)
|
|
91
|
-
|
|
92
|
-
Drop an `access.yaml` at `$DATA_ROOT/access.yaml` to add a server-level ABAC layer — useful even in key mode for super-admins and group-based defaults.
|
|
93
|
-
|
|
94
|
-
```yaml
|
|
95
|
-
mode: restricted
|
|
96
|
-
allowed_users:
|
|
97
|
-
- "*.acme.com" # email wildcard — anyone @acme.com
|
|
98
|
-
- "partner@vendor.com" # exact match
|
|
99
|
-
super_admins:
|
|
100
|
-
- "ceo@acme.com" # always allowed on every nest
|
|
101
|
-
groups:
|
|
102
|
-
engineering:
|
|
103
|
-
default_permission: write
|
|
104
|
-
members:
|
|
105
|
-
- "*.eng.acme.com"
|
|
106
|
-
viewers:
|
|
107
|
-
default_permission: read
|
|
108
|
-
members:
|
|
109
|
-
- "*.contractor.acme.com"
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
See `STEWARDSHIP.md` for how super-admins and groups interact with per-nest stewardship.
|
|
113
|
-
|
|
114
|
-
---
|
|
115
|
-
|
|
116
|
-
## Reload
|
|
117
|
-
|
|
118
|
-
Changing env vars requires a restart — the server reads them at boot. `tsx watch` in dev will pick up code changes automatically but not env changes.
|
|
1
|
+
# Configuration
|
|
2
|
+
|
|
3
|
+
All server configuration is driven by environment variables. Set them in your shell, in a `.env` file, in a Docker image, or via your process manager — however you deploy the rest of your stack.
|
|
4
|
+
|
|
5
|
+
## Auth modes
|
|
6
|
+
|
|
7
|
+
The server supports two authentication modes. You pick one per instance with `AUTH_MODE`.
|
|
8
|
+
|
|
9
|
+
### `AUTH_MODE=key` (default)
|
|
10
|
+
|
|
11
|
+
Every request requires an `Authorization: Bearer cnst_...` header. Use this for any multi-user or internet-facing deployment.
|
|
12
|
+
|
|
13
|
+
- Users register via `POST /auth/register` (email + password) or log in with PromptOwl via `POST /auth/device` → `POST /auth/promptowl`.
|
|
14
|
+
- API keys are per-user (`cnst_<64-hex>`) and can be scoped to a single nest for service-account use.
|
|
15
|
+
- Rate-limited login / register / device-auth endpoints (sliding window, per IP + per email).
|
|
16
|
+
- First PromptOwl-authenticated user becomes the server admin (atomic claim). Admin can invite teammates at `POST /auth/invite`.
|
|
17
|
+
|
|
18
|
+
Clients include the token on every request:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
curl -H 'Authorization: Bearer cnst_<your-token>' http://your-server/nests
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### `AUTH_MODE=open`
|
|
25
|
+
|
|
26
|
+
No authentication required. Every request is attributed to the anonymous admin user (`00000000-...`). Everyone effectively owns every anon-created nest.
|
|
27
|
+
|
|
28
|
+
**Use this only when:**
|
|
29
|
+
- You're running locally for yourself (`bind 127.0.0.1`), or
|
|
30
|
+
- You're on a trusted LAN and don't care about access controls, or
|
|
31
|
+
- You're behind an upstream reverse proxy that already authenticates.
|
|
32
|
+
|
|
33
|
+
**Don't use this when:**
|
|
34
|
+
- The port is reachable from the public internet
|
|
35
|
+
- Multiple people share the deployment and need isolated data
|
|
36
|
+
- You need audit trails (every write shows up under "anonymous admin")
|
|
37
|
+
|
|
38
|
+
The server prints a loud warning at startup when `AUTH_MODE=open` is active.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Full env var reference
|
|
43
|
+
|
|
44
|
+
| Var | Default | Purpose |
|
|
45
|
+
|---|---|---|
|
|
46
|
+
| `PORT` | `3838` | HTTP port the server listens on. |
|
|
47
|
+
| `DATA_ROOT` | `./data` (relative to cwd) | Root directory for SQLite DB + nest filesystem vaults. Set to an absolute path in production so it's independent of where you `cd`'d from. |
|
|
48
|
+
| `DATABASE_PATH` | `$DATA_ROOT/community.db` | Override the SQLite file location explicitly. |
|
|
49
|
+
| `AUTH_MODE` | `key` | `key` or `open`. See above. |
|
|
50
|
+
| `PROMPTOWL_API_URL` | `https://app.promptowl.ai` | PromptOwl's API origin — used for device auth, license validation, telemetry. Override for air-gapped or test setups. |
|
|
51
|
+
| `PROMPTOWL_KEY` | `""` | Your PromptOwl Community Server license key (`pk_...`). Unlicensed instances still run but some features may be limited. |
|
|
52
|
+
| `TELEMETRY_ENABLED` | `"true"` (set to `"false"` to disable) | Batched, anonymized usage events sent to PromptOwl. Off disables the loop entirely. |
|
|
53
|
+
| `TELEMETRY_INTERVAL_MS` | `3600000` (1 hour) | How often buffered telemetry is flushed to PromptOwl. |
|
|
54
|
+
| `CORS_ORIGINS` | `*` in open mode; `http://localhost:5173,http://localhost:3838` in key mode | Comma-separated allowlist. Set to `*` to allow any origin (**only** safe in open mode — in key mode with Bearer tokens this enables CSRF). |
|
|
55
|
+
| `MAX_BODY_BYTES` | `10485760` (10 MB) | Reject requests whose `Content-Length` exceeds this. Prevents giant-payload DoS. |
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Typical deployments
|
|
60
|
+
|
|
61
|
+
### Local dev / single user
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
AUTH_MODE=open DATA_ROOT=./my-data pnpm dev
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Or the default dev mode if `pnpm dev` already sets `AUTH_MODE=open` in your scripts.
|
|
68
|
+
|
|
69
|
+
### Team / multi-user behind a reverse proxy
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
AUTH_MODE=key \
|
|
73
|
+
CORS_ORIGINS="https://team.example.com,https://admin.example.com" \
|
|
74
|
+
DATA_ROOT=/var/lib/contextnest \
|
|
75
|
+
PROMPTOWL_KEY=pk_... \
|
|
76
|
+
pnpm start
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Terminate TLS at the proxy, forward `X-Forwarded-For` and `X-Real-IP` headers (the rate limiter reads them), and bind the server to `127.0.0.1` so only the proxy can reach it.
|
|
80
|
+
|
|
81
|
+
### Hosted / commercial SaaS
|
|
82
|
+
|
|
83
|
+
Same as team, plus:
|
|
84
|
+
- Run N instances behind a load balancer sharing a network-attached `DATA_ROOT` (note: SQLite + shared filesystem is not a great long-term story — migrate to the MongoDB storage adapter when scaling beyond one box).
|
|
85
|
+
- Set `TELEMETRY_ENABLED=true` and a valid `PROMPTOWL_KEY` so usage rolls up to PromptOwl.
|
|
86
|
+
- Rotate keys regularly via `DELETE /auth/keys/:id` + `POST /auth/keys`.
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## access.yaml (optional, open + key mode)
|
|
91
|
+
|
|
92
|
+
Drop an `access.yaml` at `$DATA_ROOT/access.yaml` to add a server-level ABAC layer — useful even in key mode for super-admins and group-based defaults.
|
|
93
|
+
|
|
94
|
+
```yaml
|
|
95
|
+
mode: restricted
|
|
96
|
+
allowed_users:
|
|
97
|
+
- "*.acme.com" # email wildcard — anyone @acme.com
|
|
98
|
+
- "partner@vendor.com" # exact match
|
|
99
|
+
super_admins:
|
|
100
|
+
- "ceo@acme.com" # always allowed on every nest
|
|
101
|
+
groups:
|
|
102
|
+
engineering:
|
|
103
|
+
default_permission: write
|
|
104
|
+
members:
|
|
105
|
+
- "*.eng.acme.com"
|
|
106
|
+
viewers:
|
|
107
|
+
default_permission: read
|
|
108
|
+
members:
|
|
109
|
+
- "*.contractor.acme.com"
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
See `STEWARDSHIP.md` for how super-admins and groups interact with per-nest stewardship.
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Reload
|
|
117
|
+
|
|
118
|
+
Changing env vars requires a restart — the server reads them at boot. `tsx watch` in dev will pick up code changes automatically but not env changes.
|
|
@@ -1,13 +1,74 @@
|
|
|
1
1
|
import {
|
|
2
2
|
canUserApprove,
|
|
3
3
|
resolveStewardsForNode
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-K22GWPT4.js";
|
|
5
5
|
import {
|
|
6
|
+
createVersion,
|
|
7
|
+
setApprovedVersion
|
|
8
|
+
} from "./chunk-JMZ75ZCD.js";
|
|
9
|
+
import {
|
|
10
|
+
config,
|
|
6
11
|
getDb
|
|
7
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-KQCWNHDM.js";
|
|
8
13
|
|
|
9
14
|
// src/governance/review-service.ts
|
|
10
15
|
import { v4 as uuid } from "uuid";
|
|
16
|
+
|
|
17
|
+
// src/nodes/engine.ts
|
|
18
|
+
import { join } from "path";
|
|
19
|
+
import {
|
|
20
|
+
NestStorage,
|
|
21
|
+
GraphQueryEngine,
|
|
22
|
+
VersionManager
|
|
23
|
+
} from "@promptowl/contextnest-engine";
|
|
24
|
+
var NestEngineCache = class {
|
|
25
|
+
cache = /* @__PURE__ */ new Map();
|
|
26
|
+
get(nestId) {
|
|
27
|
+
let engine = this.cache.get(nestId);
|
|
28
|
+
if (!engine) {
|
|
29
|
+
const nestPath = join(config.DATA_ROOT, "nests", nestId);
|
|
30
|
+
const storage = new NestStorage(nestPath);
|
|
31
|
+
const query = new GraphQueryEngine(storage);
|
|
32
|
+
const versions = new VersionManager(storage);
|
|
33
|
+
engine = { storage, query, versions };
|
|
34
|
+
this.cache.set(nestId, engine);
|
|
35
|
+
}
|
|
36
|
+
return engine;
|
|
37
|
+
}
|
|
38
|
+
evict(nestId) {
|
|
39
|
+
this.cache.delete(nestId);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
var engineCache = new NestEngineCache();
|
|
43
|
+
|
|
44
|
+
// src/governance/safe-publish.ts
|
|
45
|
+
import {
|
|
46
|
+
publishDocument,
|
|
47
|
+
serializeDocument
|
|
48
|
+
} from "@promptowl/contextnest-engine";
|
|
49
|
+
async function safePublishDocument(storage, docId, options) {
|
|
50
|
+
const node = await storage.readDocument(docId);
|
|
51
|
+
const cleanedFrontmatter = stripUndefinedDeep(node.frontmatter);
|
|
52
|
+
const cleanedNode = { ...node, frontmatter: cleanedFrontmatter };
|
|
53
|
+
await storage.writeDocument(docId, serializeDocument(cleanedNode));
|
|
54
|
+
return publishDocument(storage, docId, options);
|
|
55
|
+
}
|
|
56
|
+
function stripUndefinedDeep(value) {
|
|
57
|
+
if (Array.isArray(value)) {
|
|
58
|
+
return value.filter((v) => v !== void 0).map((v) => stripUndefinedDeep(v));
|
|
59
|
+
}
|
|
60
|
+
if (value && typeof value === "object") {
|
|
61
|
+
const out = {};
|
|
62
|
+
for (const [k, v] of Object.entries(value)) {
|
|
63
|
+
if (v === void 0) continue;
|
|
64
|
+
out[k] = stripUndefinedDeep(v);
|
|
65
|
+
}
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
return value;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// src/governance/review-service.ts
|
|
11
72
|
function submitForReview(params) {
|
|
12
73
|
const db = getDb();
|
|
13
74
|
const existing = db.prepare(
|
|
@@ -35,7 +96,7 @@ function submitForReview(params) {
|
|
|
35
96
|
).run(params.nestId, params.nodeId, params.version);
|
|
36
97
|
return getReviewRequest(id);
|
|
37
98
|
}
|
|
38
|
-
function approve(params) {
|
|
99
|
+
async function approve(params) {
|
|
39
100
|
const db = getDb();
|
|
40
101
|
const pending = db.prepare(
|
|
41
102
|
"SELECT * FROM review_requests WHERE nest_id = ? AND node_id = ? AND status = 'pending' ORDER BY requested_at DESC LIMIT 1"
|
|
@@ -65,12 +126,45 @@ function approve(params) {
|
|
|
65
126
|
pending.id
|
|
66
127
|
);
|
|
67
128
|
db.prepare(
|
|
68
|
-
"UPDATE node_versions SET status = '
|
|
129
|
+
"UPDATE node_versions SET status = 'published' WHERE nest_id = ? AND node_id = ? AND version = ?"
|
|
69
130
|
).run(params.nestId, params.nodeId, params.version);
|
|
70
131
|
db.prepare(
|
|
71
132
|
`INSERT OR REPLACE INTO approved_versions (nest_id, node_id, approved_version, approved_by)
|
|
72
133
|
VALUES (?, ?, ?, ?)`
|
|
73
134
|
).run(params.nestId, params.nodeId, params.version, params.approvedBy);
|
|
135
|
+
try {
|
|
136
|
+
const { storage } = engineCache.get(params.nestId);
|
|
137
|
+
const result = await safePublishDocument(storage, params.nodeId, {
|
|
138
|
+
editedBy: params.approvedBy,
|
|
139
|
+
note: params.note || `Approved review request ${pending.id}`
|
|
140
|
+
});
|
|
141
|
+
const engineVersion = result.versionEntry.version;
|
|
142
|
+
if (engineVersion !== params.version) {
|
|
143
|
+
const node = result.node;
|
|
144
|
+
const tags = node.frontmatter.tags || [];
|
|
145
|
+
createVersion({
|
|
146
|
+
nestId: params.nestId,
|
|
147
|
+
nodeId: params.nodeId,
|
|
148
|
+
version: engineVersion,
|
|
149
|
+
content: node.body || "",
|
|
150
|
+
author: params.approvedBy,
|
|
151
|
+
status: "published",
|
|
152
|
+
tags,
|
|
153
|
+
changeNote: params.note || `Approved review request ${pending.id}`
|
|
154
|
+
});
|
|
155
|
+
setApprovedVersion(
|
|
156
|
+
params.nestId,
|
|
157
|
+
params.nodeId,
|
|
158
|
+
engineVersion,
|
|
159
|
+
params.approvedBy
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
} catch (err) {
|
|
163
|
+
console.error(
|
|
164
|
+
`publishDocument failed for ${params.nestId}/${params.nodeId} on approve:`,
|
|
165
|
+
err
|
|
166
|
+
);
|
|
167
|
+
}
|
|
74
168
|
return getReviewRequest(pending.id);
|
|
75
169
|
}
|
|
76
170
|
function reject(params) {
|
|
@@ -189,6 +283,8 @@ function rowToReviewRequest(row) {
|
|
|
189
283
|
}
|
|
190
284
|
|
|
191
285
|
export {
|
|
286
|
+
engineCache,
|
|
287
|
+
safePublishDocument,
|
|
192
288
|
submitForReview,
|
|
193
289
|
approve,
|
|
194
290
|
reject,
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getDb
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-KQCWNHDM.js";
|
|
4
4
|
|
|
5
5
|
// src/governance/version-service.ts
|
|
6
6
|
import { createHash } from "crypto";
|
|
7
7
|
function hashContent(content) {
|
|
8
8
|
return createHash("sha256").update(content).digest("hex");
|
|
9
9
|
}
|
|
10
|
+
var SYSTEM_AUTHOR_PREFIX = "system:auto-publish:";
|
|
11
|
+
function systemAuthor(email) {
|
|
12
|
+
return `${SYSTEM_AUTHOR_PREFIX}${email}`;
|
|
13
|
+
}
|
|
10
14
|
function createVersion(params) {
|
|
11
15
|
const db = getDb();
|
|
12
16
|
const contentHash = hashContent(params.content);
|
|
@@ -49,8 +53,10 @@ function getVersion(nestId, nodeId, version) {
|
|
|
49
53
|
function getCurrentVersion(nestId, nodeId) {
|
|
50
54
|
const db = getDb();
|
|
51
55
|
const row = db.prepare(
|
|
52
|
-
|
|
53
|
-
|
|
56
|
+
`SELECT MAX(version) as v FROM node_versions
|
|
57
|
+
WHERE nest_id = ? AND node_id = ?
|
|
58
|
+
AND author NOT LIKE ?`
|
|
59
|
+
).get(nestId, nodeId, `${SYSTEM_AUTHOR_PREFIX}%`);
|
|
54
60
|
return row?.v || 0;
|
|
55
61
|
}
|
|
56
62
|
function getApprovedVersion(nestId, nodeId) {
|
|
@@ -70,8 +76,13 @@ function setApprovedVersion(nestId, nodeId, version, approvedBy) {
|
|
|
70
76
|
function checkConflict(nestId, nodeId, baseVersion) {
|
|
71
77
|
const db = getDb();
|
|
72
78
|
const current = db.prepare(
|
|
73
|
-
|
|
74
|
-
|
|
79
|
+
`SELECT version, content_hash, author, created_at
|
|
80
|
+
FROM node_versions
|
|
81
|
+
WHERE nest_id = ? AND node_id = ?
|
|
82
|
+
AND author NOT LIKE ?
|
|
83
|
+
ORDER BY version DESC
|
|
84
|
+
LIMIT 1`
|
|
85
|
+
).get(nestId, nodeId, `${SYSTEM_AUTHOR_PREFIX}%`);
|
|
75
86
|
if (!current) {
|
|
76
87
|
return { conflict: false, currentVersion: 0, currentHash: "" };
|
|
77
88
|
}
|
|
@@ -102,6 +113,30 @@ function getNodeTags(nestId, nodeId) {
|
|
|
102
113
|
return [];
|
|
103
114
|
}
|
|
104
115
|
}
|
|
116
|
+
function getDisplayStatus(nestId, nodeId) {
|
|
117
|
+
const db = getDb();
|
|
118
|
+
const pending = db.prepare(
|
|
119
|
+
"SELECT 1 FROM review_requests WHERE nest_id = ? AND node_id = ? AND status = 'pending' LIMIT 1"
|
|
120
|
+
).get(nestId, nodeId);
|
|
121
|
+
if (pending) return "pending_review";
|
|
122
|
+
const current = db.prepare(
|
|
123
|
+
`SELECT version, status FROM node_versions
|
|
124
|
+
WHERE nest_id = ? AND node_id = ?
|
|
125
|
+
ORDER BY version DESC LIMIT 1`
|
|
126
|
+
).get(nestId, nodeId);
|
|
127
|
+
if (!current) return "draft";
|
|
128
|
+
if (current.status === "published" || current.status === "approved") {
|
|
129
|
+
const approved = db.prepare(
|
|
130
|
+
"SELECT approved_version FROM approved_versions WHERE nest_id = ? AND node_id = ?"
|
|
131
|
+
).get(nestId, nodeId);
|
|
132
|
+
if (approved?.approved_version === current.version) {
|
|
133
|
+
return current.status === "published" ? "published" : "approved";
|
|
134
|
+
}
|
|
135
|
+
return "draft";
|
|
136
|
+
}
|
|
137
|
+
if (current.status === "rejected") return "rejected";
|
|
138
|
+
return "draft";
|
|
139
|
+
}
|
|
105
140
|
function rowToVersion(row) {
|
|
106
141
|
return {
|
|
107
142
|
version: row.version,
|
|
@@ -116,6 +151,8 @@ function rowToVersion(row) {
|
|
|
116
151
|
|
|
117
152
|
export {
|
|
118
153
|
hashContent,
|
|
154
|
+
SYSTEM_AUTHOR_PREFIX,
|
|
155
|
+
systemAuthor,
|
|
119
156
|
createVersion,
|
|
120
157
|
getVersions,
|
|
121
158
|
getVersion,
|
|
@@ -123,5 +160,6 @@ export {
|
|
|
123
160
|
getApprovedVersion,
|
|
124
161
|
setApprovedVersion,
|
|
125
162
|
checkConflict,
|
|
126
|
-
getNodeTags
|
|
163
|
+
getNodeTags,
|
|
164
|
+
getDisplayStatus
|
|
127
165
|
};
|