openworkgraph 0.3.1 → 0.3.2
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/README.md +64 -64
- package/dist/builtin-skills.d.ts +8 -0
- package/dist/builtin-skills.js +31 -14
- package/dist/cli.js +0 -0
- package/dist/graphs.js +17 -17
- package/dist/persistence/asset-business-schema.js +33 -33
- package/dist/persistence/plugin-schema.js +28 -28
- package/dist/persistence/resource-schema.js +41 -41
- package/dist/persistence/schema.js +216 -216
- package/dist/persistence/workflow-schema.js +16 -16
- package/dist/runs.js +8 -8
- package/dist/skill-configs.d.ts +7 -3
- package/dist/skill-configs.js +123 -65
- package/dist/skill-sessions.d.ts +1 -2
- package/dist/skill-sessions.js +5 -4
- package/dist/skills-management-api.js +7 -3
- package/dist/vendor/protocol/index.d.ts +1 -1
- package/dist/vendor/protocol/index.js +1 -1
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -1,64 +1,64 @@
|
|
|
1
|
-
# OpenWorkgraph
|
|
2
|
-
|
|
3
|
-
OpenWorkgraph 是基于无限画布思想的软件开发工作流产品,同时面向其他领域的工作组织与协作。
|
|
4
|
-
|
|
5
|
-
## 快速开始
|
|
6
|
-
|
|
7
|
-
打开 [OpenWorkgraph 官方编辑器](https://workgraph.giarld.com/),按照页面提供的引导完成配置即可开始使用。
|
|
8
|
-
|
|
9
|
-
## 本地构建与运行
|
|
10
|
-
|
|
11
|
-
### Agent Service
|
|
12
|
-
|
|
13
|
-
在仓库根目录构建协议包和 Agent Service:
|
|
14
|
-
|
|
15
|
-
```sh
|
|
16
|
-
npm run build
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
构建完成后,以前台方式运行 Agent Service:
|
|
20
|
-
|
|
21
|
-
```sh
|
|
22
|
-
node agent-service/dist/cli.js start
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
### Web Client
|
|
26
|
-
|
|
27
|
-
在仓库根目录构建 Web Client,并通过 Vite Preview 在 `http://127.0.0.1:4173` 运行生产构建:
|
|
28
|
-
|
|
29
|
-
```sh
|
|
30
|
-
npm --prefix web-client run build
|
|
31
|
-
npm --prefix web-client run preview -- --host 127.0.0.1 --port 4173
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
打开 Web Client 后,填写运行时地址并点击“生成客户端码”。在运行时所在机器执行以下命令,输入浏览器显示的 8 位客户端码,核对来源和公钥指纹后确认;浏览器会轮询批准状态,完成私钥证明和通讯凭据交换后自动连接:
|
|
35
|
-
|
|
36
|
-
```sh
|
|
37
|
-
node agent-service/dist/cli.js pair --client-code '<客户端码>'
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
客户端码显示为连续 8 位数字,例如 `12345678`,中间没有空格,5 分钟有效。终端默认输出英文可读的批准结果;仅脚本需要解析时加 `--output-json`(非交互批准还需 `--yes`)。
|
|
41
|
-
|
|
42
|
-
一机只运行一个服务。`--data-dir` 仅用于 `start` 和 `serve`;启动成功后自动记住目录,配对、查看状态、停止、日志和备份命令都无需再填写。停服后运行 `start` 会沿用原目录,首次启动未指定时使用用户主目录下的 `.openworkgraph`。重复启动会明确拒绝,不提供服务选择或切换步骤。
|
|
43
|
-
|
|
44
|
-
无需手填 `--origin`,不强制 HTTPS,支持可信局域网普通 HTTP。HTTP 不加密,请勿暴露公网。
|
|
45
|
-
|
|
46
|
-
### 检查与测试
|
|
47
|
-
|
|
48
|
-
```sh
|
|
49
|
-
npm test
|
|
50
|
-
npm --prefix web-client run typecheck
|
|
51
|
-
npm --prefix web-client test
|
|
52
|
-
npm --prefix web-client run build
|
|
53
|
-
npm --prefix web-client run test:e2e
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
以上命令在仓库根目录执行;Web 构建输出为 `web-client/dist/`,浏览器测试使用已安装的 Chrome。
|
|
57
|
-
|
|
58
|
-
## 参考依据与致谢
|
|
59
|
-
|
|
60
|
-
Web UI 参考了 [basketikun/infinite-canvas](https://github.com/basketikun/infinite-canvas) 的视觉风格和画布交互,感谢原作者 basketikun。该项目采用 [MIT License](https://github.com/basketikun/infinite-canvas/blob/d213a74614e0e4bd8a26383d1e1e907249e9c61b/LICENSE);复用其代码或其他适用内容时,应保留原版权与许可声明。第三方素材若有独立授权,以其各自许可为准。
|
|
61
|
-
|
|
62
|
-
## 许可证
|
|
63
|
-
|
|
64
|
-
本项目采用 [MIT License](LICENSE)。第三方来源的版权与许可声明按原始归属保留。
|
|
1
|
+
# OpenWorkgraph
|
|
2
|
+
|
|
3
|
+
OpenWorkgraph 是基于无限画布思想的软件开发工作流产品,同时面向其他领域的工作组织与协作。
|
|
4
|
+
|
|
5
|
+
## 快速开始
|
|
6
|
+
|
|
7
|
+
打开 [OpenWorkgraph 官方编辑器](https://workgraph.giarld.com/),按照页面提供的引导完成配置即可开始使用。
|
|
8
|
+
|
|
9
|
+
## 本地构建与运行
|
|
10
|
+
|
|
11
|
+
### Agent Service
|
|
12
|
+
|
|
13
|
+
在仓库根目录构建协议包和 Agent Service:
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
npm run build
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
构建完成后,以前台方式运行 Agent Service:
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
node agent-service/dist/cli.js start
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Web Client
|
|
26
|
+
|
|
27
|
+
在仓库根目录构建 Web Client,并通过 Vite Preview 在 `http://127.0.0.1:4173` 运行生产构建:
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
npm --prefix web-client run build
|
|
31
|
+
npm --prefix web-client run preview -- --host 127.0.0.1 --port 4173
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
打开 Web Client 后,填写运行时地址并点击“生成客户端码”。在运行时所在机器执行以下命令,输入浏览器显示的 8 位客户端码,核对来源和公钥指纹后确认;浏览器会轮询批准状态,完成私钥证明和通讯凭据交换后自动连接:
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
node agent-service/dist/cli.js pair --client-code '<客户端码>'
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
客户端码显示为连续 8 位数字,例如 `12345678`,中间没有空格,5 分钟有效。终端默认输出英文可读的批准结果;仅脚本需要解析时加 `--output-json`(非交互批准还需 `--yes`)。
|
|
41
|
+
|
|
42
|
+
一机只运行一个服务。`--data-dir` 仅用于 `start` 和 `serve`;启动成功后自动记住目录,配对、查看状态、停止、日志和备份命令都无需再填写。停服后运行 `start` 会沿用原目录,首次启动未指定时使用用户主目录下的 `.openworkgraph`。重复启动会明确拒绝,不提供服务选择或切换步骤。
|
|
43
|
+
|
|
44
|
+
无需手填 `--origin`,不强制 HTTPS,支持可信局域网普通 HTTP。HTTP 不加密,请勿暴露公网。
|
|
45
|
+
|
|
46
|
+
### 检查与测试
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
npm test
|
|
50
|
+
npm --prefix web-client run typecheck
|
|
51
|
+
npm --prefix web-client test
|
|
52
|
+
npm --prefix web-client run build
|
|
53
|
+
npm --prefix web-client run test:e2e
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
以上命令在仓库根目录执行;Web 构建输出为 `web-client/dist/`,浏览器测试使用已安装的 Chrome。
|
|
57
|
+
|
|
58
|
+
## 参考依据与致谢
|
|
59
|
+
|
|
60
|
+
Web UI 参考了 [basketikun/infinite-canvas](https://github.com/basketikun/infinite-canvas) 的视觉风格和画布交互,感谢原作者 basketikun。该项目采用 [MIT License](https://github.com/basketikun/infinite-canvas/blob/d213a74614e0e4bd8a26383d1e1e907249e9c61b/LICENSE);复用其代码或其他适用内容时,应保留原版权与许可声明。第三方素材若有独立授权,以其各自许可为准。
|
|
61
|
+
|
|
62
|
+
## 许可证
|
|
63
|
+
|
|
64
|
+
本项目采用 [MIT License](LICENSE)。第三方来源的版权与许可声明按原始归属保留。
|
package/dist/builtin-skills.d.ts
CHANGED
|
@@ -35,12 +35,20 @@ export declare class BuiltinSkills {
|
|
|
35
35
|
private cached;
|
|
36
36
|
catalog(refresh?: boolean): Promise<SkillCatalog>;
|
|
37
37
|
installed(): Promise<SkillCatalogItem[]>;
|
|
38
|
+
/** Local records plus already-known update information; never refresh the source. */
|
|
39
|
+
installedCatalog(): Promise<SkillCatalog>;
|
|
38
40
|
private versionDirectory;
|
|
41
|
+
/** UI reads only the installed schema and manifest, without hashing package contents. */
|
|
42
|
+
packageMetadata(skillId: string, packageVersion?: string): Promise<{
|
|
43
|
+
config: SkillConfigSchema;
|
|
44
|
+
item: SkillCatalogItem;
|
|
45
|
+
}>;
|
|
39
46
|
packageEntry(skillId: string, packageVersion?: string): Promise<{
|
|
40
47
|
path: string;
|
|
41
48
|
config: SkillConfigSchema;
|
|
42
49
|
item: SkillCatalogItem;
|
|
43
50
|
}>;
|
|
51
|
+
private readPackageEntry;
|
|
44
52
|
/** Holds the same cross-process lock as updates while a configuration write uses its schema. */
|
|
45
53
|
withPackageEntry<T>(skillId: string, expectedVersion: string, work: (entry: {
|
|
46
54
|
path: string;
|
package/dist/builtin-skills.js
CHANGED
|
@@ -343,12 +343,27 @@ export class BuiltinSkills {
|
|
|
343
343
|
return { items: [...result.values()].sort((a, b) => a.name.localeCompare(b.name)), stale: !!error, ...(error ? { error } : {}) };
|
|
344
344
|
}
|
|
345
345
|
async installed() { await this.initialize(); return Object.values((await this.state()).items).filter(item => item.installed); }
|
|
346
|
+
/** Local records plus already-known update information; never refresh the source. */
|
|
347
|
+
async installedCatalog() {
|
|
348
|
+
const items = await this.installed();
|
|
349
|
+
const cache = await this.readJson('catalog-' + key(JSON.stringify(this.source)) + '.json');
|
|
350
|
+
const versions = new Map((cache?.packages ?? []).map(pkg => [pkg.item.skillId, pkg.item.packageVersion]));
|
|
351
|
+
return { items: items.map(item => ({ ...item, updateAvailable: versions.has(item.skillId) && versions.get(item.skillId) !== item.packageVersion })).sort((a, b) => a.name.localeCompare(b.name)), stale: false };
|
|
352
|
+
}
|
|
346
353
|
versionDirectory(id, version) {
|
|
347
354
|
if (!id || id.length > 1024 || !/^[a-f0-9]{64}$/.test(version))
|
|
348
355
|
throw new ServiceError('INVALID_REQUEST', '技能身份或包版本无效。');
|
|
349
356
|
return 'versions/' + key(id) + '/' + version;
|
|
350
357
|
}
|
|
358
|
+
/** UI reads only the installed schema and manifest, without hashing package contents. */
|
|
359
|
+
async packageMetadata(skillId, packageVersion) {
|
|
360
|
+
const { config, item } = await this.readPackageEntry(skillId, packageVersion, false);
|
|
361
|
+
return { config, item };
|
|
362
|
+
}
|
|
351
363
|
async packageEntry(skillId, packageVersion) {
|
|
364
|
+
return this.readPackageEntry(skillId, packageVersion, false);
|
|
365
|
+
}
|
|
366
|
+
async readPackageEntry(skillId, packageVersion, verifyIntegrity) {
|
|
352
367
|
await this.initialize();
|
|
353
368
|
const current = (await this.state()).items[skillId];
|
|
354
369
|
const version = packageVersion ?? (current?.installed ? current.packageVersion : undefined);
|
|
@@ -359,13 +374,15 @@ export class BuiltinSkills {
|
|
|
359
374
|
if (!manifest || manifest.version !== 1 || manifest.item.skillId !== skillId || manifest.item.packageVersion !== version)
|
|
360
375
|
throw new ServiceError('NOT_FOUND', '技能固定包版本不可用。');
|
|
361
376
|
validatePackageFiles({ directory: manifest.item.directory, files: manifest.files });
|
|
362
|
-
if (
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
377
|
+
if (verifyIntegrity) {
|
|
378
|
+
if (packageDigest(manifest.files) !== version)
|
|
379
|
+
throw new ServiceError('INVALID_REQUEST', '技能包元数据摘要无效。');
|
|
380
|
+
for (const file of manifest.files) {
|
|
381
|
+
const path = await this.checked(relative + '/package/' + file.path);
|
|
382
|
+
const stat = await fs.stat(path);
|
|
383
|
+
if (!stat.isFile() || stat.size !== file.size || gitBlobSha(await fs.readFile(path)) !== file.sha)
|
|
384
|
+
throw new ServiceError('INVALID_REQUEST', '已安装技能包完整性校验失败。');
|
|
385
|
+
}
|
|
369
386
|
}
|
|
370
387
|
const config = parseSkillConfig(await fs.readFile(await this.checked(relative + '/package/config.json'), 'utf8'));
|
|
371
388
|
return { path: await this.checked(relative + '/package/SKILL.md'), config, item: current?.installed && current.packageVersion === version ? current : manifest.item };
|
|
@@ -389,18 +406,18 @@ export class BuiltinSkills {
|
|
|
389
406
|
if (manifest.version !== 1 || manifest.item.skillId !== skillId || manifest.item.packageVersion !== packageVersion)
|
|
390
407
|
throw new ServiceError('INVALID_REQUEST', '技能包元数据无效。');
|
|
391
408
|
validatePackageFiles({ directory: manifest.item.directory, files: manifest.files });
|
|
392
|
-
if (packageDigest(manifest.files) !== packageVersion)
|
|
393
|
-
throw new ServiceError('INVALID_REQUEST', '技能包元数据摘要无效。');
|
|
394
409
|
const file = manifest.files.find(file => file.path === path);
|
|
395
410
|
if (!file)
|
|
396
411
|
throw new ServiceError('NOT_FOUND', '技能包资源不存在。');
|
|
397
412
|
const target = await this.checked(relative + '/package/' + path);
|
|
398
413
|
const stat = await fs.stat(target);
|
|
399
|
-
if (!stat.isFile()
|
|
400
|
-
throw new ServiceError('INVALID_REQUEST', '
|
|
414
|
+
if (!stat.isFile())
|
|
415
|
+
throw new ServiceError('INVALID_REQUEST', '技能资源不是普通文件。');
|
|
416
|
+
if (stat.size > SKILL_LIMITS.fileBytes)
|
|
417
|
+
throw new ServiceError('PAYLOAD_TOO_LARGE', '技能资源超过大小限制。');
|
|
401
418
|
const bytes = await fs.readFile(target);
|
|
402
|
-
if (
|
|
403
|
-
throw new ServiceError('
|
|
419
|
+
if (bytes.length > SKILL_LIMITS.fileBytes)
|
|
420
|
+
throw new ServiceError('PAYLOAD_TOO_LARGE', '技能资源超过大小限制。');
|
|
404
421
|
return bytes;
|
|
405
422
|
}
|
|
406
423
|
const { cache, error } = await this.cached();
|
|
@@ -478,7 +495,7 @@ export class BuiltinSkills {
|
|
|
478
495
|
if (!existing)
|
|
479
496
|
await fs.rename(await this.checked(staging), await this.checked(target));
|
|
480
497
|
else
|
|
481
|
-
await this.
|
|
498
|
+
await this.readPackageEntry(skillId, item.packageVersion, true);
|
|
482
499
|
deadline.check();
|
|
483
500
|
state.items[skillId] = item;
|
|
484
501
|
await this.atomicJson('installed.json', state);
|
package/dist/cli.js
CHANGED
|
File without changes
|
package/dist/graphs.js
CHANGED
|
@@ -188,19 +188,19 @@ export class Graphs {
|
|
|
188
188
|
activeNode(nodeId) { return Boolean(this.db.prepare(`SELECT 1 FROM runs WHERE node_id=? AND status NOT IN (${TERMINAL_RUN_STATUSES.map(() => '?').join(',')}) LIMIT 1`).get(nodeId, ...TERMINAL_RUN_STATUSES)); }
|
|
189
189
|
activeExecutionChainNodeIds(scope) {
|
|
190
190
|
const terminal = TERMINAL_RUN_STATUSES.map(() => '?').join(',');
|
|
191
|
-
const rows = this.db.prepare(`
|
|
192
|
-
SELECT DISTINCT member.node_id
|
|
193
|
-
FROM runs member
|
|
194
|
-
JOIN run_runtime member_rt ON member_rt.run_id=member.id
|
|
195
|
-
WHERE member.graph_id=?
|
|
196
|
-
AND json_extract(member_rt.details,'$.chainBatch') IN (
|
|
197
|
-
SELECT json_extract(active_rt.details,'$.chainBatch')
|
|
198
|
-
FROM runs active
|
|
199
|
-
JOIN run_runtime active_rt ON active_rt.run_id=active.id
|
|
200
|
-
WHERE active.graph_id=?
|
|
201
|
-
AND active.status NOT IN (${terminal})
|
|
202
|
-
AND json_extract(active_rt.details,'$.chainBatch') IS NOT NULL
|
|
203
|
-
)
|
|
191
|
+
const rows = this.db.prepare(`
|
|
192
|
+
SELECT DISTINCT member.node_id
|
|
193
|
+
FROM runs member
|
|
194
|
+
JOIN run_runtime member_rt ON member_rt.run_id=member.id
|
|
195
|
+
WHERE member.graph_id=?
|
|
196
|
+
AND json_extract(member_rt.details,'$.chainBatch') IN (
|
|
197
|
+
SELECT json_extract(active_rt.details,'$.chainBatch')
|
|
198
|
+
FROM runs active
|
|
199
|
+
JOIN run_runtime active_rt ON active_rt.run_id=active.id
|
|
200
|
+
WHERE active.graph_id=?
|
|
201
|
+
AND active.status NOT IN (${terminal})
|
|
202
|
+
AND json_extract(active_rt.details,'$.chainBatch') IS NOT NULL
|
|
203
|
+
)
|
|
204
204
|
`).all(scope.graphId, scope.graphId, ...TERMINAL_RUN_STATUSES);
|
|
205
205
|
return new Set(rows.map(row => String(row['node_id'])));
|
|
206
206
|
}
|
|
@@ -271,10 +271,10 @@ export class Graphs {
|
|
|
271
271
|
const hashes = this.db.prepare('SELECT DISTINCT v.sha256 FROM canvas_resource_versions v JOIN canvas_resources r ON r.id=v.resource_id WHERE r.graph_id=?').all(id).map(row => String(row['sha256']));
|
|
272
272
|
// Keep request hashes as non-replayable tombstones: retries cannot recreate
|
|
273
273
|
// a deleted graph or disclose old snapshots through cached responses.
|
|
274
|
-
this.db.prepare(`UPDATE idempotency SET response='null',invalidated=1 WHERE
|
|
275
|
-
json_extract(response,'$.graphId')=? OR json_extract(response,'$.resource.graphId')=?
|
|
276
|
-
OR json_extract(response,'$.runId') IN (SELECT id FROM runs WHERE graph_id=?)
|
|
277
|
-
OR json_extract(response,'$.id') IN (SELECT id FROM runs WHERE graph_id=?)
|
|
274
|
+
this.db.prepare(`UPDATE idempotency SET response='null',invalidated=1 WHERE
|
|
275
|
+
json_extract(response,'$.graphId')=? OR json_extract(response,'$.resource.graphId')=?
|
|
276
|
+
OR json_extract(response,'$.runId') IN (SELECT id FROM runs WHERE graph_id=?)
|
|
277
|
+
OR json_extract(response,'$.id') IN (SELECT id FROM runs WHERE graph_id=?)
|
|
278
278
|
OR EXISTS(SELECT 1 FROM json_each(response,'$.runIds') j WHERE j.value IN (SELECT id FROM runs WHERE graph_id=?))`).run(id, id, id, id, id);
|
|
279
279
|
this.db.prepare('DELETE FROM asset_references WHERE run_id IN (SELECT id FROM runs WHERE graph_id=?) OR node_id IN (SELECT id FROM nodes WHERE graph_id=?)').run(id, id);
|
|
280
280
|
this.db.prepare('DELETE FROM canvas_resource_references WHERE graph_id=?').run(id);
|
|
@@ -1,36 +1,36 @@
|
|
|
1
1
|
/** Additive schema 6 fragment. No legacy resource conversion or file migration. */
|
|
2
|
-
export const ASSET_BUSINESS_SCHEMA = `
|
|
3
|
-
CREATE TABLE resource_uploads (
|
|
4
|
-
id TEXT PRIMARY KEY, project_id TEXT NOT NULL REFERENCES projects(id),
|
|
5
|
-
name TEXT NOT NULL, mime TEXT NOT NULL, bytes INTEGER NOT NULL CHECK(bytes >= 0),
|
|
6
|
-
expected_sha256 TEXT, received INTEGER NOT NULL DEFAULT 0 CHECK(received >= 0 AND received <= bytes),
|
|
7
|
-
state TEXT NOT NULL DEFAULT 'uploading' CHECK(state IN ('uploading','finished')),
|
|
8
|
-
sha256 TEXT REFERENCES blobs(sha256), created_at INTEGER NOT NULL,
|
|
9
|
-
CHECK((state='finished' AND sha256 IS NOT NULL) OR (state='uploading' AND sha256 IS NULL))
|
|
10
|
-
) STRICT;
|
|
11
|
-
CREATE TABLE resource_upload_chunks (
|
|
12
|
-
upload_id TEXT NOT NULL REFERENCES resource_uploads(id), offset INTEGER NOT NULL CHECK(offset >= 0),
|
|
13
|
-
bytes INTEGER NOT NULL CHECK(bytes > 0), sha256 TEXT NOT NULL, path TEXT NOT NULL UNIQUE,
|
|
14
|
-
PRIMARY KEY(upload_id,offset)
|
|
15
|
-
) STRICT;
|
|
16
|
-
CREATE TABLE resource_blob_files (sha256 TEXT PRIMARY KEY REFERENCES blobs(sha256), path TEXT NOT NULL UNIQUE) STRICT;
|
|
17
|
-
CREATE TABLE resource_file_deletions (path TEXT PRIMARY KEY, created_at INTEGER NOT NULL, completed INTEGER NOT NULL DEFAULT 0 CHECK(completed IN (0,1))) STRICT;
|
|
18
|
-
CREATE TABLE resource_read_leases (id TEXT PRIMARY KEY, sha256 TEXT NOT NULL REFERENCES blobs(sha256)) STRICT;
|
|
19
|
-
CREATE TABLE resource_maintenance_leases (id TEXT PRIMARY KEY, kind TEXT NOT NULL CHECK(kind IN ('backup','drain')), created_at INTEGER NOT NULL) STRICT;
|
|
20
|
-
CREATE TABLE resource_representations (
|
|
21
|
-
sha256 TEXT NOT NULL REFERENCES blobs(sha256), processor TEXT NOT NULL, version INTEGER NOT NULL CHECK(version > 0),
|
|
22
|
-
state TEXT NOT NULL CHECK(state IN ('ready','unsupported','failed')), mime TEXT NOT NULL, text TEXT, reason TEXT,
|
|
23
|
-
PRIMARY KEY(sha256,processor,version)
|
|
24
|
-
) STRICT;
|
|
25
|
-
CREATE TRIGGER resource_blob_file_immutable BEFORE UPDATE ON resource_blob_files BEGIN SELECT RAISE(ABORT,'immutable blob file'); END;
|
|
26
|
-
CREATE TRIGGER resource_asset_ref_reset AFTER UPDATE OF asset_id ON asset_references BEGIN
|
|
27
|
-
UPDATE assets SET unreferenced_since=NULL WHERE id IN (OLD.asset_id,NEW.asset_id);
|
|
28
|
-
END;
|
|
29
|
-
CREATE TRIGGER resource_no_rebind_deleted_asset BEFORE UPDATE OF asset_id,asset_version ON asset_references
|
|
30
|
-
WHEN (NEW.asset_id!=OLD.asset_id OR NEW.asset_version!=OLD.asset_version) AND (SELECT deleted_at FROM assets WHERE id=NEW.asset_id) IS NOT NULL
|
|
31
|
-
BEGIN SELECT RAISE(ABORT,'deleted assets reject new references'); END;
|
|
32
|
-
CREATE TRIGGER resource_output_added AFTER INSERT ON outputs BEGIN UPDATE assets SET unreferenced_since=NULL WHERE id=NEW.asset_id; END;
|
|
33
|
-
CREATE TRIGGER resource_output_removed AFTER DELETE ON outputs BEGIN UPDATE assets SET unreferenced_since=NULL WHERE id=OLD.asset_id; END;
|
|
34
|
-
CREATE TRIGGER resource_output_changed AFTER UPDATE OF asset_id ON outputs BEGIN UPDATE assets SET unreferenced_since=NULL WHERE id IN (OLD.asset_id,NEW.asset_id); END;
|
|
2
|
+
export const ASSET_BUSINESS_SCHEMA = `
|
|
3
|
+
CREATE TABLE resource_uploads (
|
|
4
|
+
id TEXT PRIMARY KEY, project_id TEXT NOT NULL REFERENCES projects(id),
|
|
5
|
+
name TEXT NOT NULL, mime TEXT NOT NULL, bytes INTEGER NOT NULL CHECK(bytes >= 0),
|
|
6
|
+
expected_sha256 TEXT, received INTEGER NOT NULL DEFAULT 0 CHECK(received >= 0 AND received <= bytes),
|
|
7
|
+
state TEXT NOT NULL DEFAULT 'uploading' CHECK(state IN ('uploading','finished')),
|
|
8
|
+
sha256 TEXT REFERENCES blobs(sha256), created_at INTEGER NOT NULL,
|
|
9
|
+
CHECK((state='finished' AND sha256 IS NOT NULL) OR (state='uploading' AND sha256 IS NULL))
|
|
10
|
+
) STRICT;
|
|
11
|
+
CREATE TABLE resource_upload_chunks (
|
|
12
|
+
upload_id TEXT NOT NULL REFERENCES resource_uploads(id), offset INTEGER NOT NULL CHECK(offset >= 0),
|
|
13
|
+
bytes INTEGER NOT NULL CHECK(bytes > 0), sha256 TEXT NOT NULL, path TEXT NOT NULL UNIQUE,
|
|
14
|
+
PRIMARY KEY(upload_id,offset)
|
|
15
|
+
) STRICT;
|
|
16
|
+
CREATE TABLE resource_blob_files (sha256 TEXT PRIMARY KEY REFERENCES blobs(sha256), path TEXT NOT NULL UNIQUE) STRICT;
|
|
17
|
+
CREATE TABLE resource_file_deletions (path TEXT PRIMARY KEY, created_at INTEGER NOT NULL, completed INTEGER NOT NULL DEFAULT 0 CHECK(completed IN (0,1))) STRICT;
|
|
18
|
+
CREATE TABLE resource_read_leases (id TEXT PRIMARY KEY, sha256 TEXT NOT NULL REFERENCES blobs(sha256)) STRICT;
|
|
19
|
+
CREATE TABLE resource_maintenance_leases (id TEXT PRIMARY KEY, kind TEXT NOT NULL CHECK(kind IN ('backup','drain')), created_at INTEGER NOT NULL) STRICT;
|
|
20
|
+
CREATE TABLE resource_representations (
|
|
21
|
+
sha256 TEXT NOT NULL REFERENCES blobs(sha256), processor TEXT NOT NULL, version INTEGER NOT NULL CHECK(version > 0),
|
|
22
|
+
state TEXT NOT NULL CHECK(state IN ('ready','unsupported','failed')), mime TEXT NOT NULL, text TEXT, reason TEXT,
|
|
23
|
+
PRIMARY KEY(sha256,processor,version)
|
|
24
|
+
) STRICT;
|
|
25
|
+
CREATE TRIGGER resource_blob_file_immutable BEFORE UPDATE ON resource_blob_files BEGIN SELECT RAISE(ABORT,'immutable blob file'); END;
|
|
26
|
+
CREATE TRIGGER resource_asset_ref_reset AFTER UPDATE OF asset_id ON asset_references BEGIN
|
|
27
|
+
UPDATE assets SET unreferenced_since=NULL WHERE id IN (OLD.asset_id,NEW.asset_id);
|
|
28
|
+
END;
|
|
29
|
+
CREATE TRIGGER resource_no_rebind_deleted_asset BEFORE UPDATE OF asset_id,asset_version ON asset_references
|
|
30
|
+
WHEN (NEW.asset_id!=OLD.asset_id OR NEW.asset_version!=OLD.asset_version) AND (SELECT deleted_at FROM assets WHERE id=NEW.asset_id) IS NOT NULL
|
|
31
|
+
BEGIN SELECT RAISE(ABORT,'deleted assets reject new references'); END;
|
|
32
|
+
CREATE TRIGGER resource_output_added AFTER INSERT ON outputs BEGIN UPDATE assets SET unreferenced_since=NULL WHERE id=NEW.asset_id; END;
|
|
33
|
+
CREATE TRIGGER resource_output_removed AFTER DELETE ON outputs BEGIN UPDATE assets SET unreferenced_since=NULL WHERE id=OLD.asset_id; END;
|
|
34
|
+
CREATE TRIGGER resource_output_changed AFTER UPDATE OF asset_id ON outputs BEGIN UPDATE assets SET unreferenced_since=NULL WHERE id IN (OLD.asset_id,NEW.asset_id); END;
|
|
35
35
|
`;
|
|
36
36
|
//# sourceMappingURL=asset-business-schema.js.map
|
|
@@ -6,33 +6,33 @@
|
|
|
6
6
|
* explicit recovery metadata; this migration cannot infer those mutations.
|
|
7
7
|
* Ambiguous preexisting read_only rows stay original, never auto-unlocked.
|
|
8
8
|
*/
|
|
9
|
-
export const PLUGIN_SCHEMA = `
|
|
10
|
-
ALTER TABLE nodes ADD COLUMN read_only_reason TEXT NOT NULL DEFAULT 'none'
|
|
11
|
-
CHECK(read_only_reason IN ('none','missing_plugin','original'));
|
|
12
|
-
UPDATE nodes SET read_only_reason='original' WHERE read_only=1;
|
|
13
|
-
CREATE TABLE node_version_schemas (
|
|
14
|
-
node_id TEXT NOT NULL,
|
|
15
|
-
version INTEGER NOT NULL CHECK(version > 0),
|
|
16
|
-
schema_version INTEGER NOT NULL CHECK(schema_version > 0),
|
|
17
|
-
PRIMARY KEY(node_id,version),
|
|
18
|
-
FOREIGN KEY(node_id,version) REFERENCES node_versions(node_id,version) ON DELETE CASCADE
|
|
19
|
-
) STRICT;
|
|
20
|
-
INSERT INTO node_version_schemas(node_id,version,schema_version)
|
|
21
|
-
SELECT v.node_id,v.version,n.schema_version FROM node_versions v JOIN nodes n ON n.id=v.node_id;
|
|
22
|
-
CREATE TRIGGER node_version_schema_capture AFTER INSERT ON node_versions BEGIN
|
|
23
|
-
INSERT INTO node_version_schemas(node_id,version,schema_version)
|
|
24
|
-
SELECT NEW.node_id,NEW.version,schema_version FROM nodes WHERE id=NEW.node_id;
|
|
25
|
-
END;
|
|
26
|
-
CREATE TRIGGER node_version_schema_immutable BEFORE UPDATE ON node_version_schemas BEGIN
|
|
27
|
-
SELECT RAISE(ABORT,'immutable node version schema');
|
|
28
|
-
END;
|
|
29
|
-
CREATE TRIGGER node_version_schema_delete_guard BEFORE DELETE ON node_version_schemas
|
|
30
|
-
WHEN EXISTS(SELECT 1 FROM node_versions WHERE node_id=OLD.node_id AND version=OLD.version) BEGIN
|
|
31
|
-
SELECT RAISE(ABORT,'retained node version requires schema history');
|
|
32
|
-
END;
|
|
33
|
-
CREATE TRIGGER node_current_schema_guard BEFORE UPDATE OF schema_version,current_version ON nodes
|
|
34
|
-
WHEN EXISTS(SELECT 1 FROM node_version_schemas s WHERE s.node_id=NEW.id AND s.version=NEW.current_version AND s.schema_version!=NEW.schema_version) BEGIN
|
|
35
|
-
SELECT RAISE(ABORT,'current node schema does not match version history');
|
|
36
|
-
END;
|
|
9
|
+
export const PLUGIN_SCHEMA = `
|
|
10
|
+
ALTER TABLE nodes ADD COLUMN read_only_reason TEXT NOT NULL DEFAULT 'none'
|
|
11
|
+
CHECK(read_only_reason IN ('none','missing_plugin','original'));
|
|
12
|
+
UPDATE nodes SET read_only_reason='original' WHERE read_only=1;
|
|
13
|
+
CREATE TABLE node_version_schemas (
|
|
14
|
+
node_id TEXT NOT NULL,
|
|
15
|
+
version INTEGER NOT NULL CHECK(version > 0),
|
|
16
|
+
schema_version INTEGER NOT NULL CHECK(schema_version > 0),
|
|
17
|
+
PRIMARY KEY(node_id,version),
|
|
18
|
+
FOREIGN KEY(node_id,version) REFERENCES node_versions(node_id,version) ON DELETE CASCADE
|
|
19
|
+
) STRICT;
|
|
20
|
+
INSERT INTO node_version_schemas(node_id,version,schema_version)
|
|
21
|
+
SELECT v.node_id,v.version,n.schema_version FROM node_versions v JOIN nodes n ON n.id=v.node_id;
|
|
22
|
+
CREATE TRIGGER node_version_schema_capture AFTER INSERT ON node_versions BEGIN
|
|
23
|
+
INSERT INTO node_version_schemas(node_id,version,schema_version)
|
|
24
|
+
SELECT NEW.node_id,NEW.version,schema_version FROM nodes WHERE id=NEW.node_id;
|
|
25
|
+
END;
|
|
26
|
+
CREATE TRIGGER node_version_schema_immutable BEFORE UPDATE ON node_version_schemas BEGIN
|
|
27
|
+
SELECT RAISE(ABORT,'immutable node version schema');
|
|
28
|
+
END;
|
|
29
|
+
CREATE TRIGGER node_version_schema_delete_guard BEFORE DELETE ON node_version_schemas
|
|
30
|
+
WHEN EXISTS(SELECT 1 FROM node_versions WHERE node_id=OLD.node_id AND version=OLD.version) BEGIN
|
|
31
|
+
SELECT RAISE(ABORT,'retained node version requires schema history');
|
|
32
|
+
END;
|
|
33
|
+
CREATE TRIGGER node_current_schema_guard BEFORE UPDATE OF schema_version,current_version ON nodes
|
|
34
|
+
WHEN EXISTS(SELECT 1 FROM node_version_schemas s WHERE s.node_id=NEW.id AND s.version=NEW.current_version AND s.schema_version!=NEW.schema_version) BEGIN
|
|
35
|
+
SELECT RAISE(ABORT,'current node schema does not match version history');
|
|
36
|
+
END;
|
|
37
37
|
`;
|
|
38
38
|
//# sourceMappingURL=plugin-schema.js.map
|
|
@@ -1,44 +1,44 @@
|
|
|
1
1
|
/** Additive only: legacy asset references, outputs and snapshot JSON are preserved. */
|
|
2
|
-
export const RESOURCE_SCHEMA = `
|
|
3
|
-
ALTER TABLE assets ADD COLUMN name TEXT NOT NULL DEFAULT '';
|
|
4
|
-
ALTER TABLE assets ADD COLUMN current_version INTEGER NOT NULL DEFAULT 0 CHECK(current_version >= 0);
|
|
5
|
-
UPDATE assets SET current_version=COALESCE((SELECT MAX(version) FROM asset_versions WHERE asset_id=assets.id),0);
|
|
6
|
-
CREATE INDEX assets_project_name ON assets(project_id,name);
|
|
7
|
-
CREATE UNIQUE INDEX runs_id_graph ON runs(id,graph_id);
|
|
8
|
-
CREATE TABLE canvas_resources (
|
|
9
|
-
id TEXT PRIMARY KEY, project_id TEXT NOT NULL, graph_id TEXT NOT NULL, name TEXT NOT NULL,
|
|
10
|
-
current_version INTEGER NOT NULL CHECK(current_version > 0), UNIQUE(id,graph_id),
|
|
11
|
-
FOREIGN KEY(graph_id,project_id) REFERENCES graphs(id,project_id),
|
|
12
|
-
FOREIGN KEY(id,current_version) REFERENCES canvas_resource_versions(resource_id,version) DEFERRABLE INITIALLY DEFERRED
|
|
13
|
-
) STRICT;
|
|
14
|
-
CREATE TABLE canvas_resource_versions (
|
|
15
|
-
resource_id TEXT NOT NULL REFERENCES canvas_resources(id) DEFERRABLE INITIALLY DEFERRED,
|
|
16
|
-
version INTEGER NOT NULL CHECK(version > 0), sha256 TEXT NOT NULL REFERENCES blobs(sha256), mime TEXT NOT NULL,
|
|
17
|
-
representation_version INTEGER, PRIMARY KEY(resource_id,version)
|
|
18
|
-
) STRICT;
|
|
19
|
-
CREATE TRIGGER canvas_version_immutable BEFORE UPDATE ON canvas_resource_versions BEGIN SELECT RAISE(ABORT,'immutable canvas resource version'); END;
|
|
20
|
-
CREATE TRIGGER canvas_binding_immutable BEFORE UPDATE OF project_id,graph_id ON canvas_resources WHEN NEW.project_id != OLD.project_id OR NEW.graph_id != OLD.graph_id BEGIN SELECT RAISE(ABORT,'canvas resource binding immutable'); END;
|
|
21
|
-
CREATE TABLE canvas_resource_references (
|
|
22
|
-
id TEXT PRIMARY KEY, resource_id TEXT NOT NULL, resource_version INTEGER NOT NULL, graph_id TEXT NOT NULL,
|
|
23
|
-
owner_kind TEXT NOT NULL CHECK(owner_kind IN ('graph','node','snapshot','history')),
|
|
24
|
-
node_id TEXT, run_id TEXT,
|
|
25
|
-
CHECK((owner_kind='graph' AND node_id IS NULL AND run_id IS NULL) OR (owner_kind='node' AND node_id IS NOT NULL AND run_id IS NULL) OR (owner_kind IN ('snapshot','history') AND node_id IS NULL AND run_id IS NOT NULL)),
|
|
26
|
-
FOREIGN KEY(resource_id,resource_version) REFERENCES canvas_resource_versions(resource_id,version),
|
|
27
|
-
FOREIGN KEY(resource_id,graph_id) REFERENCES canvas_resources(id,graph_id),
|
|
28
|
-
FOREIGN KEY(node_id,graph_id) REFERENCES nodes(id,graph_id),
|
|
29
|
-
FOREIGN KEY(run_id,graph_id) REFERENCES runs(id,graph_id)
|
|
30
|
-
) STRICT;
|
|
31
|
-
CREATE INDEX canvas_refs_resource ON canvas_resource_references(resource_id,resource_version);
|
|
32
|
-
CREATE TRIGGER frozen_resource_reference_immutable BEFORE UPDATE ON canvas_resource_references WHEN OLD.owner_kind IN ('snapshot','history') BEGIN SELECT RAISE(ABORT,'frozen resource reference'); END;
|
|
33
|
-
CREATE TABLE canvas_outputs (
|
|
34
|
-
run_id TEXT NOT NULL, graph_id TEXT NOT NULL, output_key TEXT NOT NULL, resource_id TEXT NOT NULL, resource_version INTEGER NOT NULL,
|
|
35
|
-
PRIMARY KEY(run_id,output_key), FOREIGN KEY(resource_id,resource_version) REFERENCES canvas_resource_versions(resource_id,version),
|
|
36
|
-
FOREIGN KEY(resource_id,graph_id) REFERENCES canvas_resources(id,graph_id), FOREIGN KEY(run_id,graph_id) REFERENCES runs(id,graph_id)
|
|
37
|
-
) STRICT;
|
|
38
|
-
CREATE TRIGGER no_new_canvas_asset_link BEFORE INSERT ON asset_references WHEN NEW.node_id IS NOT NULL BEGIN SELECT RAISE(ABORT,'canvas nodes must use independent canvas resources'); END;
|
|
39
|
-
CREATE TRIGGER no_rebind_canvas_asset_link BEFORE UPDATE OF asset_id,asset_version,node_id ON asset_references WHEN NEW.node_id IS NOT NULL BEGIN SELECT RAISE(ABORT,'legacy canvas asset references cannot be rebound'); END;
|
|
40
|
-
CREATE TRIGGER no_new_deleted_asset_reference BEFORE INSERT ON asset_references WHEN (SELECT deleted_at FROM assets WHERE id=NEW.asset_id) IS NOT NULL BEGIN SELECT RAISE(ABORT,'deleted assets reject new references'); END;
|
|
41
|
-
CREATE TRIGGER asset_reference_added AFTER INSERT ON asset_references BEGIN UPDATE assets SET unreferenced_since=NULL WHERE id=NEW.asset_id; END;
|
|
42
|
-
CREATE TRIGGER asset_reference_removed AFTER DELETE ON asset_references BEGIN UPDATE assets SET unreferenced_since=NULL WHERE id=OLD.asset_id; END;
|
|
2
|
+
export const RESOURCE_SCHEMA = `
|
|
3
|
+
ALTER TABLE assets ADD COLUMN name TEXT NOT NULL DEFAULT '';
|
|
4
|
+
ALTER TABLE assets ADD COLUMN current_version INTEGER NOT NULL DEFAULT 0 CHECK(current_version >= 0);
|
|
5
|
+
UPDATE assets SET current_version=COALESCE((SELECT MAX(version) FROM asset_versions WHERE asset_id=assets.id),0);
|
|
6
|
+
CREATE INDEX assets_project_name ON assets(project_id,name);
|
|
7
|
+
CREATE UNIQUE INDEX runs_id_graph ON runs(id,graph_id);
|
|
8
|
+
CREATE TABLE canvas_resources (
|
|
9
|
+
id TEXT PRIMARY KEY, project_id TEXT NOT NULL, graph_id TEXT NOT NULL, name TEXT NOT NULL,
|
|
10
|
+
current_version INTEGER NOT NULL CHECK(current_version > 0), UNIQUE(id,graph_id),
|
|
11
|
+
FOREIGN KEY(graph_id,project_id) REFERENCES graphs(id,project_id),
|
|
12
|
+
FOREIGN KEY(id,current_version) REFERENCES canvas_resource_versions(resource_id,version) DEFERRABLE INITIALLY DEFERRED
|
|
13
|
+
) STRICT;
|
|
14
|
+
CREATE TABLE canvas_resource_versions (
|
|
15
|
+
resource_id TEXT NOT NULL REFERENCES canvas_resources(id) DEFERRABLE INITIALLY DEFERRED,
|
|
16
|
+
version INTEGER NOT NULL CHECK(version > 0), sha256 TEXT NOT NULL REFERENCES blobs(sha256), mime TEXT NOT NULL,
|
|
17
|
+
representation_version INTEGER, PRIMARY KEY(resource_id,version)
|
|
18
|
+
) STRICT;
|
|
19
|
+
CREATE TRIGGER canvas_version_immutable BEFORE UPDATE ON canvas_resource_versions BEGIN SELECT RAISE(ABORT,'immutable canvas resource version'); END;
|
|
20
|
+
CREATE TRIGGER canvas_binding_immutable BEFORE UPDATE OF project_id,graph_id ON canvas_resources WHEN NEW.project_id != OLD.project_id OR NEW.graph_id != OLD.graph_id BEGIN SELECT RAISE(ABORT,'canvas resource binding immutable'); END;
|
|
21
|
+
CREATE TABLE canvas_resource_references (
|
|
22
|
+
id TEXT PRIMARY KEY, resource_id TEXT NOT NULL, resource_version INTEGER NOT NULL, graph_id TEXT NOT NULL,
|
|
23
|
+
owner_kind TEXT NOT NULL CHECK(owner_kind IN ('graph','node','snapshot','history')),
|
|
24
|
+
node_id TEXT, run_id TEXT,
|
|
25
|
+
CHECK((owner_kind='graph' AND node_id IS NULL AND run_id IS NULL) OR (owner_kind='node' AND node_id IS NOT NULL AND run_id IS NULL) OR (owner_kind IN ('snapshot','history') AND node_id IS NULL AND run_id IS NOT NULL)),
|
|
26
|
+
FOREIGN KEY(resource_id,resource_version) REFERENCES canvas_resource_versions(resource_id,version),
|
|
27
|
+
FOREIGN KEY(resource_id,graph_id) REFERENCES canvas_resources(id,graph_id),
|
|
28
|
+
FOREIGN KEY(node_id,graph_id) REFERENCES nodes(id,graph_id),
|
|
29
|
+
FOREIGN KEY(run_id,graph_id) REFERENCES runs(id,graph_id)
|
|
30
|
+
) STRICT;
|
|
31
|
+
CREATE INDEX canvas_refs_resource ON canvas_resource_references(resource_id,resource_version);
|
|
32
|
+
CREATE TRIGGER frozen_resource_reference_immutable BEFORE UPDATE ON canvas_resource_references WHEN OLD.owner_kind IN ('snapshot','history') BEGIN SELECT RAISE(ABORT,'frozen resource reference'); END;
|
|
33
|
+
CREATE TABLE canvas_outputs (
|
|
34
|
+
run_id TEXT NOT NULL, graph_id TEXT NOT NULL, output_key TEXT NOT NULL, resource_id TEXT NOT NULL, resource_version INTEGER NOT NULL,
|
|
35
|
+
PRIMARY KEY(run_id,output_key), FOREIGN KEY(resource_id,resource_version) REFERENCES canvas_resource_versions(resource_id,version),
|
|
36
|
+
FOREIGN KEY(resource_id,graph_id) REFERENCES canvas_resources(id,graph_id), FOREIGN KEY(run_id,graph_id) REFERENCES runs(id,graph_id)
|
|
37
|
+
) STRICT;
|
|
38
|
+
CREATE TRIGGER no_new_canvas_asset_link BEFORE INSERT ON asset_references WHEN NEW.node_id IS NOT NULL BEGIN SELECT RAISE(ABORT,'canvas nodes must use independent canvas resources'); END;
|
|
39
|
+
CREATE TRIGGER no_rebind_canvas_asset_link BEFORE UPDATE OF asset_id,asset_version,node_id ON asset_references WHEN NEW.node_id IS NOT NULL BEGIN SELECT RAISE(ABORT,'legacy canvas asset references cannot be rebound'); END;
|
|
40
|
+
CREATE TRIGGER no_new_deleted_asset_reference BEFORE INSERT ON asset_references WHEN (SELECT deleted_at FROM assets WHERE id=NEW.asset_id) IS NOT NULL BEGIN SELECT RAISE(ABORT,'deleted assets reject new references'); END;
|
|
41
|
+
CREATE TRIGGER asset_reference_added AFTER INSERT ON asset_references BEGIN UPDATE assets SET unreferenced_since=NULL WHERE id=NEW.asset_id; END;
|
|
42
|
+
CREATE TRIGGER asset_reference_removed AFTER DELETE ON asset_references BEGIN UPDATE assets SET unreferenced_since=NULL WHERE id=OLD.asset_id; END;
|
|
43
43
|
`;
|
|
44
44
|
//# sourceMappingURL=resource-schema.js.map
|