Skip to content

fix: review round 1 — atomic writes, sold guard, non-clobbering re-import, strict money, host contracts (0.1.1) - #2

Merged
mabry1985 merged 3 commits into
mainfrom
fix/review-round-1
Sep 12, 2026
Merged

fix: review round 1 — atomic writes, sold guard, non-clobbering re-import, strict money, host contracts (0.1.1)#2
mabry1985 merged 3 commits into
mainfrom
fix/review-round-1

Conversation

@mabry1985

Copy link
Copy Markdown
Member

Round-1 review fixes for the inventory store

Every confirmed finding from the adversarial review of the first commit, with a test per finding in tests/test_hardening.py (108 tests total, was 57). Details in the commit message.

Headline items: atomic transactions with closed connections; anchored money parsing that never turns "54%" into $54 or a blank cost into $0; the "never set sold by hand" rule enforced at the store in any spelling; sale idempotency and partial-quantity sales; a CSV re-import that cannot clobber the agent's work; URL-safe ids; delete snapshots in the audit trail; emits: and min_protoagent_version matching what the host actually reads.

Merge this first; the view (#1) and automations branches rebase onto it.

🤖 Generated with Claude Code

https://claude.ai/code/session_011UQ33xXfygwEkzbbJx3YwZ

mabry1985 and others added 3 commits September 12, 2026 03:40
…port, strict money, host contracts

Findings from the adversarial review of slice 1 (17 confirmed, 47 reproducers), all
addressed except two nits noted below.

Store
- Every write is ONE transaction: BEGIN IMMEDIATE under the process lock, COMMIT on
  success, ROLLBACK on any exception, connection closed in finally. A sale is the sale
  row + the stock change + the closed listings + the audit row, or nothing. set_price
  writes the targets and the observation together (a bad observation rolls the target
  back). Reads close their connection too.
- Money: anchored parsing ("12,50" is $12.50, "1,250" is $1,250, "(5.00)" is negative,
  "54%" / "2026-07" / "n/a" are NOT money); half-up rounding on the decimal text
  (1.005 → 101¢). A blank string is "unset", an explicit null clears, garbage is an
  error — never a silent NULL or a $0 lot cost.
- status=sold by hand is refused at the STORE (any spelling: "Sold", "SOLD ($5)"), so
  tools, API and CSV all agree; an already-sold item stays editable.
- mark_sold refuses an already sold/kept/withdrawn item unless force=True (a retried
  call must not double revenue); selling fewer units than held decrements quantity and
  leaves the item on sale; the last unit flips it to sold. end_listing(state="sold") is
  refused (the sale records that). Listing a sold/kept/withdrawn item is refused.
- lot_id must exist (or be blank); ids are URL-safe (no slashes/spaces, ≤64 chars);
  names cannot be blanked. Deletes snapshot the removed rows into the audit trail.
- summary() rolls items with no/unknown lot into `unassigned` and counts them in the
  totals. quantity 0 counts as nothing left.

CSV
- Re-import never clobbers: blank cells leave stored values alone; a sheet status never
  walks an item back from listed/pending/sold; a sheet without an id column matches by
  lot + name (with a warning) instead of minting duplicates; unknown lots get a stub lot
  and a warning; unknown status words keep the row (available + the words in notes).
- Alias PREFERENCE wins over sheet order (`target_price_usd` beats `price`);
  `detect_kind` recognises alias-only lots sheets; a malformed sheet is an `ok: False`
  answer; a cp1252 file (Excel on Windows) decodes; `kind=sales` export exists and the
  docstring says an export is a spreadsheet view, not a backup.

Tools / API
- Import/export paths are confined to `workspace_dir` when set (the manifest promises
  `filesystem: scoped`). Target fields are refused on the generic item write (targets
  need a basis → POST /price); PUT of an unknown id is 404; a malformed observation /
  quantity is 400, not 500; import errors are 400.

Host contracts
- `emits:` (with topic + summary), not `events:` — the host never read the old key.
- `min_protoagent_version: 0.148.0` (when sdk.plugin_store shipped); the fallback path
  uses `instance_paths().store(plugin_id)`.
- Fixtures are synthetic rows with the operator's real headers.

Not done (nits, tracked in the review): date strings are compared as text; a tool
cannot clear a text field to "" (falsy args are dropped).

108 host-free tests (was 57). 0.1.1.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011UQ33xXfygwEkzbbJx3YwZ
The blank-name guard ran before the existence check, so a sheet row with an empty name
got the update-path message. Order it after the lookup; the create path strips too.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011UQ33xXfygwEkzbbJx3YwZ
@protoreview

protoreview Bot commented Sep 12, 2026

Copy link
Copy Markdown

⚠️ QA panel exhausted — this PR has not been reviewed.
The review panel failed after 2 attempt(s) on head 743b72788c24. No verdict was posted.
A new push will re-trigger the review.

@mabry1985

Copy link
Copy Markdown
Member Author

@vera review

@mabry1985

Copy link
Copy Markdown
Member Author

Merging on CI green + the in-session adversarial review (17 findings, each pinned by a test in tests/test_hardening.py). Vera's panel exhausted on this repo tonight from upstream rate limiting (OpenAIRateLimitError on every attempt in her logs), not from a verdict — she is advisory and posted none.

@mabry1985
mabry1985 merged commit 6f6b4ff into main Sep 12, 2026
1 of 2 checks passed
@mabry1985
mabry1985 deleted the fix/review-round-1 branch September 12, 2026 10:48
@protoreview

protoreview Bot commented Sep 12, 2026

Copy link
Copy Markdown

⚠️ QA panel exhausted — this PR has not been reviewed.
The review panel failed after 2 attempt(s) on head cfaddd5ac1d5. No verdict was posted.
A new push will re-trigger the review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant