Skip to content
Merged
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
32 changes: 32 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,38 @@ jobs:
node ./wrangler-env.ts d1 migrations apply APP_DB --remote --config
"$WRANGLER_CONFIG"

- name: 🪪 Materialize stable user IDs
shell: bash
env:
DEPLOY_URL: ${{ vars.APP_BASE_URL }}
CAPABILITY_REINDEX_SECRET: ${{ secrets.CAPABILITY_REINDEX_SECRET }}
run: |
set -euo pipefail
if [ -z "${DEPLOY_URL:-}" ]; then
echo "APP_BASE_URL is required for the stable user id migration." >&2
exit 1
fi
if [ -z "${CAPABILITY_REINDEX_SECRET:-}" ]; then
echo "CAPABILITY_REINDEX_SECRET is required for the stable user id migration." >&2
exit 1
fi

migration_url="${DEPLOY_URL%/}/__maintenance/backfill-stable-user-ids"
status="$(curl --silent --show-error --location --max-time 180 \
-X POST \
-H "Authorization: Bearer ${CAPABILITY_REINDEX_SECRET}" \
-H "Accept: application/json" \
--output stable-user-id-migration.json \
--write-out "%{http_code}" \
"$migration_url")"
cat stable-user-id-migration.json
echo
if [ "$status" -lt 200 ] || [ "$status" -ge 300 ]; then
echo "Stable user id migration failed with HTTP ${status}." >&2
exit 1
fi
node -e "const fs = require('node:fs'); const result = JSON.parse(fs.readFileSync('stable-user-id-migration.json', 'utf8')); if (result.ok !== true || result.failed !== 0) { console.error('stable-user-id-migration-incomplete', result); process.exit(1); } console.log('stable-user-id-migration-complete', result);"

- name: ☁️ Deploy to Cloudflare Workers
id: deploy
env:
Expand Down
29 changes: 17 additions & 12 deletions docs/contributing/adding-capabilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,20 +176,21 @@ metadata or session-scoped data.

### Admin domain

The `admin` domain is for MCP-accessible account administration and the narrow
platform-feedback reviewer surface. Capabilities in this domain must set
The `admin` domain is for MCP-accessible account administration and narrow
operator review/metadata surfaces. Capabilities in this domain must set
`requiredRole: 'admin'` and must preserve the RBAC privacy boundary from
[Authorization](./architecture/authorization.md). Admin access is limited to
user/role account metadata, sanitized audit metadata, and feedback that a user
explicitly approved for admin review.

Within the built-in `admin` MCP domain, platform feedback is the only capability
surface that reviews user-authored content. Its list capability returns triage
summaries without full submission details, while its get capability returns only
the approved submission. Admin feedback capabilities must not join or expose
unrelated account content. All other admin capabilities must never return or
join against user content tables such as packages, secrets, values, memories,
jobs, email, chat threads, storage buckets, OAuth grants, or remote connectors.
user/role account metadata, sanitized audit metadata, feedback that a user
explicitly approved for admin review, operator-owned system email, and the
documented metadata projection for activity on public community listings.

Platform feedback list/get is the only admin capability surface that reviews
user-authored private text, and only after explicit approval. Community activity
returns public-listing metadata, acting username, timestamps, and rating scores;
it omits rating notes and private forked package content. Admin capabilities
must not join or expose unrelated account content such as packages, secrets,
values, memories, jobs, user email, chat threads, storage buckets, OAuth grants,
or remote connectors.

The `summary` field returned by feedback list/get operations and the `details`
field returned by the get operation are untrusted user-authored content. Admin
Expand All @@ -205,12 +206,16 @@ Current admin capabilities:
- `admin_user_create`
- `admin_user_update`
- `admin_audit_log_query`
- `admin_user_usage`
- `admin_feature_flag_list`
- `admin_feature_flag_set`
- `admin_feature_flag_override`
- `admin_system_email_list`
- `admin_system_email_get`
- `admin_platform_feedback_list`
- `admin_platform_feedback_get`
- `admin_platform_feedback_update`
- `admin_community_activity_list`

When adding more admin actions, expose service-layer functions by adding new
`admin/*` capability files that call those service functions directly, set
Expand Down
38 changes: 36 additions & 2 deletions docs/contributing/architecture/authorization.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ crosses into the admin review surface only after the user explicitly approves
it. The exception covers that attributed submission and its triage state, never
unrelated user content.

