docs(schema): refine event payloads and keys

Amolith and Crush created

Co-authored-by: Crush <crush@charm.land>

Change summary

docs/Potential schema.md | 28 ++++++++++++++++------------
1 file changed, 16 insertions(+), 12 deletions(-)

Detailed changes

docs/Potential schema.md 🔗

@@ -39,7 +39,7 @@ SPDX-License-Identifier: CC0-1.0
 
 #### Archived sessions (queryable by time and by dir)
 - idx/archived/{ts_be}/{sid} -> {dir_hash}          // for global archive lists in chronological order
-- dir/{dir_hash}/archived/{ts_be} -> {sid}          // for "archive history" per working directory
+- dir/{dir_hash}/archived/{ts_be}/{sid} -> ""       // for "archive history" per working directory
   - {ts_be} = 8-byte big-endian Unix nanos, hex-encoded lowercase, zero-padded to 16 hex chars; sorts chronologically
 
 #### Per-session namespace
@@ -56,7 +56,13 @@ SPDX-License-Identifier: CC0-1.0
 - s/{sid}/evt/{seq_be} -> JSON event record
   - {seq_be} = 8-byte big-endian u64 counter, hex-encoded lowercase, zero-padded to 16 hex chars; assures correct chronological iteration
   - { seq, at, type, reason: null|string, cmd: "np ...", payload: {...} }
-  - Types you'll likely use: session_started, goal_set, goal_updated, task_added, task_updated, task_status_changed, session_archived, note
+  - Event payloads capture immutable snapshots so subscribers can render changes without extra lookups:
+    - type=goal_set → `{"goal":{"title":"…","description":"…","updated_at":"RFC3339"}}`
+    - type=goal_updated → `{"goal_before":{"title":"…","description":"…","updated_at":"RFC3339"},"goal_after":{"title":"…","description":"…","updated_at":"RFC3339"}}`
+    - type=task_added → `{"task":{"id":"a1b2c3","title":"…","description":"…","status":"pending","created_at":"RFC3339","updated_at":"RFC3339","created_seq":1}}`
+    - type=task_updated → `{"task_id":"a1b2c3","before":{"title":"…","description":"…","updated_at":"RFC3339"},"after":{"title":"…","description":"…","updated_at":"RFC3339"}}`
+    - type=task_status_changed → `{"task_id":"a1b2c3","title":"…","status_before":"pending","status_after":"in_progress","updated_at":"RFC3339"}`
+  - Supported event types: goal_set, goal_updated, task_added, task_updated, task_status_changed
 
 ### Core operations (transactional)
 
@@ -67,11 +73,10 @@ SPDX-License-Identifier: CC0-1.0
    - If it exists, read the active {sid} and print: "Session {sid} is already active for this directory. There's already an active session for this directory; ask your operator whether they want to resume or archive it."
    - Return 0 (idempotent operation).
 4) If no active session exists, begin txn:
-   - Generate new sid = ULID (time-ordered) or blake3(rand) hex; keep as short as practical (e.g., 26 char ULID).
+   - Generate new sid = ULID (canonical 26-character string for uniqueness and natural sort order).
    - Put s/{sid}/meta (state=active), s/{sid}/meta/evt_seq=0 (do NOT create goal yet; goal is created when first set).
    - Put dir/{dir_hash}/active -> {sid}.
    - Put idx/active/{sid} -> {dir_hash}.
-   - Append event s/{sid}/evt/{0000000000000001} type=session_started.
 5) Commit.
 
 #### Set goal (np g s …)
@@ -82,7 +87,7 @@ SPDX-License-Identifier: CC0-1.0
 3) txn:
    - Create s/{sid}/goal JSON.
    - Update s/{sid}/meta.last_updated_at.
