citevahti 0.8.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. citevahti-0.8.0/.gitignore +13 -0
  2. citevahti-0.8.0/CHANGELOG.md +480 -0
  3. citevahti-0.8.0/LICENSE +201 -0
  4. citevahti-0.8.0/NOTICE +10 -0
  5. citevahti-0.8.0/PKG-INFO +268 -0
  6. citevahti-0.8.0/README.md +245 -0
  7. citevahti-0.8.0/pyproject.toml +48 -0
  8. citevahti-0.8.0/src/citevahti/__init__.py +29 -0
  9. citevahti-0.8.0/src/citevahti/agent/__init__.py +40 -0
  10. citevahti-0.8.0/src/citevahti/agent/mcp_server.py +71 -0
  11. citevahti-0.8.0/src/citevahti/agent/policy.py +47 -0
  12. citevahti-0.8.0/src/citevahti/agent/tools.py +184 -0
  13. citevahti-0.8.0/src/citevahti/assess/__init__.py +9 -0
  14. citevahti-0.8.0/src/citevahti/assess/service.py +102 -0
  15. citevahti-0.8.0/src/citevahti/bbt/__init__.py +5 -0
  16. citevahti-0.8.0/src/citevahti/bbt/client.py +66 -0
  17. citevahti-0.8.0/src/citevahti/bibsync/__init__.py +23 -0
  18. citevahti-0.8.0/src/citevahti/bibsync/extract.py +140 -0
  19. citevahti-0.8.0/src/citevahti/bibsync/provider.py +87 -0
  20. citevahti-0.8.0/src/citevahti/bibsync/service.py +207 -0
  21. citevahti-0.8.0/src/citevahti/bootstrap/__init__.py +10 -0
  22. citevahti-0.8.0/src/citevahti/bootstrap/service.py +135 -0
  23. citevahti-0.8.0/src/citevahti/capabilities.py +149 -0
  24. citevahti-0.8.0/src/citevahti/cite.py +95 -0
  25. citevahti-0.8.0/src/citevahti/claimcheck/__init__.py +9 -0
  26. citevahti-0.8.0/src/citevahti/claimcheck/service.py +78 -0
  27. citevahti-0.8.0/src/citevahti/claims/__init__.py +16 -0
  28. citevahti-0.8.0/src/citevahti/claims/candidates.py +81 -0
  29. citevahti-0.8.0/src/citevahti/claims/decisions.py +85 -0
  30. citevahti-0.8.0/src/citevahti/claims/service.py +101 -0
  31. citevahti-0.8.0/src/citevahti/claims/support.py +217 -0
  32. citevahti-0.8.0/src/citevahti/cli.py +1478 -0
  33. citevahti-0.8.0/src/citevahti/corpus/__init__.py +21 -0
  34. citevahti-0.8.0/src/citevahti/corpus/diff.py +162 -0
  35. citevahti-0.8.0/src/citevahti/corpus/snapshot.py +70 -0
  36. citevahti-0.8.0/src/citevahti/corpus/source.py +156 -0
  37. citevahti-0.8.0/src/citevahti/credentials.py +152 -0
  38. citevahti-0.8.0/src/citevahti/evidence/__init__.py +11 -0
  39. citevahti-0.8.0/src/citevahti/evidence/map_ops.py +217 -0
  40. citevahti-0.8.0/src/citevahti/export/__init__.py +10 -0
  41. citevahti-0.8.0/src/citevahti/export/agreement.py +319 -0
  42. citevahti-0.8.0/src/citevahti/export/evidence.py +290 -0
  43. citevahti-0.8.0/src/citevahti/export/kappa.py +56 -0
  44. citevahti-0.8.0/src/citevahti/extract/__init__.py +9 -0
  45. citevahti-0.8.0/src/citevahti/extract/fields.py +96 -0
  46. citevahti-0.8.0/src/citevahti/extract/service.py +88 -0
  47. citevahti-0.8.0/src/citevahti/intake/__init__.py +25 -0
  48. citevahti-0.8.0/src/citevahti/intake/dedupe.py +105 -0
  49. citevahti-0.8.0/src/citevahti/intake/manual.py +145 -0
  50. citevahti-0.8.0/src/citevahti/intake/service.py +225 -0
  51. citevahti-0.8.0/src/citevahti/onboarding.py +175 -0
  52. citevahti-0.8.0/src/citevahti/prisma/__init__.py +6 -0
  53. citevahti-0.8.0/src/citevahti/prisma/service.py +117 -0
  54. citevahti-0.8.0/src/citevahti/probe/__init__.py +30 -0
  55. citevahti-0.8.0/src/citevahti/probe/client.py +67 -0
  56. citevahti-0.8.0/src/citevahti/probe/probe.py +201 -0
  57. citevahti-0.8.0/src/citevahti/pubmed/__init__.py +25 -0
  58. citevahti-0.8.0/src/citevahti/pubmed/parse.py +97 -0
  59. citevahti-0.8.0/src/citevahti/pubmed/provider.py +217 -0
  60. citevahti-0.8.0/src/citevahti/rating/__init__.py +11 -0
  61. citevahti-0.8.0/src/citevahti/rating/ai.py +43 -0
  62. citevahti-0.8.0/src/citevahti/rating/engine.py +165 -0
  63. citevahti-0.8.0/src/citevahti/report/__init__.py +6 -0
  64. citevahti-0.8.0/src/citevahti/report/claim_report.py +116 -0
  65. citevahti-0.8.0/src/citevahti/report/markdown.py +81 -0
  66. citevahti-0.8.0/src/citevahti/retraction/__init__.py +21 -0
  67. citevahti-0.8.0/src/citevahti/retraction/provider.py +51 -0
  68. citevahti-0.8.0/src/citevahti/retraction/service.py +115 -0
  69. citevahti-0.8.0/src/citevahti/retrieval/__init__.py +24 -0
  70. citevahti-0.8.0/src/citevahti/retrieval/service.py +116 -0
  71. citevahti-0.8.0/src/citevahti/retrieval/source.py +125 -0
  72. citevahti-0.8.0/src/citevahti/retrieval/text.py +62 -0
  73. citevahti-0.8.0/src/citevahti/schemas/__init__.py +90 -0
  74. citevahti-0.8.0/src/citevahti/schemas/bibsync.py +38 -0
  75. citevahti-0.8.0/src/citevahti/schemas/bootstrap.py +43 -0
  76. citevahti-0.8.0/src/citevahti/schemas/candidate.py +63 -0
  77. citevahti-0.8.0/src/citevahti/schemas/claim.py +55 -0
  78. citevahti-0.8.0/src/citevahti/schemas/claim_support.py +89 -0
  79. citevahti-0.8.0/src/citevahti/schemas/claimcheck.py +36 -0
  80. citevahti-0.8.0/src/citevahti/schemas/common.py +95 -0
  81. citevahti-0.8.0/src/citevahti/schemas/config.py +217 -0
  82. citevahti-0.8.0/src/citevahti/schemas/corpus.py +44 -0
  83. citevahti-0.8.0/src/citevahti/schemas/decision.py +45 -0
  84. citevahti-0.8.0/src/citevahti/schemas/evidence_map.py +118 -0
  85. citevahti-0.8.0/src/citevahti/schemas/export.py +66 -0
  86. citevahti-0.8.0/src/citevahti/schemas/extract.py +36 -0
  87. citevahti-0.8.0/src/citevahti/schemas/frame.py +120 -0
  88. citevahti-0.8.0/src/citevahti/schemas/intake.py +88 -0
  89. citevahti-0.8.0/src/citevahti/schemas/passage.py +39 -0
  90. citevahti-0.8.0/src/citevahti/schemas/prisma.py +44 -0
  91. citevahti-0.8.0/src/citevahti/schemas/rating.py +130 -0
  92. citevahti-0.8.0/src/citevahti/schemas/report.py +69 -0
  93. citevahti-0.8.0/src/citevahti/schemas/results.py +79 -0
  94. citevahti-0.8.0/src/citevahti/schemas/snapshot.py +56 -0
  95. citevahti-0.8.0/src/citevahti/schemas/transaction.py +43 -0
  96. citevahti-0.8.0/src/citevahti/schemas/validation_record.py +60 -0
  97. citevahti-0.8.0/src/citevahti/schemas/writeback.py +59 -0
  98. citevahti-0.8.0/src/citevahti/state/__init__.py +6 -0
  99. citevahti-0.8.0/src/citevahti/state/audit.py +90 -0
  100. citevahti-0.8.0/src/citevahti/state/store.py +488 -0
  101. citevahti-0.8.0/src/citevahti/tools.py +657 -0
  102. citevahti-0.8.0/src/citevahti/util.py +51 -0
  103. citevahti-0.8.0/src/citevahti/validators/__init__.py +46 -0
  104. citevahti-0.8.0/src/citevahti/validators/candidate.py +30 -0
  105. citevahti-0.8.0/src/citevahti/validators/claim.py +27 -0
  106. citevahti-0.8.0/src/citevahti/validators/claim_support.py +97 -0
  107. citevahti-0.8.0/src/citevahti/validators/config.py +52 -0
  108. citevahti-0.8.0/src/citevahti/validators/decision.py +42 -0
  109. citevahti-0.8.0/src/citevahti/validators/errors.py +39 -0
  110. citevahti-0.8.0/src/citevahti/validators/evidence_map.py +160 -0
  111. citevahti-0.8.0/src/citevahti/validators/frame.py +57 -0
  112. citevahti-0.8.0/src/citevahti/validators/intake.py +35 -0
  113. citevahti-0.8.0/src/citevahti/validators/prisma.py +32 -0
  114. citevahti-0.8.0/src/citevahti/validators/probe.py +27 -0
  115. citevahti-0.8.0/src/citevahti/validators/rating.py +145 -0
  116. citevahti-0.8.0/src/citevahti/validators/transaction.py +34 -0
  117. citevahti-0.8.0/src/citevahti/warehouse.py +106 -0
  118. citevahti-0.8.0/src/citevahti/writeback/__init__.py +32 -0
  119. citevahti-0.8.0/src/citevahti/writeback/backend.py +155 -0
  120. citevahti-0.8.0/src/citevahti/writeback/layer.py +175 -0
  121. citevahti-0.8.0/src/citevahti/writeback/service.py +310 -0
  122. citevahti-0.8.0/src/citevahti/writeback/transaction.py +188 -0
  123. citevahti-0.8.0/src/citevahti/writeback/webapi.py +200 -0
  124. citevahti-0.8.0/src/citevahti/zotero/__init__.py +21 -0
  125. citevahti-0.8.0/src/citevahti/zotero/connect.py +160 -0
  126. citevahti-0.8.0/src/citevahti/zotero/library.py +59 -0
  127. citevahti-0.8.0/src/citevahti/zotero/read.py +274 -0