Community activity metadata is a fourth narrow exception. Admins may see who
forked or rated a deliberately public community listing and when, plus rating
scores. This boundary never exposes forked package source, rating notes,
secrets, or unrelated account content.

For browser and MCP authentication mechanics, see
[Authentication](./authentication.md).

Expand Down Expand Up @@ -320,6 +325,28 @@ deletion, and attributed community reports can be reviewed and resolved. Those
community surfaces expose content users chose to publish or report; they do not
grant access to private package source or unrelated account content.

**Admins can inspect activity metadata for public community listings.** The
`admin_community_activity_list` capability returns a paginated, newest-first
feed of fork and rating rows with listing id/name/kody id, acting username,
timestamp, and rating scores. Existing storage does not distinguish a one-click
install from an ordinary fork, so both appear as `fork`. The capability omits
stable user ids, forked package/source ids, origin commits, target kody ids,
rating notes, private package source, and all unrelated account content. Fork
rows snapshot the public listing name and kody id so intentional listing
deletion does not erase retained fork provenance; legacy orphan rows whose
listing identity can no longer be recovered use explicit deleted/unknown
placeholders.

New fork and rating writes enqueue an opaque activity id for durable
`community.activity.recorded` package-subscription delivery. The Queue consumer
reloads only the same metadata projection and fans out only to packages whose
owners hold the admin role at processing time. A non-admin may declare the topic
but never receives it, and role revocation applies on the next attempt. Each
write gets a distinct event id for package-invocation idempotency. Deleted
activity is acknowledged as a permanent cancellation; transient lookup,
discovery, and package-invocation infrastructure failures retry and can reach
the dedicated DLQ.

**Admins can see** operator-owned system mail for reserved platform addresses
(`kody`, `support`, `abuse`, `postmaster`, `security`, and `admin`). That mail
is stored under `system:email` as platform content, not under Kent's or any
Expand All @@ -346,13 +373,20 @@ This boundary is enforced structurally:
boundary. Admin list reads use a summary projection that omits full details;
get and triage operations address only the selected approved submission. They
never join unrelated user-content tables.
4. **A shape test pins the admin users API payload.**
4. **Community activity has a dedicated role-gated metadata projection.**
`admin_community_activity_list` unions only `community_forks` and
`community_ratings`, uses snapshotted/public listing identity plus acting
username, and projects an explicit field allowlist. It never selects package
source, rating notes, email, stable user ids, or unrelated user-content
tables. Delivery reuses the same projection through fresh admin-owner
fan-out.
5. **A shape test pins the admin users API payload.**
`adminUserListItemFieldNames` in `admin-users.ts` defines the allowed fields
(`id`, `username`, `email`, `email_verified`, `email_verified_at`, `plan`,
`created_at`, `updated_at`, `roles`). The unit test in
`admin-users.node.test.ts` asserts every user object in the list response has
exactly those keys — an accidental widening fails `npm run validate`.
5. **Existing owner-only paths take no admin bypass.** Secret reveal remains
6. **Existing owner-only paths take no admin bypass.** Secret reveal remains
session-authenticated and owner-only.

Platform feedback remains user-owned for account lifecycle operations. Account
Expand Down
10 changes: 10 additions & 0 deletions docs/contributing/architecture/data-storage.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,16 @@ explicitly approved enters that role-gated admin surface. The stored submitter
id makes feedback attributed rather than anonymous; the exception never grants
admins access to unrelated account data.

Community forks and ratings remain user-owned rows for deletion and export, with
a narrow role-gated admin metadata projection. It joins those rows only to
public listing identity and the actor's username. Fork rows snapshot the public
listing name and kody id so retained provenance remains intelligible after a
listing is deleted. The projection never reads the forked Artifacts source, the
public snapshot file tree, rating notes, or unrelated account data. Actor
usernames resolve through the materialized stable-id index; email and stable
user ids remain absent from activity results and events. One-click installs and
ordinary forks share the same row shape and therefore appear as `fork`.

## Account deletion inventory

Account deletion is implemented in `packages/worker/src/app/account-deletion.ts`
Expand Down
15 changes: 15 additions & 0 deletions docs/contributing/architecture/primitives.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,21 @@ primitives:
- docs/contributing/architecture/authorization.md
- docs/guides/package-subscriptions.md