-   - Increment s/{sid}/meta/evt_seq and write s/{sid}/evt/{seq} with type=goal_set (no reason required).
+   - Increment s/{sid}/meta/evt_seq (read current value, add 1, persist) before writing the corresponding event, then write s/{sid}/evt/{seq} with type=goal_set (no reason required).
 
 #### Update goal (np g u …)
 1) Lookup sid via dir/{dir_hash}/active.
@@ -93,7 +98,7 @@ SPDX-License-Identifier: CC0-1.0
 4) txn:
    - Update s/{sid}/goal JSON.
    - Update s/{sid}/meta.last_updated_at.
-   - Increment s/{sid}/meta/evt_seq and write s/{sid}/evt/{seq} with type=goal_updated, reason in payload.
+   - Increment s/{sid}/meta/evt_seq (read, add 1, persist) before writing the event, then write s/{sid}/evt/{seq} with type=goal_updated, reason in payload.
 
 #### Add tasks (np t a …)
 1) For each task:
@@ -103,11 +108,11 @@ SPDX-License-Identifier: CC0-1.0
      - Treat adds as idempotent: if the same task (same title+description) is re-added, it will resolve to the same id and be a no-op.
      - If a user wants to retry a cancelled task with the exact same title and description, they should update the existing task's status rather than adding a new one, or modify the title/description slightly to differentiate it.
    - txn:
-     - If s/{sid}/task/{id} absent, increment evt_seq and create task with status=pending, created_at=now, created_seq=evt_seq.
+     - If s/{sid}/task/{id} absent, read and increment evt_seq (persisting the new value) and create the task with status=pending, created_at=now, created_seq=evt_seq.
      - Put s/{sid}/idx/status/pending/{id}.
      - Update meta.last_updated_at.
      - Append event task_added with task payload.
-   - Note: When adding multiple tasks in a single np t a invocation, use a single transaction and increment evt_seq for each task to preserve the order they were provided on the command line. Tasks should be displayed sorted by created_seq (not created_at) to maintain stable, predictable ordering.
+   - Note: When adding multiple tasks in a single np t a invocation, use a single transaction (one task, or batch of sequenced tasks, per transaction) and increment evt_seq for each task to preserve the order they were provided on the command line. Tasks should be displayed sorted by created_seq (not created_at) to maintain stable, predictable ordering.
 
 #### Update task status/title/description (np t u …)
 1) Determine if reason is required:
@@ -119,7 +124,7 @@ SPDX-License-Identifier: CC0-1.0
    - If status changes: delete old s/{sid}/idx/status/{old}/{id}, put new s/{sid}/idx/status/{new}/{id}.
    - Update task JSON.
    - Update meta.last_updated_at.
-   - Increment evt_seq, append event task_updated or task_status_changed with reason (if provided).
+   - Increment evt_seq (read, add 1, persist) and append event task_updated or task_status_changed with reason (if provided).
 
 #### Archive session (np a)
 1) Read s/{sid}/meta:
@@ -129,8 +134,7 @@ SPDX-License-Identifier: CC0-1.0
    - Delete idx/active/{sid}.
    - Update s/{sid}/meta.state="archived", archived_at=now, last_updated_at.
    - Put idx/archived/{ts_be}/{sid} -> {dir_hash}.
-   - Put dir/{dir_hash}/archived/{ts_be} -> {sid}.
-   - Increment evt_seq, append session_archived event.
+   - Put dir/{dir_hash}/archived/{ts_be}/{sid} -> "".
 
 ### Query patterns
 
@@ -245,7 +249,7 @@ Note on `reason` field:
 - Enforce one active session per dir:
   - np s checks if dir/{dir_hash}/active exists before creating a new session (idempotent behavior documented above).
 - Deterministic task IDs:
-  - Use blake3(normalized(title)+"|"+normalized(description)+"|"+sid), take first 8 hex.
+- Use blake3(normalized(title)+"|"+normalized(description)+"|"+sid), take first 6 hex.
   - Never recompute on updates; the id is "creation id," not a content hash of current state.
   - Re-adding the same task (same title+description) resolves to the same id and is treated as a no-op.