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
Binary file modified .agents/skills/constructive-sdk-events.zip
Binary file not shown.
63 changes: 61 additions & 2 deletions .agents/skills/constructive-sdk-events/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: constructive-sdk-events
description: "Events, achievements, and gamification — EventTracker blueprint node for recording events on row changes, blueprint achievements[] for defining levels with requirements and credit rewards (limit_credit + meter_credit with expires_interval), invite-based achievements (has_invite_achievements), period-aware event_aggregates (lazy count reset), re-triggerable achievements (per-period re-qualification), EventReferral for attributing events to inviters, and the full virality chain. Use when asked to 'add analytics', 'track events', 'add achievements', 'gamification', 'record events', 'EventTracker', 'level requirements', 'achievement rewards', 'invite achievements', 'invite virality', 'credit grants for achievements', 'meter_credit', 'expires_interval', 'period_interval', 'recurring credits', 'referral credits', 'EventReferral', or when working with events_module in blueprints."
description: "Events, achievements, and gamification — EventTracker blueprint node for recording events on row changes, blueprint achievements[] for defining levels with requirements and credit rewards (limit_credit + meter_credit with expires_interval), invite-based achievements (has_invite_achievements), period-aware event_aggregates (lazy count reset), re-triggerable achievements (per-period re-qualification), EventReferral for attributing events to inviters (with multi-level max_depth for MLM referral chains), and the full virality chain. Use when asked to 'add analytics', 'track events', 'add achievements', 'gamification', 'record events', 'EventTracker', 'level requirements', 'achievement rewards', 'invite achievements', 'invite virality', 'credit grants for achievements', 'meter_credit', 'expires_interval', 'period_interval', 'recurring credits', 'referral credits', 'EventReferral', 'max_depth', 'multi-level referral', 'MLM', 'referral chain', or when working with events_module in blueprints."
metadata:
author: constructive-io
version: "1.0.0"
Expand All @@ -14,7 +14,7 @@ Three capabilities compose together:
- **`EventTracker`** — table-level node. Attach to any table to declaratively record events when rows change. Same compound condition system as JobTrigger.
- **`achievements[]`** — top-level blueprint section. Define levels with requirements (event counts) and optional rewards (`limit_credit` or `meter_credit` grants, with optional `expires_interval`).
- **`has_invite_achievements`** — entity type flag. Auto-attaches EventTracker to `claimed_invites` and wires the invitee achievement virality chain.
- **`EventReferral`** — entity type node. Wires referral attribution so that when an invitee's event is recorded, the inviter also gets an attributed event.
- **`EventReferral`** — table-level node. Wires referral attribution so that when an invitee performs an action, the inviter gets an attributed event. Supports `max_depth` (1–10) for multi-level referral chains — walks up the `claimed_invites` chain N levels, crediting each ancestor.
- **Period-aware counting** — event types with `period_interval` auto-reset aggregate counts each period (lazy reset). Enables re-triggerable achievements for recurring credit grants.

Related skills:
Expand Down Expand Up @@ -116,6 +116,64 @@ See [references/event-tracker.md](references/event-tracker.md) for compound cond

---

## EventReferral Blueprint Node

