Skip to content

feat(schema): goals primitive — top-level handle for watcher hierarchy#813

Merged
buremba merged 2 commits into
mainfrom
feat/goals-primitive
May 17, 2026
Merged

feat(schema): goals primitive — top-level handle for watcher hierarchy#813
buremba merged 2 commits into
mainfrom
feat/goals-primitive

Conversation

@buremba
Copy link
Copy Markdown
Member

@buremba buremba commented May 17, 2026

Summary

  • New goals table (org-scoped: slug, name, status, optional template_key, jsonb metadata) plus a nullable watchers.goal_id FK with ON DELETE SET NULL. Goals are the user-facing intent that groups watchers; watchers stay the executable unit.
  • manage_goals admin tool with create / update / get / list / archive / delete actions, wired into the REST tool surface, ClientSDK (client.goals), method-metadata, and tool-access policy. Modeled on manage_view_templates + manage_feeds.
  • manage_watchers accepts an optional goal_id on create/update (with cross-org validation — the FK alone would accept any goal id). WatcherMetadata + get_watcher now surface goal_id.
  • Idempotent embedded-schema patch (goals-primitive) mirrors the dbmate migration so PGlite restarts replay cleanly. Sequenced after agents-per-org-pk-phase-c; composes with the pending watcher-schema additions (Watcher schema: add device_worker_id, agent_kind, notification opt-in, cooldown columns #799) since the only watcher-side change here is the new column.

Test plan

  • make typecheck clean.
  • make build-packages clean (server + cli bundles).
  • bun test src/__tests__/unit/sandbox/method-metadata.test.ts — 8/8 passing (covers the new goals.* metadata + SDK enumeration).
  • Boot the embedded server on PGlite twice against the same LOBU_DATA_DIR — first boot runs migrations, second boot runs the embedded patch (goals-primitive) without errors and emits the expected "already exists, skipping" notices for the idempotent paths.
  • CI integration suite (goals-crud.test.ts) covering create/update/list/archive/delete, cross-org isolation, slug validation, duplicate rejection, watcher linkage, ON DELETE SET NULL on goal delete, cross-org goal_id rejection, and org-cascade.

Scope

Closes #800. Composes with #796 (product epic) and is the schema dependency for #801 (canvas surface). Goal-template loading from on-disk YAML is out of scope — that's a separate owletto-side concern.

Notes

Summary by CodeRabbit

  • New Features

    • Goals management: create, update, list, get, archive, delete organization-scoped goals with status, metadata, and unique slugs
    • Link/unlink watchers to goals; watcher responses now include goal_id
    • SDK/tooling: new goals namespace in client SDK and admin/manage_goals tool added
  • Tests

    • Integration tests covering full goals CRUD, watcher linking/unlinking, and org-scoped behavior

Review Change Stack

Adds a `goals` table (org-scoped, slug + name + status + optional template
key + jsonb metadata) and a nullable `watchers.goal_id` FK (ON DELETE SET
NULL). Goals are the surface the canvas (#801) hangs off — watchers stay
the executable unit, goals are the user-facing intent that groups them.

- Migration: db/migrations/20260517150000_goals_primitive.sql + idempotent
  mirror in packages/server/src/db/embedded-schema-patches.ts so PGlite
  restarts replay cleanly. Boot-twice verified locally.
- Tool: manage_goals (create/update/get/list/archive/delete) modeled on
  manage_view_templates + manage_feeds, with org-scope checks, lifecycle
  events, and slug validation. Wired into the REST tool surface,
  ClientSDK (client.goals), method-metadata, and tool-access policy.
- Watchers: optional goal_id on create/update with org-scope validation
  (the FK alone allows cross-org goal ids). Surfaced in WatcherMetadata
  and selected by get_watcher.
- Tests: goals-crud integration suite covers create/update/list/archive/
  delete, cross-org isolation, slug validation, duplicate rejection,
  watcher linkage, ON DELETE SET NULL on goal delete, cross-org goal_id
  rejection, and org-cascade.

Refs #800. Composes with #796 (product epic) and #801 (canvas surface).
Goal-template loading from disk remains out of scope — owletto-side.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 17, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7f6eaa63-e42d-4989-85ff-41cbda971ea7

📥 Commits

Reviewing files that changed from the base of the PR and between b5ecea0 and 5c2cfbf.

📒 Files selected for processing (3)
  • db/schema.sql
  • packages/server/src/db/embedded-schema-patches.ts
  • packages/server/src/tools/admin/manage_watchers.ts

📝 Walkthrough

Walkthrough

Adds an organization-scoped goals primitive: DB migration and embedded patch, admin manage_goals tool with CRUD/list/archive/delete, sandbox SDK + ClientSDK wiring, watcher FK integration (goal_id), auth/tool registration, and end-to-end integration tests.

Changes

Goals Feature — Database, API, and SDK

Layer / File(s) Summary
Database schema and migrations
db/migrations/20260517150000_goals_primitive.sql, db/schema.sql, packages/server/src/db/embedded-schema-patches.ts
Adds public.goals table, sequence, status CHECK, (organization_id, slug) uniqueness, nullable public.watchers.goal_id with ON DELETE SET NULL, indexes, and migration up/down plus embedded schema patch.
Goal management tool implementation
packages/server/src/tools/admin/manage_goals.ts
Implements manage_goals with create, update, get, list, archive, delete. Validates slug/status, enforces org scoping, supports tri-state updates and metadata merge/replace, enriches with watcher_count, and emits lifecycle events.
Sandbox SDK namespace and method metadata
packages/server/src/sandbox/namespaces/goals.ts, packages/server/src/sandbox/method-metadata.ts, packages/server/src/sandbox/namespaces/index.ts
Adds GoalsNamespace, input/filter types, buildGoalsNamespace, and METHOD_METADATA entries for goal operations.
ClientSDK integration and test client binding
packages/server/src/sandbox/client-sdk.ts, packages/server/src/__tests__/setup/test-mcp-client.ts
Wires goals into ClientSDK, extends interface with goals: GoalsNamespace, constructs the namespace in the SDK builder, and exposes client.goals in test helper.
Watcher and goal FK integration
packages/server/src/sandbox/namespaces/watchers.ts, packages/server/src/tools/admin/manage_watchers.ts, packages/server/src/tools/get_watchers.ts, packages/server/src/types/watchers.ts
Adds optional `goal_id?: number
Authorization policies and tool registration
packages/server/src/auth/tool-access.ts, packages/server/src/tools/admin/index.ts
Adds manage_goals to member-write (create,update,archive,delete) and public-read (get,list) action lists and registers the manage_goals tool in the admin tool index.
Goal lifecycle and watcher integration tests
packages/server/src/__tests__/integration/watchers/goals-crud.test.ts
Integration tests cover create/read/update (metadata merge/replace), list by status, slug validation, UNIQUE constraint, cross-org isolation, watcher linking/unlinking, cascade null on goal delete, org deletion cascade, and tool-surface smoke checks.

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested labels

skip-size-check

"I hopped through rows of schema and code,
Planted a goal where watchers can grow.
Slugs and statuses keep order true,
Link, unlink, archive — the pathways new.
A rabbit cheers for tests that show."

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 29.41% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: introduction of a 'goals' primitive as a top-level organizational handle for watcher hierarchy.
Description check ✅ Passed The PR description is comprehensive, covering summary, implementation details, test plan, scope, and notes. It aligns well with the template structure despite not using explicit section headers.
Linked Issues check ✅ Passed All primary coding requirements from issue #800 are met: goals table with org scoping and constraints, goal_id FK in watchers with ON DELETE SET NULL, CRUD operations via manage_goals tool, watcher linkage support, and cross-org isolation.
Out of Scope Changes check ✅ Passed All changes directly support the goals primitive implementation. The watcher-related modifications are necessary for goal linkage, SDK/tool surface integration is required for API exposure, and embedded patches are needed for PGlite compatibility.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/goals-primitive

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b5ecea0198

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +360 to +364
goal_id: Type.Optional(
Type.Union([Type.Number(), Type.Null()], {
description:
'[create/update] Optional goal_id linking this watcher to a goal (manage_goals). Pass null to unlink.',
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Expose goal_id in watcher list results

When callers set goal_id on create/update, handleList still does not select i.goal_id, so client.watchers.list() / list_watchers cannot tell which returned watchers belong to which goal. In the canvas/hierarchy flow this forces an N+1 watchers.get() lookup per watcher (or raw SQL) just to reconstruct the grouping, and bulk consumers will render goal-linked watchers as ungrouped. Include i.goal_id in the list SELECT/result whenever this field is accepted here.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
packages/server/src/__tests__/integration/watchers/goals-crud.test.ts (2)

108-134: ⚡ Quick win

Consider adding test coverage for invalid status values.

The test creates goals with valid statuses ('active', 'paused'), but there's no test verifying that invalid status values are rejected. The PR summary states that manage_goals validates status values.

Suggested additional test case
it('rejects an invalid status', async () => {
  await expect(
    owner.goals.create({ slug: 'test-invalid-status', name: 'Test', status: 'invalid_status' })
  ).rejects.toThrow(/invalid.*status/i);
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/server/src/__tests__/integration/watchers/goals-crud.test.ts` around
lines 108 - 134, Add a test that asserts invalid status values are rejected by
the goals API: call owner.goals.create with a bad status string (e.g.
'invalid_status') and assert the promise rejects with an error mentioning
invalid status (use expect(...).rejects.toThrow(/invalid.*status/i) or similar).
Place this alongside the existing tests in
packages/server/src/__tests__/integration/watchers/goals-crud.test.ts and
reference owner.goals.create for creation and owner.goals.delete if you need
cleanup; ensure the assertion checks that manage_goals validation prevents
invalid statuses.

167-220: ⚡ Quick win

Add verification for watcher counts in goal results.

The PR summary states that goal operations "enrich results with watcher counts," but the tests don't verify that watcher_count appears in the response. Consider adding assertions to verify this field after creating linked watchers.

Example verification

After creating a linked watcher (around line 180), verify the count:

const goalWithCount = (await owner.goals.get({ goal_id: goal.goal.id })) as {
  goal: { watcher_count: number };
};
expect(goalWithCount.goal.watcher_count).toBe(1);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/server/src/__tests__/integration/watchers/goals-crud.test.ts` around
lines 167 - 220, The test does not assert that goal responses include the new
watcher_count field; update the test around the watcher creation and subsequent
changes to call owner.goals.get({ goal_id }) and assert goal.watcher_count
equals expected values (e.g., 1 after creating the linked watcher, 0 after
unlink/goal delete). Specifically add checks using owner.goals.get for
goal.goal.id after owner.watchers.create, after owner.watchers.update(...
goal_id: null), and after owner.goals.delete to confirm watcher_count is updated
as expected.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/server/src/tools/admin/manage_goals.ts`:
- Around line 352-386: The handleList function currently returns total:
goals.length (capped by limit) which breaks pagination; add a separate COUNT
query using the same filters to compute the true total number of matching goals
for ctx.organizationId (and args.status when provided). Specifically, after
building/performing the main SELECT (or before), run a sql`SELECT COUNT(*)::int
FROM goals WHERE organization_id = ${ctx.organizationId}` and include the status
filter when args.status is set, store that count in a variable (e.g.,
totalCount) and return total: totalCount instead of goals.length; keep the
existing watcher_count query and limit/offset logic unchanged.

---

Nitpick comments:
In `@packages/server/src/__tests__/integration/watchers/goals-crud.test.ts`:
- Around line 108-134: Add a test that asserts invalid status values are
rejected by the goals API: call owner.goals.create with a bad status string
(e.g. 'invalid_status') and assert the promise rejects with an error mentioning
invalid status (use expect(...).rejects.toThrow(/invalid.*status/i) or similar).
Place this alongside the existing tests in
packages/server/src/__tests__/integration/watchers/goals-crud.test.ts and
reference owner.goals.create for creation and owner.goals.delete if you need
cleanup; ensure the assertion checks that manage_goals validation prevents
invalid statuses.
- Around line 167-220: The test does not assert that goal responses include the
new watcher_count field; update the test around the watcher creation and
subsequent changes to call owner.goals.get({ goal_id }) and assert
goal.watcher_count equals expected values (e.g., 1 after creating the linked
watcher, 0 after unlink/goal delete). Specifically add checks using
owner.goals.get for goal.goal.id after owner.watchers.create, after
owner.watchers.update(... goal_id: null), and after owner.goals.delete to
confirm watcher_count is updated as expected.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 10a2928c-9251-4654-9f0f-f0d7ae5f86f7

📥 Commits

Reviewing files that changed from the base of the PR and between 76aaf2d and b5ecea0.

📒 Files selected for processing (16)
  • db/migrations/20260517150000_goals_primitive.sql
  • db/schema.sql
  • packages/server/src/__tests__/integration/watchers/goals-crud.test.ts
  • packages/server/src/__tests__/setup/test-mcp-client.ts
  • packages/server/src/auth/tool-access.ts
  • packages/server/src/db/embedded-schema-patches.ts
  • packages/server/src/sandbox/client-sdk.ts
  • packages/server/src/sandbox/method-metadata.ts
  • packages/server/src/sandbox/namespaces/goals.ts
  • packages/server/src/sandbox/namespaces/index.ts
  • packages/server/src/sandbox/namespaces/watchers.ts
  • packages/server/src/tools/admin/index.ts
  • packages/server/src/tools/admin/manage_goals.ts
  • packages/server/src/tools/admin/manage_watchers.ts
  • packages/server/src/tools/get_watchers.ts
  • packages/server/src/types/watchers.ts

Comment on lines +352 to +386
async function handleList(
args: Extract<ManageGoalsArgs, { action: 'list' }>,
ctx: ToolContext
): Promise<ManageGoalsResult> {
const sql = getDb();
await requireOrgReadAccess(sql, ctx);
if (!ctx.organizationId) {
throw new Error('Organization context is required');
}
if (args.status !== undefined) {
assertValidStatus(args.status);
}

const limit = Math.min(args.limit ?? 100, 500);
const offset = args.offset ?? 0;

const rows = args.status
? await sql`
SELECT g.*, (SELECT COUNT(*)::int FROM watchers w WHERE w.goal_id = g.id) AS watcher_count
FROM goals g
WHERE g.organization_id = ${ctx.organizationId} AND g.status = ${args.status}
ORDER BY g.created_at DESC
LIMIT ${limit} OFFSET ${offset}
`
: await sql`
SELECT g.*, (SELECT COUNT(*)::int FROM watchers w WHERE w.goal_id = g.id) AS watcher_count
FROM goals g
WHERE g.organization_id = ${ctx.organizationId}
ORDER BY g.created_at DESC
LIMIT ${limit} OFFSET ${offset}
`;

const goals = (rows as Record<string, unknown>[]).map(mapGoalRow);
return { action: 'list', goals, total: goals.length, limit, offset };
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix the total field to return actual database count, not fetched rows.

Line 385 returns total: goals.length, which is the count of rows fetched (capped by limit), not the total count of matching goals in the database. This breaks pagination—clients cannot determine whether more results exist.

For example, if there are 1,000 goals and limit=100, the response will incorrectly show total: 100 instead of total: 1000.

🔧 Proposed fix: add a separate COUNT query
  const limit = Math.min(args.limit ?? 100, 500);
  const offset = args.offset ?? 0;

+ // Get total count of matching goals
+ const countRows = args.status
+   ? await sql`
+       SELECT COUNT(*)::int AS total
+       FROM goals g
+       WHERE g.organization_id = ${ctx.organizationId} AND g.status = ${args.status}
+     `
+   : await sql`
+       SELECT COUNT(*)::int AS total
+       FROM goals g
+       WHERE g.organization_id = ${ctx.organizationId}
+     `;
+ const total = Number(countRows[0]?.total ?? 0);
+
  const rows = args.status
    ? await sql`
        SELECT g.*, (SELECT COUNT(*)::int FROM watchers w WHERE w.goal_id = g.id) AS watcher_count
        FROM goals g
        WHERE g.organization_id = ${ctx.organizationId} AND g.status = ${args.status}
        ORDER BY g.created_at DESC
        LIMIT ${limit} OFFSET ${offset}
      `
    : await sql`
        SELECT g.*, (SELECT COUNT(*)::int FROM watchers w WHERE w.goal_id = g.id) AS watcher_count
        FROM goals g
        WHERE g.organization_id = ${ctx.organizationId}
        ORDER BY g.created_at DESC
        LIMIT ${limit} OFFSET ${offset}
      `;

  const goals = (rows as Record<string, unknown>[]).map(mapGoalRow);
- return { action: 'list', goals, total: goals.length, limit, offset };
+ return { action: 'list', goals, total, limit, offset };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async function handleList(
args: Extract<ManageGoalsArgs, { action: 'list' }>,
ctx: ToolContext
): Promise<ManageGoalsResult> {
const sql = getDb();
await requireOrgReadAccess(sql, ctx);
if (!ctx.organizationId) {
throw new Error('Organization context is required');
}
if (args.status !== undefined) {
assertValidStatus(args.status);
}
const limit = Math.min(args.limit ?? 100, 500);
const offset = args.offset ?? 0;
const rows = args.status
? await sql`
SELECT g.*, (SELECT COUNT(*)::int FROM watchers w WHERE w.goal_id = g.id) AS watcher_count
FROM goals g
WHERE g.organization_id = ${ctx.organizationId} AND g.status = ${args.status}
ORDER BY g.created_at DESC
LIMIT ${limit} OFFSET ${offset}
`
: await sql`
SELECT g.*, (SELECT COUNT(*)::int FROM watchers w WHERE w.goal_id = g.id) AS watcher_count
FROM goals g
WHERE g.organization_id = ${ctx.organizationId}
ORDER BY g.created_at DESC
LIMIT ${limit} OFFSET ${offset}
`;
const goals = (rows as Record<string, unknown>[]).map(mapGoalRow);
return { action: 'list', goals, total: goals.length, limit, offset };
}
async function handleList(
args: Extract<ManageGoalsArgs, { action: 'list' }>,
ctx: ToolContext
): Promise<ManageGoalsResult> {
const sql = getDb();
await requireOrgReadAccess(sql, ctx);
if (!ctx.organizationId) {
throw new Error('Organization context is required');
}
if (args.status !== undefined) {
assertValidStatus(args.status);
}
const limit = Math.min(args.limit ?? 100, 500);
const offset = args.offset ?? 0;
// Get total count of matching goals
const countRows = args.status
? await sql`
SELECT COUNT(*)::int AS total
FROM goals g
WHERE g.organization_id = ${ctx.organizationId} AND g.status = ${args.status}
`
: await sql`
SELECT COUNT(*)::int AS total
FROM goals g
WHERE g.organization_id = ${ctx.organizationId}
`;
const total = Number(countRows[0]?.total ?? 0);
const rows = args.status
? await sql`
SELECT g.*, (SELECT COUNT(*)::int FROM watchers w WHERE w.goal_id = g.id) AS watcher_count
FROM goals g
WHERE g.organization_id = ${ctx.organizationId} AND g.status = ${args.status}
ORDER BY g.created_at DESC
LIMIT ${limit} OFFSET ${offset}
`
: await sql`
SELECT g.*, (SELECT COUNT(*)::int FROM watchers w WHERE w.goal_id = g.id) AS watcher_count
FROM goals g
WHERE g.organization_id = ${ctx.organizationId}
ORDER BY g.created_at DESC
LIMIT ${limit} OFFSET ${offset}
`;
const goals = (rows as Record<string, unknown>[]).map(mapGoalRow);
return { action: 'list', goals, total, limit, offset };
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/server/src/tools/admin/manage_goals.ts` around lines 352 - 386, The
handleList function currently returns total: goals.length (capped by limit)
which breaks pagination; add a separate COUNT query using the same filters to
compute the true total number of matching goals for ctx.organizationId (and
args.status when provided). Specifically, after building/performing the main
SELECT (or before), run a sql`SELECT COUNT(*)::int FROM goals WHERE
organization_id = ${ctx.organizationId}` and include the status filter when
args.status is set, store that count in a variable (e.g., totalCount) and return
total: totalCount instead of goals.length; keep the existing watcher_count query
and limit/offset logic unchanged.

# Conflicts:
#	db/schema.sql
#	packages/server/src/db/embedded-schema-patches.ts
@buremba buremba merged commit 70e2b6e into main May 17, 2026
14 of 19 checks passed
@buremba buremba deleted the feat/goals-primitive branch May 17, 2026 04:36
@codecov-commenter
Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

buremba added a commit that referenced this pull request May 17, 2026
#823)

Goals (added in #813) had no behavior of their own — just a nullable FK
from watchers.goal_id into a parallel `goals` table plus a parallel CRUD
surface. Agents already encapsulate the watcher-grouping use case via
watchers.agent_id; goals were the redundant layer. The Mac app stopped
using the primitive in lobu-ai/owletto#151, and the primitive never
shipped in a release (v7.0.0 doesn't include it; v7.1.0 is still open).

Removed:
- db/migrations/20260517160000_drop_goals_primitive.sql (drops the
  watchers.goal_id column and the goals table; reversible)
- packages/server/src/db/embedded-schema-patches.ts: drop the
  `goals-primitive` patch entry
- packages/server/src/tools/admin/manage_goals.ts and its registration
- packages/server/src/sandbox/namespaces/goals.ts (client.goals SDK)
  and its method-metadata entries
- goal_id from manage_watchers.ts (create/update/list), get_watchers.ts,
  and WatcherMetadata
- manage_goals from auth/tool-access scope tables
- goals-crud integration test

Untouched: the dispatcher (#814), the watcher schema additions from
#811, and packages/owletto (separate submodule).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add goals primitive — goal → watchers mapping

2 participants