Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 28 additions & 8 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"scripts": {
"build": "./bun run --filter '@memory.build/cli' build",
"build:all": "./bun scripts/build-all.ts",
"check": "./bun i --silent && ./bun run typecheck && ./bun run lint --write && ./bun run test --only-failures",
"check": "./bun i --silent && ./bun scripts/bundle-web-assets.ts && ./bun run typecheck && ./bun run lint --write && ./bun run test --only-failures",
"clean": "rm -rf packages/cli/dist dist",
"docs": "./bun --filter @memory.build/docs-site dev",
"docs:build": "./bun --filter @memory.build/docs-site build",
Expand All @@ -21,6 +21,7 @@
"install:local": "./bun scripts/install-local.ts",
"lint": "biome check",
"me": "./bun run packages/cli/index.ts",
"migrate:db": "./bun scripts/migrate-db.ts",
"pg": "./bun run pg:build && docker run -d --name me-postgres -e POSTGRES_HOST_AUTH_METHOD=trust -p 127.0.0.1:5432:5432 me-postgres",
"pg:build": "docker build -t me-postgres -f docker/Dockerfile.postgres docker/",
"pg:rm": "docker rm -f me-postgres",
Expand Down
2 changes: 2 additions & 0 deletions packages/core/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { type MigrateCoreOptions, migrateCore } from "./migrate/migrate";
export { CORE_SCHEMA_VERSION } from "./version";
35 changes: 35 additions & 0 deletions packages/core/migrate/idempotent/000_update.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
-- generic trigger function to update updated_at timestamp
create or replace function core.update_updated_at()
returns trigger
as $func$
begin
new.updated_at = pg_catalog.now();
return new;
end;
$func$ language plpgsql volatile security definer
set search_path to core, pg_temp;

create or replace trigger space_before_update_trg
before update on core.space
for each row
execute function core.update_updated_at();

create or replace trigger principal_before_update_trg
before update on core.principal
for each row
execute function core.update_updated_at();

create or replace trigger principal_space_before_update_trg
before update on core.principal_space
for each row
execute function core.update_updated_at();

create or replace trigger group_member_before_update_trg
before update on core.group_member
for each row
execute function core.update_updated_at();

create or replace trigger tree_access_before_update_trg
before update on core.tree_access
for each row
execute function core.update_updated_at();
41 changes: 41 additions & 0 deletions packages/core/migrate/idempotent/001_principal_space.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
-------------------------------------------------------------------------------
-- is_principal_in_space
-------------------------------------------------------------------------------
create or replace function core.is_principal_in_space
( _principal_id uuid
, _space_id uuid
)
returns bool
as $func$
select exists
(
select 1
from core.principal_space ps
where ps.principal_id = _principal_id
and ps.space_id = _space_id
)
$func$ language sql stable security invoker
;

-------------------------------------------------------------------------------
-- is_principal_space_admin
-------------------------------------------------------------------------------
create or replace function core.is_principal_space_admin
( _principal_id uuid
, _space_id uuid
)
returns bool
as $func$
select coalesce
(
(
select ps.admin and (not p.kind = 'a') -- agents cannot be space admins
from core.principal_space ps
inner join core.principal p on (ps.principal_id = p.id)
where ps.principal_id = _principal_id
and ps.space_id = _space_id
)
, false
)
$func$ language sql stable security invoker
;
26 changes: 26 additions & 0 deletions packages/core/migrate/idempotent/002_group_member.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@

-------------------------------------------------------------------------------
-- member_groups
-------------------------------------------------------------------------------
create or replace function core.member_groups
( _member_id uuid
, _space_id uuid
)
returns table
( group_id uuid
, admin bool
)
as $func$
select
gm.group_id
, gm.admin and (not m.kind = 'a') -- agent's cannot be group admins
from core.principal m -- the member
-- assert the member belongs to the space
inner join core.principal_space psm on (m.id = psm.principal_id and psm.space_id = _space_id)
-- find the groups the member belongs to in the space
inner join core.group_member gm on (m.member_id = gm.member_id and gm.space_id = _space_id)
-- assert the group belongs to the space
inner join core.principal_space psg on (gm.group_id = psg.principal_id and psg.space_id = _space_id)
where m.member_id = _member_id -- the member
$func$ language sql stable security invoker
;
128 changes: 128 additions & 0 deletions packages/core/migrate/idempotent/003_tree_access.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
-------------------------------------------------------------------------------
-- member_tree_access
-------------------------------------------------------------------------------
create or replace function core.member_tree_access
( _member_id uuid
, _space_id uuid
)
returns table
( tree_path ltree
, access int
)
as $func$
-- member's grants via groups
select
ta.tree_path
, ta.access
from core.member_groups(_member_id, _space_id) mg
inner join core.tree_access ta on (mg.group_id = ta.principal_id and ta.space_id = _space_id)
$func$ language sql stable security invoker
;

-------------------------------------------------------------------------------
-- user_tree_access
-------------------------------------------------------------------------------
create or replace function core.user_tree_access
( _user_id uuid
, _space_id uuid
)
returns table
( tree_path ltree
, access int
)
as $func$
-- user's direct grants
select
ta.tree_path
, ta.access
from core.principal u
inner join core.principal_space psu on (u.id = psu.principal_id and psu.space_id = _space_id)
inner join core.tree_access ta on (u.id = ta.principal_id and ta.space_id = _space_id)
where u.user_id = _user_id
union
-- user's access via groups
select
x.tree_path
, x.access
from core.member_tree_access(_user_id, _space_id) x
$func$ language sql stable security invoker
;

-------------------------------------------------------------------------------
-- agent_tree_access
-------------------------------------------------------------------------------
create or replace function core.agent_tree_access
( _agent_id uuid
, _space_id uuid
)
returns table
( tree_path ltree
, access int
)
as $func$
with agent_access as materialized
(
-- agent's direct grants
select
ta.tree_path
, ta.access
from core.principal a
inner join core.principal_space ps on (a.id = ps.principal_id and ps.space_id = _space_id)
inner join core.tree_access ta on (a.id = ta.principal_id and ta.space_id = _space_id)
where a.agent_id = _agent_id
union
-- agent's access via groups
select
x.tree_path
, x.access
from core.member_tree_access(_agent_id, _space_id) x
)
, owner_access as materialized
(
-- get the access for the user that owns the agent
select
x.tree_path
, x.access
from
(
select p.owner_id
from core.principal p
where p.agent_id = _agent_id
) a
cross join lateral core.user_tree_access(a.owner_id, _space_id) x
)
select
x.tree_path
, max(x.access)
from
(
-- take the agent's access when it is covered by the owner's access
select
aa.tree_path
, aa.access
from agent_access aa
where exists
(
-- the owner must have access that is the same or greater than the agent's
select 1
from owner_access oa
where oa.tree_path @> aa.tree_path
and oa.access >= aa.access
)
union
-- when the agent has more access than the owner, take the owner's access
select
oa.tree_path
, oa.access
from owner_access oa
where exists
(
select 1
from agent_access aa
where aa.tree_path @> oa.tree_path
and aa.access >= oa.access
)
) x
group by x.tree_path
$func$ language sql stable security invoker
;
4 changes: 4 additions & 0 deletions packages/core/migrate/idempotent/sql.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
declare module "*.sql" {
const sql: string;
export default sql;
}
12 changes: 12 additions & 0 deletions packages/core/migrate/incremental/001_space.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
-------------------------------------------------------------------------------
-- space
-------------------------------------------------------------------------------
create table core.space
( id uuid not null primary key default uuidv7() check (uuid_extract_version(id) = 7)
, slug text not null unique check (slug ~ '^[a-z0-9]{12}$')
, name citext not null
, language text not null default 'english' check (language ~ '^[a-z_]+$')
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would not put language support in now

Copy link
Copy Markdown
Collaborator Author

@jgpruitt jgpruitt May 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one way or the other you have to configure pg_textsearch indexes with a language. The database migrations for the spaces are already wired to configure pg_textsearch languages.

-- we likely need columns for embedding provider, model, dimensions
, created_at timestamptz not null default now()
, updated_at timestamptz
);
Loading