@electric-ax/agents-server 0.4.15 → 0.4.17
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/dist/entrypoint.js +1230 -235
- package/dist/index.cjs +1233 -229
- package/dist/index.d.cts +1319 -318
- package/dist/index.d.ts +1319 -318
- package/dist/index.js +1235 -231
- package/drizzle/0011_entity_permissions.sql +100 -0
- package/drizzle/0012_horton_user_manage_permission.sql +25 -0
- package/drizzle/0013_worker_user_manage_permission.sql +25 -0
- package/drizzle/0014_entity_type_slash_commands.sql +1 -0
- package/drizzle/meta/_journal.json +28 -0
- package/package.json +7 -7
- package/src/db/schema.ts +199 -0
- package/src/electric-agents-types.ts +80 -2
- package/src/entity-bridge-manager.ts +57 -6
- package/src/entity-manager.ts +124 -61
- package/src/entity-projector.ts +76 -17
- package/src/entity-registry.ts +615 -5
- package/src/index.ts +11 -0
- package/src/permissions.ts +239 -0
- package/src/routing/context.ts +2 -0
- package/src/routing/durable-streams-router.ts +125 -4
- package/src/routing/electric-proxy-router.ts +4 -0
- package/src/routing/entities-router.ts +347 -20
- package/src/routing/entity-types-router.ts +267 -15
- package/src/routing/hooks.ts +1 -0
- package/src/routing/observations-router.ts +2 -1
- package/src/runtime.ts +34 -0
- package/src/scheduler.ts +2 -0
- package/src/server.ts +5 -0
- package/src/utils/server-utils.ts +192 -12
- package/src/wake-registry.ts +8 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
CREATE TABLE "entity_type_permission_grants" (
|
|
2
|
+
"id" bigserial PRIMARY KEY NOT NULL,
|
|
3
|
+
"tenant_id" text DEFAULT 'default' NOT NULL,
|
|
4
|
+
"entity_type" text NOT NULL,
|
|
5
|
+
"permission" text NOT NULL,
|
|
6
|
+
"subject_kind" text NOT NULL,
|
|
7
|
+
"subject_value" text NOT NULL,
|
|
8
|
+
"created_by" text,
|
|
9
|
+
"expires_at" timestamp with time zone,
|
|
10
|
+
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
11
|
+
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
12
|
+
CONSTRAINT "chk_type_permission_grants_permission" CHECK ("entity_type_permission_grants"."permission" IN ('spawn', 'manage')),
|
|
13
|
+
CONSTRAINT "chk_type_permission_grants_subject_kind" CHECK ("entity_type_permission_grants"."subject_kind" IN ('principal', 'principal_kind'))
|
|
14
|
+
);
|
|
15
|
+
--> statement-breakpoint
|
|
16
|
+
CREATE TABLE "entity_lineage" (
|
|
17
|
+
"tenant_id" text DEFAULT 'default' NOT NULL,
|
|
18
|
+
"ancestor_url" text NOT NULL,
|
|
19
|
+
"descendant_url" text NOT NULL,
|
|
20
|
+
"depth" integer NOT NULL,
|
|
21
|
+
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
22
|
+
CONSTRAINT "entity_lineage_pkey" PRIMARY KEY ("tenant_id", "ancestor_url", "descendant_url"),
|
|
23
|
+
CONSTRAINT "chk_entity_lineage_depth" CHECK ("entity_lineage"."depth" >= 0)
|
|
24
|
+
);
|
|
25
|
+
--> statement-breakpoint
|
|
26
|
+
CREATE TABLE "entity_permission_grants" (
|
|
27
|
+
"id" bigserial PRIMARY KEY NOT NULL,
|
|
28
|
+
"tenant_id" text DEFAULT 'default' NOT NULL,
|
|
29
|
+
"entity_url" text NOT NULL,
|
|
30
|
+
"permission" text NOT NULL,
|
|
31
|
+
"subject_kind" text NOT NULL,
|
|
32
|
+
"subject_value" text NOT NULL,
|
|
33
|
+
"propagation" text DEFAULT 'self' NOT NULL,
|
|
34
|
+
"copy_to_children" boolean DEFAULT false NOT NULL,
|
|
35
|
+
"created_by" text,
|
|
36
|
+
"expires_at" timestamp with time zone,
|
|
37
|
+
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
38
|
+
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
39
|
+
CONSTRAINT "chk_entity_permission_grants_permission" CHECK ("entity_permission_grants"."permission" IN ('read', 'write', 'delete', 'signal', 'fork', 'schedule', 'spawn', 'manage')),
|
|
40
|
+
CONSTRAINT "chk_entity_permission_grants_subject_kind" CHECK ("entity_permission_grants"."subject_kind" IN ('principal', 'principal_kind')),
|
|
41
|
+
CONSTRAINT "chk_entity_permission_grants_propagation" CHECK ("entity_permission_grants"."propagation" IN ('self', 'descendants'))
|
|
42
|
+
);
|
|
43
|
+
--> statement-breakpoint
|
|
44
|
+
CREATE TABLE "entity_effective_permissions" (
|
|
45
|
+
"id" bigserial PRIMARY KEY NOT NULL,
|
|
46
|
+
"tenant_id" text DEFAULT 'default' NOT NULL,
|
|
47
|
+
"entity_url" text NOT NULL,
|
|
48
|
+
"source_entity_url" text NOT NULL,
|
|
49
|
+
"source_grant_id" bigint NOT NULL,
|
|
50
|
+
"permission" text NOT NULL,
|
|
51
|
+
"subject_kind" text NOT NULL,
|
|
52
|
+
"subject_value" text NOT NULL,
|
|
53
|
+
"expires_at" timestamp with time zone,
|
|
54
|
+
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
55
|
+
CONSTRAINT "uq_entity_effective_permission" UNIQUE ("tenant_id", "entity_url", "source_grant_id"),
|
|
56
|
+
CONSTRAINT "chk_entity_effective_permissions_permission" CHECK ("entity_effective_permissions"."permission" IN ('read', 'write', 'delete', 'signal', 'fork', 'schedule', 'spawn', 'manage')),
|
|
57
|
+
CONSTRAINT "chk_entity_effective_permissions_subject_kind" CHECK ("entity_effective_permissions"."subject_kind" IN ('principal', 'principal_kind'))
|
|
58
|
+
);
|
|
59
|
+
--> statement-breakpoint
|
|
60
|
+
CREATE TABLE "shared_state_links" (
|
|
61
|
+
"tenant_id" text DEFAULT 'default' NOT NULL,
|
|
62
|
+
"shared_state_id" text NOT NULL,
|
|
63
|
+
"owner_entity_url" text NOT NULL,
|
|
64
|
+
"manifest_key" text NOT NULL,
|
|
65
|
+
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
66
|
+
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
67
|
+
CONSTRAINT "shared_state_links_pkey" PRIMARY KEY ("tenant_id", "owner_entity_url", "manifest_key")
|
|
68
|
+
);
|
|
69
|
+
--> statement-breakpoint
|
|
70
|
+
CREATE INDEX "idx_type_permission_grants_lookup" ON "entity_type_permission_grants" USING btree ("tenant_id", "entity_type", "permission", "subject_kind", "subject_value");
|
|
71
|
+
--> statement-breakpoint
|
|
72
|
+
CREATE INDEX "idx_type_permission_grants_expiry" ON "entity_type_permission_grants" USING btree ("tenant_id", "expires_at");
|
|
73
|
+
--> statement-breakpoint
|
|
74
|
+
CREATE INDEX "idx_entity_lineage_descendant" ON "entity_lineage" USING btree ("tenant_id", "descendant_url");
|
|
75
|
+
--> statement-breakpoint
|
|
76
|
+
CREATE INDEX "idx_entity_permission_grants_entity" ON "entity_permission_grants" USING btree ("tenant_id", "entity_url");
|
|
77
|
+
--> statement-breakpoint
|
|
78
|
+
CREATE INDEX "idx_entity_permission_grants_subject" ON "entity_permission_grants" USING btree ("tenant_id", "permission", "subject_kind", "subject_value");
|
|
79
|
+
--> statement-breakpoint
|
|
80
|
+
CREATE INDEX "idx_entity_permission_grants_expiry" ON "entity_permission_grants" USING btree ("tenant_id", "expires_at");
|
|
81
|
+
--> statement-breakpoint
|
|
82
|
+
CREATE INDEX "idx_entity_effective_permissions_lookup" ON "entity_effective_permissions" USING btree ("tenant_id", "permission", "subject_kind", "subject_value", "entity_url");
|
|
83
|
+
--> statement-breakpoint
|
|
84
|
+
CREATE INDEX "idx_entity_effective_permissions_entity" ON "entity_effective_permissions" USING btree ("tenant_id", "entity_url");
|
|
85
|
+
--> statement-breakpoint
|
|
86
|
+
CREATE INDEX "idx_entity_effective_permissions_expiry" ON "entity_effective_permissions" USING btree ("tenant_id", "expires_at");
|
|
87
|
+
--> statement-breakpoint
|
|
88
|
+
CREATE INDEX "idx_shared_state_links_shared_state" ON "shared_state_links" USING btree ("tenant_id", "shared_state_id");
|
|
89
|
+
--> statement-breakpoint
|
|
90
|
+
CREATE INDEX "idx_shared_state_links_owner" ON "shared_state_links" USING btree ("tenant_id", "owner_entity_url");
|
|
91
|
+
--> statement-breakpoint
|
|
92
|
+
-- Pre-permission entity bridge rows do not carry principal attribution. Drop them
|
|
93
|
+
-- so observation bridges are rebuilt with principal_url/principal_kind scoping.
|
|
94
|
+
DELETE FROM "entity_bridges";
|
|
95
|
+
--> statement-breakpoint
|
|
96
|
+
ALTER TABLE "entity_bridges" ADD COLUMN "principal_url" text;
|
|
97
|
+
--> statement-breakpoint
|
|
98
|
+
ALTER TABLE "entity_bridges" ADD COLUMN "principal_kind" text;
|
|
99
|
+
--> statement-breakpoint
|
|
100
|
+
CREATE INDEX "idx_entity_bridges_principal" ON "entity_bridges" USING btree ("tenant_id", "principal_kind", "principal_url");
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
INSERT INTO "entity_type_permission_grants" (
|
|
2
|
+
"tenant_id",
|
|
3
|
+
"entity_type",
|
|
4
|
+
"permission",
|
|
5
|
+
"subject_kind",
|
|
6
|
+
"subject_value"
|
|
7
|
+
)
|
|
8
|
+
SELECT
|
|
9
|
+
"entity_types"."tenant_id",
|
|
10
|
+
"entity_types"."name",
|
|
11
|
+
'manage',
|
|
12
|
+
'principal_kind',
|
|
13
|
+
'user'
|
|
14
|
+
FROM "entity_types"
|
|
15
|
+
WHERE "entity_types"."name" = 'horton'
|
|
16
|
+
AND NOT EXISTS (
|
|
17
|
+
SELECT 1
|
|
18
|
+
FROM "entity_type_permission_grants"
|
|
19
|
+
WHERE "entity_type_permission_grants"."tenant_id" = "entity_types"."tenant_id"
|
|
20
|
+
AND "entity_type_permission_grants"."entity_type" = "entity_types"."name"
|
|
21
|
+
AND "entity_type_permission_grants"."permission" = 'manage'
|
|
22
|
+
AND "entity_type_permission_grants"."subject_kind" = 'principal_kind'
|
|
23
|
+
AND "entity_type_permission_grants"."subject_value" = 'user'
|
|
24
|
+
AND "entity_type_permission_grants"."expires_at" IS NULL
|
|
25
|
+
);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
INSERT INTO "entity_type_permission_grants" (
|
|
2
|
+
"tenant_id",
|
|
3
|
+
"entity_type",
|
|
4
|
+
"permission",
|
|
5
|
+
"subject_kind",
|
|
6
|
+
"subject_value"
|
|
7
|
+
)
|
|
8
|
+
SELECT
|
|
9
|
+
"entity_types"."tenant_id",
|
|
10
|
+
"entity_types"."name",
|
|
11
|
+
'manage',
|
|
12
|
+
'principal_kind',
|
|
13
|
+
'user'
|
|
14
|
+
FROM "entity_types"
|
|
15
|
+
WHERE "entity_types"."name" = 'worker'
|
|
16
|
+
AND NOT EXISTS (
|
|
17
|
+
SELECT 1
|
|
18
|
+
FROM "entity_type_permission_grants"
|
|
19
|
+
WHERE "entity_type_permission_grants"."tenant_id" = "entity_types"."tenant_id"
|
|
20
|
+
AND "entity_type_permission_grants"."entity_type" = "entity_types"."name"
|
|
21
|
+
AND "entity_type_permission_grants"."permission" = 'manage'
|
|
22
|
+
AND "entity_type_permission_grants"."subject_kind" = 'principal_kind'
|
|
23
|
+
AND "entity_type_permission_grants"."subject_value" = 'user'
|
|
24
|
+
AND "entity_type_permission_grants"."expires_at" IS NULL
|
|
25
|
+
);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ALTER TABLE "entity_types" ADD COLUMN "slash_commands" jsonb;
|
|
@@ -78,6 +78,34 @@
|
|
|
78
78
|
"when": 1779062400000,
|
|
79
79
|
"tag": "0010_sandbox_profiles",
|
|
80
80
|
"breakpoints": true
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"idx": 11,
|
|
84
|
+
"version": "7",
|
|
85
|
+
"when": 1779050000000,
|
|
86
|
+
"tag": "0011_entity_permissions",
|
|
87
|
+
"breakpoints": true
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"idx": 12,
|
|
91
|
+
"version": "7",
|
|
92
|
+
"when": 1780584581000,
|
|
93
|
+
"tag": "0012_horton_user_manage_permission",
|
|
94
|
+
"breakpoints": true
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"idx": 13,
|
|
98
|
+
"version": "7",
|
|
99
|
+
"when": 1780588695000,
|
|
100
|
+
"tag": "0013_worker_user_manage_permission",
|
|
101
|
+
"breakpoints": true
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"idx": 14,
|
|
105
|
+
"version": "7",
|
|
106
|
+
"when": 1780600000000,
|
|
107
|
+
"tag": "0014_entity_type_slash_commands",
|
|
108
|
+
"breakpoints": true
|
|
81
109
|
}
|
|
82
110
|
]
|
|
83
111
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@electric-ax/agents-server",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.17",
|
|
4
4
|
"description": "Electric Agents entity runtime server",
|
|
5
5
|
"author": "Durable Stream contributors",
|
|
6
6
|
"bin": {
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@anthropic-ai/sdk": "^0.78.0",
|
|
39
39
|
"@durable-streams/client": "^0.2.6",
|
|
40
|
-
"@durable-streams/server": "^0.3.
|
|
41
|
-
"@durable-streams/state": "^0.
|
|
40
|
+
"@durable-streams/server": "^0.3.7",
|
|
41
|
+
"@durable-streams/state": "^0.3.1",
|
|
42
42
|
"@electric-sql/client": "^1.5.20",
|
|
43
43
|
"@mariozechner/pi-agent-core": "^0.70.2",
|
|
44
44
|
"@opentelemetry/api": "^1.9.1",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"pino-pretty": "^13.0.0",
|
|
55
55
|
"postgres": "^3.4.0",
|
|
56
56
|
"undici": "^7.24.7",
|
|
57
|
-
"@electric-ax/agents-runtime": "0.3.
|
|
57
|
+
"@electric-ax/agents-runtime": "0.3.10"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"@types/node": "^22.19.15",
|
|
@@ -65,9 +65,9 @@
|
|
|
65
65
|
"tsx": "^4.19.0",
|
|
66
66
|
"typescript": "^5.0.0",
|
|
67
67
|
"vitest": "^4.1.0",
|
|
68
|
-
"@electric-ax/agents": "0.4.
|
|
69
|
-
"@electric-ax/agents-server-
|
|
70
|
-
"@electric-ax/agents-server-
|
|
68
|
+
"@electric-ax/agents": "0.4.14",
|
|
69
|
+
"@electric-ax/agents-server-ui": "0.4.17",
|
|
70
|
+
"@electric-ax/agents-server-conformance-tests": "0.1.11"
|
|
71
71
|
},
|
|
72
72
|
"files": [
|
|
73
73
|
"dist",
|
package/src/db/schema.ts
CHANGED
|
@@ -24,6 +24,7 @@ export const entityTypes = pgTable(
|
|
|
24
24
|
creationSchema: jsonb(`creation_schema`),
|
|
25
25
|
inboxSchemas: jsonb(`inbox_schemas`),
|
|
26
26
|
stateSchemas: jsonb(`state_schemas`),
|
|
27
|
+
slashCommands: jsonb(`slash_commands`),
|
|
27
28
|
serveEndpoint: text(`serve_endpoint`),
|
|
28
29
|
defaultDispatchPolicy: jsonb(`default_dispatch_policy`),
|
|
29
30
|
revision: integer(`revision`).notNull().default(1),
|
|
@@ -72,6 +73,197 @@ export const entities = pgTable(
|
|
|
72
73
|
]
|
|
73
74
|
)
|
|
74
75
|
|
|
76
|
+
export const entityTypePermissionGrants = pgTable(
|
|
77
|
+
`entity_type_permission_grants`,
|
|
78
|
+
{
|
|
79
|
+
id: bigserial(`id`, { mode: `number` }).primaryKey(),
|
|
80
|
+
tenantId: text(`tenant_id`).notNull().default(`default`),
|
|
81
|
+
entityType: text(`entity_type`).notNull(),
|
|
82
|
+
permission: text(`permission`).notNull(),
|
|
83
|
+
subjectKind: text(`subject_kind`).notNull(),
|
|
84
|
+
subjectValue: text(`subject_value`).notNull(),
|
|
85
|
+
createdBy: text(`created_by`),
|
|
86
|
+
expiresAt: timestamp(`expires_at`, { withTimezone: true }),
|
|
87
|
+
createdAt: timestamp(`created_at`, { withTimezone: true })
|
|
88
|
+
.notNull()
|
|
89
|
+
.defaultNow(),
|
|
90
|
+
updatedAt: timestamp(`updated_at`, { withTimezone: true })
|
|
91
|
+
.notNull()
|
|
92
|
+
.defaultNow(),
|
|
93
|
+
},
|
|
94
|
+
(table) => [
|
|
95
|
+
index(`idx_type_permission_grants_lookup`).on(
|
|
96
|
+
table.tenantId,
|
|
97
|
+
table.entityType,
|
|
98
|
+
table.permission,
|
|
99
|
+
table.subjectKind,
|
|
100
|
+
table.subjectValue
|
|
101
|
+
),
|
|
102
|
+
index(`idx_type_permission_grants_expiry`).on(
|
|
103
|
+
table.tenantId,
|
|
104
|
+
table.expiresAt
|
|
105
|
+
),
|
|
106
|
+
check(
|
|
107
|
+
`chk_type_permission_grants_permission`,
|
|
108
|
+
sql`${table.permission} IN ('spawn', 'manage')`
|
|
109
|
+
),
|
|
110
|
+
check(
|
|
111
|
+
`chk_type_permission_grants_subject_kind`,
|
|
112
|
+
sql`${table.subjectKind} IN ('principal', 'principal_kind')`
|
|
113
|
+
),
|
|
114
|
+
]
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
export const entityLineage = pgTable(
|
|
118
|
+
`entity_lineage`,
|
|
119
|
+
{
|
|
120
|
+
tenantId: text(`tenant_id`).notNull().default(`default`),
|
|
121
|
+
ancestorUrl: text(`ancestor_url`).notNull(),
|
|
122
|
+
descendantUrl: text(`descendant_url`).notNull(),
|
|
123
|
+
depth: integer(`depth`).notNull(),
|
|
124
|
+
createdAt: timestamp(`created_at`, { withTimezone: true })
|
|
125
|
+
.notNull()
|
|
126
|
+
.defaultNow(),
|
|
127
|
+
},
|
|
128
|
+
(table) => [
|
|
129
|
+
primaryKey({
|
|
130
|
+
columns: [table.tenantId, table.ancestorUrl, table.descendantUrl],
|
|
131
|
+
}),
|
|
132
|
+
index(`idx_entity_lineage_descendant`).on(
|
|
133
|
+
table.tenantId,
|
|
134
|
+
table.descendantUrl
|
|
135
|
+
),
|
|
136
|
+
check(`chk_entity_lineage_depth`, sql`${table.depth} >= 0`),
|
|
137
|
+
]
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
export const entityPermissionGrants = pgTable(
|
|
141
|
+
`entity_permission_grants`,
|
|
142
|
+
{
|
|
143
|
+
id: bigserial(`id`, { mode: `number` }).primaryKey(),
|
|
144
|
+
tenantId: text(`tenant_id`).notNull().default(`default`),
|
|
145
|
+
entityUrl: text(`entity_url`).notNull(),
|
|
146
|
+
permission: text(`permission`).notNull(),
|
|
147
|
+
subjectKind: text(`subject_kind`).notNull(),
|
|
148
|
+
subjectValue: text(`subject_value`).notNull(),
|
|
149
|
+
propagation: text(`propagation`).notNull().default(`self`),
|
|
150
|
+
copyToChildren: boolean(`copy_to_children`).notNull().default(false),
|
|
151
|
+
createdBy: text(`created_by`),
|
|
152
|
+
expiresAt: timestamp(`expires_at`, { withTimezone: true }),
|
|
153
|
+
createdAt: timestamp(`created_at`, { withTimezone: true })
|
|
154
|
+
.notNull()
|
|
155
|
+
.defaultNow(),
|
|
156
|
+
updatedAt: timestamp(`updated_at`, { withTimezone: true })
|
|
157
|
+
.notNull()
|
|
158
|
+
.defaultNow(),
|
|
159
|
+
},
|
|
160
|
+
(table) => [
|
|
161
|
+
index(`idx_entity_permission_grants_entity`).on(
|
|
162
|
+
table.tenantId,
|
|
163
|
+
table.entityUrl
|
|
164
|
+
),
|
|
165
|
+
index(`idx_entity_permission_grants_subject`).on(
|
|
166
|
+
table.tenantId,
|
|
167
|
+
table.permission,
|
|
168
|
+
table.subjectKind,
|
|
169
|
+
table.subjectValue
|
|
170
|
+
),
|
|
171
|
+
index(`idx_entity_permission_grants_expiry`).on(
|
|
172
|
+
table.tenantId,
|
|
173
|
+
table.expiresAt
|
|
174
|
+
),
|
|
175
|
+
check(
|
|
176
|
+
`chk_entity_permission_grants_permission`,
|
|
177
|
+
sql`${table.permission} IN ('read', 'write', 'delete', 'signal', 'fork', 'schedule', 'spawn', 'manage')`
|
|
178
|
+
),
|
|
179
|
+
check(
|
|
180
|
+
`chk_entity_permission_grants_subject_kind`,
|
|
181
|
+
sql`${table.subjectKind} IN ('principal', 'principal_kind')`
|
|
182
|
+
),
|
|
183
|
+
check(
|
|
184
|
+
`chk_entity_permission_grants_propagation`,
|
|
185
|
+
sql`${table.propagation} IN ('self', 'descendants')`
|
|
186
|
+
),
|
|
187
|
+
]
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
export const entityEffectivePermissions = pgTable(
|
|
191
|
+
`entity_effective_permissions`,
|
|
192
|
+
{
|
|
193
|
+
id: bigserial(`id`, { mode: `number` }).primaryKey(),
|
|
194
|
+
tenantId: text(`tenant_id`).notNull().default(`default`),
|
|
195
|
+
entityUrl: text(`entity_url`).notNull(),
|
|
196
|
+
sourceEntityUrl: text(`source_entity_url`).notNull(),
|
|
197
|
+
sourceGrantId: bigint(`source_grant_id`, { mode: `number` }).notNull(),
|
|
198
|
+
permission: text(`permission`).notNull(),
|
|
199
|
+
subjectKind: text(`subject_kind`).notNull(),
|
|
200
|
+
subjectValue: text(`subject_value`).notNull(),
|
|
201
|
+
expiresAt: timestamp(`expires_at`, { withTimezone: true }),
|
|
202
|
+
createdAt: timestamp(`created_at`, { withTimezone: true })
|
|
203
|
+
.notNull()
|
|
204
|
+
.defaultNow(),
|
|
205
|
+
},
|
|
206
|
+
(table) => [
|
|
207
|
+
unique(`uq_entity_effective_permission`).on(
|
|
208
|
+
table.tenantId,
|
|
209
|
+
table.entityUrl,
|
|
210
|
+
table.sourceGrantId
|
|
211
|
+
),
|
|
212
|
+
index(`idx_entity_effective_permissions_lookup`).on(
|
|
213
|
+
table.tenantId,
|
|
214
|
+
table.permission,
|
|
215
|
+
table.subjectKind,
|
|
216
|
+
table.subjectValue,
|
|
217
|
+
table.entityUrl
|
|
218
|
+
),
|
|
219
|
+
index(`idx_entity_effective_permissions_entity`).on(
|
|
220
|
+
table.tenantId,
|
|
221
|
+
table.entityUrl
|
|
222
|
+
),
|
|
223
|
+
index(`idx_entity_effective_permissions_expiry`).on(
|
|
224
|
+
table.tenantId,
|
|
225
|
+
table.expiresAt
|
|
226
|
+
),
|
|
227
|
+
check(
|
|
228
|
+
`chk_entity_effective_permissions_permission`,
|
|
229
|
+
sql`${table.permission} IN ('read', 'write', 'delete', 'signal', 'fork', 'schedule', 'spawn', 'manage')`
|
|
230
|
+
),
|
|
231
|
+
check(
|
|
232
|
+
`chk_entity_effective_permissions_subject_kind`,
|
|
233
|
+
sql`${table.subjectKind} IN ('principal', 'principal_kind')`
|
|
234
|
+
),
|
|
235
|
+
]
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
export const sharedStateLinks = pgTable(
|
|
239
|
+
`shared_state_links`,
|
|
240
|
+
{
|
|
241
|
+
tenantId: text(`tenant_id`).notNull().default(`default`),
|
|
242
|
+
sharedStateId: text(`shared_state_id`).notNull(),
|
|
243
|
+
ownerEntityUrl: text(`owner_entity_url`).notNull(),
|
|
244
|
+
manifestKey: text(`manifest_key`).notNull(),
|
|
245
|
+
createdAt: timestamp(`created_at`, { withTimezone: true })
|
|
246
|
+
.notNull()
|
|
247
|
+
.defaultNow(),
|
|
248
|
+
updatedAt: timestamp(`updated_at`, { withTimezone: true })
|
|
249
|
+
.notNull()
|
|
250
|
+
.defaultNow(),
|
|
251
|
+
},
|
|
252
|
+
(table) => [
|
|
253
|
+
primaryKey({
|
|
254
|
+
columns: [table.tenantId, table.ownerEntityUrl, table.manifestKey],
|
|
255
|
+
}),
|
|
256
|
+
index(`idx_shared_state_links_shared_state`).on(
|
|
257
|
+
table.tenantId,
|
|
258
|
+
table.sharedStateId
|
|
259
|
+
),
|
|
260
|
+
index(`idx_shared_state_links_owner`).on(
|
|
261
|
+
table.tenantId,
|
|
262
|
+
table.ownerEntityUrl
|
|
263
|
+
),
|
|
264
|
+
]
|
|
265
|
+
)
|
|
266
|
+
|
|
75
267
|
export const users = pgTable(
|
|
76
268
|
`users`,
|
|
77
269
|
{
|
|
@@ -436,6 +628,8 @@ export const entityBridges = pgTable(
|
|
|
436
628
|
sourceRef: text(`source_ref`).notNull(),
|
|
437
629
|
tags: jsonb(`tags`).notNull(),
|
|
438
630
|
streamUrl: text(`stream_url`).notNull(),
|
|
631
|
+
principalUrl: text(`principal_url`),
|
|
632
|
+
principalKind: text(`principal_kind`),
|
|
439
633
|
shapeHandle: text(`shape_handle`),
|
|
440
634
|
shapeOffset: text(`shape_offset`),
|
|
441
635
|
lastObserverActivityAt: timestamp(`last_observer_activity_at`, {
|
|
@@ -453,6 +647,11 @@ export const entityBridges = pgTable(
|
|
|
453
647
|
(table) => [
|
|
454
648
|
primaryKey({ columns: [table.tenantId, table.sourceRef] }),
|
|
455
649
|
unique(`uq_entity_bridges_stream_url`).on(table.tenantId, table.streamUrl),
|
|
650
|
+
index(`idx_entity_bridges_principal`).on(
|
|
651
|
+
table.tenantId,
|
|
652
|
+
table.principalKind,
|
|
653
|
+
table.principalUrl
|
|
654
|
+
),
|
|
456
655
|
]
|
|
457
656
|
)
|
|
458
657
|
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import type {
|
|
6
6
|
PullWakeRunnerHealth,
|
|
7
|
+
SlashCommandDefinition,
|
|
7
8
|
WebhookNotification,
|
|
8
9
|
} from '@electric-ax/agents-runtime'
|
|
9
10
|
import type { Principal } from './principal.js'
|
|
@@ -67,6 +68,78 @@ export type RunnerKind = `local` | `cloud-worker` | `sandbox` | `ci` | `server`
|
|
|
67
68
|
export type RunnerAdminStatus = `enabled` | `disabled`
|
|
68
69
|
export type RunnerLiveness = `online` | `offline`
|
|
69
70
|
|
|
71
|
+
export type PermissionSubjectKind = `principal` | `principal_kind`
|
|
72
|
+
export type PermissionSubject = {
|
|
73
|
+
subject_kind: PermissionSubjectKind
|
|
74
|
+
subject_value: string
|
|
75
|
+
}
|
|
76
|
+
export type EntityPermission =
|
|
77
|
+
| `read`
|
|
78
|
+
| `write`
|
|
79
|
+
| `delete`
|
|
80
|
+
| `signal`
|
|
81
|
+
| `fork`
|
|
82
|
+
| `schedule`
|
|
83
|
+
| `spawn`
|
|
84
|
+
| `manage`
|
|
85
|
+
export type EntityTypePermission = `spawn` | `manage`
|
|
86
|
+
export type EntityPermissionPropagation = `self` | `descendants`
|
|
87
|
+
|
|
88
|
+
export interface EntityPermissionGrant extends PermissionSubject {
|
|
89
|
+
id: number
|
|
90
|
+
entity_url: string
|
|
91
|
+
permission: EntityPermission
|
|
92
|
+
propagation: EntityPermissionPropagation
|
|
93
|
+
copy_to_children: boolean
|
|
94
|
+
created_by?: string
|
|
95
|
+
expires_at?: string
|
|
96
|
+
created_at: string
|
|
97
|
+
updated_at: string
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface EntityTypePermissionGrant extends PermissionSubject {
|
|
101
|
+
id: number
|
|
102
|
+
entity_type: string
|
|
103
|
+
permission: EntityTypePermission
|
|
104
|
+
created_by?: string
|
|
105
|
+
expires_at?: string
|
|
106
|
+
created_at: string
|
|
107
|
+
updated_at: string
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface EntityTypePermissionGrantInput extends PermissionSubject {
|
|
111
|
+
permission: EntityTypePermission
|
|
112
|
+
expires_at?: string
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export type AuthorizationResource =
|
|
116
|
+
| { kind: `entity`; entity: ElectricAgentsEntity }
|
|
117
|
+
| { kind: `entity_type`; entityType: ElectricAgentsEntityType }
|
|
118
|
+
| { kind: `entity_type_registration`; entityTypeName: string }
|
|
119
|
+
| {
|
|
120
|
+
kind: `shared_state`
|
|
121
|
+
sharedStateId: string
|
|
122
|
+
linkedEntityUrls: Array<string>
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export type AuthorizationDecision = {
|
|
126
|
+
decision: `allow` | `deny`
|
|
127
|
+
expires_at?: string
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export type AuthorizeRequest = (input: {
|
|
131
|
+
tenant: string
|
|
132
|
+
principal: Principal
|
|
133
|
+
verb: EntityPermission | EntityTypePermission
|
|
134
|
+
resource: AuthorizationResource
|
|
135
|
+
request?: {
|
|
136
|
+
method: string
|
|
137
|
+
url: string
|
|
138
|
+
headers: Record<string, string>
|
|
139
|
+
}
|
|
140
|
+
builtInAllowed: boolean
|
|
141
|
+
}) => Promise<AuthorizationDecision> | AuthorizationDecision
|
|
142
|
+
|
|
70
143
|
const VALID_RUNNER_KINDS = new Set<string>([
|
|
71
144
|
`local`,
|
|
72
145
|
`cloud-worker`,
|
|
@@ -356,7 +429,6 @@ export interface ElectricAgentsEntity {
|
|
|
356
429
|
status: EntityStatus
|
|
357
430
|
streams: {
|
|
358
431
|
main: string
|
|
359
|
-
error: string
|
|
360
432
|
}
|
|
361
433
|
subscription_id: string
|
|
362
434
|
dispatch_policy?: DispatchPolicy
|
|
@@ -385,7 +457,7 @@ export interface PublicElectricAgentsEntity {
|
|
|
385
457
|
url: string
|
|
386
458
|
type: string
|
|
387
459
|
status: EntityStatus
|
|
388
|
-
streams: { main: string
|
|
460
|
+
streams: { main: string }
|
|
389
461
|
dispatch_policy?: DispatchPolicy
|
|
390
462
|
tags: Record<string, string>
|
|
391
463
|
spawn_args?: Record<string, unknown>
|
|
@@ -428,6 +500,7 @@ export interface ElectricAgentsEntityType {
|
|
|
428
500
|
creation_schema?: Record<string, unknown>
|
|
429
501
|
inbox_schemas?: Record<string, Record<string, unknown>>
|
|
430
502
|
state_schemas?: Record<string, Record<string, unknown>>
|
|
503
|
+
slash_commands?: Array<SlashCommandDefinition>
|
|
431
504
|
serve_endpoint?: string
|
|
432
505
|
default_dispatch_policy?: DispatchPolicy
|
|
433
506
|
revision: number
|
|
@@ -441,8 +514,10 @@ export interface RegisterEntityTypeRequest {
|
|
|
441
514
|
creation_schema?: Record<string, unknown>
|
|
442
515
|
inbox_schemas?: Record<string, Record<string, unknown>>
|
|
443
516
|
state_schemas?: Record<string, Record<string, unknown>>
|
|
517
|
+
slash_commands?: Array<SlashCommandDefinition>
|
|
444
518
|
serve_endpoint?: string
|
|
445
519
|
default_dispatch_policy?: DispatchPolicy
|
|
520
|
+
permission_grants?: Array<EntityTypePermissionGrantInput>
|
|
446
521
|
}
|
|
447
522
|
|
|
448
523
|
export interface TypedSpawnRequest {
|
|
@@ -458,6 +533,7 @@ export interface TypedSpawnRequest {
|
|
|
458
533
|
*/
|
|
459
534
|
sandbox?: SandboxChoice
|
|
460
535
|
initialMessage?: unknown
|
|
536
|
+
initialMessageType?: string
|
|
461
537
|
created_by?: string
|
|
462
538
|
wake?: {
|
|
463
539
|
subscriberUrl: string
|
|
@@ -477,6 +553,8 @@ export interface TypedSpawnRequest {
|
|
|
477
553
|
|
|
478
554
|
export interface SendRequest {
|
|
479
555
|
from?: string
|
|
556
|
+
from_principal?: string
|
|
557
|
+
from_agent?: string
|
|
480
558
|
payload?: unknown
|
|
481
559
|
key?: string
|
|
482
560
|
type?: string
|