Skip to content
Cockpit0.29.0
A snapshot: read at commit 707a04c7ce8 on 2026-10-18T00:06:01Z by majordomus 0.29.0. Nothing on this page is read again. See it live

test/cases/61_session_envelope.sh

An object of kind test

What it is

URI
majordomus://test/test/cases/61_session_envelope.sh
Kind
test
Identity
test/cases/61_session_envelope.sh
Path
test/cases/61_session_envelope.sh
Provenance
declared
Media type
text/plain
As a capability
test.test/cases/61_session_envelope.sh

Front matter

{}

Content

# A closed session is an envelope of references, and it names its own episode only.
#
# Two properties are load-bearing and each has a way of failing quietly:
#
#   1. The record references; it does not copy. A session that inlined the bodies it names
#      would be a transcript with better formatting, which is the thing the whole design
#      refuses.
#   2. The references are selected by the session stamp the writer put on each ledger line,
#      not by a time range. The ledger is one file per repository. The first real run of
#      this code used a time window and collected another worker's tasks, checkpoints and
#      handovers, because nothing in a timestamp can tell two concurrent workers apart.
. "$ROOT/test/lib.sh"

"$MJ" init >/dev/null
# init adds the local-state ignore line; commit it so the task that follows starts from a clean tree
git add .gitignore >/dev/null 2>&1; git commit -qm "ignore local ai state" >/dev/null 2>&1 || true
LED=.ai/local/state/ledger.jsonl
# A closed episode is a shared object of the layer, in the section the manifest names
# (ADR 0014); the open episode's own state stays in the checkout-local half.
SES=.ai/repo/sessions

# --- closing with nothing open is a missing artifact, not a silent success
expect_exit 12 "$MJ" session close
expect_grep 'no open session'

# --- an episode across two tasks names both, and copies neither
"$MJ" session start --owner tester >/dev/null
"$MJ" start "first piece" --scope lib >/dev/null
printf 'the cause is in normalisation, not in the comparison\n' | "$MJ" checkpoint >/dev/null
"$MJ" decision add "Normalise before comparing" --why "the fixture proved the comparison was innocent" >/dev/null
printf '# Objective\no\n\n# Current State\nc\n\n# Next Action\nn\n' | "$MJ" handover --close >/dev/null
"$MJ" start "second piece" --scope test >/dev/null
printf 'second piece under way\n' | "$MJ" checkpoint >/dev/null
rec="$(printf 'both pieces moved\n' | "$MJ" session close)"
[ -f "$rec" ] || { echo "    close printed a path that is not a file: $rec"; exit 1; }