- id: community-activity-dispatch-queue
group: storage
name: Community activity dispatch queue
summary:
Durable opaque event delivery from persisted community activity to admin
packages.
code:
- packages/worker/src/community/activity-dispatch-queue
- packages/worker/src/community/activity-package-subscriptions
- packages/worker/src/community/activity-subscription-event
docs:
- docs/contributing/setup-manifest.md
- docs/contributing/architecture/authorization.md
- docs/guides/package-subscriptions.md

- id: community-assets-r2
group: storage
name: Community asset storage (R2)
Expand Down
33 changes: 33 additions & 0 deletions docs/contributing/community-packages.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ Core logic: `packages/worker/src/community/`
| `social-repo.ts` | D1 for profiles, follows, stars, activity, public package lists |
| `install.ts` | One-click install: fork + publish checks + projection publish |
| `repo.ts` | D1 queries |
| `activity-*` | Admin activity feed and durable admin subscription dispatch |
| `snapshot.ts` | KV snapshot I/O |
| `fork-scan.ts` | Manifest rewrite + cross-scope `kody:@…` / `kody.dependencies` scan |
| `og-image.ts` | Community listing 1200×630 PNG on the shared `#worker/og` pipeline |
Expand All @@ -175,6 +176,19 @@ id to the forker's scope, scans cross-scope references, calls
ratings from the listing owner. `reportCommunityListing` stores denormalized
listing metadata for the admin queue.

`listCommunityActivityForAdmin` exposes a role-gated metadata projection over
forks and ratings. Rows contain public listing identity, acting username,
timestamp, and rating scores; they omit rating notes, forked source/package ids,
stable user ids, and package source. Rating rows use `updated_at`, so the feed
shows the latest value for each user/listing rating. Since one-click install and
agent fork both persist through `community_forks`, historical data cannot
distinguish them and reports both as `fork`. New fork rows snapshot the public
listing name and kody id; migration `0070-community-fork-listing-snapshots.sql`
backfills existing rows while their listings still exist, preserving readable
fork provenance after a later hard delete. Pre-snapshot orphan rows use explicit
deleted/unknown placeholders. Actor usernames use the materialized stable-id
index; neither email nor stable user id enters the feed or event.

`installCommunityListing` (one-click install) composes `forkCommunityListing`
with `runRepoChecks` over the fork's rewritten snapshot files and, when checks
pass, `refreshSavedPackageProjection` — the same projection step
Expand Down Expand Up @@ -207,6 +221,9 @@ Capabilities:
- `community_set_trusted` (admin-only via `requiredRole`)
- `community_set_featured` (admin-only via `requiredRole`)

The admin domain also exposes `admin_community_activity_list`, guarded by
`requiredRole: 'admin'`, for the narrow operator activity feed.

Register the domain in `builtinDomains` and `capabilityDomainNames` like other
builtin domains (see [Adding capabilities](./adding-kody.md)). Do not surface
community listings through the general capability/package search path.
Expand Down Expand Up @@ -253,6 +270,13 @@ Queue shows reporter, reason, and listing metadata. Actions use double-confirm:
`resolveCommunityReport` in `service.ts` implements dismiss, delist, and delete.
`banCommunityUser` / `unbanCommunityUser` manage community-wide bans.

Successful fork and rating writes enqueue `{ eventId, kind, activityId }` for
durable `community.activity.recorded` delivery. The Queue consumer reloads the
same metadata-only projection and uses the shared admin package-subscription
fan-out, which resolves admin package owners fresh for every attempt. The event
therefore reaches only admin-owned subscribed packages and is suitable for
operator notifications such as Discord.

## Inert fork mechanism

Forks create an **`entity_sources`** row and Artifacts snapshot but **no**
Expand Down Expand Up @@ -284,6 +308,15 @@ contain cross-scope static imports or foreign `kody.dependencies` entries

Empty queries sort by Bayesian score, then `publishedAt`.

Fork counts are live `COUNT(*)` aggregates over `community_forks` grouped by
listing id. Detail reads always count the selected listing. Browse and search
counts are also correct for every materialized result, although unfiltered
browse intentionally ranks only the newest 500 candidates. The reported
production mismatch for `@kentcdodds/github` was therefore consistent with a
data snapshot/cache artifact rather than a defect in the aggregate SQL; the
worker integration test pins that a successful fork increments the surfaced
count.

`computeCommunityBayesianScore` in `service.ts` implements the prior so a few
5-star ratings do not beat many good ratings.

