Skip to content

Review mitigation for the 1c5s/xkr7 batch (nxf 6j6v.1c5s, xkr7) - #56

Merged
cabcookie merged 3 commits into
mainfrom
fix/1c5s-xkr7-review
Oct 11, 2026
Merged

cabcookie merged 3 commits into
mainfrom
fix/1c5s-xkr7-review

Conversation

@cabcookie

Copy link
Copy Markdown
Member

Stacked on #55 (→ #54); this PR is the one to merge — it contains all three.

Mitigates the independent review of #55 (.nxf/review/2026-10-11T18-13-12Z.md).

Table contract and index revision: unchanged from #55. No new GSI and no new key attribute; the index revision stays 3. The watermark row gets one new non-key attribute, refold_until, which a clear sets. Embedders of nxs-fold-ddb see API changes: Table::get_many is a new required trait method (app-foundations' test Part table has to implement it on the next bump), Folder::clear(table, until) takes a new argument, and RecordsError::Refolding, Watermark::refold_until and the Vec that board::reindex now returns are new. Every item op now costs one more GetItem, because the row is weighed before the op writes.

Mitigation

Finding Disposition Notes
Hosted read cost unbounded (Integrity #1 · High, Code #1, Test #7) Fixed An unranked list keeps and cuts on the row before any join: ListReads::select, read::list_select, ListBoard::cut, and list_board_page uses the total counted before the cut. Only the page reads joins. Point reads go through the new Table::get_many: BatchGetItem, 100 keys a request, UnprocessedKeys retried with backoff. Guard tests: an_unranked_list_costs_its_page_not_the_stream (300 closed items add no requests) and a_ranked_list_with_next_and_a_graph_costs_what_its_scope_holds. A ranked list still reads its types' whole scope, per spec §12; this is documented in E4 §5.1 and spec §6. The hard per-call budget is not built: filed as 6j6v.wq9e.
Refold window serves partial data (Integrity #2 · Medium) Fixed Folder::clear(table, until) first rewrites the watermark as a sentinel with no revisions. Readers answer NotCurrent, then Refolding until folded_through >= until. Replay-safe, because nothing is written to end the window. Snapshot import writes the sentinel first. Test: a_refolding_stream_is_refused_from_its_clear_until_the_refold_reaches_the_head. Changelog wording fixed.
with_ranking skips the revision check; hollow rev-2 test (Integrity #3, Code #4, Test #1) Fixed check_current now runs at the start of with_ranking. The rev-2 test asserts the refusal, and also that the stripped entries read as an empty graph under a false rev-3 watermark while the store has edges.
refresh_item drops TooLarge (Integrity #4 · Medium) Fixed Folder::fold weighs each item row it writes: the stored row, plus this op's writes, plus its index attributes. Too large means Folded::Refused before anything is written. refresh_item's Outcome is reported by board::after and reindex. Test: an_item_with_no_room_for_its_index_attributes_is_refused_rather_than_left_off_an_index.
typed_partition escaping untested (Test #2) Fixed a_typed_partition_escapes_the_type_so_no_stream_and_type_share_one: no cross-stream collision, and a type of # characters is refused once escaped.
typed_items doc overstates the guard (Code #2, Integrity #8, Test #3) Fixed (doc) + test The doc now says what is actually checked: the copy's own cells, which covers a run that died between the row write and the attribute write. Index lag is documented as by design. Test: an_item_whose_typed_attribute_lags_its_row_is_not_listed_under_its_old_type.
Stale prose (Code #3) Fixed Spec :162, "third" index, Cargo.toml description.
Redundant graph_for (Code #5) Fixed Replaced by a debug_assert, since next_active already validated coverage.
Board-wide counters (Test #4) Fixed The counters now count removed edges and deleted ends at an active ticket.
Weak list counters (Test #5) Fixed Added counters: closed and archived neighbours, inbound contributes_to into a listed item, archived items of a graph list type (now asserted out of scope), and cut boards. Three unranked queries added.
typed: re-create, type set to None (Test #6) Fixed Covered in the robustness typed test, with the store as the oracle.
present_edges trusts the copy (Integrity #5) Fixed Entries that do not name the item as an end are dropped.
is_current downgrade (Integrity #6) Documented "No mixed fold versions" added to the fold doc, E4 §5.1 and the changelog. Not enforced in code: is_current stays a bool, and an older build cannot know a newer revision's meaning.
DynamoDB Local coverage (Integrity #7) Fixed Two tests: the_ranked_list_and_the_ranking_graph_read_back_as_from_memory, and a_hub_whose_adjacency_spans_query_pages_reads_every_edge (450 entries of about 2.9 KB, more than 1 MB). The recorder also checks the BatchGetItem count.
Info #6/#8–#11 No action Context only.

Follow-ups filed (P3): 6j6v.wq9e (per-call request budget), 6j6v.jtfy (snapshot::import should report TooLarge rows and items left off an index), 6j6v.4r15 (batch active_board's point reads).

Tests run locally

nxs-fold-ddb lib, chat_and_memory, derived_differential, fold_differential, list_differential, records_differential, robustness: all pass. Facade derived_ranking, list_query, next_query: all pass. cargo check -p nxs-fold-ddb --features dynamodb --tests, cargo xtask docs check, cargo xtask changelog check origin/main, cargo fmt --all. The DynamoDB Local tests run in CI.

🤖 Generated with Claude Code

https://claude.ai/code/session_016q5uMUkZJQW4fckJoA3FME

cabcookie and others added 3 commits October 11, 2026 18:29
The store-free `next` (read::next_active) now ranks a plugin with
`related` / `count` / `sum` keys from a RankingGraph on ActiveBoard, by
the same evaluation the store path runs. Owner decision 2026-10-11: a
key reads every LIVE neighbour (closed and archived count, deleted do
not) on both paths.

- facade: ActiveBoard::ranking, RankingReads, RankingGraph, Neighbour,
  next_ranking_reads, with_ranking (the parity oracle); derived.rs split
  into plan / context fill (store or graph) / finish. A [ranking.next]
  order reading the rank of a type only [ranking.list] orders is refused
  on the store-free path until xkr7.
- fold-ddb: .adj# entries copy from_id/to_id/kind/lamport/site and a
  removed flag, kept on edge removes; INDEX_REVISION 2 -> 3 (old streams
  NotCurrent until refolded); records::with_ranking reads the graph.
  Table contract unchanged (no new GSI, no new key attribute).
- core: EdgeKind and graph::Edge derive Ord.
- tests: derived_differential (SQL = Board = fold-ddb on generated
  boards, determinism, request-count guard, revision-2 refold), facade
  seam tests for live neighbours and the list-rank refusal.
- spec §6/§10.5/§11/§12 amended.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016q5uMUkZJQW4fckJoA3FME
A hosted board answers `list` ranked by `[ranking.list]`, derived keys
included, as a replica does, and a hosted `next` whose keys read a
`[ranking.list]` rank no longer is refused (spec §6, decision §10.4).

- fold-ddb: third sparse GSI `typed` (nxf_typed = <stream>#<type>, the
  type escaped; sort key sk; projection ALL) on every live item row,
  closed and archived included, kept by refresh_item after the row.
  Table::query_typed (MemTable, DynamoDbTable, create_table). A type
  whose partition passes 2,048 bytes is refused at fold time. Ships in
  index revision 3 with the self-describing .adj# entries (#54,
  unreleased): no revision 4. records::list_board reads a ListBoard;
  the graph builder is shared with with_ranking and reads the live
  items of RankingReads::list_types off the index.
- facade: ListReads, ListItem, ListBoard, list_reads, list_board (store
  oracle), list_board_page, list_board_value; RankingReads::list_types,
  RankingGraph::listed (additive); next_ranking_reads no longer refuses
  a next order reading a list-only type's rank. Not paged, as the hosted
  next: limit cuts, paginate/token are validation.
- No new adjacency: the revision-3 .adj# entries carry everything.
- tests: list_differential (SQL = Board = fold-ddb for list and next on
  generated boards with retypes, cross-section ranks, JSON, cost guard,
  revision refusal), typed index in fold_differential, robustness and
  dynamodb_local, facade seam tests.
- docs: crate table contract, E4 §5.1, spec §6/§9/§11/§12; 1c5s
  fragment aligned (contract change and lifted refusal).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016q5uMUkZJQW4fckJoA3FME
Mitigates the independent review of PR #55
(.nxf/review/2026-10-11T18-13-12Z.md):

- Bounded hosted read cost: an unranked list keeps and cuts on the item
  rows before any join (ListReads::select, read::list_select,
  ListBoard::cut); point reads go by BatchGetItem (Table::get_many, 100
  keys a request, UnprocessedKeys retried with backoff); request-count
  guard tests for the untyped list and a ranked list with next + graph.
  A ranked list still reads its types' whole list scope (spec section 12).
- Closed the refold window: Folder::clear(table, until) rewrites the
  watermark first as a sentinel without revisions; readers answer
  NotCurrent, then Refolding until the refold reaches `until`. Snapshot
  import writes the same sentinel first.
- records::with_ranking checks the index revision; the revision-2 test
  reads the stripped entries through it.
- An item op whose row would pass 400 KB with its index attributes is
  refused before it writes; refresh_item's Outcome is surfaced.
- present_edges drops entries that do not name their item; typed_items
  doc; redundant graph_for removed; targeted seen counters; typed index
  re-create and type None; typed_partition escaping; DynamoDB Local
  coverage of list_board / with_ranking including a multi-page hub.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016q5uMUkZJQW4fckJoA3FME
@cabcookie
cabcookie merged commit 1096422 into main Oct 11, 2026
12 checks passed
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