PATCH and DELETE /journal/:id were never registered. Both handlers existed, were
covered by service-level tests, and were completely unreachable — my edit to the
router anchored on a string that only exists on another branch, so it silently
did nothing. Registered, and covered by an API-level test that walks the whole
lifecycle through the router, because that is the only kind of test that could
have caught it. Anything addressed by its own id now has one.
An entry about a plop was invisible under its bed's filter. Naming only a
planting left object_id null, so "notes about this bed" silently excluded every
note written about something growing IN the bed — the two filters disagreed
about what an entry is about, which is precisely the question the reader is
asking. The parent object is now derived from the planting.
checkJournalTarget flattened every store error to ErrInvalidInput, so a real
database failure surfaced as a 400 telling the caller their request was bad, and
never reached the logs. Only "no such row" is a bad reference now; anything else
passes through.
ListJournalEntries repeated the column scan order inline, one column different
from scanJournalEntry — the classic way for a shared column list to drift out of
step with its readers. Both now build from journalScanTargets, with the list
appending the joined author name.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01H3zbym8Doka2d7D48maSgZ
"Take notes through the season on each bed and plant." There is already
free-text notes on gardens, objects and plants, but it is a single mutable
field: writing "powdery mildew on the west bed" overwrites what you wrote in
June. The distinction worth keeping is that notes says what this thing IS, while
a journal entry says what HAPPENED, and when. Both stay.
garden_id is NOT NULL even when an entry is about a bed or a single plop, and
that is the whole trick: permission checks reuse requireGardenRole unchanged and
no second ACL path is invented. object_id/planting_id narrow the target; the
garden always anchors it. Because the garden anchors permission, the target is
checked to actually live in that garden — otherwise a valid object id from
someone else's garden would be storable here and then leak through the list
read. A plop-level entry that also names an object must name the right one, or
the two filters would disagree about what an entry is about.
observed_at is distinct from created_at: you write up Saturday's observations on
Sunday, and Saturday is the date that matters. Listing orders by observation,
newest first, with id breaking ties inside a day.
Roles follow the existing shape. Editors write, viewers read, strangers get
ErrNotFound. An author may edit their own entries; the garden owner may DELETE
any entry in their garden but may not edit one — rewriting somebody else's
observation under their name is a different act from removing it.
Deliberately not wired into the revision history, per the decision on #52:
entries are already append-shaped and individually versioned, and undoing a note
is just deleting it. There's a test asserting journal writes produce no change
sets, so that decision can't quietly reverse itself later.
Closes#52
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01H3zbym8Doka2d7D48maSgZ