Add `EventReferral` to a table's `nodes[]` to credit the actor's inviter (and optionally their inviter's inviter, etc.) when a row changes:

```json
{
"tables": [{
"table_name": "user_uploads",
"nodes": [
{ "$type": "EventReferral", "data": {
"event_name": "invitee_uploaded",
"events": ["INSERT"],
"actor_field": "owner_id",
"max_depth": 5
}}
]
}]
}
```

### Configuration Reference

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `event_name` | string | **(required)** | Event type name to record for each ancestor in the invite chain |
| `events` | `("INSERT" \| "UPDATE" \| "DELETE")[]` | `["INSERT"]` | Which DML events fire the trigger |
| `actor_field` | string (column-ref) | `"owner_id"` | Column containing the invitee (actor) ID — used to look up the referrer via `claimed_invites.receiver_id` |
| `entity_field` | string (column-ref) | — | Column containing the entity ID for entity-scoped referral events. **Cannot be combined with `max_depth > 1`.** |
| `max_depth` | integer | `1` | How many levels up the invite chain to walk. `1` = direct inviter only (default, backward compatible). `2`–`10` = multi-level referral chain. Hard cap at 10. |
| `auto_register_type` | boolean | `true` | Automatically register the `event_name` in the `event_types` catalog during provisioning |
| `conditions` | object \| array | — | Compound conditions for WHEN clause (same syntax as EventTracker) |

### Toggles & Controls

- **Build-time:** `max_depth` is the toggle. Default `1` = today's single-hop behavior. Set `2`–`10` to opt in to multi-level. Omitting or setting to `1` produces the exact same trigger as before.
- **Runtime:** `event_types.is_active` — flip to `false` to pause referral rewards without redeploying.
- **Scope constraint:** `max_depth > 1` requires app-level scope only (`entity_field` must be omitted). The generator raises an exception if both are set.

### How Multi-Level Works

When `max_depth > 1`, the trigger builds a FOR loop that walks `claimed_invites`:

```
User action (INSERT on user_uploads)
→ trigger resolves NEW.owner_id
→ FOR depth IN 1..max_depth:
SELECT sender_id FROM claimed_invites WHERE receiver_id = current
EXIT WHEN no sender found
record_event(event_name, sender_id)
current := sender_id
```

Each ancestor in the chain receives the same event. Combined with tiered achievement thresholds, this creates natural attenuation — direct inviters accumulate events quickly (low threshold, high reward), while deeper ancestors accumulate slowly (high threshold, low reward).

See [references/event-referral.md](references/event-referral.md) for multi-level blueprint examples, the viral loop pattern, and attenuation design.

---

## Blueprint Achievements

The top-level `achievements[]` section defines levels with requirements and optional rewards. Processed in **Phase 7** of `constructBlueprint()` — after tables, relations, and entity types.
Expand Down Expand Up @@ -265,6 +323,7 @@ See [references/invite-virality.md](references/invite-virality.md) for detailed
| File | Contents |
|------|----------|
| [references/event-tracker.md](references/event-tracker.md) | Full EventTracker parameter reference, compound conditions, toggle mode, entity-scoped examples |
| [references/event-referral.md](references/event-referral.md) | EventReferral parameter reference, multi-level `max_depth` chain walk, MLM blueprint examples, attenuation design |
| [references/achievements.md](references/achievements.md) | Achievement definitions, requirements, rewards (limit_credit + meter_credit), expires_interval, period-aware aggregates, re-triggerable achievements |
| [references/invite-virality.md](references/invite-virality.md) | Simple + meta invite tiers, full virality chain, cross-entity examples |
| [references/triggers.md](references/triggers.md) | Internal trigger reference: tg_check_achievements, tg_achievement_reward, tg_invitee_achievement |
176 changes: 176 additions & 0 deletions .agents/skills/constructive-sdk-events/references/event-referral.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
# EventReferral Reference

EventReferral is a table-level blueprint node that attributes events to the actor's inviter(s) when a row changes. It resolves the referrer via the invites module's `claimed_invites` table.

## Single-Level Referral (Default)

With `max_depth: 1` (or omitted), EventReferral credits only the direct inviter:

```json
{
"tables": [{
"table_name": "user_profiles",
"nodes": [
{ "$type": "EventReferral", "data": {
"event_name": "invitee_completed_profile",
"events": ["UPDATE"],
"actor_field": "owner_id"
}}
]
}]
}
```

When User B (invited by User A) updates their profile:
1. Trigger fires → resolves `NEW.owner_id` (User B)
2. Looks up `claimed_invites WHERE receiver_id = B` → finds User A
3. Calls `record_event('invitee_completed_profile', A)`

## Multi-Level Referral (max_depth > 1)

Set `max_depth` to walk up the invite chain multiple levels:

```json
{
"tables": [{
"table_name": "user_uploads",
"nodes": [
{ "$type": "EventReferral", "data": {
"event_name": "invitee_uploaded",
"events": ["INSERT"],
"actor_field": "owner_id",
"max_depth": 5
}}
]
}]
}
```

When User D (chain: A invited B invited C invited D) uploads a file:
1. Depth 1: `claimed_invites WHERE receiver_id = D` → C. Records event for C.
2. Depth 2: `claimed_invites WHERE receiver_id = C` → B. Records event for B.
3. Depth 3: `claimed_invites WHERE receiver_id = B` → A. Records event for A.
4. Depth 4: `claimed_invites WHERE receiver_id = A` → NULL. Loop exits.

All three ancestors (C, B, A) receive the same `invitee_uploaded` event. The loop stops early if the chain is shorter than `max_depth`.

## Configuration Reference

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `event_name` | string | **(required)** | Event type name to record for each ancestor |
| `events` | `("INSERT" \| "UPDATE" \| "DELETE")[]` | `["INSERT"]` | Which DML events fire the trigger |
| `actor_field` | string (column-ref) | `"owner_id"` | Column containing the invitee (actor) ID |
| `entity_field` | string (column-ref) | — | Entity ID column for entity-scoped events. **Cannot be combined with `max_depth > 1`.** |
| `max_depth` | integer | `1` | Levels to walk up the invite chain. Range: 1–10. |
| `auto_register_type` | boolean | `true` | Auto-register `event_name` in `event_types` during provisioning |
| `conditions` | object \| array | — | Compound conditions for WHEN clause |

## Constraints

- **`max_depth` range:** 1–10. The generator raises an exception for values outside this range.
- **App-level scope only:** When `max_depth > 1`, `entity_field` must be omitted. The chain walk uses `claimed_invites` which is scoped by membership_type at the app level. Entity-scoped actions still credit the chain — the trigger resolves the *user* who performed the action, not the entity.
- **Backward compatible:** `max_depth: 1` (or omitted) produces the exact same single-lookup trigger as before the feature existed.

## Toggles

| Toggle | Type | How |
|--------|------|-----|
| Build-time on/off | `max_depth` parameter | `1` = off (single hop), `2`–`10` = on (multi-level) |
| Runtime on/off | `event_types.is_active` | Set to `false` to pause referral event recording without redeploying |

## Multi-Level MLM Blueprint Example

A complete blueprint showing 5-level referral rewards with tiered achievements:

```json
{
"entity_types": [
{
"name": "App Members",
"prefix": "app",
"has_invites": true,
"has_levels": true,
"has_limits": true
}
],

"tables": [
{
"table_name": "databases",
"fields": [
{ "name": "name", "type": "text", "is_required": true },
{ "name": "owner_id", "type": "uuid", "is_required": true }
],
"nodes": [
{ "$type": "EventReferral", "data": {
"event_name": "invitee_created_db",
"events": ["INSERT"],
"actor_field": "owner_id",
"max_depth": 5
}},
{ "$type": "LimitCounter", "data": { "limit_name": "databases" } }
]
}
],

"achievements": [
{
"name": "referral_bronze",
"description": "3 people in your network created a database",
"priority": 10,
"requirements": [
{ "event_name": "invitee_created_db", "count": 3 }
],
"rewards": [
{ "reward_type": "limit_credit", "target_name": "databases", "amount": 5 }
]
},
{
"name": "referral_silver",
"description": "10 people in your network created a database",
"priority": 20,
"requirements": [
{ "event_name": "invitee_created_db", "count": 10 }
],
"rewards": [
{ "reward_type": "limit_credit", "target_name": "databases", "amount": 3 }
]
},
{
"name": "referral_gold",
"description": "25 people in your network created a database",
"priority": 30,
"requirements": [
{ "event_name": "invitee_created_db", "count": 25 }
],
"rewards": [
{ "reward_type": "limit_credit", "target_name": "databases", "amount": 2 }
]
}
]
}
```

## Attenuation Design

Multi-level referrals create natural attenuation without any per-depth tracking:

- **Direct inviters** (depth 1) see events frequently — their invitees' actions directly generate events. They hit achievement thresholds quickly.
- **2nd-degree ancestors** see events less often — only when their invitees' invitees act.
- **5th-degree ancestors** accumulate events very slowly.

The tiered achievement thresholds (3 → 10 → 25) create decreasing rewards at each tier. Combined with the natural event decay at deeper levels, this produces an MLM-style attenuation curve without any schema changes or depth-tracking infrastructure.

### Performance

Each depth level is one indexed lookup on `claimed_invites(receiver_id)`. With `max_depth=10`, that's at most 10 index scans per trigger fire — negligible overhead.

## Composing with Invite Virality

EventReferral composes with `has_invite_achievements`. They serve different purposes:

- **`has_invite_achievements`**: Credits the inviter when an invite is *claimed* (`invite_claimed` event) and when an invitee *earns an achievement* (`invitee_achieved_*` events). Always single-level.
- **`EventReferral`**: Credits the inviter(s) when an invitee performs a *table action*. Supports multi-level via `max_depth`.

Both can be used simultaneously. A common pattern is `has_invite_achievements` for the social/gamification loop and `EventReferral` with `max_depth > 1` for the MLM referral reward chain.
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,21 @@ User A invites User B
→ A invites more people (viral loop)
```

## Multi-Level Referral Chains (EventReferral + max_depth)

For MLM-style referral rewards that go beyond direct inviters, use `EventReferral` with `max_depth > 1` on table nodes. This walks the `claimed_invites` chain up to N levels:

```
User A invites B, B invites C, C invites D

D creates a database (EventReferral with max_depth=5):
→ C gets 'invitee_created_db' event (depth 1)
→ B gets 'invitee_created_db' event (depth 2)
→ A gets 'invitee_created_db' event (depth 3)
```

This is separate from `has_invite_achievements` (which only tracks invite claims and achievement completions). See [event-referral.md](event-referral.md) for the full reference, blueprint examples, and attenuation design.

## Entity-Scoped Invites

Each entity type gets its own independent invite achievement tracking. If both `app` and `org` have `has_invite_achievements: true`:
Expand Down