Tattvabodha
Shared Standards · Events & Effects

Karma & Phala

One shared history. Many ways to understand what grows.

Karma records meaningful events across Tattvabodha, Piñon, Loomer, and Hello Island Flower. Phala accumulates their validated effects by canonical entity. The event ledger is the source of truth; accumulated totals can always be rebuilt from it.

Specification v1.0.0 · Shared across worlds

Karma records what happened. Phala records its effects.

01 · Shared foundation

One ledger, one projection

Each person has one private Karma ledger and one Phala collection. Apps contribute events through the shared service; world-specific state stays in each app's own namespace.

Immutable history

Karma

A uniquely identified event records the action, source app, time, subject, and validated effects. Replaying its event ID never reapplies the effects.

users/{uid}/karma/{eventId}
Rebuildable totals

Phala

Per-entity totals are a projection of Karma. The trusted service maintains them atomically and can reconstruct them from the event history.

users/{uid}/phala/{entityKey}
Practice completes

The player measures active time and reports only objects and skills actually engaged.

Shared service validates

It authenticates the user, checks canonical IDs and event rules, then creates the event once.

Phala updates once

A transaction applies each effect and acknowledges the stable event ID.

02 · Entity effects

Canonical references, clear measures

Effects point to shared ontology IDs whenever possible. Time is stored in seconds; applications can display it as minutes or hours.

secondsActive time spent engaging with an entity.
encountersNumber of distinct recorded encounters.
completedNumber of explicitly completed practices or actions.
givenItems given by the acting user.
receivedItems received by the ledger owner.
wishesSentWishes explicitly offered by the acting user.
wishesReceivedWishes explicitly received by the ledger owner through a separately authorized event.

Practice completion: active Object Focus seconds can be credited independently to the practice, objects actually engaged, skills actually exercised, and canonically cultivated capacities. Those are separate exposure totals; they do not need to sum to the session duration. A fruit is never inferred from practice completion. The user must explicitly report it.

03 · Experience Player

Measure what happened

Configured time is only a target. Completion reports actual active Object Focus time, excludes pauses and introductory or closing phases, and happens only after an explicit completion action.

Practice completion payload
{
  "sessionId": "session_123",
  "practiceId": "shamatha",
  "occurredAt": "2026-10-23T13:30:00Z",
  "durationSeconds": 600,
  "objects": [
    "breath"
  ],
  "skills": [
    "sustain",
    "recognize",
    "return"
  ],
  "completed": true
}

The payload contains no user ID: the service derives it from authenticated server context. Interrupted sessions do not emit practice.completed. Offline retries reuse the same event ID and exact payload, and stay pending until the service acknowledges them.

04 · Trust boundary

Clients report; the service records

Private by default

Owner-only history

Users read their own records. Friends do not see events unless a separate explicit sharing action publishes a summary.

Server-owned totals

No client increments

Clients cannot write finalized Karma events or increment Phala. A trusted service validates, deduplicates, and commits event plus effects atomically.

Recipient boundaries

Separate authorization

A sender can record their own relationship to a recipient. A received or wishesReceived effect requires a separate authorized recipient-side update.

Existing transactions first

Gifts stay validated

Karma records a completed gift; it does not create an item or bypass inventory and gifting rules.

05 · Portable contract

Copy the specification

Use the JSON contract and schemas to implement the same event semantics in other Tattvabodha applications.