@@ -0,0 +1,13 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .venv/
4
+ .pytest_cache/
5
+ *.egg-info/
6
+ dist/
7
+ build/
8
+ # project state is per-user/local; do not commit a working .citevahti into the tool repo
9
+ .citevahti/
10
+ # secrets, logs, OS cruft
11
+ .env
12
+ *.log
13
+ .DS_Store
@@ -0,0 +1,480 @@
1
+ # Changelog
2
+
3
+ All notable changes to CiteVahti (a product of Vahtian; formerly developed as
4
+ ZotSynth). The project was built in reviewed steps, each on its own branch off the
5
+ previous one.
6
+
7
+ ## Unreleased
8
+
9
+ _Nothing yet._
10
+
11
+ ## 0.8.0 — Full rename to CiteVahti + a richer inline review card (2026-06-05)
12
+
13
+ The product fully sheds the `zotsynth` name (ADR-0006), the inline evidence card
14
+ gains the data a researcher actually weighs, and "Change reference" becomes a real
15
+ search-and-link flow. Folds in the post-0.7.0 work (rebrand, guided Zotero
16
+ connect, safety hardening, QUICKSTART). 502 offline tests.
17
+
18
+ - **refactor(rename): full rename `zotsynth` → `citevahti` (ADR-0006).** The
19
+ importable package (`src/citevahti/`), the CLI (only `citevahti` /
20
+ `citevahti-mcp` now), the OS-keychain service (`CiteVahti`), the env vars
21
+ (`CITEVAHTI_*`), and the on-disk state dir (`.citevahti/`) all move to the brand
22
+ name; `ZotSynthStore` → `CiteVahtiStore`. **Supersedes ADR-0004 §2.3a/§6** (the
23
+ stable-alias decision): pre-1.0, single-user, no installed base, so the
24
+ disruptive rename is free now and won't be later. History (ADRs, this changelog,
25
+ release notes) keeps the old name as the record.
26
+ - **feat(report): PICO fit + excerpt on the inline evidence card.** The report now
27
+ surfaces — **only from the committed human rating, never the blinded AI** — each
28
+ candidate's PICO + claim fit subscores, a citation-fit score (`n/8`,
29
+ Strong/Moderate/Weak), and the supporting excerpt, rendered as fit-check chips on
30
+ the VS Code card. Mirrors the existing `ai_support` blinding so the card can't
31
+ leak the AI assessment. +1 test.
32
+ - **feat(change-ref): a real "Change reference" flow.** New `--json` output on
33
+ `literature-search` (batch id + staged hits) and `claim-link-candidates`
34
+ (linked/skipped/total); the VS Code **"⇄ Change reference…"** action searches
35
+ PubMed (verbatim query), lets you pick results, and links them as new candidates.
36
+ Swapping among already-linked candidates is afforded directly on the card. Links
37
+ only — no rating, decision, or Zotero write. +2 tests.
38
+ - **docs(readme): "Try it" + "What to test".** A followable 4-step inline-review
39
+ walk-through and a what-to-test block (offline suite + extension build + a manual
40
+ acceptance checklist); fixes the rename artifact in the header note and the test
41
+ count (→ 502).
42
+ - **fix(safety): stress-test findings — preview-first CLI write + honest Zotero scope.**
43
+ - **Sev-4 (the important one):** `claim-commit --commit` no longer one-call writes.
44
+ Without `--confirm-token` it now **shows the preview and requires explicit
45
+ confirmation** (interactive `y/N`); non-interactive callers (scripts/agents)
46
+ must replay the token, and `--json` returns `preview_required /
47
+ missing_confirm_token` — so nothing is ever written unseen. `claim-decide`'s
48
+ hint reworded to "review+write (shows a preview and asks)".
49
+ - **Sev-3:** `connect-zotero` now reports **personal-library write vs group-library
50
+ access** honestly (no false confidence), adds `--groups none|read|write` (and a
51
+ VS Code "Include shared/group" choice) to pre-select shared-library scope.
52
+ - **Sev-2:** `decision-list` now prints each `decision_id` (+ the `claim-commit`
53
+ command) so the write id is recoverable; PyPI build verified end-to-end and the
54
+ recipe captured in `docs/RELEASING.md`.
55
+ - **Sev-1:** README test count corrected (→ 499).
56
+ - **docs: `QUICKSTART.md` — zero to first verified citation (~10 min).** Install →
57
+ `connect-zotero` → add a claim → PubMed search → rate → decide → guarded write →
58
+ report, with both the VS Code review loop and the full CLI path. Linked from the
59
+ README. Also: `claim-decide` now prints the `decision_id` and the exact
60
+ `claim-commit` command (removes a `decision-list` lookup on the CLI write path).
61
+ - **feat(zotero): guided one-paste connection — no hand-crafted API keys (ADR-0005).**
62
+ A spike confirmed Zotero's local API is read-only and the connector write path is
63
+ fragile/undocumented, and that OAuth needs a callback server (→ a hosted feature).
64
+ So the beta connects keyless reads + a **one-paste key** for writes:
65
+ `citevahti connect-zotero` (and the VS Code **“Connect Zotero”** command) opens
66
+ Zotero's new-key page **pre-filled** (name + `write_access=1`), takes the pasted
67
+ key, validates it against the Web API, **learns the userID automatically**, stores
68
+ the key in the **OS keychain** (never config/argv/logs — the extension passes it
69
+ by env), and enables the guarded `web_api` backend. Read-only/invalid keys are
70
+ refused; write-back stays decision-gated, previewed, and undoable. New
71
+ `zotero.ZoteroConnectService`, `tools.connect_zotero` / `zotero_new_key_url`,
72
+ `connect-zotero` CLI, and the `citevahti.connectZotero` command. +9 tests.
73
+ - **chore(brand): rebrand the product to CiteVahti (a product of Vahtian).** The
74
+ distribution is now `citevahti`; the CLI is `citevahti` / `citevahti-mcp`; the VS
75
+ Code extension is `vahtian.citevahti` (command `CiteVahti: Verify claims`, config
76
+ namespace `citevahti.*`); README, NOTICE, and forward-facing docs are rebranded.
77
+ **Kept as stable aliases so nothing breaks:** the Python import path `zotsynth`,
78
+ the `zotsynth` / `zotsynth-mcp` CLI commands, the `ZotSynth` OS-keychain service
79
+ (stored secrets survive), and the `.zotsynth/` state directory. See
80
+ [ADR-0004](docs/adr/0004-brand-ip-and-entity.md). Also fixed a stale
81
+ `__version__` (0.6.0 → 0.7.0).
82
+ - **docs: add `CONTRIBUTING.md`** — DCO sign-off gate + the open-core ground rules,
83
+ so external contributions come in cleanly and the Apache/BSL boundary holds.
84
+
85
+ ## 0.7.0 — Manuscript surfaces: interactive review, editor report, revision diff (2026-06-04)
86
+
87
+ The manuscript becomes the workspace. Three surfaces over the one ledger
88
+ (claim → candidate → blinded rating → decision → guarded write): an **inline
89
+ VS Code review loop**, an **editor-mode Markdown report** for supervisors/editors,
90
+ and an **agent-proposes / human-accepts revision diff**. Hardened across two
91
+ headless-reviewer passes and a **live VS Code F5 pass** (disposable workspace +
92
+ fake Zotero Web API): the decision loop, preview→commit collection/token binding,
93
+ and undo all verified live; the revision-accept path verified live after the
94
+ manuscript-location fix below.
95
+
96
+ - **fix(F5): bugs found and fixed during the live extension run.**
97
+ - **Undo retry no longer carries a stale `undo_unavailable`.** A successful undo
98
+ after a prior failed one now clears `error_code`/`remediation`
99
+ ([writeback/transaction.py]); regression test
100
+ `test_successful_retry_clears_prior_undo_failure_fields`.
101
+ - **Nonce-based webview CSP.** The panel sets
102
+ `default-src 'none'; style-src/script-src 'nonce-…'` and stamps the inline
103
+ `<style>`/`<script>` with the nonce — no missing-CSP warning, no inline-script
104
+ escape hatch.
105
+ - **Revision accept no longer depends on the active editor.** The pending rewrite
106
+ carries `manuscript_location` to the webview; accept opens the manuscript from
107
+ that location if needed, then applies the edit.
108
+ - **fix(safety): headless-reviewer hardening of the write + revision paths.**
109
+ - The card's **preview→commit is now tightly bound**: the preview sends
110
+ `--collection-key` and the commit replays the preview's **`confirm_token`**
111
+ (`--confirm-token`) — no commit without a confirmable preview, and the target
112
+ collection is shown in the confirm modal.
113
+ - **`oo` no longer double-records.** A single-`o` keypress is held on a short
114
+ timer so a fast `o o` resolves to one `accept`, never `accepted_with_caution`
115
+ then `accept`.
116
+ - **Revision accept can no longer diverge** manuscript text from ZotSynth state:
117
+ single-span selection / duplicate guard, rollback of the manuscript edit if the
118
+ CLI fails, and a **stale-diff guard** — `claim-accept-revision --expected-text`
119
+ (and `accept_revision(expected_text=…)`) refuses to apply if the pending rewrite
120
+ changed since it was previewed.
121
+ - **feat(revision): the revision-diff loop — propose → review the diff → accept/reject.**
122
+ A claim can carry a *pending rewrite*. An agent may **propose** one
123
+ (`propose_revision`, flagged `ai` with a pinned model) but can **never apply** it
124
+ (`accept_revision` is a forbidden agent capability); only a human accepts. The
125
+ inline card renders the pending rewrite as a **−was / +now diff** with *Accept
126
+ revision* / *Keep original*, plus *✎ Revise wording…* for a human-authored
127
+ rewrite. **Accept applies a visible `WorkspaceEdit`** to the manuscript text and
128
+ then updates the stored claim — the claim text is **never silently edited**, and
129
+ the change is audited with the before/after. New CLI: `claim-propose-revision`,
130
+ `claim-accept-revision`, `claim-reject-revision`; the report row + editor-mode
131
+ Markdown surface the pending rewrite.
132
+ - **feat(vscode): the interactive `oo/o/r/d` decision loop.** Expanding a claim
133
+ shows its candidate evidence cards (paper, human rating, AI rating *blinded
134
+ until the human rates*, recorded decision). Focus a candidate and press a
135
+ fit-code (or click): `oo`→accept · `o`→accepted_with_caution · `r`→
136
+ needs_second_review · `d`→reject. The extension prompts for a reason and runs
137
+ `claim-decide` (the human decides; ZotSynth records/audits/undoes), then
138
+ refreshes the report + decorations. The mission invariant is enforced by the
139
+ CLI and surfaced in the UI.
140
+ - **feat(report): evidence carries `rating_id` + blinded human/AI support** so the
141
+ card has what it needs to act (the AI value is `"hidden"` until the human rates).
142
+ - **feat(report): editor-mode Markdown report (`--format md`).** A shareable
143
+ **Citation-Integrity Report** for supervisors / journal editors / methodologists
144
+ — read-only, claim-by-claim with state, evidence, ratings, and decisions, an
145
+ "attention needed" section, and a non-overclaim footer. `claim-report --format
146
+ text|md|json --output <file>`; new `report.render_markdown`. No Zotero write.
147
+ - **feat(vscode): staged, undoable Zotero write from the card.** "✓ Add to Zotero"
148
+ on an accepted candidate **previews** (`claim-commit --json` dry-run; you confirm
149
+ the item + dedupe status), **commits** through the decision-gated transaction,
150
+ and offers **Undo** (deletes only what it created). `dedupe_unverified` surfaces
151
+ an explicit *Override and add* — never a silent duplicate. Adds `--json` to
152
+ `claim-commit` / `txn-undo`, `decision_id` to the report evidence, and a
153
+ `zotsynth.collectionKey` setting.
154
+
155
+ ## 0.6.0 — citation-integrity report + VS Code surface + Apache-2.0
156
+
157
+ Tag: `v0.6.0`. The 4-state report (the VS Code / editor / agent data) and the VS Code
158
+ extension first cut; relicensed to Apache-2.0.
159
+
160
+ - **feat(report): the 4-state citation-integrity report.** Treats the manuscript
161
+ like code — each claim is a unit test whose state is *derived* (read-only) from
162
+ the ledger: `[oo] verified` (accepted supporting evidence), `[o ] needs_support`
163
+ (no accepted evidence yet), `[r ] review_needed` (unresolved discordance / a
164
+ 2nd-review decision), `[d ] decision_recorded` (all candidates settled, none
165
+ accepted). New `schemas/report.py`, `report/ClaimReportService`,
166
+ `tools.claim_report`, and a CI-style `claim-report` CLI (`--json` for tooling;
167
+ exits non-zero when claims still need attention).
168
+ - **feat(agent): `verify_claims`** — the read-only report added to the constrained
169
+ agent surface so an agent can run the citation tests (still no write power).
170
+ - **feat(vscode): VS Code extension (first cut).** `vscode-extension/` — a thin
171
+ client over `claim-report --json` that highlights each claim in the open
172
+ manuscript by its 4-state result (amber/teal/violet/rose, no green/red) and
173
+ shows a side report. The interactive `oo/o/r/d` keystroke flow + evidence-card
174
+ popover (prototyped in `mockups/zotsynth-inline/`) wire to the MCP tools next.
175
+
176
+ ## 0.5.0 — constrained agent (MCP) surface
177
+
178
+ Tag: `v0.5.0`. ZotSynth is now safely callable by AI agents (MCP): capability without power.
179
+
180
+ - **feat(agent): the constrained agent tool surface.** Exposes ZotSynth to AI
181
+ agents (Codex/Claude Code) as a small, fixed set of safe verbs — *capability
182
+ without power*. `pubmed_search`, `propose_claim`, `link_candidates`,
183
+ `start_support_rating`, `submit_ai_support_rating` (recorded **blind**, value
184
+ not echoed), `preview_write` → `commit_write(approval_token)`, `undo`,
185
+ `get_provenance` (AI rating **blinded until the human rates**), `status`. An
186
+ agent can NEVER reach a raw Zotero write, a one-call commit, the human's rating,
187
+ the final decision, the AI rating before the human, or credentials — enforced by
188
+ `agent/policy.py` (asserted at import + serve). New `agent/` package, a lazy
189
+ `mcp-serve` MCP server (`zotsynth-mcp`, optional `[mcp]` extra), `agent-tools`
190
+ CLI, `ClaimSupportEngine.submit_ai_rating`, and `docs/AGENT.md`. +9 tests.
191
+
192
+ ## 0.4.1 — beta hardening (second stress test)
193
+
194
+ Tag: `v0.4.1`. Closes the agent-write-boundary + dedupe-unverified findings.
195
+
196
+ - **fix(writeback): agent-write boundary — a confirmed validated write requires a
197
+ prior preview's approval token.** `commit_for_decision(dry_run=False)` /
198
+ `tools.commit_decision` now refuse (`missing_confirm_token`) unless given a
199
+ token from a prior dry-run preview, so an agent cannot one-call write to Zotero
200
+ without a user-visible preview/approval step. The CLI `claim-commit --commit`
201
+ previews then commits (human one-command path); an explicit `--confirm-token`
202
+ is also accepted.
203
+ - **fix(writeback): `dedupe_unverified` no longer proceeds silently.** When the
204
+ write-target existence check is unavailable (Zotero search down), a validated
205
+ write is **refused** (`dedupe_unverified`) rather than risking a duplicate;
206
+ override with `allow_unverified_dedupe` / `--allow-unverified-dedupe`. Dry-run
207
+ previews warn.
208
+ - **fix(writeback): block writes from `review_required` intake batches.** A batch
209
+ flagged for review (malformed/translated PubMed query) is blocked from
210
+ committing (`batch_review_required`) unless `--allow-review-required`.
211
+ - **fix(ui): mockup language + blinded validation.** The `d` state reads
212
+ **Unsupported** (not "Delete"); actions are "Remove candidate from claim" /
213
+ "Remove candidate + strike claim (diff)" with explicit diff/undo framing; the
214
+ inline mock now hides the AI rating during blinded human validation (was
215
+ leaking it).
216
+ - **fix(cli): `claim-commit --commit` returns non-zero on a non-committed write**
217
+ (reviewer-contributed, with a regression test).
218
+
219
+ ## 0.4.0 — validation warehouse + UI direction + duplicate-safety
220
+
221
+ **First public beta milestone.** Tag: `v0.4.0`. Completes the ADR-0001 §10 build
222
+ sequence (the de-identified validation warehouse, step 6), records the inline
223
+ review-layer UI direction (ADR-0002), and closes the stress-test duplicate-safety
224
+ blockers. 443 tests, fully offline. Beta scope: local-first, single-user,
225
+ PubMed-only; the hosted layer and the VS Code review-layer UI are the next phase.
226
+
227
+ ### Inline review-layer UI (ADR-0002)
228
+ - **docs(design): ADR-0002 — the `[oo/o/r/d]` inline review layer.** Citation
229
+ integrity lives *inside* the writing surface (a VS Code-style editor companion),
230
+ not a separate dashboard. Four operational fit-codes over the ledger
231
+ (`oo` supported→accept · `o` partly→accepted_with_caution · `r` revise→2nd
232
+ review · `d` delete→reject), four distinct accessible status hues
233
+ (amber/teal/violet/rose; lilac brand; no green/red), claim text never edited
234
+ silently (inline diffs), and the `r`/`d` cards each expose their two real
235
+ choices. Adds `docs/design/` (tokens, reference, logo) and runnable mockups
236
+ (`mockups/zotsynth-inline` primary; `zotsynth-ui` dashboard demoted to legacy).
237
+
238
+ ### Duplicate-safety hardening (stress-test Sev-4)
239
+
240
+ - **fix(writeback): cross-boundary duplicate protection.** Library dedupe checks
241
+ the *local* Zotero API, so a paper created via the Web API but not yet synced
242
+ locally looked "new" and could be duplicated. Backends gain
243
+ `find_existing(pmid, doi)` (queries the **write target**); the validated
244
+ `commit_for_decision` re-checks it and **refuses** with a `failed` /
245
+ `duplicate_on_write_target` transaction (dry-run warns), and an uncheckable
246
+ result degrades to "proceed" (never blocks a write because the check was down).
247
+ - **fix(writeback): `intake-push` hardening.** The generic staging push now
248
+ enforces the same rules as the validated path — it skips `duplicate_in_run`
249
+ records, records with **no PMID/DOI**, items already in the local library, and
250
+ items already on the Web-API write target (was: staged all of them and minted a
251
+ token).
252
+ - **fix(cli): clean errors for transaction/state failures.** `_safe` now catches
253
+ `TransactionError` / `StateError` / `WriteUnavailable` (e.g. undoing an
254
+ already-undone transaction) and prints a one-line message instead of a traceback.
255
+ - **feat(claims): `implementation` claim type** (implementation-science claims are
256
+ first-class for guideline/QI users).
257
+ - **fix(writeback): committed `intake-push` records a transaction + undo.** The
258
+ staging write now produces a `ZoteroTransaction` (`validated=False`) with an
259
+ undo path, like the validated path. The CLI shows created keys / collection /
260
+ transaction id (post-write verification).
261
+ - **fix(intake): `review_required` on malformed/translated queries.** PubMed
262
+ warnings or a query re-translation flag the intake record and surface a
263
+ prominent CLI "REVIEW REQUIRED" banner (Sev-3).
264
+ - **fix(cli): unsupported previews stop printing a blank `confirm_token`** — they
265
+ say no confirmable write was produced and exit non-zero (Sev-2).
266
+ ### Validation warehouse (ADR-0001 step 6)
267
+ - **feat(warehouse): de-identified validation warehouse (ADR-0001 step 6).** The
268
+ moat — but privacy-bounded. **Opt-in, default-off** (`config.validation_warehouse`).
269
+ When enabled, a final decision becomes one append-only, de-identified
270
+ `ValidationRecord`: `claim_type`, a one-way claim-text hash, the public PMID/DOI,
271
+ the AI/human/final support ratings, PICO fit, and agreement. It stores **no**
272
+ identity, manuscript text, Zotero keys, or project-internal ids. Claim text is a
273
+ top-sensitivity tier kept only on a second opt-in (`include_claim_text`). Records
274
+ are append-only (`validation/records.jsonl`); the warehouse is purgeable (consent
275
+ withdrawal) and `auto_emit` lets labels emerge from the workflow. New
276
+ `schemas/validation_record.py`, `warehouse.py`, config block, store CRUD
277
+ (`validation.record` / `validation.purge` audit), and `warehouse-status/-emit/
278
+ -export/-purge` CLI. Completes the ADR-0001 §10 build sequence.
279
+
280
+ ## 0.3.0 — citation-integrity ledger (ADR-0001 steps 1–5) + capability foundation
281
+
282
+ Tag: `v0.3.0-citation-ledger`. Reorients ZotSynth around the **claim** (ADR-0001):
283
+ the ledger is `claim → candidate → blinded support rating → final decision →
284
+ decision-gated, undoable Zotero write`, every step hash-chain audited. Also folds
285
+ in the connection/capability hardening sprint. 421 tests, fully offline.
286
+
287
+ ### Citation-integrity direction (ADR-0001)
288
+
289
+ - **docs(adr): ADR-0001 — ZotSynth is Citation Integrity Infrastructure.** The
290
+ claim (not the paper) is the spine; the evidence-decision ledger is the asset;
291
+ writes are decision-gated. Reconciles the manifesto against the current code;
292
+ records four accepted decisions and a six-step local-first build sequence.
293
+ - **feat(claims): the claim entity (ADR-0001 step 1).** First-class,
294
+ manuscript-anchored `Claim` (`claim_text`, controlled `claim_type`,
295
+ `manuscript_location`, extraction provenance), persisted to
296
+ `.zotsynth/claims/`, provenance-stamped and audited (`claim.write`).
297
+ AI-extracted claims must name their model (mirrors the AI-needs-provenance
298
+ rule). New `claims/` package, `schemas/claim.py`, `validators/claim.py`, store
299
+ CRUD, and `claim-add` / `claim-list` CLI. Mutates no Zotero state, decides
300
+ nothing — the spine only.
301
+ - **feat(claims): claim ↔ candidate linkage (ADR-0001 step 2).** Link staged
302
+ intake hits to a claim as `ClaimPaperCandidate`s, preserving retrieval
303
+ query/source/rank/why-found, deduped per claim by normalized PMID/DOI (never
304
+ title-only). Persisted to `.zotsynth/candidates/<claim_id>.json`, audited
305
+ (`candidate.link`). New `schemas/candidate.py`, `validators/candidate.py`,
306
+ `CandidateService`, store CRUD, and `claim-link-candidates` / `candidate-list`
307
+ CLI. Asserts no support, decides nothing, writes nothing to Zotero.
308
+ - **feat(claims): claim-support dual rating (ADR-0001 step 3).** The core asset
309
+ dimension — *does this paper support **this claim**?* — distinct from study
310
+ quality. A `ClaimSupportRating` keyed to `(claim_id, candidate_id)` with a
311
+ controlled support vocabulary (`directly_supports … contradicts`/`unclear`) +
312
+ PICO fit subscores (0/1/2). Rides on the same dual-rating invariants and
313
+ **reuses** the proven value blocks (`AIProvenance`/`Comparison`/`Adjudication`/
314
+ `Blinding`): human value locked, AI blind + advisory + never final, discordance
315
+ needs human/panel adjudication, final never sourced from AI. New
316
+ `schemas/claim_support.py`, `validators/claim_support.py`, `ClaimSupportEngine`
317
+ + `ClaimSupportRater` seam (`FakeClaimSupportRater`), store CRUD with the
318
+ human-lock guard, and the `claim-support-*` CLI family.
319
+ - **feat(claims): final decisions (ADR-0001 step 4).** The human-owned terminal
320
+ judgment per (claim, candidate): `accept | reject | needs_second_review |
321
+ accepted_with_caution`, recording the final support status it rests on, the
322
+ human/AI agreement status, decider, and reason. **Mission invariant** (enforced):
323
+ you cannot `accept`/`accepted_with_caution` a candidate whose final support
324
+ status does not support the claim, and you cannot finalize accept/reject on an
325
+ unresolved discordance (adjudicate first or record `needs_second_review`). New
326
+ `schemas/decision.py`, `validators/decision.py`, `DecisionService`, store CRUD
327
+ (`decision.final` audit), `claim-decide` / `decision-list` CLI. This is the
328
+ object the decision-gated write (step 5) will require.
329
+ - **feat(writeback): decision-gated write transactions + undo (ADR-0001 step 5).**
330
+ Promotes the one-use write token into a durable `ZoteroTransaction`
331
+ (`previewed | committed | undone | failed`) with an `undo_snapshot`, enforcing
332
+ the §6 invariant: a *validated* Zotero write exists only for a final `accept`/
333
+ `accepted_with_caution` decision and always carries its chain (claim · candidate
334
+ · decision · provenance · transaction · audit · undo). Refuses a candidate with
335
+ no PMID/DOI (anti-fabrication); previews by default; degrades honestly to a
336
+ `failed` transaction with no silent write. `undo` deletes **only** the keys the
337
+ transaction created, version-guarded (`If-Unmodified-Since-Version`) so a user
338
+ edit aborts the delete (HTTP 412) instead of clobbering it. New
339
+ `schemas/transaction.py`, `validators/transaction.py`, `TransactionService`,
340
+ backend `undo()` capability (+ `HttpClient.delete`), store CRUD
341
+ (`zotero.transaction.*` audit), `claim-commit` / `txn-list` / `txn-show` /
342
+ `txn-undo` CLI. Closes the stress-test's "no undo" gap.
343
+
344
+ ### Connection & capability foundation
345
+
346
+ Hardening sprint driven by an external persona stress test. Builds a
347
+ truth-telling foundation and fixes four verified bugs (each a violation of
348
+ ZotSynth's own integrity invariants).
349
+
350
+ - **feat: `zotsynth status` — Connection & Capabilities (read-only).** Reports
351
+ live Zotero/BBT connection + versions, PubMed email + secret *state* (source,
352
+ never the value), and the configured write backend's **actual** supported vs
353
+ unsupported operations + a permission summary. `capabilities.py` +
354
+ `CapabilityStatusService`.
355
+ - **fix(credentials): keyring errors degrade gracefully.** A macOS
356
+ `KeyringError(-50)` during NCBI-key lookup used to crash `literature_search`
357
+ (`resolve_secret` only caught `CredentialError`). `KeyringCredentialStore` now
358
+ raises a clean `CredentialError` → resolved to keyless, not a crash; status
359
+ reports `store_unavailable`.
360
+ - **fix(pubmed): capture search diagnostics.** `esearch` now surfaces the true
361
+ total count, NCBI query translation, and `warninglist`/`errorlist`. A
362
+ malformed query (`lung cancer AND (`) is flagged (`warnings`) or degrades
363
+ (`pubmed_query_error`) instead of silently staging unintended hits. Carried
364
+ onto the intake record + printed by the CLI. The exact query is still preserved.
365
+ - **fix(writeback): capability-honest previews + audited failures.** Backends
366
+ expose `supports(kind)`. A preview for an op the backend can't perform (e.g.
367
+ `note_add`/`tag_add` on the web_api backend) now fails **early** with
368
+ `operation_unsupported` and mints **no** token, instead of previewing success
369
+ then failing on confirm. Every failed write attempt (unsupported, unavailable,
370
+ backend error) now appends a `zotero.write.failed` audit event.
371
+
372
+ ## 0.2.0 — write-back + secure onboarding
373
+
374
+ - **feat(writeback): Zotero Web API item-creation backend.** `WebApiWriteBackend`
375
+ (api.zotero.org) creates items (`item_add` / `intake_push`) and assigns them to
376
+ a collection at creation. `make_backend` wires `web_api` when enabled with
377
+ credentials resolved from env/keyring; missing creds → UnavailableBackend (no
378
+ silent fallback). All write guards intact (dry-run default, one-use token,
379
+ audit, honest degradation).
380
+ - **feat: secure onboarding (`zotsynth onboard`).** Non-secret identifiers
381
+ (PubMed email, Zotero user/library id, default collection) → config; secret
382
+ keys (Zotero write key, NCBI key) → OS keyring via `keyring`, with
383
+ `ZOTSYNTH_*` env escape hatch. Secrets are validated, then stored, and never
384
+ written to config/logs/history or echoed. Adds `credentials.py`,
385
+ `onboarding.py`, config fields, and the `keyring` optional dependency.
386
+ - **fix: resolve citekeys from Better BibTeX CSL-JSON (`item.search`).** The
387
+ shared resolver (map_bootstrap / extract / claim_check) now parses the Zotero
388
+ key from the CSL `id` URI instead of a non-existent `itemKey` field; contract
389
+ test pins the real response shape.
390
+
391
+ ## 0.1.1 — patch
392
+
393
+ - **fix(pubmed): efetch DOI from the article's own id list, not cited
394
+ references.** efetch parsing used `.//ArticleIdList/ArticleId`, which descends
395
+ into `PubmedData/ReferenceList` and could surface a *cited reference's* DOI as
396
+ the article's DOI. Now scoped to the article's own `PubmedData/ArticleIdList`.
397
+ Citation-integrity fix surfaced by a live `literature_search` run; covered by a
398
+ regression test with decoy reference DOIs.
399
+ - Package metadata and runtime `__version__` bumped to `0.1.1`.
400
+
401
+ ## 0.1.0 — integrity spine (steps 1–9)
402
+ > The `0.1.0` line was released from the build below (internally versioned 0.7.0
403
+ > during development, then aligned to 0.1.0). Tag: `v0.1.0-integrity-spine`.
404
+
405
+ ## 0.7.0 — full build (steps 1–9)
406
+
407
+ ### Step 1 — probe layer + state (`bbc0b37`, hardened in `319cd88`)
408
+ - Startup probe (probe-not-proof): Zotero `/api/` (read-only/GET-only), Better
409
+ BibTeX `api.ready`, CAYW `probe=1`, with remediation strings.
410
+ - Honest version parsing: Zotero app version from `x-zotero-version`; schema
411
+ version (`42`) and local-API version never surfaced as the app version; BBT
412
+ version read live from `api.ready` (`betterbibtex` field), never hardcoded.
413
+ - `.zotsynth/` state layer: `config.json`, version-stamped frames, evidence map
414
+ + citekey-centered reverse index, per-rating records, snapshots, intake,
415
+ prisma, and a hash-chained `audit_log.jsonl`.
416
+ - Binding validators: model-pin-required, rating-vs-assist task split,
417
+ frame/subject keying, and the rating-record validity invariant.
418
+
419
+ ### Step 2 — read/discover + cite (`778d3f8`)
420
+ - Read-only `zot_search`/`zot_item`/`zot_collections`/`zot_attachments` honoring
421
+ the personal/group/all library selector; honest degradation when absent.
422
+ - `cite` resolves citekeys by exact match via Better BibTeX; never invents keys.
423
+
424
+ ### Step 3 — bib_sync + evidence map (`5f7d7e5`)
425
+ - `bib_sync`: multi-file Pandoc/LaTeX citekey extraction (code/URL/email masked),
426
+ exact-match resolution, orphan/unused reporting, per-file + master exports,
427
+ honest degradation.
428
+ - Operational evidence-map model: typed nodes/attachments with per-kind scope
429
+ rules and a citekey-centered reverse index; all mutations audited.
430
+
431
+ ### Step 4 — extraction + claim_check (`e2ef3ce`)
432
+ - Deterministic passage retrieval over Zotero full text + annotations.
433
+ - `extract`: assistive regex/rule extraction with passages; unverifiable when
434
+ absent; never guesses; never writes the evidence map.
435
+ - `claim_check`: lexical support only — `supported_candidate` / `no_support_found`
436
+ / `unverifiable`; never asserts truth; never invents keys.
437
+
438
+ ### Step 5 — PubMed intake + manual import (`50b9db5`)
439
+ - PubMed-only provider (E-utilities) with rate-limit (3/10 rps) + 429 retry and
440
+ honest degradation (`missing_ncbi_email` / `pubmed_unavailable`).
441
+ - `literature_search` (verbatim user query) + `import_results` (RIS/CSV/BibTeX),
442
+ dedupe (in-run / prior-intake / library by DOI+PMID, never title-only),
443
+ pre-decision intake records (`decision: null`).
444
+
445
+ ### Step 6 — snapshot / corpus_diff / surveillance / map_bootstrap (`e5abf40`)
446
+ - `snapshot`: hashed read-only corpus + evidence-map capture; never invents
447
+ citekeys; no fake snapshot when Zotero is down.
448
+ - `corpus_diff`: identity-continuity diffing; reverse-index-driven staleness.
449
+ - `surveillance_refresh`: re-run a saved query from its own last-run date
450
+ (mechanical date append, not a redesign).
451
+ - `map_bootstrap` (minimal): section/study/explicit-outcome seeding; dry-run
452
+ default; orphans never invented.
453
+
454
+ ### Step 7 — dual-rating + assess + retraction + PRISMA (`e197ea4`)
455
+ - Dual-rating engine (`rating_start`/`commit_human`/`run_ai`/`compare`/
456
+ `adjudicate`): blinded advisory AI behind an `AiRater` seam; the hardening
457
+ invariants enforced and audited.
458
+ - `assess`: human-chosen controlled values only; tag-mirror deferred to step 9.
459
+ - `retraction_scan`: DOI/PMID via a provider seam (no title-only truth).
460
+ - `prisma_ledger`: human-only decisions; AI votes referenced by `rating_id` only.
461
+
462
+ ### Step 8 — evidence export + agreement report (`d45f916`)
463
+ - `evidence_export`: neutral CSV/Markdown/CSL-JSON; AI values excluded by
464
+ default and clearly labelled/separated when requested; mutates nothing.
465
+ - `agreement_report`: raw agreement, Cohen κ, ordinal weighted κ (ROBINS-I
466
+ *No information* excluded + reported), adjudication rate; refuses κ across
467
+ mixed schemes; PRISMA-trAIce / RAISE-style transparency section that disclaims
468
+ any compliance/endorsement claim.
469
+
470
+ ### Step 9 — guarded Zotero write-back (`fcbaf18`)
471
+ - Optional write layer: dry-run default, payload-bound one-use confirmation
472
+ tokens, distinct `zotero.write.applied` audit event, **no silent fallback**.
473
+ - Tools: `note_add`, `annotation_add`, `item_add`, `tag_add`, `tag_remove`,
474
+ `collection_add_item`, `intake_push`, `assessment_tag_mirror` (mirrors only
475
+ human/final values; replaces prior same-scheme tag). Live default backend is
476
+ clearly degraded (`write_layer_unavailable`); no network writes.
477
+
478
+ ### Testing
479
+ - 308 tests, fully offline (fake Zotero/BBT/PubMed/write/AI seams). No live
480
+ PubMed calls and no live Zotero writes occur during the suite.