zettelkasten.dashboard.backend.routes.reviews¶
zettelkasten.dashboard.backend.routes.reviews ¶
Literature-review CRUD, claim/theme, grounding, and board routes.
Split out of the former flat routes.py; behaviour is unchanged.
FramingSeed ¶
Bases: BaseModel
A frame_question framing payload used to SEED a new review.
The same shape the read-only /frame lens accepts. When carried on a
POST /reviews body it does not project anything itself — it just supplies
the review's guiding question (and, when the review's own project is
blank, the framing project scope) so a review can be born already framed
around a research question.
Source code in zettelkasten/dashboard/backend/routes/reviews.py
UpdateReviewRequest ¶
Bases: BaseModel
Overlay mutations for PUT /reviews/{name} (all optional).
Mirrors review.update_review: every field defaults to None (no
change) so a partial body applies exactly the mutations it carries.
Source code in zettelkasten/dashboard/backend/routes/reviews.py
EditReviewMetadataRequest ¶
Bases: BaseModel
Scalar metadata edits for a review (PATCH /reviews/{name}/metadata).
Distinct from the overlay PUT: this edits the manifest's OWN scalars —
title / question and the projection project / graph scope —
and can RENAME the review's slug via new_name (which moves the manifest
plus its regenerable .md / .tables.json sidecars on disk). Every
field is optional; None means "leave unchanged" while an empty string is
a meaningful value for the scope scalars (clears them back to the full
corpus).
Source code in zettelkasten/dashboard/backend/routes/reviews.py
CreateClaimRequest ¶
Bases: BaseModel
Body for POST /reviews/{name}/claims — mirrors claim_producer.create_claim.
The caller supplies the grounded sentence (title + body) and the
member refs for the stance edges; the producer never fabricates claim text or
members. Omit claim_id to mint a fresh id from the title; pass it to make
the write an idempotent create-or-overwrite. body/status are None
/"" sentinels for "unset" handled by the producer's read-merge.
Source code in zettelkasten/dashboard/backend/routes/reviews.py
PromoteClaimRequest ¶
Bases: BaseModel
Body for POST /reviews/{name}/promote-claim — accept a tested hypothesis.
Mirrors the promote descriptor that claim_producer.test_claim returns:
the grounded sentence (title), a stable claim_id for idempotency, the
member refs for the supports / contradicts stance edges, and the
retrieved verbatim quotes to write as evidence. The caller passes the
descriptor's args through unchanged.
Source code in zettelkasten/dashboard/backend/routes/reviews.py
GroundClaimRequest ¶
Bases: BaseModel
Body for POST /reviews/{name}/ground-claim — close an evidential gap.
Identifies the under-evidenced _cross claim to ground by re-reading its
OWN supporting source documents and extracting verbatim quotes. The route
derives the corpus scope from the review manifest, so only the claim id is
needed.
Source code in zettelkasten/dashboard/backend/routes/reviews.py
GroundPaperRequest ¶
Bases: BaseModel
Body for POST /reviews/{name}/ground-paper — fill a paper's CCC gap.
Identifies the paper whose missing CCC notes (definition / mechanism / numbers
/ verbatim quotes) the outline's [GAP:<paper-id>] marker flagged. The route
derives the corpus scope from the review manifest, so only the paper id (the
gather paper_id carried by the marker) is needed.
Source code in zettelkasten/dashboard/backend/routes/reviews.py
TestClaimRequest ¶
SuggestClaimsRequest ¶
Bases: BaseModel
Body for POST /reviews/{name}/suggest-claims — a pitched topic.
Source code in zettelkasten/dashboard/backend/routes/reviews.py
StructuralThemesRequest ¶
Bases: BaseModel
Body for POST /reviews/{name}/structural-themes — a structural axis.
kind selects the deterministic partition axis (source | note_type
| tag | spine); spine names the organization id whose dimensions
become themes (REQUIRED when kind == 'spine', ignored otherwise); and
include_unplaced appends the claims that matched no group as a trailing
"Unplaced" theme.
Source code in zettelkasten/dashboard/backend/routes/reviews.py
InboundClaimsRequest ¶
Bases: BaseModel
Body for POST /reviews/{name}/inbound-claims — pull claims into a theme.
Source code in zettelkasten/dashboard/backend/routes/reviews.py
MineClaimsRequest ¶
Bases: BaseModel
Body for POST /reviews/{name}/mine-claims — a pitched topic to mine.
Source code in zettelkasten/dashboard/backend/routes/reviews.py
MineSourcesRequest ¶
Bases: BaseModel
Body for POST /reviews/{name}/mine-sources — a theme to find sources for.
Source code in zettelkasten/dashboard/backend/routes/reviews.py
MineAtomicRequest ¶
Bases: BaseModel
Body for POST /reviews/{name}/mine-atomic — sources to extract from.
Source code in zettelkasten/dashboard/backend/routes/reviews.py
PromoteThemeRequest ¶
Bases: BaseModel
Body for POST /reviews/{name}/promote-theme — a draft section to promote.
Source code in zettelkasten/dashboard/backend/routes/reviews.py
DetachSpineRequest ¶
Bases: BaseModel
Body for POST /reviews/{name}/detach-spine — the outline to detach.
outlineId selects WHICH of the review's outlines to detach (empty /
"outline" = the manifest-backed default). This is the SHARED CONTRACT the
frontend codes against — keep the field name exactly.
Source code in zettelkasten/dashboard/backend/routes/reviews.py
SpineMemberClaim ¶
Bases: BaseModel
A claim to attach to a spine dimension: its note id + graph home.
Source code in zettelkasten/dashboard/backend/routes/reviews.py
SpineMemberGroup ¶
Bases: BaseModel
Claims to attach to one spine dimension (a dimension node id).
Source code in zettelkasten/dashboard/backend/routes/reviews.py
AttachSpineMembersRequest ¶
Bases: BaseModel
Body for POST /reviews/{name}/attach-spine-members.
spine is the organization id whose dimensions receive the members;
members groups the author-placed claims by dimension node id. outlineId
is carried for symmetry (the write targets the shared spine, not one outline).
Source code in zettelkasten/dashboard/backend/routes/reviews.py
ExecuteActionRequest ¶
Bases: BaseModel
Body for POST /reviews/{name}/actions — execute a gap/missing-paper action.
Mirrors the action descriptors emitted by analyze_gaps /
rank_missing_papers: tool is the producer-loop tool to run
(expand_corpus | promote_citation | expand_citations | claim),
action is the sub-action for the claim tool (e.g. "test"), and
args are that tool's keyword arguments.
Source code in zettelkasten/dashboard/backend/routes/reviews.py
get_reviews ¶
List local review manifests plus federated reviews as a read-only overlay.
Mirrors /graphs and /projects: local reviews first, then each
configured federated repo's reviews appended with namespaced names
(<repo_id>:<name>) and read_only: True (federated reviews are a
picker overlay only — they have no write path). LOCAL and FEDERATED rows are
both run through :func:_review_summary_row so every row shares one
overlay-free summary shape (the full reconciled view is served on demand by
GET /reviews/{name}).
Source code in zettelkasten/dashboard/backend/routes/reviews.py
get_review ¶
get_review(name: str, outline: str = '', request: Request = None, response: Response = None) -> dict
The reconciled view for a review: fresh projection + applied overlay.
Resolves name via _resolve_ref so a federated (namespaced) review is
loaded from its own repo's .zettelkasten/ and projected with that repo's
namespace — GET is read-only and allowed on federated reviews. Mirrors the
project 404 mapping: an unsafe/unknown name → 404. The returned state is a
DERIVED, read-only view (never persisted back).
outline selects which outline's structure to reconcile (empty / "outline"
= the default; any other id = that additional outline's sidecar overlay).
ETag / conditional-GET (mirrors the Graph A polled-route fast path): the ETag
is derived from :func:_review_reconcile_signature — every input the reconcile
reads — so a still-needed refetch after an edit is a cheap bodyless 304 when
nothing that feeds the view changed, and a full 200 (with a fresh ETag) the
moment it does. Reconcile is a fresh uncached full projection, so the 304 path
skips the whole projection + serialization, not just the wire bytes.
Source code in zettelkasten/dashboard/backend/routes/reviews.py
194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 | |
get_review_bibliography ¶
Home-source bibliography for a review — BibTeX + an author-year list.
The bibliography is exactly the deduplicated set of HOME SOURCES behind the
review's claims (the paper each claim was extracted from), rendered two ways
so the client can offer a .bib download and/or a ## References section
without re-deriving anything. key_map maps each claim uid to its
source's BibTeX key, so the exported markdown can emit \cite{key}.
Read-only, so federated (namespaced) reviews are allowed: name resolves
via _resolve_ref and each claim's (namespaced) source is localized before
its _meta.yaml is read from the owning repo's base.
Source code in zettelkasten/dashboard/backend/routes/reviews.py
create_review_route ¶
Create (idempotent) a local review manifest, optionally framing-seeded.
Federated repos are a read-only overlay — a namespaced name resolving to a
configured repo is rejected with 403 BEFORE any write. create_review
validates the name (path-traversal guard); a bad name → 400. When a
frame framing payload is supplied it seeds the review's question
(and the projection project scope when the review left it blank) so the
review starts life framed around the research question; explicit
question/project on the request always win over the framing seed.
Source code in zettelkasten/dashboard/backend/routes/reviews.py
update_review_route ¶
update_review_route(name: str, req: UpdateReviewRequest) -> dict
Apply overlay mutations to an existing local review.
Federated (namespaced) targets are read-only → 403 before any write. A bad
name → 400 (validate_id); a missing review → 404.
Source code in zettelkasten/dashboard/backend/routes/reviews.py
edit_review_metadata_route ¶
edit_review_metadata_route(name: str, req: EditReviewMetadataRequest) -> dict
Edit a local review's scalar metadata and/or rename its slug.
Federated (namespaced) targets are read-only → 403 before any write. Scalar
edits (title/question/project/graph) are applied first on the current slug,
then the rename (if any) moves the files — so the returned manifest reflects
the final on-disk state. A bad/colliding name → 400; a missing review → 404.
Returns the raw manifest (its name is authoritative after a rename).
Source code in zettelkasten/dashboard/backend/routes/reviews.py
delete_review_route ¶
Hard-delete a local review's durable overlay (manifest + outline sidecar).
The HTTP counterpart of the MCP review('delete') action. Federated
(namespaced) targets are a read-only overlay → 403 before any write. Deletion
is the one NON-REGENERABLE destructive review action, so it is also refused
when deletes are disabled (ZK_DISABLE_DELETE), mirroring that barrier.
Claims and papers are untouched — only the editorial overlay is removed. A
missing review is reported as {"deleted": False} rather than a 404.
Source code in zettelkasten/dashboard/backend/routes/reviews.py
create_review_claim ¶
create_review_claim(name: str, req: CreateClaimRequest) -> dict
Create-or-overwrite a grounded _cross claim (the producer write path).
The review name is the editorial context; the claim itself is authored
into the shared _cross graph via claim_producer.create_claim (per-
claim lock + atomic write + scheduled git commit). A federated (namespaced)
review is read-only → 403 before any write; the producer ALSO refuses a
federated _cross/member target (→ 403). A bad id / out-of-range
confidence → 400.
Source code in zettelkasten/dashboard/backend/routes/reviews.py
promote_review_claim ¶
promote_review_claim(name: str, req: PromoteClaimRequest) -> dict
Promote a tested hypothesis into a grounded _cross claim — WRITES.
The acceptance step for the hypothesis tester's promote descriptor:
delegates to claim_producer.materialize_claim (a create_claim that
ALSO writes the verbatim evidence quote notes), under the gated producer
write path (per-claim lock + atomic write + scheduled git commit). Unlike the
read-only test, this WRITES, so a federated (namespaced) review is read-only →
403 before any write; the producer ALSO refuses a federated _cross /
member target (→ 403). A bad id / out-of-range confidence / missing title →
400. (The generic /actions route does NOT dispatch materialize_claim;
this is its dedicated write surface.)
Source code in zettelkasten/dashboard/backend/routes/reviews.py
ground_review_claim ¶
ground_review_claim(name: str, req: GroundClaimRequest) -> dict
Scrape a thin claim's own sources for verbatim supporting quotes — WRITES.
The actionable side of an evidential research gap: instead of only saying
"this claim is thin", it re-reads the claim's supporting source PDFs, asks a
write-free extraction agent for verbatim passages that support the claim, and
MECHANICALLY VERIFIES each candidate against the source text
(grounding.verify_quote) before writing it as a grounded quote note —
so the agent surfaces but never fabricates evidence. Scope is taken from the
review manifest's project/graph. WRITES to _cross → a federated
(namespaced) review is read-only → 403 before any write. Missing review → 404
/ corrupt → 422; a bad / unknown claim id → 400.
Source code in zettelkasten/dashboard/backend/routes/reviews.py
ground_review_paper ¶
ground_review_paper(name: str, req: GroundPaperRequest) -> dict
Scrape a paper's own source for its missing CCC notes — WRITES.
The actionable side of the outline's per-paper [GAP:<paper-id>] marker:
instead of only saying "this paper has no definition / mechanism / numbers /
quote notes on file", it resolves the paper to its owned source graph, re-reads
that source's extracted full text, asks a write-free extraction agent for
VERBATIM passages bucketed by CCC slot, and MECHANICALLY VERIFIES each candidate
against the source text (grounding.verify_quote) before writing it as a
typed grounded note — so the agent surfaces but never fabricates. Scope is taken
from the review manifest's project/graph. WRITES to the source graph → a
federated (namespaced) review is read-only → 403 before any write. Missing
review → 404 / corrupt → 422; a bad / unknown paper id → 400.
Source code in zettelkasten/dashboard/backend/routes/reviews.py
delete_review_claim ¶
delete_review_claim(name: str, claim_id: str = Query(..., description='Id of the _cross claim to soft-delete')) -> dict
Soft-delete a _cross claim (status=discarded + tombstoned edges).
Reversible, git-committed, NOT the hard delete_note — delegates to
claim_producer.delete_claim. A federated review is read-only → 403 before
any write. An unknown claim → 404; a non-claim id / bad id → 400.
Source code in zettelkasten/dashboard/backend/routes/reviews.py
delete_review_theme ¶
delete_review_theme(name: str, theme_id: str = Query(..., description='Id of the _cross landscape concept hub to soft-delete')) -> dict
Soft-delete a materialized theme (its _cross landscape concept hub).
The hub-typed counterpart of DELETE /reviews/{name}/claims: a real
(non-proposed) review theme is a type="concept" landscape hub, which
delete_claim refuses, so deletion is delegated to
claim_producer.delete_theme instead (status=discarded, reversible and
git-committed, dropped from every active landscape view). A federated review
is read-only → 403 before any write. An unknown hub → 404; a non-concept id
/ bad id → 400.
Source code in zettelkasten/dashboard/backend/routes/reviews.py
autosave_review_overlay ¶
autosave_review_overlay(name: str, req: UpdateReviewRequest) -> dict
Autosaving overlay-mutation endpoint: persist EVERY editorial edit.
The write the frontend fires on each overlay edit — theme accept, narrative
reorder, inserted note/question/todo, retier, exclude. It delegates to
review.update_review, the M3a durable substrate (per-review lock → atomic
temp+rename → debounced zettelkasten-mcp git commit), so an edit is
crash-safe and versioned and is NEVER silently dropped: a validation problem
surfaces as 400, a missing review as 404, and any substrate failure
propagates (500) rather than being swallowed. Federated (namespaced) targets
are read-only → 403 before any write. Twin of PUT /reviews/{name} — the
explicit autosave verb the producer UI calls.
Source code in zettelkasten/dashboard/backend/routes/reviews.py
test_review_claim ¶
test_review_claim(name: str, req: TestClaimRequest) -> dict
Test a hypothesis against the review's corpus — READ-ONLY (writes nothing).
Scope is taken from the review manifest's project/graph (the SAME
corpus the review projects). Delegates to claim_producer.test_claim: an
ephemeral embed + deterministic evidence GATHER + a write-free stance agent →
a verdict (supported | contested | refuted | untested-in-corpus) with
for/against tally, the classified evidence, the top gaps, and a gated
materialize promote descriptor. Allowed on a federated review (read-only).
Missing review → 404 / corrupt → 422; an empty hypothesis → 400.
Source code in zettelkasten/dashboard/backend/routes/reviews.py
suggest_review_claims ¶
suggest_review_claims(name: str, req: SuggestClaimsRequest) -> dict
Rank EXISTING corpus claims relevant to a pitched topic — READ-ONLY.
The retrieve half of "propose a theme": embeds the topic as an ephemeral
vector and ranks the review corpus's existing claims by similarity, so the
caller can auto-place the matches under a freshly-pitched draft section. Scope
is the review manifest's project/graph. Writes nothing and proposes no
new claims — allowed on a federated review. Missing review → 404 / corrupt →
422; an empty topic → 400.
Source code in zettelkasten/dashboard/backend/routes/reviews.py
propose_review_placements ¶
propose_review_placements(name: str, outline: str = '', granularity: int = Query(50, ge=10, le=90, description='Topic granularity (10=fewer/broader, 90=more/narrower)'), whole_pool: bool = Query(False, description='Cluster EVERY in-scope claim (create mode) instead of only the unplaced leftovers relative to the structure (edit/rebuild mode)')) -> dict
Propose a home for each unplaced claim — READ-ONLY.
The placement companion to "propose a theme": reconciles the review, then for
each unplaced claim proposes EITHER an existing theme (the claim sits close to
that theme's claim centroid) OR membership in a clustered NEW theme (leftovers
that cohere among themselves). Deterministic and write-free — proposes nothing
the author cannot reject and creates no claim or theme; the caller lands an
approved placement via set_placement and an approved new theme via
add_proposed_theme. Allowed on a federated review (no writes). Missing
review → 404 / corrupt → 422.
granularity (10..90, mirroring the Concepts-graph slider) steers the
leftover-cluster cosine cut via
:func:claim_producer.cluster_threshold_for_granularity — low = fewer/broader
topics, high = more/narrower; the midpoint (50) is the legacy default.
whole_pool switches the clustering POOL: with it set (the wizard's CREATE
mode) every in-scope claim is clustered as a fresh topic, ignoring the default
structure's existing placements; without it (the wizard's REBUILD mode and the
Unplaced-card button) only the structure's UNPLACED leftovers are clustered.
Source code in zettelkasten/dashboard/backend/routes/reviews.py
1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 | |
review_structural_themes ¶
review_structural_themes(name: str, req: StructuralThemesRequest, outline: str = '') -> dict
Partition the review's claim pool into STRUCTURAL themes — READ-ONLY.
The structural counterpart to /propose-placements: instead of discovering
themes by claim-vector similarity, it reconciles the review (the SAME
projection path propose-placements builds its claim pool from) and partitions
that pool by a deterministic, PRE-BUILT axis — the supporting note's source
graph (source), its frontmatter note_type / tags, or the dimension
of a named spine organization it belongs to. The axis vocabulary and
membership mirror the matrix builder's row axes and the outline's spine section
partition 1:1, so NO semantic clustering happens here. Returns
{scope, axis, themes:[{key, title, claims, count}]} (themes sorted by count
desc). Allowed on a federated review (no writes). Missing review → 404 /
corrupt → 422; an unknown axis or a missing/unresolvable spine → 400.
Source code in zettelkasten/dashboard/backend/routes/reviews.py
propose_review_inbound_claims ¶
propose_review_inbound_claims(name: str, req: InboundClaimsRequest) -> dict
Propose claims to pull INTO one theme — READ-ONLY.
The per-theme inverse of "propose placements": given a target theme, surfaces
the Unplaced claims that fit its claim centroid and (when scope is "all")
EVERY claim currently placed in another theme that fits this theme above the
floor — grouped by where each claim currently sits, each with its current-theme
fit so the author can judge the move. Deterministic and write-free — proposes
nothing the author cannot reject and moves nothing; the caller lands an approved
move via set_placement. Allowed on a federated review (no writes). Missing
review → 404 / corrupt → 422.
Source code in zettelkasten/dashboard/backend/routes/reviews.py
mine_review_claims ¶
mine_review_claims(name: str, req: MineClaimsRequest) -> dict
Mine NEW grounded claim candidates for a pitched topic — full-text, READ-ONLY.
The mine half of "propose a theme": gathers the corpus notes most similar to
the topic, RE-READS their full note bodies AND each source's extracted PDF
fulltext, and asks a write-free phrasing agent to paraphrase grounded claim
candidates. Scope is the review manifest's project/graph. Writes
nothing and creates no graph claim — the caller lands accepted candidates as
overlay draft claims. Allowed on a federated review. Missing review → 404 /
corrupt → 422; an empty topic → 400.
Source code in zettelkasten/dashboard/backend/routes/reviews.py
propose_review_mine_sources ¶
propose_review_mine_sources(name: str, req: MineSourcesRequest) -> dict
Propose candidate SOURCE papers to mine for one theme — READ-ONLY.
The first half of the interactive "Mine from sources" flow: returns the source
graphs already backing the target theme (in_theme) plus other in-scope
source papers most similar to the theme (proposed), each ranked for the
author to pick which to extract atomic claims from. Scope stays within the
review manifest's project/graph — the "proposed" search never leaves
the current review scope. Writes nothing. Allowed on a federated review.
Missing review → 404 / corrupt → 422.
Source code in zettelkasten/dashboard/backend/routes/reviews.py
mine_review_atomic_claims ¶
mine_review_atomic_claims(name: str, req: MineAtomicRequest) -> dict
Extract MULTIPLE atomic, grounded claims from selected sources — READ-ONLY.
The second half of the interactive "Mine from sources" flow: for each selected
source graph, re-reads its extracted PDF fulltext and asks a write-free agent
for atomic, theme-focused claim sentences, each carrying a VERBATIM quote that
is mechanically verified against the source — ungroundable claims are dropped
server-side. Scope is the review manifest's project/graph. Writes
nothing and creates no graph claim — the caller lands surviving claims as
overlay draft claims. Allowed on a federated review. Missing review → 404 /
corrupt → 422.
Source code in zettelkasten/dashboard/backend/routes/reviews.py
promote_review_theme ¶
promote_review_theme(name: str, req: PromoteThemeRequest, outline: str = '') -> dict
Promote a draft (proposed) section into a REAL landscape hub — WRITES.
Turns an overlay-only pitched section into a first-class _cross concept
hub: its hub members are the SOURCE PAPERS behind the section's placed claims
(each real claim's supporting sources + each mined draft's grounding refs),
every mined DRAFT claim is materialized into a real grounded _cross claim,
and the overlay is reconciled so the now-real hub inherits the draft's slot,
rename, placements, and editorial state. outline selects WHICH structure's
overlay the draft section lives in (empty / "outline" → the default structure;
an additional structure reads its sidecar overlay and the promote is persisted
back to it). Refuses (400) a section with no claim that resolves to a source
paper. Writes to _cross → 403 on a federated / read-only review. Missing
review → 404 / corrupt → 422; unknown section → 404.
Source code in zettelkasten/dashboard/backend/routes/reviews.py
1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 | |
undo_review_promote ¶
Reverse the most recent promote — WRITES.
The inverse of promote-theme: soft-discards (status=discarded,
reversible, git-committed) the _cross landscape hub and every claim that
promote materialized, then restores the overlay snapshot stashed at promote
time (re-adding the draft section + draft claims and their editorial state).
outline selects WHICH structure's overlay holds the undo snapshot (empty /
"outline" → the default structure). Single-level — only the MOST RECENT
promote is undoable, and the restore discards overlay edits made after that
promote. Writes to _cross → 403 on a federated / read-only review. Missing
review → 404 / corrupt → 422; nothing to undo → 404.
Source code in zettelkasten/dashboard/backend/routes/reviews.py
detach_review_spine ¶
detach_review_spine(name: str, req: DetachSpineRequest) -> dict
Detach a spine-organized outline into an editable theme outline — WRITES.
Snapshots the outline's live SPINE partition into its overlay (proposed
themes + single-home placements, resolved via the SAME source the export
uses so a post-detach re-gather matches the pre-detach board), then clears the
outline's spine ref so the (previously overridden) board overlay becomes
the authoritative structure. The overlay write happens FIRST and the spine
clear LAST, so a mid-detach failure stays recoverable (the dormant themes sit
under the still-active spine; a retry finishes the detach). Federated
(namespaced) targets are read-only → 403 before any write. A missing review /
outline → 404; an outline with no spine, or a spine that does not resolve →
400 (nothing to detach). Returns the reconciled review projection over the
now-detached outline (mirroring GET /reviews/{name}).
Source code in zettelkasten/dashboard/backend/routes/reviews.py
review_attach_spine_members ¶
review_attach_spine_members(name: str, req: AttachSpineMembersRequest) -> dict
Attach author-placed claims to a spine's dimension nodes — WRITES.
Powers the Structure builder's spine-axis build: claims the author places
under a spine dimension theme are written as spine-member edges on the
spine graph so they surface in that dimension's section on the (read-only,
spine-organized) board — editing the spine IS how you edit such a board.
Idempotent + additive (never removes membership); the base notes are untouched.
Federated (namespaced) targets are read-only → 403 before any write. A missing
review → 404; an empty/unresolvable spine → 400. Returns
{spine, attached, skipped}.
Source code in zettelkasten/dashboard/backend/routes/reviews.py
review_gaps ¶
review_gaps(name: str, limit: int = Query(25, ge=0, description='Max ranked gaps to return; 0 = none'), gap_types: str = Query('', description='Comma-separated gap-type filter; empty = all')) -> dict
Ranked, typed research-agenda gaps over the review's corpus (READ-ONLY).
Delegates to claims.analyze_gaps (the successor to find_gaps) over the
review manifest's project/graph scope: each gap names WHY a region is
incomplete (type evidential | dialectical | structural | coverage |
comprehensiveness | temporal), scores a bounded severity, and wires the
producer-loop action that would close it. Allowed on a federated review.
Six OPT-IN negative-space "opportunity" families — asymmetry | bridge
| crux | orphaned_question | void | transfer — are emitted only
when named in the gap_types filter (they scan MISSING edges + unmatched
nodes across the graph and the memory↔zettelkasten boundary). The analyzer
output is returned intact, so any new anchor fields those families add (e.g. a
bridge's target/similarity) flow through to the frontend unchanged.
Missing review → 404 / corrupt → 422.
Source code in zettelkasten/dashboard/backend/routes/reviews.py
review_missing_papers ¶
review_missing_papers(name: str, limit: int = Query(25, ge=0, description='Max ranked acquisition targets; 0 = none')) -> dict
Ranked cited-but-unowned ACQUISITION targets over the review's corpus (READ-ONLY).
The acquisition companion to /reviews/{name}/gaps: delegates to
claims.rank_missing_papers over the review manifest's project/graph
scope. Each target is a cited-but-unowned work scored on claim-unlock +
corpus influence + external citation mass, wired to the promote_citation
action that acquires it. Allowed on a federated review. Missing review → 404
/ corrupt → 422.
Source code in zettelkasten/dashboard/backend/routes/reviews.py
execute_review_action ¶
execute_review_action(name: str, req: ExecuteActionRequest) -> dict
Execute a gap/missing-paper ACTION affordance against the corpus.
The write-side companion to /gaps and /missing-papers: those surface
executable action descriptors, and this dispatches one to the underlying
primitive and returns its structured result. Supported tools:
expand_corpus— acquire a cited-but-unowned work end-to-end (fetch + ingest + promote).args:{citation_id, zotero_key?}.promote_citation— promote a citation to an owned source.args:{citation_id}.expand_citations— pull a source's OpenAlex citation graph.args:{source?, doi?, direction?, limit?, dry_run?}.claimwithaction="test"— test a hypothesis against the review's corpus (READ-ONLY).args:{text, top_k?}.
The three corpus-mutating tools are WRITE actions: they REJECT a federated
(namespaced) review with 403, and are blocked with 403 when the server runs
with ZK_DISABLE_WRITE (mirroring the MCP write-tool barrier — the
underlying expand_corpus / promote_citation / expand_citations are
@_write_tools). claim/test is read-only and allowed on a federated
review, routed to that repo's scope (mirrors /test-claim). Missing review
→ 404 / corrupt → 422; an unsupported tool or missing arg → 400.
Source code in zettelkasten/dashboard/backend/routes/reviews.py
1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 | |
review_claim_map ¶
review_claim_map(name: str, limit: int = Query(200, ge=0, description='Max claim nodes to emit; 0 = none (mirrors /gaps)')) -> dict
Claim nodes + typed claim→claim edges + the narrative reading path (READ-ONLY).
Over the review's project/graph scope:
- NODES — the scope's active claims (
claims.build_claim_index), each with its derived status (claims.claim_status), capped tolimitso an empty-scope review cannot dump the entire store.totalis the uncapped count andtruncatedflags whether the map was clipped (limit=0emits no nodes, mirroring how/gapstreats0). - EDGES — the typed claim→claim relations (support / counter / structural)
whose BOTH endpoints are in the CAPPED node set (tombstoned edges already
dropped by the index), keyed by the
<graph>::<id>claim uid. - NARRATIVE PATH — the foundations-first ordering the outline engine derives
(
outline.gather_outline_material→outline.narrative_ordering), as a flat list of unit uids (themed sections in order, then unplaced), filtered to only uids present in the emitted nodes (the gather's claim-sparse fallback can emit non-claim source uids, which are not nodes). Best-effort: a gather failure yields an empty path rather than a 500.
Allowed on a federated review (read-only). Missing review → 404 / corrupt → 422.
Source code in zettelkasten/dashboard/backend/routes/reviews.py
1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 | |