Download JSON ↓
Tattvabodha Karma & Phala · v1.0.0
{
  "specificationId": "tattvabodha-karma-phala",
  "specificationName": "Tattvabodha Karma & Phala Specification",
  "specificationVersion": "1.0.0",
  "purpose": "A shared, portable contract for recording meaningful events as Karma and maintaining reconstructable accumulated effects as Phala across Tattvabodha applications.",
  "principles": {
    "karma": "Karma records what happened as an immutable event.",
    "phala": "Phala is a materialized projection of event effects and can be rebuilt from Karma.",
    "sharedIdentity": "All participating applications use the same Firebase Authentication user UID and the same per-user Karma and Phala collections.",
    "privacy": "Events are private to their owner unless a separate explicit sharing action publishes a summary.",
    "nonGuarantee": "Practice relationships describe what may be cultivated or arise; they do not prove that a capacity or fruit occurred. Record a reported fruit only through an explicit observation event."
  },
  "storage": {
    "root": "users/{uid}",
    "karma": "users/{uid}/karma/{eventId}",
    "phala": "users/{uid}/phala/{entityKey}",
    "worldState": "users/{uid}/worlds/{worldId}/...",
    "worldIds": [
      "loomer",
      "pinon",
      "tattvabodha",
      "hello-island-flower"
    ],
    "rules": [
      "There is one Karma collection and one Phala collection per user, shared across applications.",
      "Keep application-specific state under worlds; do not copy Karma or Phala into a world namespace.",
      "Use Firestore Timestamp values for stored event and projection timestamps. JSON interchange uses RFC 3339 UTC strings.",
      "The event ledger is authoritative. Phala documents are disposable projections that can be reconstructed from it."
    ]
  },
  "karmaEvent": {
    "schema": "/schemas/tattvabodha-karma-event-v1.0.0.schema.json",
    "requiredFields": [
      "schemaVersion",
      "type",
      "source",
      "occurredAt",
      "subject",
      "effects"
    ],
    "fields": {
      "schemaVersion": "Integer schema version; currently 1.",
      "type": "Registered event type, for example practice.completed, gift.given, wish.sent, or fruit.observed.",
      "source": "Verified originating application ID; never accept an unverified client-supplied app identity.",
      "occurredAt": "Time the event happened, provided as RFC 3339 UTC in JSON and stored as a Firestore Timestamp.",
      "sessionId": "Optional stable Experience Player session identifier.",
      "subject": "Canonical entity type and ID primarily involved in the event.",
      "data": "Optional event-specific details. It must not contain credentials or private information unrelated to the event.",
      "effects": "Zero or more validated changes to Phala entities. The trusted service derives or validates these effects."
    },
    "serverMetadata": {
      "createdAt": "Server-assigned Firestore Timestamp.",
      "processedAt": "Server-assigned Firestore Timestamp when all effects are applied.",
      "processingStatus": "Server-owned state; a synchronous transaction records only processed events. An asynchronous processor may use pending, processing, processed, and failed states."
    },
    "immutability": [
      "Generate one Firestore-safe event ID per logical event and reuse it for every retry.",
      "Create the event and apply all effects atomically in a Firestore transaction for the initial implementation.",
      "If the event ID already exists with the same logical payload, return its existing result without applying effects again.",
      "If the event ID exists with a different payload, reject the request as an idempotency conflict.",
      "Clients cannot edit or delete finalized Karma events. Corrections are new events with an explicit correction type."
    ]
  },
  "phalaEffect": {
    "schema": "/schemas/tattvabodha-phala-effect-v1.0.0.schema.json",
    "requiredFields": [
      "entity",
      "measure",
      "amount"
    ],
    "fields": {
      "entity": "Canonical entity reference, represented as {type, id} in stored projections and type:id in effect interchange.",
      "measure": "Registered measure being accumulated.",
      "amount": "Finite nonzero numeric change. Normal events use positive increments; explicit correction events may apply a negative change, but totals cannot fall below zero."
    }
  },
  "entityReferences": {
    "format": "type:id",
    "canonicalExamples": [
      "practice:shamatha",
      "skill:observe",
      "capacity:stability",
      "fruit:clarity",
      "object:breath",
      "symbol:star",
      "flower:bellbutton",
      "figure:saraswati",
      "user:{uid}"
    ],
    "rules": [
      "Use canonical Tattvabodha entity IDs whenever they exist.",
      "Resolve entity type and ID against the shared ontology before accepting a client event.",
      "A user entity may represent the actor's private relationship history with another user; it does not write to that recipient's ledger.",
      "Never assume an app-local object has a globally shared ID unless it is registered in the shared ontology."
    ]
  },
  "measures": {
    "initial": [
      {
        "id": "seconds",
        "meaning": "Active time spent engaging with an entity."
      },
      {
        "id": "encounters",
        "meaning": "Number of distinct recorded encounters."
      },
      {
        "id": "completed",
        "meaning": "Number of explicitly completed practices or actions."
      },
      {
        "id": "given",
        "meaning": "Items given by the acting user."
      },
      {
        "id": "received",
        "meaning": "Items received by the ledger owner."
      },
      {
        "id": "wishesSent",
        "meaning": "Wishes explicitly offered by the acting user."
      },
      {
        "id": "wishesReceived",
        "meaning": "Wishes explicitly received by the ledger owner through a separately authorized event."
      }
    ],
    "durationUnit": "seconds",
    "extensionRule": "New measures require a compatible specification update and validation registration; clients cannot invent arbitrary measures."
  },
  "phalaDocument": {
    "schema": "/schemas/tattvabodha-phala-v1.0.0.schema.json",
    "path": "users/{uid}/phala/{entityKey}",
    "entityKey": {
      "algorithm": "Base64URL-encode the UTF-8 bytes of the exact canonical entity reference type:id, without padding.",
      "example": {
        "entity": "object:breath",
        "entityKey": "b2JqZWN0OmJyZWF0aA"
      }
    },
    "fields": {
      "entity": "Canonical {type, id} pair.",
      "totals": "Map of registered measures to accumulated numeric totals.",
      "firstEngagedAt": "Timestamp of the earliest applied effect for this entity.",
      "lastEngagedAt": "Timestamp of the latest applied effect for this entity.",
      "updatedAt": "Server-assigned timestamp of the latest projection update."
    },
    "rebuild": "Recompute from the owner's Karma events, grouping validated effects by deterministic entityKey. Never trust a client-supplied Phala total."
  },
  "eventTypes": [
    {
      "id": "practice.completed",
      "subject": "practice",
      "description": "A user explicitly completes a practice session."
    },
    {
      "id": "gift.given",
      "subject": "gift or flower",
      "description": "A separately validated gift transaction was completed."
    },
    {
      "id": "wish.sent",
      "subject": "user",
      "description": "A user explicitly offers a wish to another person."
    },
    {
      "id": "fruit.observed",
      "subject": "fruit",
      "description": "A user explicitly reports a possible fruit as an observation; never infer it from practice completion."
    }
  ],
  "examples": {
    "shamathaCompleted": {
      "schemaVersion": 1,
      "type": "practice.completed",
      "source": "pinon",
      "occurredAt": "2026-10-23T13:30:00Z",
      "sessionId": "session_123",
      "subject": {
        "type": "practice",
        "id": "shamatha"
      },
      "data": {
        "durationSeconds": 600,
        "objectId": "breath"
      },
      "effects": [
        {
          "entity": "practice:shamatha",
          "measure": "seconds",
          "amount": 600
        },
        {
          "entity": "skill:sustain",
          "measure": "seconds",
          "amount": 600
        },
        {
          "entity": "object:breath",
          "measure": "seconds",
          "amount": 600
        },
        {
          "entity": "object:breath",
          "measure": "encounters",
          "amount": 1
        },
        {
          "entity": "capacity:stability",
          "measure": "seconds",
          "amount": 600
        },
        {
          "entity": "capacity:concentration",
          "measure": "seconds",
          "amount": 600
        },
        {
          "entity": "practice:shamatha",
          "measure": "completed",
          "amount": 1
        }
      ]
    },
    "wishSent": {
      "schemaVersion": 1,
      "type": "wish.sent",
      "source": "loomer",
      "occurredAt": "2026-10-23T13:30:00Z",
      "subject": {
        "type": "user",
        "id": "friendUid"
      },
      "data": {
        "recipientId": "friendUid"
      },
      "effects": [
        {
          "entity": "user:friendUid",
          "measure": "wishesSent",
          "amount": 1
        }
      ]
    },
    "flowerGiftGiven": {
      "schemaVersion": 1,
      "type": "gift.given",
      "source": "hello-island-flower",
      "occurredAt": "2026-10-23T13:30:00Z",
      "subject": {
        "type": "flower",
        "id": "bellbutton"
      },
      "data": {
        "recipientId": "friendUid"
      },
      "effects": [
        {
          "entity": "flower:bellbutton",
          "measure": "given",
          "amount": 1
        },
        {
          "entity": "user:friendUid",
          "measure": "given",
          "amount": 1
        }
      ]
    }
  },
  "practiceCompletion": {
    "schema": "/schemas/tattvabodha-practice-completion-v1.0.0.schema.json",
    "payload": {
      "sessionId": "session_123",
      "practiceId": "shamatha",
      "occurredAt": "2026-10-23T13:30:00Z",
      "durationSeconds": 600,
      "objects": [
        "breath"
      ],
      "skills": [
        "sustain",
        "recognize",
        "return"
      ],
      "completed": true
    },
    "validation": [
      "Derive uid from authenticated server context; the payload does not choose its owner.",
      "Accept completion only after an explicit user completion action; leaving or abandoning a session does not complete it.",
      "durationSeconds is measured active Object Focus time, not configured duration. Paused time and setup, threshold, guidance, and emergence phases do not count.",
      "objects lists canonical objects actually engaged during the session; skills lists only skills actually exercised, not every skill in the practice definition.",
      "Record practice seconds and one completed count; apply active seconds to each listed engaged object and exercised skill. These are separate per-entity exposure totals, not portions that must sum to session duration.",
      "Capacities may receive seconds as practice exposure only when present in the resolved practice's canonical cultivates list. Do not represent exposure as proof of growth.",
      "Do not create fruit effects automatically. A reported fruit requires a separate explicit fruit.observed event.",
      "Validate all IDs and the practice-to-object, practice-to-skill, and practice-to-capacity relationships against canonical content."
    ]
  },
  "sharedService": {
    "operation": "recordKarma({eventId, event})",
    "authentication": "Require authenticated identity and derive uid on the server. Verify the calling app/source through an allowlist and app attestation or equivalent trusted server identity.",
    "authorization": "Validate that the actor may perform the event. Validate gift inventory and recipient rules in the gifting service before recording gift.given.",
    "processing": [
      "Validate event schema, source, registered type and measure IDs, canonical entity references, and nonnegative finite amounts.",
      "Aggregate duplicate entity/measure effects before writing.",
      "In one Firestore transaction, create the immutable Karma event if absent and increment each owner's Phala total exactly once.",
      "Return the event ID and processed status. A retry with the same event ID is idempotent."
    ],
    "recipientEffects": "A sender event may add a private user:{recipientUid} relationship effect in the sender's Phala. Any wishesReceived or received effect in the recipient's ledger requires a separate authorized recipient-side event.",
    "clientBoundary": "Experience Players submit validated lifecycle payloads to the shared service. Clients never write Karma effects or increment Phala directly.",
    "offline": "Create a stable event ID before queueing. Queue the payload in app-owned local storage and retry that exact event ID and payload. Show pending state until acknowledged. Do not report Phala as updated while offline or before service acknowledgement.",
    "largeEvents": "If effects exceed transaction limits, use a durable idempotent server-side processor with explicit processing state; do not partially apply effects."
  },
  "playerLifecycle": {
    "configure": "Resolve the canonical practice, selected objects, configured duration, and candidate skill relationships.",
    "begin": "Create a stable session ID and monotonic active-time tracker. A separate session-start Karma event is optional and is not a completed practice.",
    "engage": "Track active Object Focus time, pause intervals, objects actually engaged, and skills actually exercised.",
    "complete": "After the practitioner explicitly completes, submit the shared practiceCompletion payload to recordKarma.",
    "abandon": "An interrupted or abandoned session does not emit practice.completed. A separate abandonment event may be added only if a product needs it.",
    "preview": "Before acknowledgement, show possible effects only. A timer reaching zero does not itself create Karma or prove a fruit."
  },
  "views": {
    "circle": "Presentation over Phala for practices, skills, capacities, and fruits.",
    "heart": "Presentation over Phala for objects, symbols, flowers, figures, and people.",
    "history": "Chronological view over the private Karma ledger.",
    "practiceDetail": "Relevant Phala totals and private Karma history for the selected practice and related entities.",
    "friends": "Only explicit, separately authorized shared events or summaries; never expose private ledger events by default."
  },
  "privacyAndSecurity": [
    "Users can read only their own Karma and Phala records.",
    "Clients cannot create, edit, or delete finalized Karma or write Phala totals directly.",
    "Only the trusted shared service writes Karma and Phala.",
    "A sender cannot mutate another user's ledger. Recipient-side effects are separately authorized.",
    "A completed event is private unless the user explicitly shares a derived summary."
  ],
  "conformance": {
    "eventSchema": "/schemas/tattvabodha-karma-event-v1.0.0.schema.json",
    "effectSchema": "/schemas/tattvabodha-phala-effect-v1.0.0.schema.json",
    "phalaSchema": "/schemas/tattvabodha-phala-v1.0.0.schema.json",
    "practiceCompletionSchema": "/schemas/tattvabodha-practice-completion-v1.0.0.schema.json"
  },
  "versioning": "Compatible additions increment the minor version; clarifications and fixes increment the patch version; breaking event, effect, identity, or storage changes increment the major version."
}