expect_grep '^tasks:$' "$rec"
[ "$(grep -c '^  - "t-' "$rec")" = 2 ] || { echo "    expected two tasks in the envelope"; sed -n '/^tasks:/,/^issues:/p' "$rec"; exit 1; }

# --- it references bodies, it does not carry them
expect_no_grep 'the cause is in normalisation' "$rec"
expect_no_grep 'the fixture proved the comparison was innocent' "$rec"
expect_grep '^checkpoints:$' "$rec"
expect_grep '^  - "\.ai/local/state/checkpoints/' "$rec"
expect_grep '^  - "\.ai/local/state/handovers/' "$rec"
expect_grep '^decisions:$' "$rec"
expect_grep '^  - "Normalise before comparing"$' "$rec"
# the authored summary is the only prose in the file
expect_grep '^both pieces moved$' "$rec"

# --- every referenced path exists; a reference is only worth what it resolves to
sed -n 's/^  - "\(\.ai\/local\/state\/[^"]*\)"$/\1/p' "$rec" | while IFS= read -r p; do
  [ -f "$p" ] || { echo "    envelope references a file that does not exist: $p"; exit 1; }
done

# --- the open record is gone and exactly one close event was appended
[ -f .ai/local/state/session-current.yaml ] && { echo "    close left the open record behind"; exit 1; }
[ "$(grep -c '"event":"session.closed"' "$LED")" = 1 ] || { echo "    expected one session.closed event"; exit 1; }

# --- absent and empty are different facts, so an empty list is written as one
expect_grep '^questions: \[\]$' "$rec"
expect_grep '^commits: \[\]$' "$rec"

# --- a second episode does not inherit the first one's records
"$MJ" session start --owner tester >/dev/null
printf 'a different sitting entirely\n' | "$MJ" checkpoint >/dev/null
rec2="$(printf 'second episode\n' | "$MJ" session close)"
[ "$rec2" != "$rec" ] || { echo "    the second close overwrote the first record"; exit 1; }
expect_no_grep 'Normalise before comparing' "$rec2"
[ "$(grep -c '^  - "\.ai/local/state/checkpoints/' "$rec2")" = 1 ] \
  || { echo "    the second episode claimed the first episode's checkpoints"; exit 1; }

# --- one task spanning two sessions is named by both. The first record is not rewritten to
#     mention the second; later information supersedes, it never edits.
this_task="$(sed -n 's/^id: //p' .ai/local/state/current.yaml)"
grep -q "\"$this_task\"" "$rec2" || { echo "    the second episode did not name the task it worked on"; exit 1; }
grep -q "\"$this_task\"" "$rec" || { echo "    the first episode did not name the task it worked on"; exit 1; }

# --- THE REGRESSION: an event stamped by another session is not this session's work, and
#     neither is an event stamped by no session at all. This is the defect the first real
#     run produced, and a time-window implementation passes every assertion above and fails
#     these two. The injected timestamps are far in the future on purpose: a first draft of
#     this case used past ones, and every time-window implementation passed it, because the
#     lines fell outside the window for the wrong reason. The mutation was run against both
#     drafts; only this one fails it.
"$MJ" session start --owner tester >/dev/null
printf '{"ts":"2099-01-01T00:00:00Z","event":"task.checkpoint","head":"deadbee","branch":"master","by":"majordomus/0.1.0","session":"s-99999999999999-ffff","task_id":"t-somebody-else","checkpoint_path":".ai/local/state/checkpoints/not-mine.md"}\n' >> "$LED"
printf '{"ts":"2099-01-01T00:00:01Z","event":"decision.recorded","head":"deadbee","branch":"master","by":"majordomus/0.1.0","task_id":"t-nobody","decision":"A decision no episode claimed"}\n' >> "$LED"
printf 'mine only\n' | "$MJ" checkpoint >/dev/null
rec3="$(printf 'attribution\n' | "$MJ" session close)"
expect_no_grep 't-somebody-else' "$rec3"
expect_no_grep 'not-mine.md' "$rec3"
expect_no_grep 'A decision no episode claimed' "$rec3"

# --- commits made during the episode are listed, shortest form
"$MJ" session start --owner tester >/dev/null
printf 'one\n' > file-one.txt
git add file-one.txt && git commit -q -m "one"
short="$(git rev-parse --short=7 HEAD)"
rec4="$("$MJ" session close)"
expect_grep "^  - \"$short\"\$" "$rec4"

# --- a summary that forges identity is refused; a body that looks checkable and is not
#     is worse than no body
"$MJ" session start --owner tester >/dev/null
expect_exit 10 sh -c 'printf "head: 0000000\n" | "$0" session close' "$MJ"
expect_grep 'must not contain identity fields'
[ -f .ai/local/state/session-current.yaml ] || { echo "    a refused close still removed the open record"; exit 1; }

# --- the store is append-only in practice: closing again writes a new file and never
#     touches the last one. The records are readable rather than private: they are shared
#     objects now, tracked and published, and a mode of 600 would be a claim otherwise.
before="$(cksum < "$rec4")"
"$MJ" session close >/dev/null
[ "$(cksum < "$rec4")" = "$before" ] || { echo "    closing a session rewrote an earlier record"; exit 1; }
for f in "$SES"/*.md; do
  case "$(file_mode "$f")" in
    6[04][04]) ;;
    *) echo "    $f is $(file_mode "$f"), expected a readable regular mode"; exit 1 ;;
  esac
done