Expand Down
8 changes: 4 additions & 4 deletions docs/contributing/environment-variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,15 +150,15 @@ Worker bindings (see `packages/worker/wrangler.jsonc`):
require it because `WRANGLER_IS_LOCAL_DEV`, `SENTRY_ENVIRONMENT=test`, or a
missing non-production binding keeps search on the deterministic offline path.

Optional Worker secret:
Worker secrets:

- **`AI_GATEWAY_ID`** — Cloudflare AI Gateway id. When set, embedding calls use
the Workers AI binding `gateway` option so production and preview inference is
logged/routed through AI Gateway. When unset, the Worker calls Workers AI
directly.
- **`CAPABILITY_REINDEX_SECRET`** — Bearer token for
`POST /__maintenance/reindex-capabilities` (production deploy workflow calls
it after healthcheck when the GitHub secret is set). Use this endpoint after
- **`CAPABILITY_REINDEX_SECRET`** — required in production; bearer token for the
pre-deploy stable-user-id migration and
`POST /__maintenance/reindex-capabilities`. Use the reindex endpoint after
changing the embedding model, pooling, or Vectorize index dimensions; it
rebuilds built-in capability, memory, job, and saved-package vectors with
per-user `userId` metadata on user-owned rows. Local dev uses offline search
Expand Down
10 changes: 10 additions & 0 deletions docs/contributing/packages-and-manifests.md
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,16 @@ rerunning, making the DLQ the recovery surface. Terminal handler execution
failures remain isolated from sibling subscribers, and fan-out uses bounded
concurrency.

Successful community fork and rating writes similarly enqueue
`community.activity.recorded` for admin-only package-subscription delivery. The
event contains a unique event id, public listing id/name/kody id, activity kind,
acting username, timestamp, and rating scores when applicable. It omits stable
user ids, email, rating notes, forked source/package identifiers, package
source, and unrelated account content. One-click installs appear as `fork`
because both paths share the existing `community_forks` row shape. Consumer-time
admin role checks, lazy metadata reload, retry behavior, and terminal-handler
isolation match platform-feedback dispatch.

## Package-owned workflows

Packages declare workflow entrypoints in runtime code, not in
Expand Down
15 changes: 9 additions & 6 deletions docs/contributing/project-intent.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,14 @@ shared state between users.
Optimize for:

- Per-user isolation as a first-class invariant, enforced at the storage,
durable-object, vectorize, and runtime layers. Three narrow, documented
durable-object, vectorize, and runtime layers. Four narrow, documented
exceptions exist: RBAC account administration (`access = 'any'`, limited to
`user` and `role` entities), operator-owned system email for reserved platform
addresses stored under `system:email`, and attributed platform feedback that a
user explicitly approved for role-gated admin review. The feedback exception
covers only the approved submission and never unrelated user content. See
user explicitly approved for role-gated admin review, plus role-gated metadata
about activity on deliberately public community listings. The community
exception covers who forked or rated which listing and when, including rating
scores, but never package source, rating notes, or unrelated user content. See
[Authorization](./architecture/authorization.md).
- Fast iteration on the personal-assistant experience
- Interoperability across MCP-capable hosts
Expand Down Expand Up @@ -89,7 +91,8 @@ When working in this repo, do not assume:
cross-user boundaries are RBAC account administration (`:any` on `user`/`role`
only, behind explicit guards), operator-owned system email for reserved
platform addresses, and explicitly approved, attributed platform feedback
exposed through role-gated admin review capabilities — see
exposed through role-gated admin review capabilities, plus role-gated
community activity metadata for public listings — see
[Authorization](./architecture/authorization.md).
- The main goal is enterprise-grade least-privilege design for many users.

Expand Down Expand Up @@ -118,8 +121,8 @@ If you are an agent working in this repo:
at the runtime layer, and by user-aware filters at the search/vector layer.
Cross-user access requires an explicit guard and one of the documented narrow
boundaries: account administration, operator-owned system email, or
user-approved platform feedback — see
[Authorization](./architecture/authorization.md).
user-approved platform feedback, or public-listing community activity metadata
— see [Authorization](./architecture/authorization.md).
- Avoid proposing a large static MCP tool catalog as the default direction.
- Keep interoperability with MCP hosts in mind, especially around compact tool
surfaces and clear server instructions.
Loading
Loading