{
  "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."
}
