openworkgraph 0.2.12
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 -0
- package/dist/api-image-agent.d.ts +32 -0
- package/dist/api-image-agent.js +132 -0
- package/dist/api-image-output.d.ts +8 -0
- package/dist/api-image-output.js +41 -0
- package/dist/api.d.ts +35 -0
- package/dist/api.js +153 -0
- package/dist/auth.d.ts +58 -0
- package/dist/auth.js +273 -0
- package/dist/backend/adapter.d.ts +35 -0
- package/dist/backend/adapter.js +439 -0
- package/dist/backend/capabilities.d.ts +23 -0
- package/dist/backend/capabilities.js +75 -0
- package/dist/backend/diagnostics.d.ts +9 -0
- package/dist/backend/diagnostics.js +80 -0
- package/dist/backend/index.d.ts +6 -0
- package/dist/backend/index.js +7 -0
- package/dist/backend/models.d.ts +15 -0
- package/dist/backend/models.js +43 -0
- package/dist/backend/paths.d.ts +6 -0
- package/dist/backend/paths.js +69 -0
- package/dist/backend/stdio.d.ts +66 -0
- package/dist/backend/stdio.js +172 -0
- package/dist/backend/types.d.ts +90 -0
- package/dist/backend/types.js +11 -0
- package/dist/backup-api.d.ts +10 -0
- package/dist/backup-api.js +40 -0
- package/dist/blob-store.d.ts +40 -0
- package/dist/blob-store.js +218 -0
- package/dist/cli-help.d.ts +1 -0
- package/dist/cli-help.js +63 -0
- package/dist/cli-output.d.ts +4 -0
- package/dist/cli-output.js +130 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +284 -0
- package/dist/client-pairing.d.ts +8 -0
- package/dist/client-pairing.js +30 -0
- package/dist/directories.d.ts +14 -0
- package/dist/directories.js +34 -0
- package/dist/errors.d.ts +12 -0
- package/dist/errors.js +7 -0
- package/dist/events.d.ts +22 -0
- package/dist/events.js +155 -0
- package/dist/execution-chains.d.ts +24 -0
- package/dist/execution-chains.js +231 -0
- package/dist/execution-settings.d.ts +10 -0
- package/dist/execution-settings.js +38 -0
- package/dist/graph-document-history.d.ts +43 -0
- package/dist/graph-document-history.js +275 -0
- package/dist/graph-files.d.ts +14 -0
- package/dist/graph-files.js +59 -0
- package/dist/graph-transfer.d.ts +53 -0
- package/dist/graph-transfer.js +501 -0
- package/dist/graphs.d.ts +68 -0
- package/dist/graphs.js +771 -0
- package/dist/history.d.ts +20 -0
- package/dist/history.js +61 -0
- package/dist/http.d.ts +2 -0
- package/dist/http.js +42 -0
- package/dist/image-credentials.d.ts +11 -0
- package/dist/image-credentials.js +130 -0
- package/dist/image-providers.d.ts +51 -0
- package/dist/image-providers.js +271 -0
- package/dist/input-materialization.d.ts +17 -0
- package/dist/input-materialization.js +95 -0
- package/dist/inputs.d.ts +74 -0
- package/dist/inputs.js +301 -0
- package/dist/local-control.d.ts +13 -0
- package/dist/local-control.js +37 -0
- package/dist/network.d.ts +11 -0
- package/dist/network.js +48 -0
- package/dist/openai-image-driver.d.ts +42 -0
- package/dist/openai-image-driver.js +368 -0
- package/dist/operations/backups.d.ts +40 -0
- package/dist/operations/backups.js +391 -0
- package/dist/operations/capacity.d.ts +7 -0
- package/dist/operations/capacity.js +35 -0
- package/dist/operations/cli-context.d.ts +7 -0
- package/dist/operations/cli-context.js +58 -0
- package/dist/operations/daemon.d.ts +36 -0
- package/dist/operations/daemon.js +159 -0
- package/dist/operations/files.d.ts +12 -0
- package/dist/operations/files.js +84 -0
- package/dist/operations/lifecycle.d.ts +28 -0
- package/dist/operations/lifecycle.js +155 -0
- package/dist/operations/local-admin.d.ts +6 -0
- package/dist/operations/local-admin.js +38 -0
- package/dist/operations/logs.d.ts +16 -0
- package/dist/operations/logs.js +37 -0
- package/dist/operations/plugins.d.ts +10 -0
- package/dist/operations/plugins.js +11 -0
- package/dist/operations/snapshot-worker.d.ts +1 -0
- package/dist/operations/snapshot-worker.js +18 -0
- package/dist/operations/snapshot.d.ts +1 -0
- package/dist/operations/snapshot.js +14 -0
- package/dist/operations/update.d.ts +26 -0
- package/dist/operations/update.js +216 -0
- package/dist/persistence/asset-business-schema.d.ts +2 -0
- package/dist/persistence/asset-business-schema.js +36 -0
- package/dist/persistence/database.d.ts +7 -0
- package/dist/persistence/database.js +91 -0
- package/dist/persistence/plugin-schema.d.ts +9 -0
- package/dist/persistence/plugin-schema.js +38 -0
- package/dist/persistence/repositories.d.ts +20 -0
- package/dist/persistence/repositories.js +106 -0
- package/dist/persistence/resource-schema.d.ts +2 -0
- package/dist/persistence/resource-schema.js +44 -0
- package/dist/persistence/schema.d.ts +6 -0
- package/dist/persistence/schema.js +236 -0
- package/dist/persistence/workflow-schema.d.ts +2 -0
- package/dist/persistence/workflow-schema.js +19 -0
- package/dist/plugin-api.d.ts +10 -0
- package/dist/plugin-api.js +40 -0
- package/dist/plugin-management.d.ts +36 -0
- package/dist/plugin-management.js +112 -0
- package/dist/plugins.d.ts +55 -0
- package/dist/plugins.js +239 -0
- package/dist/project-discovery.d.ts +3 -0
- package/dist/project-discovery.js +57 -0
- package/dist/project-files-api.d.ts +16 -0
- package/dist/project-files-api.js +243 -0
- package/dist/project-files.d.ts +97 -0
- package/dist/project-files.js +557 -0
- package/dist/project-lock.d.ts +4 -0
- package/dist/project-lock.js +35 -0
- package/dist/project-probe-worker.d.ts +1 -0
- package/dist/project-probe-worker.js +17 -0
- package/dist/project-probe.d.ts +22 -0
- package/dist/project-probe.js +67 -0
- package/dist/projects.d.ts +29 -0
- package/dist/projects.js +219 -0
- package/dist/publication.d.ts +31 -0
- package/dist/publication.js +642 -0
- package/dist/resource-api.d.ts +15 -0
- package/dist/resource-api.js +256 -0
- package/dist/resources-orphans.d.ts +41 -0
- package/dist/resources-orphans.js +235 -0
- package/dist/resources.d.ts +246 -0
- package/dist/resources.js +714 -0
- package/dist/run-api.d.ts +10 -0
- package/dist/run-api.js +308 -0
- package/dist/run-context.d.ts +54 -0
- package/dist/run-context.js +137 -0
- package/dist/runs.d.ts +82 -0
- package/dist/runs.js +506 -0
- package/dist/runtime-backend.d.ts +24 -0
- package/dist/runtime-backend.js +214 -0
- package/dist/runtime.d.ts +97 -0
- package/dist/runtime.js +553 -0
- package/dist/scheduler.d.ts +85 -0
- package/dist/scheduler.js +344 -0
- package/dist/service.d.ts +29 -0
- package/dist/service.js +515 -0
- package/dist/skills-api.d.ts +18 -0
- package/dist/skills-api.js +120 -0
- package/dist/storage-api.d.ts +21 -0
- package/dist/storage-api.js +92 -0
- package/dist/transfer-api.d.ts +10 -0
- package/dist/transfer-api.js +60 -0
- package/dist/vendor/protocol/contracts.typecheck.d.ts +1 -0
- package/dist/vendor/protocol/contracts.typecheck.js +35 -0
- package/dist/vendor/protocol/execution-chain.d.ts +6 -0
- package/dist/vendor/protocol/execution-chain.js +26 -0
- package/dist/vendor/protocol/graph-binary.d.ts +15 -0
- package/dist/vendor/protocol/graph-binary.js +121 -0
- package/dist/vendor/protocol/graph-title.d.ts +2 -0
- package/dist/vendor/protocol/graph-title.js +23 -0
- package/dist/vendor/protocol/index.d.ts +617 -0
- package/dist/vendor/protocol/index.js +20 -0
- package/dist/vendor/protocol/pairing.d.ts +29 -0
- package/dist/vendor/protocol/pairing.js +71 -0
- package/dist/vendor/protocol/preview.d.ts +2 -0
- package/dist/vendor/protocol/preview.js +9 -0
- package/dist/vendor/protocol/resource-mime.d.ts +2 -0
- package/dist/vendor/protocol/resource-mime.js +35 -0
- package/dist/video-thumbnail.d.ts +2 -0
- package/dist/video-thumbnail.js +34 -0
- package/package.json +9 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { RESOURCE_SCHEMA } from './resource-schema.js';
|
|
2
|
+
import { WORKFLOW_SCHEMA } from './workflow-schema.js';
|
|
3
|
+
import { ASSET_BUSINESS_SCHEMA } from './asset-business-schema.js';
|
|
4
|
+
import { PLUGIN_SCHEMA } from './plugin-schema.js';
|
|
5
|
+
export const MIGRATIONS = [{ version: 1, sql: `
|
|
6
|
+
CREATE TABLE identity (singleton INTEGER PRIMARY KEY CHECK(singleton = 1), service_id TEXT NOT NULL UNIQUE, created_at TEXT NOT NULL) STRICT;
|
|
7
|
+
CREATE TABLE settings (key TEXT PRIMARY KEY, value TEXT NOT NULL CHECK(json_valid(value))) STRICT;
|
|
8
|
+
CREATE TABLE sessions (id TEXT PRIMARY KEY, token_hash TEXT NOT NULL UNIQUE, browser_name TEXT NOT NULL, paired_at INTEGER NOT NULL, last_used_at INTEGER NOT NULL, revoked_at INTEGER) STRICT;
|
|
9
|
+
CREATE TABLE pairing_codes (code_hash TEXT PRIMARY KEY, expires_at INTEGER NOT NULL, consumed_at INTEGER) STRICT;
|
|
10
|
+
CREATE TABLE projects (id TEXT PRIMARY KEY, canonical_path TEXT NOT NULL UNIQUE, name TEXT NOT NULL, state TEXT NOT NULL CHECK(state IN ('active','inactive')), is_git INTEGER NOT NULL CHECK(is_git IN (0,1))) STRICT;
|
|
11
|
+
CREATE TABLE graphs (id TEXT PRIMARY KEY, project_id TEXT NOT NULL REFERENCES projects(id), title TEXT NOT NULL, archived INTEGER NOT NULL DEFAULT 0 CHECK(archived IN (0,1)), execution_revision INTEGER NOT NULL DEFAULT 0 CHECK(execution_revision >= 0), layout_revision INTEGER NOT NULL DEFAULT 0 CHECK(layout_revision >= 0), UNIQUE(id,project_id)) STRICT;
|
|
12
|
+
CREATE TRIGGER graph_binding_immutable BEFORE UPDATE OF project_id ON graphs WHEN NEW.project_id != OLD.project_id BEGIN SELECT RAISE(ABORT,'graph binding immutable'); END;
|
|
13
|
+
CREATE TABLE nodes (id TEXT PRIMARY KEY, graph_id TEXT NOT NULL REFERENCES graphs(id), type TEXT NOT NULL, schema_version INTEGER NOT NULL CHECK(schema_version > 0), current_version INTEGER NOT NULL CHECK(current_version > 0), x REAL NOT NULL, y REAL NOT NULL, read_only INTEGER NOT NULL DEFAULT 0 CHECK(read_only IN (0,1)), UNIQUE(id,graph_id), FOREIGN KEY(id,current_version) REFERENCES node_versions(node_id,version) DEFERRABLE INITIALLY DEFERRED) STRICT;
|
|
14
|
+
CREATE TABLE node_versions (node_id TEXT NOT NULL REFERENCES nodes(id) DEFERRABLE INITIALLY DEFERRED, version INTEGER NOT NULL CHECK(version > 0), content TEXT NOT NULL CHECK(json_valid(content)), PRIMARY KEY(node_id,version)) STRICT;
|
|
15
|
+
CREATE TRIGGER node_version_immutable BEFORE UPDATE ON node_versions BEGIN SELECT RAISE(ABORT,'immutable node version'); END;
|
|
16
|
+
CREATE TABLE edges (id TEXT PRIMARY KEY, graph_id TEXT NOT NULL REFERENCES graphs(id), source_id TEXT NOT NULL, target_id TEXT NOT NULL, kind TEXT NOT NULL CHECK(kind IN ('reference','delivery')), CHECK(source_id != target_id), UNIQUE(graph_id,source_id,target_id), FOREIGN KEY(source_id,graph_id) REFERENCES nodes(id,graph_id), FOREIGN KEY(target_id,graph_id) REFERENCES nodes(id,graph_id)) STRICT;
|
|
17
|
+
CREATE TABLE blobs (sha256 TEXT PRIMARY KEY CHECK(length(sha256) = 64), bytes INTEGER NOT NULL CHECK(bytes >= 0)) STRICT;
|
|
18
|
+
CREATE TABLE assets (id TEXT PRIMARY KEY, project_id TEXT NOT NULL REFERENCES projects(id), shared INTEGER NOT NULL DEFAULT 0 CHECK(shared IN (0,1)), deleted_at INTEGER, unreferenced_since INTEGER) STRICT;
|
|
19
|
+
CREATE TABLE asset_versions (asset_id TEXT NOT NULL REFERENCES assets(id), version INTEGER NOT NULL CHECK(version > 0), sha256 TEXT NOT NULL REFERENCES blobs(sha256), mime TEXT NOT NULL, state TEXT NOT NULL CHECK(state IN ('processing','ready','failed')), representation_version INTEGER, PRIMARY KEY(asset_id,version)) STRICT;
|
|
20
|
+
CREATE TRIGGER asset_version_content_immutable BEFORE UPDATE OF sha256,mime,asset_id,version ON asset_versions BEGIN SELECT RAISE(ABORT,'immutable asset version'); END;
|
|
21
|
+
CREATE TABLE runs (sequence INTEGER PRIMARY KEY AUTOINCREMENT, id TEXT NOT NULL UNIQUE, project_id TEXT NOT NULL, graph_id TEXT NOT NULL, node_id TEXT NOT NULL, kind TEXT NOT NULL CHECK(kind IN ('execution','text_generation','image_generation')), status TEXT NOT NULL CHECK(status IN ('accepted','queued','preparing','running','waiting_answer','waiting_approval','agent_completed','finalizing','cancelling','reconciling','paused_restore','succeeded','failed','cancelled','interrupted')), input_digest TEXT NOT NULL, created_at TEXT NOT NULL, history_state TEXT NOT NULL DEFAULT 'retained' CHECK(history_state IN ('retained','cleared')), FOREIGN KEY(graph_id,project_id) REFERENCES graphs(id,project_id), FOREIGN KEY(node_id,graph_id) REFERENCES nodes(id,graph_id)) STRICT;
|
|
22
|
+
CREATE UNIQUE INDEX one_active_run_per_node ON runs(node_id) WHERE status NOT IN ('succeeded','failed','cancelled','interrupted');
|
|
23
|
+
CREATE TABLE snapshots (run_id TEXT PRIMARY KEY REFERENCES runs(id), input_digest TEXT NOT NULL, payload TEXT NOT NULL CHECK(json_valid(payload))) STRICT;
|
|
24
|
+
CREATE TRIGGER snapshot_immutable BEFORE UPDATE ON snapshots BEGIN SELECT RAISE(ABORT,'immutable input snapshot'); END;
|
|
25
|
+
CREATE TABLE outputs (run_id TEXT NOT NULL REFERENCES runs(id), output_key TEXT NOT NULL, asset_id TEXT NOT NULL, asset_version INTEGER NOT NULL, node_id TEXT REFERENCES nodes(id), PRIMARY KEY(run_id,output_key), FOREIGN KEY(asset_id,asset_version) REFERENCES asset_versions(asset_id,version)) STRICT;
|
|
26
|
+
CREATE TABLE asset_references (id TEXT PRIMARY KEY, asset_id TEXT NOT NULL, asset_version INTEGER NOT NULL, node_id TEXT REFERENCES nodes(id), run_id TEXT REFERENCES runs(id), CHECK((node_id IS NOT NULL) != (run_id IS NOT NULL)), FOREIGN KEY(asset_id,asset_version) REFERENCES asset_versions(asset_id,version)) STRICT;
|
|
27
|
+
CREATE INDEX asset_reference_version ON asset_references(asset_id,asset_version);
|
|
28
|
+
CREATE TABLE events (sequence INTEGER PRIMARY KEY AUTOINCREMENT, id TEXT NOT NULL UNIQUE, type TEXT NOT NULL, project_id TEXT REFERENCES projects(id), graph_id TEXT REFERENCES graphs(id), entity_id TEXT NOT NULL, revision INTEGER NOT NULL CHECK(revision >= 0), occurred_at TEXT NOT NULL, payload TEXT NOT NULL CHECK(json_valid(payload))) STRICT;
|
|
29
|
+
CREATE TABLE interactions (id TEXT PRIMARY KEY, run_id TEXT NOT NULL REFERENCES runs(id), epoch TEXT NOT NULL, version INTEGER NOT NULL CHECK(version > 0), kind TEXT NOT NULL CHECK(kind IN ('question','approval')), status TEXT NOT NULL CHECK(status IN ('pending','answered','expired')), payload TEXT NOT NULL CHECK(json_valid(payload)), answer TEXT CHECK(answer IS NULL OR json_valid(answer))) STRICT;
|
|
30
|
+
CREATE TABLE occupancy (run_id TEXT PRIMARY KEY REFERENCES runs(id), slot INTEGER NOT NULL UNIQUE CHECK(slot >= 0), project_id TEXT UNIQUE REFERENCES projects(id)) STRICT;
|
|
31
|
+
CREATE TABLE idempotency (scope TEXT NOT NULL, key TEXT NOT NULL, request_hash TEXT NOT NULL, response TEXT NOT NULL CHECK(json_valid(response)), PRIMARY KEY(scope,key)) STRICT;
|
|
32
|
+
CREATE TABLE plugin_contracts (type TEXT NOT NULL, schema_version INTEGER NOT NULL CHECK(schema_version > 0), api_version TEXT NOT NULL, contract TEXT NOT NULL CHECK(json_valid(contract)), PRIMARY KEY(type,schema_version)) STRICT;
|
|
33
|
+
CREATE TABLE backups (id TEXT PRIMARY KEY, state TEXT NOT NULL CHECK(state IN ('creating','ready','failed')), created_at TEXT NOT NULL, bytes INTEGER, sha256 TEXT, path TEXT NOT NULL) STRICT;
|
|
34
|
+
` }, { version: 2, sql: `
|
|
35
|
+
CREATE INDEX runs_project_sequence ON runs(project_id,sequence);
|
|
36
|
+
CREATE INDEX events_graph_sequence ON events(graph_id,sequence);
|
|
37
|
+
CREATE INDEX sessions_last_used ON sessions(last_used_at);
|
|
38
|
+
INSERT INTO settings(key,value) VALUES ('capacity','4'), ('modelDefaults','null');
|
|
39
|
+
` }, { version: 3, sql: `
|
|
40
|
+
CREATE TABLE trusted_origins (origin TEXT PRIMARY KEY, created_at INTEGER NOT NULL) STRICT;
|
|
41
|
+
ALTER TABLE pairing_codes ADD COLUMN origin TEXT NOT NULL DEFAULT '';
|
|
42
|
+
ALTER TABLE sessions ADD COLUMN origin TEXT NOT NULL DEFAULT '';
|
|
43
|
+
` }, { version: 4, sql: RESOURCE_SCHEMA }, { version: 5, sql: WORKFLOW_SCHEMA }, { version: 6, sql: ASSET_BUSINESS_SCHEMA }, { version: 7, sql: PLUGIN_SCHEMA }, { version: 8, sql: `
|
|
44
|
+
ALTER TABLE graphs ADD COLUMN trashed INTEGER NOT NULL DEFAULT 0 CHECK(trashed IN (0,1));
|
|
45
|
+
ALTER TABLE idempotency ADD COLUMN invalidated INTEGER NOT NULL DEFAULT 0 CHECK(invalidated IN (0,1));
|
|
46
|
+
CREATE TABLE run_file_deletions (run_id TEXT PRIMARY KEY, completed INTEGER NOT NULL DEFAULT 0 CHECK(completed IN (0,1))) STRICT;
|
|
47
|
+
` }, { version: 9, sql: `
|
|
48
|
+
ALTER TABLE nodes ADD COLUMN width REAL CHECK(width IS NULL OR (width>0 AND width<=1000000000));
|
|
49
|
+
ALTER TABLE nodes ADD COLUMN height REAL CHECK(height IS NULL OR (height>0 AND height<=1000000000));
|
|
50
|
+
CREATE TABLE IF NOT EXISTS run_file_deletions (run_id TEXT PRIMARY KEY, completed INTEGER NOT NULL DEFAULT 0 CHECK(completed IN (0,1))) STRICT;
|
|
51
|
+
` }, { version: 10, sql: `
|
|
52
|
+
ALTER TABLE pairing_codes ADD COLUMN client_code TEXT;
|
|
53
|
+
` }, { version: 11, sql: `
|
|
54
|
+
-- Historical nodes have no recoverable creation time. Freeze their insertion
|
|
55
|
+
-- order before nodes created after this migration.
|
|
56
|
+
ALTER TABLE nodes ADD COLUMN created_at INTEGER NOT NULL DEFAULT 0 CHECK(created_at >= 0);
|
|
57
|
+
ALTER TABLE nodes ADD COLUMN creation_order INTEGER NOT NULL DEFAULT 0 CHECK(creation_order >= 0);
|
|
58
|
+
UPDATE nodes SET creation_order=rowid;
|
|
59
|
+
CREATE UNIQUE INDEX nodes_creation_order ON nodes(creation_order);
|
|
60
|
+
CREATE INDEX nodes_graph_creation ON nodes(graph_id,created_at,creation_order);
|
|
61
|
+
` }, { version: 12, sql: `
|
|
62
|
+
CREATE TABLE image_providers (id TEXT PRIMARY KEY, name TEXT NOT NULL, driver TEXT NOT NULL CHECK(driver IN ('openai')), endpoint TEXT NOT NULL, enabled INTEGER NOT NULL DEFAULT 0 CHECK(enabled IN (0,1)), revision INTEGER NOT NULL CHECK(revision > 0), credential_revision INTEGER, deleted INTEGER NOT NULL DEFAULT 0 CHECK(deleted IN (0,1))) STRICT;
|
|
63
|
+
CREATE TABLE image_provider_models (provider_id TEXT NOT NULL REFERENCES image_providers(id), id TEXT NOT NULL, name TEXT NOT NULL, modes TEXT NOT NULL CHECK(json_valid(modes)), formats TEXT NOT NULL CHECK(json_valid(formats)), sizes TEXT NOT NULL CHECK(json_valid(sizes)), qualities TEXT NOT NULL CHECK(json_valid(qualities)), is_default INTEGER NOT NULL DEFAULT 0 CHECK(is_default IN (0,1)), verified_at TEXT, PRIMARY KEY(provider_id,id)) STRICT;
|
|
64
|
+
CREATE UNIQUE INDEX image_provider_default_model ON image_provider_models(provider_id) WHERE is_default=1;
|
|
65
|
+
` }, { version: 13, sql: `
|
|
66
|
+
CREATE TABLE image_provider_config_versions (provider_id TEXT NOT NULL REFERENCES image_providers(id), revision INTEGER NOT NULL CHECK(revision > 0), payload TEXT NOT NULL CHECK(json_valid(payload)), PRIMARY KEY(provider_id,revision)) STRICT;
|
|
67
|
+
CREATE TRIGGER image_provider_config_immutable BEFORE UPDATE ON image_provider_config_versions BEGIN SELECT RAISE(ABORT,'immutable image provider configuration'); END;
|
|
68
|
+
INSERT INTO image_provider_config_versions(provider_id,revision,payload)
|
|
69
|
+
SELECT p.id,p.revision,json_object('endpoint',p.endpoint,'driver',p.driver,'models',
|
|
70
|
+
coalesce((SELECT json_group_array(json_object('id',m.id,'name',m.name,'modes',json(m.modes),'formats',json(m.formats),'sizes',json(m.sizes),'qualities',json(m.qualities),'isDefault',json(m.is_default)))
|
|
71
|
+
FROM image_provider_models m WHERE m.provider_id=p.id),json('[]')))
|
|
72
|
+
FROM image_providers p;
|
|
73
|
+
` }, { version: 14, sql: `
|
|
74
|
+
ALTER TABLE image_providers ADD COLUMN credential_counter INTEGER NOT NULL DEFAULT 0 CHECK(credential_counter >= 0);
|
|
75
|
+
UPDATE image_providers SET credential_counter=coalesce(credential_revision,0);
|
|
76
|
+
CREATE TABLE image_provider_revoked_credentials (provider_id TEXT NOT NULL REFERENCES image_providers(id), revision INTEGER NOT NULL CHECK(revision > 0), PRIMARY KEY(provider_id,revision)) STRICT;
|
|
77
|
+
` }, { version: 15, sql: `
|
|
78
|
+
ALTER TABLE nodes ADD COLUMN undo_expires_at INTEGER;
|
|
79
|
+
CREATE INDEX nodes_undo_expiry ON nodes(undo_expires_at) WHERE undo_expires_at IS NOT NULL;
|
|
80
|
+
` }, { version: 16, sql: `
|
|
81
|
+
CREATE TABLE execution_outputs (
|
|
82
|
+
node_id TEXT PRIMARY KEY REFERENCES nodes(id) ON DELETE CASCADE,
|
|
83
|
+
execution_node_id TEXT NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
|
|
84
|
+
CHECK(node_id != execution_node_id)
|
|
85
|
+
) STRICT;
|
|
86
|
+
CREATE INDEX execution_outputs_owner ON execution_outputs(execution_node_id);
|
|
87
|
+
-- Infer only service originals backed by an execution Run.
|
|
88
|
+
INSERT INTO execution_outputs(node_id,execution_node_id)
|
|
89
|
+
SELECT n.id,r.node_id FROM nodes n JOIN node_versions v ON v.node_id=n.id AND v.version=n.current_version
|
|
90
|
+
JOIN runs r ON r.id=json_extract(v.content,'$.runId') AND r.graph_id=n.graph_id AND r.kind='execution'
|
|
91
|
+
JOIN nodes owner ON owner.id=r.node_id AND owner.deleted=0
|
|
92
|
+
WHERE n.deleted=0 AND n.read_only_reason='original' AND n.id!=r.node_id;
|
|
93
|
+
INSERT OR IGNORE INTO execution_outputs(node_id,execution_node_id)
|
|
94
|
+
SELECT e.target_id,e.source_id FROM edges e JOIN nodes s ON s.id=e.source_id JOIN nodes t ON t.id=e.target_id
|
|
95
|
+
WHERE e.kind='delivery' AND s.type='execution' AND s.deleted=0 AND t.deleted=0;
|
|
96
|
+
-- Only remove legacy artifact-to-artifact references within the same Run.
|
|
97
|
+
DELETE FROM edges WHERE id IN (
|
|
98
|
+
SELECT e.id FROM edges e JOIN execution_outputs s ON s.node_id=e.source_id
|
|
99
|
+
JOIN execution_outputs t ON t.node_id=e.target_id AND t.execution_node_id=s.execution_node_id
|
|
100
|
+
JOIN nodes sn ON sn.id=s.node_id JOIN node_versions sv ON sv.node_id=sn.id AND sv.version=sn.current_version
|
|
101
|
+
JOIN nodes tn ON tn.id=t.node_id JOIN node_versions tv ON tv.node_id=tn.id AND tv.version=tn.current_version
|
|
102
|
+
WHERE e.kind='reference' AND sn.type='document' AND tn.type='image'
|
|
103
|
+
AND sn.read_only_reason='original' AND tn.read_only_reason='original'
|
|
104
|
+
AND json_extract(sv.content,'$.mime')='text/markdown'
|
|
105
|
+
AND json_extract(sv.content,'$.runId')=json_extract(tv.content,'$.runId')
|
|
106
|
+
);
|
|
107
|
+
INSERT INTO edges(id,graph_id,source_id,target_id,kind)
|
|
108
|
+
SELECT 'output-'||lower(hex(randomblob(16))),n.graph_id,o.execution_node_id,o.node_id,'delivery'
|
|
109
|
+
FROM execution_outputs o JOIN nodes n ON n.id=o.node_id
|
|
110
|
+
WHERE NOT EXISTS(SELECT 1 FROM edges e WHERE e.source_id=o.execution_node_id AND e.target_id=o.node_id);
|
|
111
|
+
CREATE TRIGGER register_execution_output AFTER INSERT ON edges WHEN NEW.kind='delivery'
|
|
112
|
+
BEGIN INSERT INTO execution_outputs(node_id,execution_node_id) VALUES(NEW.target_id,NEW.source_id)
|
|
113
|
+
ON CONFLICT(node_id) DO UPDATE SET execution_node_id=excluded.execution_node_id; END;
|
|
114
|
+
UPDATE graphs SET execution_revision=execution_revision+1 WHERE id IN
|
|
115
|
+
(SELECT n.graph_id FROM execution_outputs o JOIN nodes n ON n.id=o.node_id);
|
|
116
|
+
UPDATE idempotency SET invalidated=1 WHERE scope LIKE '%:graph.command:%';
|
|
117
|
+
` }, { version: 17, sql: `
|
|
118
|
+
CREATE TABLE project_file_bindings (
|
|
119
|
+
node_id TEXT PRIMARY KEY REFERENCES nodes(id) ON DELETE CASCADE,
|
|
120
|
+
service_id TEXT NOT NULL,
|
|
121
|
+
project_id TEXT NOT NULL REFERENCES projects(id),
|
|
122
|
+
relative_path TEXT NOT NULL CHECK(relative_path != ''),
|
|
123
|
+
UNIQUE(project_id,relative_path,node_id)
|
|
124
|
+
) STRICT;
|
|
125
|
+
CREATE INDEX project_file_bindings_path ON project_file_bindings(project_id,relative_path);
|
|
126
|
+
CREATE TABLE project_file_observations (
|
|
127
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
128
|
+
relative_path TEXT NOT NULL CHECK(relative_path != ''),
|
|
129
|
+
state TEXT NOT NULL CHECK(state IN ('available','missing','unavailable')),
|
|
130
|
+
name TEXT NOT NULL,
|
|
131
|
+
mime TEXT,
|
|
132
|
+
bytes INTEGER CHECK(bytes IS NULL OR bytes >= 0),
|
|
133
|
+
change_token TEXT,
|
|
134
|
+
observed_at INTEGER NOT NULL CHECK(observed_at >= 0),
|
|
135
|
+
PRIMARY KEY(project_id,relative_path)
|
|
136
|
+
) STRICT;
|
|
137
|
+
CREATE TABLE project_file_outputs (
|
|
138
|
+
run_id TEXT NOT NULL REFERENCES runs(id) ON DELETE CASCADE,
|
|
139
|
+
output_key TEXT NOT NULL,
|
|
140
|
+
project_id TEXT NOT NULL REFERENCES projects(id),
|
|
141
|
+
relative_path TEXT NOT NULL CHECK(relative_path != ''),
|
|
142
|
+
bytes INTEGER NOT NULL CHECK(bytes >= 0),
|
|
143
|
+
sha256 TEXT NOT NULL CHECK(length(sha256) = 64),
|
|
144
|
+
PRIMARY KEY(run_id,output_key),
|
|
145
|
+
UNIQUE(run_id,relative_path)
|
|
146
|
+
) STRICT;
|
|
147
|
+
CREATE INDEX project_file_outputs_path ON project_file_outputs(project_id,relative_path);
|
|
148
|
+
|
|
149
|
+
-- Keep the current-source index correct for every writer of node_versions,
|
|
150
|
+
-- including imports, generation acceptance, undo/restore and future commands.
|
|
151
|
+
CREATE TRIGGER project_file_binding_on_version_insert AFTER INSERT ON node_versions
|
|
152
|
+
WHEN (SELECT current_version FROM nodes WHERE id=NEW.node_id)=NEW.version
|
|
153
|
+
BEGIN
|
|
154
|
+
DELETE FROM project_file_bindings WHERE node_id=NEW.node_id;
|
|
155
|
+
INSERT INTO project_file_bindings(node_id,service_id,project_id,relative_path)
|
|
156
|
+
SELECT NEW.node_id,json_extract(NEW.content,'$.source.serviceId'),json_extract(NEW.content,'$.source.projectId'),json_extract(NEW.content,'$.source.relativePath')
|
|
157
|
+
WHERE json_extract(NEW.content,'$.source.kind')='project-file';
|
|
158
|
+
END;
|
|
159
|
+
CREATE TRIGGER project_file_binding_on_current_version AFTER UPDATE OF current_version ON nodes
|
|
160
|
+
BEGIN
|
|
161
|
+
DELETE FROM project_file_bindings WHERE node_id=NEW.id;
|
|
162
|
+
INSERT INTO project_file_bindings(node_id,service_id,project_id,relative_path)
|
|
163
|
+
SELECT NEW.id,json_extract(v.content,'$.source.serviceId'),json_extract(v.content,'$.source.projectId'),json_extract(v.content,'$.source.relativePath')
|
|
164
|
+
FROM node_versions v WHERE v.node_id=NEW.id AND v.version=NEW.current_version
|
|
165
|
+
AND json_extract(v.content,'$.source.kind')='project-file';
|
|
166
|
+
END;
|
|
167
|
+
INSERT INTO project_file_bindings(node_id,service_id,project_id,relative_path)
|
|
168
|
+
SELECT n.id,json_extract(v.content,'$.source.serviceId'),json_extract(v.content,'$.source.projectId'),json_extract(v.content,'$.source.relativePath')
|
|
169
|
+
FROM nodes n JOIN node_versions v ON v.node_id=n.id AND v.version=n.current_version
|
|
170
|
+
JOIN graphs g ON g.id=n.graph_id
|
|
171
|
+
WHERE json_extract(v.content,'$.source.kind')='project-file'
|
|
172
|
+
AND json_extract(v.content,'$.source.projectId')=g.project_id;
|
|
173
|
+
` }, { version: 18, sql: `
|
|
174
|
+
CREATE TABLE edges_next (id TEXT PRIMARY KEY, graph_id TEXT NOT NULL REFERENCES graphs(id), source_id TEXT NOT NULL, target_id TEXT NOT NULL, kind TEXT NOT NULL CHECK(kind IN ('reference','execution','delivery')), CHECK(source_id != target_id), UNIQUE(graph_id,source_id,target_id), FOREIGN KEY(source_id,graph_id) REFERENCES nodes(id,graph_id), FOREIGN KEY(target_id,graph_id) REFERENCES nodes(id,graph_id)) STRICT;
|
|
175
|
+
INSERT INTO edges_next SELECT * FROM edges;
|
|
176
|
+
DROP TRIGGER register_execution_output;
|
|
177
|
+
DROP TABLE edges;
|
|
178
|
+
ALTER TABLE edges_next RENAME TO edges;
|
|
179
|
+
CREATE INDEX execution_edges_source ON edges(graph_id,source_id) WHERE kind='execution';
|
|
180
|
+
CREATE TRIGGER register_execution_output AFTER INSERT ON edges WHEN NEW.kind='delivery'
|
|
181
|
+
BEGIN INSERT INTO execution_outputs(node_id,execution_node_id) VALUES(NEW.target_id,NEW.source_id)
|
|
182
|
+
ON CONFLICT(node_id) DO UPDATE SET execution_node_id=excluded.execution_node_id; END;
|
|
183
|
+
` }, { version: 19, sql: `
|
|
184
|
+
CREATE TABLE execution_input_snapshots (run_id TEXT PRIMARY KEY REFERENCES runs(id),payload TEXT NOT NULL CHECK(json_valid(payload))) STRICT;
|
|
185
|
+
CREATE TRIGGER execution_input_snapshot_immutable BEFORE UPDATE ON execution_input_snapshots BEGIN SELECT RAISE(ABORT,'immutable execution input snapshot'); END;
|
|
186
|
+
` }, { version: 20, sql: `
|
|
187
|
+
CREATE TABLE run_notifications (
|
|
188
|
+
run_id TEXT PRIMARY KEY REFERENCES runs(id) ON DELETE CASCADE,
|
|
189
|
+
status TEXT NOT NULL CHECK(status IN ('succeeded','failed','waiting_answer','waiting_approval','interrupted')),
|
|
190
|
+
revision INTEGER NOT NULL CHECK(revision > 0),
|
|
191
|
+
created_at TEXT NOT NULL,
|
|
192
|
+
read_at TEXT
|
|
193
|
+
) STRICT;
|
|
194
|
+
CREATE INDEX run_notifications_unread ON run_notifications(read_at,created_at) WHERE read_at IS NULL;
|
|
195
|
+
INSERT INTO run_notifications(run_id,status,revision,created_at,read_at)
|
|
196
|
+
SELECT id,status,1,created_at,NULL FROM runs WHERE status IN ('waiting_answer','waiting_approval');
|
|
197
|
+
` }, { version: 21, sql: `
|
|
198
|
+
UPDATE settings SET value='4' WHERE key='capacity' AND value='2';
|
|
199
|
+
` }, { version: 22, sql: `
|
|
200
|
+
CREATE TABLE graph_document_versions (
|
|
201
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
202
|
+
graph_id TEXT NOT NULL REFERENCES graphs(id) ON DELETE CASCADE,
|
|
203
|
+
document TEXT NOT NULL CHECK(json_valid(document))
|
|
204
|
+
) STRICT;
|
|
205
|
+
CREATE INDEX graph_document_versions_graph ON graph_document_versions(graph_id,id);
|
|
206
|
+
CREATE TABLE graph_document_heads (
|
|
207
|
+
graph_id TEXT PRIMARY KEY REFERENCES graphs(id) ON DELETE CASCADE,
|
|
208
|
+
cursor INTEGER NOT NULL REFERENCES graph_document_versions(id),
|
|
209
|
+
execution_revision INTEGER NOT NULL,
|
|
210
|
+
layout_revision INTEGER NOT NULL,
|
|
211
|
+
run_sequence INTEGER NOT NULL,
|
|
212
|
+
expires_at INTEGER NOT NULL
|
|
213
|
+
) STRICT;
|
|
214
|
+
CREATE TABLE graph_document_resources (
|
|
215
|
+
version_id INTEGER NOT NULL REFERENCES graph_document_versions(id) ON DELETE CASCADE,
|
|
216
|
+
resource_id TEXT NOT NULL,
|
|
217
|
+
resource_version INTEGER NOT NULL,
|
|
218
|
+
PRIMARY KEY(version_id,resource_id,resource_version),
|
|
219
|
+
FOREIGN KEY(resource_id,resource_version) REFERENCES canvas_resource_versions(resource_id,version)
|
|
220
|
+
) STRICT;
|
|
221
|
+
CREATE INDEX graph_document_resources_resource ON graph_document_resources(resource_id);
|
|
222
|
+
` }, { version: 23, sql: `
|
|
223
|
+
ALTER TABLE graphs ADD COLUMN updated_at TEXT;
|
|
224
|
+
UPDATE graphs SET updated_at=(SELECT MAX(occurred_at) FROM events WHERE graph_id=graphs.id AND type='graph.changed');
|
|
225
|
+
CREATE TRIGGER graph_modified_at AFTER INSERT ON events WHEN NEW.type='graph.changed' AND NEW.graph_id IS NOT NULL
|
|
226
|
+
BEGIN UPDATE graphs SET updated_at=NEW.occurred_at WHERE id=NEW.graph_id; END;
|
|
227
|
+
` }, { version: 24, sql: `
|
|
228
|
+
CREATE TABLE graph_document_run_gates (
|
|
229
|
+
run_id TEXT PRIMARY KEY REFERENCES runs(id) ON DELETE CASCADE,
|
|
230
|
+
graph_id TEXT NOT NULL REFERENCES graphs(id) ON DELETE CASCADE,
|
|
231
|
+
cursor INTEGER NOT NULL REFERENCES graph_document_versions(id) ON DELETE CASCADE
|
|
232
|
+
) STRICT;
|
|
233
|
+
CREATE INDEX graph_document_run_gates_cursor ON graph_document_run_gates(graph_id,cursor);
|
|
234
|
+
` }];
|
|
235
|
+
export const SCHEMA_VERSION = 24;
|
|
236
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/** Additive workflow storage. Deletion keeps Run identity/FK records as tombstones. */
|
|
2
|
+
export declare const WORKFLOW_SCHEMA = "\nALTER TABLE nodes ADD COLUMN deleted INTEGER NOT NULL DEFAULT 0 CHECK(deleted IN (0,1));\nCREATE TABLE node_resource_history (\n node_id TEXT NOT NULL, node_version INTEGER NOT NULL, resource_id TEXT NOT NULL, resource_version INTEGER NOT NULL,\n PRIMARY KEY(node_id,node_version,resource_id,resource_version),\n FOREIGN KEY(node_id,node_version) REFERENCES node_versions(node_id,version),\n FOREIGN KEY(resource_id,resource_version) REFERENCES canvas_resource_versions(resource_id,version)\n) STRICT;\nCREATE TABLE graph_groups (id TEXT PRIMARY KEY,graph_id TEXT NOT NULL REFERENCES graphs(id),run_id TEXT REFERENCES runs(id),title TEXT NOT NULL) STRICT;\nCREATE TABLE group_members (group_id TEXT NOT NULL REFERENCES graph_groups(id),node_id TEXT NOT NULL REFERENCES nodes(id),PRIMARY KEY(group_id,node_id)) STRICT;\nCREATE TABLE run_runtime (run_id TEXT PRIMARY KEY REFERENCES runs(id),epoch TEXT NOT NULL,revision INTEGER NOT NULL DEFAULT 0,details TEXT NOT NULL CHECK(json_valid(details))) STRICT;\nCREATE TABLE run_process_records (id INTEGER PRIMARY KEY AUTOINCREMENT,run_id TEXT NOT NULL REFERENCES runs(id),occurred_at TEXT NOT NULL,kind TEXT NOT NULL,payload TEXT NOT NULL CHECK(json_valid(payload))) STRICT;\nCREATE TABLE generation_candidates (run_id TEXT PRIMARY KEY REFERENCES runs(id),node_id TEXT NOT NULL REFERENCES nodes(id),base_version INTEGER NOT NULL,content TEXT NOT NULL CHECK(json_valid(content)),state TEXT NOT NULL CHECK(state IN ('pending','accepted','discarded'))) STRICT;\nCREATE TABLE publication_manifests (run_id TEXT PRIMARY KEY REFERENCES runs(id),manifest TEXT NOT NULL CHECK(json_valid(manifest)),sha256 TEXT NOT NULL) STRICT;\nCREATE TRIGGER immutable_publication_manifest BEFORE UPDATE ON publication_manifests BEGIN SELECT RAISE(ABORT,'publication manifest is immutable'); END;\nINSERT INTO settings(key,value) VALUES('eventFloor','0'),('acceptingRuns','true');\n";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** Additive workflow storage. Deletion keeps Run identity/FK records as tombstones. */
|
|
2
|
+
export const WORKFLOW_SCHEMA = `
|
|
3
|
+
ALTER TABLE nodes ADD COLUMN deleted INTEGER NOT NULL DEFAULT 0 CHECK(deleted IN (0,1));
|
|
4
|
+
CREATE TABLE node_resource_history (
|
|
5
|
+
node_id TEXT NOT NULL, node_version INTEGER NOT NULL, resource_id TEXT NOT NULL, resource_version INTEGER NOT NULL,
|
|
6
|
+
PRIMARY KEY(node_id,node_version,resource_id,resource_version),
|
|
7
|
+
FOREIGN KEY(node_id,node_version) REFERENCES node_versions(node_id,version),
|
|
8
|
+
FOREIGN KEY(resource_id,resource_version) REFERENCES canvas_resource_versions(resource_id,version)
|
|
9
|
+
) STRICT;
|
|
10
|
+
CREATE TABLE graph_groups (id TEXT PRIMARY KEY,graph_id TEXT NOT NULL REFERENCES graphs(id),run_id TEXT REFERENCES runs(id),title TEXT NOT NULL) STRICT;
|
|
11
|
+
CREATE TABLE group_members (group_id TEXT NOT NULL REFERENCES graph_groups(id),node_id TEXT NOT NULL REFERENCES nodes(id),PRIMARY KEY(group_id,node_id)) STRICT;
|
|
12
|
+
CREATE TABLE run_runtime (run_id TEXT PRIMARY KEY REFERENCES runs(id),epoch TEXT NOT NULL,revision INTEGER NOT NULL DEFAULT 0,details TEXT NOT NULL CHECK(json_valid(details))) STRICT;
|
|
13
|
+
CREATE TABLE run_process_records (id INTEGER PRIMARY KEY AUTOINCREMENT,run_id TEXT NOT NULL REFERENCES runs(id),occurred_at TEXT NOT NULL,kind TEXT NOT NULL,payload TEXT NOT NULL CHECK(json_valid(payload))) STRICT;
|
|
14
|
+
CREATE TABLE generation_candidates (run_id TEXT PRIMARY KEY REFERENCES runs(id),node_id TEXT NOT NULL REFERENCES nodes(id),base_version INTEGER NOT NULL,content TEXT NOT NULL CHECK(json_valid(content)),state TEXT NOT NULL CHECK(state IN ('pending','accepted','discarded'))) STRICT;
|
|
15
|
+
CREATE TABLE publication_manifests (run_id TEXT PRIMARY KEY REFERENCES runs(id),manifest TEXT NOT NULL CHECK(json_valid(manifest)),sha256 TEXT NOT NULL) STRICT;
|
|
16
|
+
CREATE TRIGGER immutable_publication_manifest BEFORE UPDATE ON publication_manifests BEGIN SELECT RAISE(ABORT,'publication manifest is immutable'); END;
|
|
17
|
+
INSERT INTO settings(key,value) VALUES('eventFloor','0'),('acceptingRuns','true');
|
|
18
|
+
`;
|
|
19
|
+
//# sourceMappingURL=workflow-schema.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { IncomingMessage } from 'node:http';
|
|
2
|
+
import type { ApiResult } from './api.js';
|
|
3
|
+
import { Auth } from './auth.js';
|
|
4
|
+
import { PluginManagement } from './plugin-management.js';
|
|
5
|
+
export declare class PluginApi {
|
|
6
|
+
readonly plugins: PluginManagement;
|
|
7
|
+
readonly auth: Auth;
|
|
8
|
+
constructor(plugins: PluginManagement, auth: Auth);
|
|
9
|
+
handle(request: IncomingMessage, path: string, token: string, origin: string): Promise<ApiResult>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Auth } from './auth.js';
|
|
2
|
+
import { PluginManagement } from './plugin-management.js';
|
|
3
|
+
import { readJson } from './http.js';
|
|
4
|
+
import { ServiceError } from './errors.js';
|
|
5
|
+
export class PluginApi {
|
|
6
|
+
plugins;
|
|
7
|
+
auth;
|
|
8
|
+
constructor(plugins, auth) {
|
|
9
|
+
this.plugins = plugins;
|
|
10
|
+
this.auth = auth;
|
|
11
|
+
}
|
|
12
|
+
async handle(request, path, token, origin) {
|
|
13
|
+
const match = /^[/]v1[/]projects[/]([a-zA-Z0-9_-]+)[/]graphs[/]([a-zA-Z0-9_-]+)[/]nodes[/]([a-zA-Z0-9_-]+)[/](plugin|restore-plugin|migrate-plugin|versions[/][1-9][0-9]*)$/.exec(path);
|
|
14
|
+
if (!match)
|
|
15
|
+
return { handled: false };
|
|
16
|
+
this.auth.withSession(token, origin, () => undefined);
|
|
17
|
+
const scope = { serviceId: this.plugins.graphs.serviceId, projectId: match[1], graphId: match[2] }, nodeId = match[3], action = match[4];
|
|
18
|
+
const body = request.method === 'POST' ? await readJson(request) : {};
|
|
19
|
+
return this.auth.withSession(token, origin, current => {
|
|
20
|
+
this.plugins.graphs.scope(scope);
|
|
21
|
+
if (request.method === 'GET' && action.startsWith('versions/'))
|
|
22
|
+
return { handled: true, body: this.plugins.readVersion(scope, nodeId, Number(action.split('/')[1])) };
|
|
23
|
+
if (request.method === 'GET' && action === 'plugin') {
|
|
24
|
+
const node = this.plugins.graphs.snapshot(scope).nodes.find(n => n.id === nodeId);
|
|
25
|
+
if (!node)
|
|
26
|
+
throw new ServiceError('NOT_FOUND', '节点不存在。');
|
|
27
|
+
return { handled: true, body: this.plugins.registry.inspect(node.type, node.schemaVersion, node.content) };
|
|
28
|
+
}
|
|
29
|
+
if (request.method === 'POST' && ['restore-plugin', 'migrate-plugin'].includes(action)) {
|
|
30
|
+
const keys = ['expectedExecutionRevision', 'expectedContentVersion', 'expectedSchemaVersion', 'idempotencyKey', ...(action === 'migrate-plugin' ? ['toSchemaVersion'] : [])];
|
|
31
|
+
if (Object.keys(body).some(k => !keys.includes(k)))
|
|
32
|
+
throw new ServiceError('INVALID_REQUEST', '插件变更包含未知字段。');
|
|
33
|
+
const input = { ...body, ...scope, nodeId };
|
|
34
|
+
return { handled: true, body: action === 'restore-plugin' ? this.plugins.restoreNode(input, current.id) : this.plugins.migrateNode(input, current.id) };
|
|
35
|
+
}
|
|
36
|
+
throw new ServiceError('NOT_FOUND', '插件节点接口不存在。');
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=plugin-api.js.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { GraphScope, GraphSnapshot, Json } from "./vendor/protocol/index.js";
|
|
2
|
+
import { Graphs } from './graphs.js';
|
|
3
|
+
import { PluginRegistry } from './plugins.js';
|
|
4
|
+
import type { PluginContract } from './plugins.js';
|
|
5
|
+
export interface PluginNodeChange extends GraphScope {
|
|
6
|
+
nodeId: string;
|
|
7
|
+
expectedExecutionRevision: number;
|
|
8
|
+
expectedContentVersion: number;
|
|
9
|
+
expectedSchemaVersion: number;
|
|
10
|
+
idempotencyKey: string;
|
|
11
|
+
}
|
|
12
|
+
export interface PluginNodeMigration extends PluginNodeChange {
|
|
13
|
+
toSchemaVersion: number;
|
|
14
|
+
}
|
|
15
|
+
export interface HistoricalPluginNode {
|
|
16
|
+
nodeId: string;
|
|
17
|
+
type: string;
|
|
18
|
+
schemaVersion: number;
|
|
19
|
+
contentVersion: number;
|
|
20
|
+
content: Json;
|
|
21
|
+
}
|
|
22
|
+
/** Local declarative registration and explicit node lifecycle operations only.
|
|
23
|
+
* The HTTP surface MUST NOT expose registerTrustedLocal. Restore/migrate require
|
|
24
|
+
* the caller's authenticated graph scope; both nest in that authorization transaction. */
|
|
25
|
+
export declare class PluginManagement {
|
|
26
|
+
readonly graphs: Graphs;
|
|
27
|
+
readonly registry: PluginRegistry;
|
|
28
|
+
constructor(graphs: Graphs, registry?: PluginRegistry);
|
|
29
|
+
registerTrustedLocal(manifest: unknown): PluginContract;
|
|
30
|
+
private storage;
|
|
31
|
+
private validate;
|
|
32
|
+
private guardedNode;
|
|
33
|
+
migrateNode(request: PluginNodeMigration, principal?: string): GraphSnapshot;
|
|
34
|
+
restoreNode(request: PluginNodeChange, principal?: string): GraphSnapshot;
|
|
35
|
+
readVersion(scope: GraphScope, nodeId: string, version: number): HistoricalPluginNode;
|
|
36
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { Graphs } from './graphs.js';
|
|
2
|
+
import { PluginRegistry, checkedJson } from './plugins.js';
|
|
3
|
+
import { atomic } from './persistence/database.js';
|
|
4
|
+
import { canonicalJson } from './persistence/repositories.js';
|
|
5
|
+
import { ServiceError } from './errors.js';
|
|
6
|
+
const core = new Set(['text', 'image', 'document', 'video', 'file', 'preview', 'execution']);
|
|
7
|
+
/** Local declarative registration and explicit node lifecycle operations only.
|
|
8
|
+
* The HTTP surface MUST NOT expose registerTrustedLocal. Restore/migrate require
|
|
9
|
+
* the caller's authenticated graph scope; both nest in that authorization transaction. */
|
|
10
|
+
export class PluginManagement {
|
|
11
|
+
graphs;
|
|
12
|
+
registry;
|
|
13
|
+
constructor(graphs, registry) {
|
|
14
|
+
this.graphs = graphs;
|
|
15
|
+
this.registry = registry ?? new PluginRegistry(graphs.db);
|
|
16
|
+
}
|
|
17
|
+
registerTrustedLocal(manifest) { return atomic(this.graphs.db, () => this.registry.registerTrusted(manifest)); }
|
|
18
|
+
storage() {
|
|
19
|
+
if (!this.graphs.db.prepare('PRAGMA table_info(nodes)').all().some(c => c['name'] === 'read_only_reason') || !this.graphs.db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='node_version_schemas'").get())
|
|
20
|
+
throw new ServiceError('SCHEMA_UNSUPPORTED', 'Plugin migration requires read_only_reason and node_version_schemas storage');
|
|
21
|
+
}
|
|
22
|
+
validate(request) {
|
|
23
|
+
if (typeof request.nodeId !== 'string' || !request.nodeId || typeof request.idempotencyKey !== 'string' || !request.idempotencyKey)
|
|
24
|
+
throw new ServiceError('INVALID_REQUEST', 'Node and idempotency key are required');
|
|
25
|
+
for (const value of [request.expectedContentVersion, request.expectedSchemaVersion])
|
|
26
|
+
if (!Number.isSafeInteger(value) || value < 1)
|
|
27
|
+
throw new ServiceError('INVALID_REQUEST', 'Expected positive node/schema version');
|
|
28
|
+
if (!Number.isSafeInteger(request.expectedExecutionRevision) || request.expectedExecutionRevision < 0)
|
|
29
|
+
throw new ServiceError('INVALID_REQUEST', 'Expected graph execution revision');
|
|
30
|
+
}
|
|
31
|
+
guardedNode(request) {
|
|
32
|
+
const graph = this.graphs.writable(request);
|
|
33
|
+
if (graph['execution_revision'] !== request.expectedExecutionRevision)
|
|
34
|
+
throw new ServiceError('REVISION_CONFLICT', 'Graph execution revision changed');
|
|
35
|
+
const node = this.graphs.db.prepare('SELECT n.*,v.content FROM nodes n JOIN node_versions v ON v.node_id=n.id AND v.version=n.current_version WHERE n.id=? AND n.graph_id=? AND n.deleted=0').get(request.nodeId, request.graphId);
|
|
36
|
+
if (!node)
|
|
37
|
+
throw new ServiceError('NOT_FOUND', 'Plugin node not found');
|
|
38
|
+
if (node['current_version'] !== request.expectedContentVersion || node['schema_version'] !== request.expectedSchemaVersion)
|
|
39
|
+
throw new ServiceError('REVISION_CONFLICT', 'Node content/schema version changed');
|
|
40
|
+
this.graphs.assertNodeEditable(request.nodeId);
|
|
41
|
+
if (core.has(String(node['type'])) || node['read_only_reason'] === 'original' || (node['read_only'] && node['read_only_reason'] !== 'missing_plugin') || this.graphs.db.prepare("SELECT 1 FROM edges WHERE graph_id=? AND target_id=? AND kind='delivery'").get(request.graphId, request.nodeId))
|
|
42
|
+
throw new ServiceError('NODE_LOCKED', 'Original delivery or ambiguous read-only node cannot be migrated/unlocked');
|
|
43
|
+
const history = this.graphs.db.prepare('SELECT schema_version FROM node_version_schemas WHERE node_id=? AND version=?').get(request.nodeId, request.expectedContentVersion);
|
|
44
|
+
if (!history || history['schema_version'] !== request.expectedSchemaVersion)
|
|
45
|
+
throw new ServiceError('SCHEMA_UNSUPPORTED', 'Current node schema history is missing or inconsistent');
|
|
46
|
+
if (this.graphs.db.prepare('SELECT 1 FROM node_versions v LEFT JOIN node_version_schemas s ON s.node_id=v.node_id AND s.version=v.version WHERE v.node_id=? AND s.node_id IS NULL LIMIT 1').get(request.nodeId))
|
|
47
|
+
throw new ServiceError('SCHEMA_UNSUPPORTED', 'Retained node versions must all have schema history before migration');
|
|
48
|
+
return node;
|
|
49
|
+
}
|
|
50
|
+
migrateNode(request, principal = 'local') {
|
|
51
|
+
this.validate(request);
|
|
52
|
+
if (!Number.isSafeInteger(request.toSchemaVersion) || request.toSchemaVersion <= request.expectedSchemaVersion)
|
|
53
|
+
throw new ServiceError('INVALID_REQUEST', 'Migration requires a newer explicit target schema');
|
|
54
|
+
return atomic(this.graphs.db, () => {
|
|
55
|
+
this.storage();
|
|
56
|
+
this.graphs.writable(request);
|
|
57
|
+
return this.graphs.repo.idempotent(principal + ':plugin.migrate:' + request.graphId, request.idempotencyKey, request, () => {
|
|
58
|
+
const node = this.guardedNode(request);
|
|
59
|
+
const content = checkedJson(this.registry.migrate(String(node['type']), request.expectedSchemaVersion, request.toSchemaVersion, JSON.parse(String(node['content']))));
|
|
60
|
+
let count = 0;
|
|
61
|
+
const visit = (v) => { if (++count > 100000)
|
|
62
|
+
throw new ServiceError('PAYLOAD_TOO_LARGE', 'Migrated content has too many values'); if (v && typeof v === 'object')
|
|
63
|
+
for (const c of Object.values(v))
|
|
64
|
+
visit(c); };
|
|
65
|
+
visit(content);
|
|
66
|
+
const version = request.expectedContentVersion + 1;
|
|
67
|
+
// Deferrable node version FK permits changing the pointer first. The
|
|
68
|
+
// node_versions INSERT trigger then records the NEW schema, not the old.
|
|
69
|
+
const result = this.graphs.db.prepare("UPDATE nodes SET schema_version=?,current_version=?,read_only=0,read_only_reason='none' WHERE id=? AND graph_id=? AND current_version=? AND schema_version=? AND deleted=0").run(request.toSchemaVersion, version, request.nodeId, request.graphId, request.expectedContentVersion, request.expectedSchemaVersion);
|
|
70
|
+
if (result.changes !== 1)
|
|
71
|
+
throw new ServiceError('REVISION_CONFLICT', 'Node changed during migration');
|
|
72
|
+
this.graphs.db.prepare('INSERT INTO node_versions(node_id,version,content) VALUES(?,?,?)').run(request.nodeId, version, canonicalJson(content));
|
|
73
|
+
const schema = this.graphs.db.prepare('SELECT schema_version FROM node_version_schemas WHERE node_id=? AND version=?').get(request.nodeId, version);
|
|
74
|
+
if (schema?.['schema_version'] !== request.toSchemaVersion)
|
|
75
|
+
throw new ServiceError('SCHEMA_UNSUPPORTED', 'Schema history INSERT trigger is missing or inconsistent');
|
|
76
|
+
this.graphs.retainResources(request, request.nodeId, version, content);
|
|
77
|
+
this.graphs.db.prepare('UPDATE graphs SET execution_revision=execution_revision+1 WHERE id=?').run(request.graphId);
|
|
78
|
+
this.graphs.event(request);
|
|
79
|
+
return this.graphs.snapshot(request);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
restoreNode(request, principal = 'local') {
|
|
84
|
+
this.validate(request);
|
|
85
|
+
return atomic(this.graphs.db, () => {
|
|
86
|
+
this.storage();
|
|
87
|
+
this.graphs.writable(request);
|
|
88
|
+
return this.graphs.repo.idempotent(principal + ':plugin.restore:' + request.graphId, request.idempotencyKey, request, () => {
|
|
89
|
+
const node = this.guardedNode(request);
|
|
90
|
+
if (node['read_only_reason'] !== 'missing_plugin')
|
|
91
|
+
throw new ServiceError('NODE_LOCKED', 'Only a proven missing-plugin placeholder can be restored');
|
|
92
|
+
if (this.registry.inspect(String(node['type']), request.expectedSchemaVersion, JSON.parse(String(node['content']))).state !== 'available')
|
|
93
|
+
throw new ServiceError('PLUGIN_UNAVAILABLE', 'The exact schema contract is not available or content is invalid');
|
|
94
|
+
this.graphs.db.prepare("UPDATE nodes SET read_only=0,read_only_reason='none' WHERE id=?").run(request.nodeId);
|
|
95
|
+
this.graphs.db.prepare('UPDATE graphs SET execution_revision=execution_revision+1 WHERE id=?').run(request.graphId);
|
|
96
|
+
this.graphs.event(request);
|
|
97
|
+
return this.graphs.snapshot(request);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
readVersion(scope, nodeId, version) {
|
|
102
|
+
this.storage();
|
|
103
|
+
this.graphs.scope(scope);
|
|
104
|
+
if (!Number.isSafeInteger(version) || version < 1)
|
|
105
|
+
throw new ServiceError('INVALID_REQUEST', 'Invalid content version');
|
|
106
|
+
const row = this.graphs.db.prepare('SELECT n.type,v.content,s.schema_version FROM nodes n JOIN node_versions v ON v.node_id=n.id JOIN node_version_schemas s ON s.node_id=v.node_id AND s.version=v.version WHERE n.id=? AND n.graph_id=? AND v.version=?').get(nodeId, scope.graphId, version);
|
|
107
|
+
if (!row)
|
|
108
|
+
throw new ServiceError('NOT_FOUND', 'Node version/schema history not found');
|
|
109
|
+
return { nodeId, type: String(row['type']), schemaVersion: Number(row['schema_version']), contentVersion: version, content: JSON.parse(String(row['content'])) };
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=plugin-management.js.map
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { DatabaseSync } from 'node:sqlite';
|
|
2
|
+
import type { Json, ResourceEnvelope } from "./vendor/protocol/index.js";
|
|
3
|
+
type ObjectJson = {
|
|
4
|
+
[key: string]: Json;
|
|
5
|
+
};
|
|
6
|
+
export interface ContentSchema {
|
|
7
|
+
type: 'object' | 'array' | 'string' | 'number' | 'integer' | 'boolean' | 'null';
|
|
8
|
+
properties?: Record<string, ContentSchema>;
|
|
9
|
+
required?: string[];
|
|
10
|
+
items?: ContentSchema;
|
|
11
|
+
additionalProperties?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface ResourceMapping {
|
|
14
|
+
kind: ResourceEnvelope['kind'];
|
|
15
|
+
textField?: string;
|
|
16
|
+
resourceIdField?: string;
|
|
17
|
+
resourceVersionField?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface DeclarativeMigration {
|
|
20
|
+
fromSchemaVersion: number;
|
|
21
|
+
rename?: Record<string, string>;
|
|
22
|
+
defaults?: ObjectJson;
|
|
23
|
+
}
|
|
24
|
+
export interface PluginContract {
|
|
25
|
+
typeId: string;
|
|
26
|
+
pluginId: string;
|
|
27
|
+
version: string;
|
|
28
|
+
apiVersion: string;
|
|
29
|
+
schemaVersion: number;
|
|
30
|
+
contentSchema: ContentSchema;
|
|
31
|
+
resources: ResourceMapping[];
|
|
32
|
+
migrations?: DeclarativeMigration[];
|
|
33
|
+
}
|
|
34
|
+
/** Bounded pure JSON only: no getters, functions, cycles or exotic prototypes. */
|
|
35
|
+
export declare function checkedJson(value: unknown, maxBytes?: number, maxDepth?: number): Json;
|
|
36
|
+
export declare function readField(content: Json, field: string): Json | undefined;
|
|
37
|
+
export interface PluginInformation {
|
|
38
|
+
state: 'available' | 'missing' | 'incompatible' | 'invalid';
|
|
39
|
+
typeId: string;
|
|
40
|
+
schemaVersion: number;
|
|
41
|
+
content: Json;
|
|
42
|
+
reason: string | null;
|
|
43
|
+
contract: PluginContract | null;
|
|
44
|
+
}
|
|
45
|
+
export declare class PluginRegistry {
|
|
46
|
+
private readonly db;
|
|
47
|
+
constructor(db: DatabaseSync);
|
|
48
|
+
/** Local control only; never call for untrusted graph import or from browser routes. */
|
|
49
|
+
registerTrusted(input: unknown): PluginContract;
|
|
50
|
+
get(type: string, schemaVersion: number): PluginContract | null;
|
|
51
|
+
inspect(type: string, schemaVersion: number, input: unknown): PluginInformation;
|
|
52
|
+
/** Pure copy-on-success migration. Caller persists new node version atomically; old version is never changed. */
|
|
53
|
+
migrate(type: string, fromSchema: number, toSchema: number, input: unknown): Json;
|
|
54
|
+
}
|
|
55
|
+
export {};
|