···220220 continue
221221 owner = str(raw_commitment.get("owner") or "").strip()
222222 action = str(raw_commitment.get("action") or "").strip()
223223+ # Skip malformed story data rather than minting unusable ledger items with blank actions.
223224 if not owner or not action:
224225 continue
225226···283284 if not isinstance(raw_closure, dict):
284285 continue
285286 action = str(raw_closure.get("action") or "").strip()
287287+ # Skip malformed story data rather than pairing closures with an empty fuzzy-match target.
286288 if not action:
287289 continue
288290 owner_entity_id = raw_closure.get("owner_entity_id")
···357359 consumed_story_closures.add(index)
358360359361 closure_sources = matched_story_sources + manual_closes.get(entry["id"], [])
362362+ # State/closed_at follow the earliest close across story and manual sources; later manual edits remain visible in sources but do not rewrite the first close.
360363 closure_sources.sort(key=lambda candidate: candidate["sort_key"])
361364 first_close = closure_sources[0] if closure_sources else None
362365 state = first_close["state"] if first_close is not None else "open"
+8-4
think/tools/ledger.py
···2727 typer.echo(jsonlib.dumps(payload, indent=2, sort_keys=False))
282829293030+def _echo_json_items(items: list[object]) -> None:
3131+ _echo_json([dataclasses.asdict(item) for item in items])
3232+3333+3034def _render_table(headers: list[str], rows: list[list[str]]) -> None:
3135 if not rows:
3236 return
···117121 except ValueError as exc:
118122 raise typer.BadParameter(str(exc)) from exc
119123 if json:
120120- _echo_json([dataclasses.asdict(item) for item in items])
124124+ _echo_json_items(items)
121125 return
122126 _render_items(items)
123127···130134 typer.echo(f"ledger item not found: {item_id}", err=True)
131135 raise typer.Exit(1)
132136 if json:
133133- _echo_json(dataclasses.asdict(item))
137137+ _echo_json_items([item])
134138 return
135139 _render_items([item])
136140···153157 except ValueError as exc:
154158 raise typer.BadParameter(str(exc)) from exc
155159 if json:
156156- _echo_json(dataclasses.asdict(item))
160160+ _echo_json_items([item])
157161 return
158162 _render_items([item])
159163···179183 except ValueError as exc:
180184 raise typer.BadParameter(str(exc)) from exc
181185 if json:
182182- _echo_json([dataclasses.asdict(item) for item in items])
186186+ _echo_json_items(items)
183187 return
184188 _render_decisions(items)