Skip to content

Derived ranking keys on the hosted server for next (nxf 6j6v.1c5s) - #54

Closed
cabcookie wants to merge 1 commit into
mainfrom
feat/1c5s-server-derived-keys
Closed

cabcookie wants to merge 1 commit into
mainfrom
feat/1c5s-server-derived-keys

Conversation

@cabcookie

Copy link
Copy Markdown
Member

What

The hosted server now ranks next by derived ranking keys (related / count / sum), using the same evaluation as the local SQL path. This implements spec docs/specs/derived-ranking-keys.md §6. Owner decision 2026-10-11 (§10.5): a key reads every LIVE neighbour on both paths. Deleted neighbours are gone; closed and archived ones still count. This replaces the first wording of §6 ("a counterpart that is not active counts as absent"), which is now the rule of the lanes only.

facade (nexus-flow-facade), additive

  • ActiveBoard::ranking: Option<RankingGraph> (the struct is #[non_exhaustive]).
  • New RankingReads { kinds, labels, custom } and next_ranking_reads(cfg), which says what the graph must hold for a plugin.
  • New RankingGraph { reads, edges, neighbours }. edges holds every present edge of the kinds read that has an active end and two live ends. parent edges are the present_parent projection. neighbours holds the live items that are not active, with their row (including belongs_to), labels and custom values.
  • New Neighbour.
  • read::with_ranking(store, board, reads): the parity oracle for the server read.
  • next_active ranks from the graph. It refuses with validation when the graph is missing or was read for less than the keys need.
  • Refused on the store-free path until xkr7: a [ranking.next] order that reads the rank of a type only [ranking.list] orders, directly or down the chain. The refusal is narrow: every other derived next key works hosted.
  • derived.rs is split into plan, context fill (from a store or from a graph) and finish. Both paths evaluate keys through the same Context.

fold-ddb (nxs-fold-ddb)

  • .adj#<item>#<tag> entries are now self-describing. They copy from_id, to_id, kind, lamport and site and carry a removed flag, which is rewritten when the remove is folded.
  • Every edge add already had such an entry under both ends, whatever its kind or activity. So one Query per item now reads all its edges, in and out, with their presence.
  • INDEX_REVISION goes from 2 to 3.
  • records::with_ranking(table, board, reads) costs:
    • one Query per active ticket;
    • one GetItem per inactive neighbour;
    • for each live neighbour, one Query for its parent, plus one each for labels and custom values when the keys read them.

core

  • EdgeKind and graph::Edge derive Ord.
  • graph::Board is otherwise unchanged. It holds no rows, and the lanes read no non-active neighbour, so the rows and edges the keys need live on the facade's ActiveBoard. The spec text is amended to say so.

Deployment facts (for app-foundations)

  • Table contract: UNCHANGED. No new GSI and no new key attribute. The only change is more non-key attributes on the existing .adj# rows, which are on no index.
  • Refold: YES. With the index revision bump every hosted stream is not current. records::active_board answers RecordsError::NotCurrent { found: Some(2) } until the fold run has cleared and refolded the stream (the existing Folder::is_current rule; no new step).
  • Facade break: NO (facade: changed, additive).
  • Fold cost per edge remove: +1 GetItem (for kinds other than dep/parent) and +2 writes, because both adjacency entries are rewritten. Edge add: +1 GetItem for kinds other than dep/parent. The flip fan-out is unchanged.

Tests

  • crates/fold-ddb/tests/derived_differential.rs (new):
    • 60 generated three-type boards (goal, project, action), written by two replicas and merged. They include every status, archived and deleted items, re-parenting from two replicas (two present parent edges), contributes_to / mentions / dep / parent edges with removes, waiting labels and the goals' order custom field. Each is folded in shuffled order with duplicate ops.
    • The test plugin uses every key kind, each able to read non-active neighbours, down a three-type chain (action → project → goal).
    • Assertions: SQL path == Board path == fold-ddb path (order and JSON); the store's and the server's RankingGraph are equal; the same graph comes back from a second fold in a different order (determinism); coverage counters (closed and archived neighbours, inbound contributes_to, mentions from inactive items, inactive children, dropped deleted ends, children parented twice, projects ordered against their priority).
    • A request-count guard: the graph read does not grow with edge history.
    • A revision-2 stream is refused, then refolded, then ranks.
    • Mutation check (done by hand, not committed): with the server reading only active neighbours, the test fails on seed 2.
  • crates/facade/tests/derived_ranking.rs:
    • The spec's §8 chain ranks the same through the store-free path.
    • A board read without the graph, or with a graph read for too little, is refused.
    • Closed, archived and deleted neighbours give the same order through the store and the store-free path, checked against an explicit expected order.
    • The list-rank refusal: refused store-free, ranked by the store.
  • Run locally, all green: fold-ddb (lib, chat_and_memory, derived_differential, fold_differential, records_differential, robustness; dynamodb_local skipped because no local DynamoDB), core graph_differential and graph unit tests, facade derived_ranking, next_query, next_rank_per_type and read tests, facade lib read tests, cargo xtask docs check, cargo xtask changelog check origin/main, cargo fmt --all. Clippy was not run locally (CI).

Left for xkr7

  • The typed GSI: the table-contract change, stack first, then the read.
  • A read of a type's live items through it, to give the list scope. Then lift the store-free refusal of cross-section of = "rank" (derived::reads_for_next / list_scope_refusal).
  • The ranked hosted list read.
  • The edge side is already done: since revision 3, .adj# entries exist and describe themselves for EVERY edge between ANY items, so a list-ranked item reads its edges with one Query on its own prefix. records::with_ranking shows how: present_edges, current_parent, then neighbour rows, labels and custom values.

Refs nxf 6j6v.1c5s; spec 6j6v.e354.

🤖 Generated with Claude Code

https://claude.ai/code/session_016q5uMUkZJQW4fckJoA3FME

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
cabcookie added a commit that referenced this pull request Oct 11, 2026
…SI and a ranked hosted list (nxf 6j6v.1c5s, xkr7) (#56)

Folds #54 (1c5s) and #55 (xkr7) plus the mitigation of their combined independent review.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016q5uMUkZJQW4fckJoA3FME
@cabcookie

Copy link
Copy Markdown
Member Author

Contained in #56, which folds #54 and #55 plus the review mitigation and was merged in one squash.

@cabcookie cabcookie closed this Oct 11, 2026
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