From b818c290b528712836425164059a870123b8c68e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 02:01:49 +0900 Subject: [PATCH 01/14] test(zotero): require abstention review context --- .../tests/steward_review_context.rs | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 crates/conceptweave-zotero/tests/steward_review_context.rs diff --git a/crates/conceptweave-zotero/tests/steward_review_context.rs b/crates/conceptweave-zotero/tests/steward_review_context.rs new file mode 100644 index 00000000..f8d46f47 --- /dev/null +++ b/crates/conceptweave-zotero/tests/steward_review_context.rs @@ -0,0 +1,48 @@ +use conceptweave_zotero::{Disposition, ItemData, ZoteroItem, classify_snapshot}; + +fn item(key: &str, title: &str, abstract_note: &str) -> ZoteroItem { + ZoteroItem { + key: key.into(), + version: 7, + data: ItemData { + item_type: "journalArticle".into(), + title: title.into(), + abstract_note: abstract_note.into(), + doi: String::new(), + parent_item: String::new(), + collections: vec![], + tags: vec![], + }, + } +} + +#[test] +fn abstentions_retain_only_the_abstract_needed_for_local_steward_review() { + let review_abstract = "A domain-specific vocabulary outside the deterministic rules."; + let report = classify_snapshot( + "9.0.6".into(), + None, + 42, + vec![ + item("REVIEW01", "Unmatched domain study", review_abstract), + item( + "DECIDED1", + "Ontology learning", + "This abstract must not be copied into review-only context.", + ), + item("EMPTY001", "Unmatched title", ""), + ], + ); + + let review = &report.classified_items[0]; + assert_eq!(review.proposed_disposition, Disposition::NeedsStewardReview); + assert_eq!(review.review_abstract_note.as_deref(), Some(review_abstract)); + + let decided = &report.classified_items[1]; + assert_eq!(decided.proposed_disposition, Disposition::Generation); + assert!(decided.review_abstract_note.is_none()); + + let empty = &report.classified_items[2]; + assert_eq!(empty.proposed_disposition, Disposition::NeedsStewardReview); + assert!(empty.review_abstract_note.is_none()); +} From c93a50bf490fa7fe647cad351be6a1c9f1e9ef4c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 02:02:05 +0900 Subject: [PATCH 02/14] feat(zotero): retain minimal steward review context --- crates/conceptweave-zotero/src/lib.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/conceptweave-zotero/src/lib.rs b/crates/conceptweave-zotero/src/lib.rs index e5439ad4..988ce4e7 100644 --- a/crates/conceptweave-zotero/src/lib.rs +++ b/crates/conceptweave-zotero/src/lib.rs @@ -131,6 +131,9 @@ pub struct ClassifiedItem { pub item_type: String, /// Human-readable title retained in the local report only. pub title: String, + /// Nonempty abstract retained only when a steward must classify the item. + #[serde(skip_serializing_if = "Option::is_none")] + pub review_abstract_note: Option, /// Collection keys observed with the item. pub collection_keys: Vec, /// Tag text observed with the item. @@ -2459,6 +2462,9 @@ fn classify_item(item: &ZoteroItem, child_item_keys: Vec) -> ClassifiedI item_version: item.version, item_type: item.data.item_type.clone(), title: item.data.title.clone(), + review_abstract_note: (proposed_disposition == Disposition::NeedsStewardReview + && !item.data.abstract_note.trim().is_empty()) + .then(|| item.data.abstract_note.clone()), collection_keys: item.data.collections.clone(), tags: item.data.tags.clone(), proposed_disposition, From 1b7ad0901dc28d4f65b35ca1141ca5aed73e140f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 02:02:28 +0900 Subject: [PATCH 03/14] test(zotero): select review fixtures by identity --- .../tests/steward_review_context.rs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/crates/conceptweave-zotero/tests/steward_review_context.rs b/crates/conceptweave-zotero/tests/steward_review_context.rs index f8d46f47..4588b696 100644 --- a/crates/conceptweave-zotero/tests/steward_review_context.rs +++ b/crates/conceptweave-zotero/tests/steward_review_context.rs @@ -34,15 +34,27 @@ fn abstentions_retain_only_the_abstract_needed_for_local_steward_review() { ], ); - let review = &report.classified_items[0]; + let review = report + .classified_items + .iter() + .find(|item| item.item_key == "REVIEW01") + .unwrap(); assert_eq!(review.proposed_disposition, Disposition::NeedsStewardReview); assert_eq!(review.review_abstract_note.as_deref(), Some(review_abstract)); - let decided = &report.classified_items[1]; + let decided = report + .classified_items + .iter() + .find(|item| item.item_key == "DECIDED1") + .unwrap(); assert_eq!(decided.proposed_disposition, Disposition::Generation); assert!(decided.review_abstract_note.is_none()); - let empty = &report.classified_items[2]; + let empty = report + .classified_items + .iter() + .find(|item| item.item_key == "EMPTY001") + .unwrap(); assert_eq!(empty.proposed_disposition, Disposition::NeedsStewardReview); assert!(empty.review_abstract_note.is_none()); } From e5af1b8a7946853d806343a6cc7146d2a219c713 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 02:04:43 +0900 Subject: [PATCH 04/14] docs(zotero): define sensitive steward review context --- CHANGELOG.md | 1 + SECURITY.md | 4 +++- docs/PRD.md | 2 +- docs/TRD.md | 4 +++- docs/adr/0006-zotero-research-intake.md | 2 +- docs/product-technical-gap-baseline.md | 2 ++ 6 files changed, 11 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac34db04..e08e34d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ All notable changes to ConceptWeave are documented here. - Draft 2020-12 JSON Schema for the semantic-candidate public contract. - Standards and research doctoring covering stable W3C ontology standards, 2026 RDF/SHACL work in progress, Apache Ossie, and recent LLM ontology-engineering research. - Read-only delayed reconciliation receipts for indeterminate Zotero rollback operations. +- Minimal local abstract context for Zotero items that require steward classification. ### Security diff --git a/SECURITY.md b/SECURITY.md index 548c11e5..a0a98d12 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -4,6 +4,8 @@ All source artifacts, generated candidate payloads, external ontology files, model outputs, and future web-retrieved content are untrusted input. +Local Zotero classification reports can contain bibliographic titles, tags, matched values, and abstention abstracts. They are sensitive review material, remain outside the repository, and are not publication artifacts. + ## Required controls - source size, type, nesting, archive/decompression, and parser-time bounds; @@ -38,4 +40,4 @@ Zotero 10+ write authorization and mutation use the provider-defined loopback HT A hostile same-host process capable of binding, observing, or interposing on the loopback endpoint therefore remains an unresolved credential-confidentiality threat. The currently documented Zotero Local API does not provide an HTTPS or OS-authenticated IPC write endpoint that ConceptWeave can substitute. Consequently, mock/local orchestration may be tested, but enterprise-secure live write-back remains fail closed. It may become release-eligible only if Zotero provides a protected transport or an explicit product-security/governance decision narrows the supported threat model and accepts the residual same-host risk. The detailed actor, asset, residual-risk, and release decision is maintained in `THREAT_MODEL.md`, and `docs/TRD.md` carries the same technical boundary. -Security findings become tests before the related runtime capability can be marked release-ready. \ No newline at end of file +Security findings become tests before the related runtime capability can be marked release-ready. diff --git a/docs/PRD.md b/docs/PRD.md index 9b0576d1..147b0184 100644 --- a/docs/PRD.md +++ b/docs/PRD.md @@ -56,7 +56,7 @@ All LLM-backed induction uses `contextual-orchestrator`. Model output is untrust ### FR-9 Research evidence intake -Read one immutable Zotero Local API library-version snapshot and propose exactly one research disposition for every top-level bibliographic item. Each proposal retains the item key/version, exact matched metadata values, rule revision, linked child records, and any model receipt. Weak evidence and evidence that matches multiple specific disposition families must abstain into steward review. Duplicate DOI/title identities are review candidates only: intake never merges, deletes, or silently mutates Zotero records. +Read one immutable Zotero Local API library-version snapshot and propose exactly one research disposition for every top-level bibliographic item. Each proposal retains the item key/version, exact matched metadata values, rule revision, linked child records, and any model receipt. Weak evidence and evidence that matches multiple specific disposition families must abstain into steward review. A local abstention retains its nonempty abstract as review context; decided items omit that extra copy. Duplicate DOI/title identities are review candidates only: intake never merges, deletes, or silently mutates Zotero records. For every connected duplicate component, accept externally verified steward decisions selecting one component-level canonical item. Produce a local-only manifest that binds the decisions to the raw snapshot, its complete item-key/item-version coordinates, and exact duplicate-candidate membership, and records every component source revision plus before, after, and rollback canonical-key mappings. Classification preserves every Zotero source record. diff --git a/docs/TRD.md b/docs/TRD.md index ace6dfa9..0645c0c7 100644 --- a/docs/TRD.md +++ b/docs/TRD.md @@ -61,13 +61,15 @@ Evaluation must separate extraction recall, semantic correctness, structural cor `conceptweave-zotero` reads only the loopback Local API with at most 100 records per page, an 8 MiB page-body limit, a 50,000-item whole-snapshot limit, a 256 MiB cumulative body limit, redirects disabled, and finite connect/response/body/global timeouts. Every request pins `Zotero-API-Version: 3`; every response must report API version 3, while its schema version is recorded and must remain stable across the snapshot. Before another request is issued, exhausted whole-snapshot budgets fail closed. Before a parsed page is accumulated, checked item-count and byte arithmetic must remain within both the advertised total and the configured whole-snapshot budgets. `Total-Results`, `Last-Modified-Version`, Zotero version, and server identity must remain identical across all pages; contract drift, malformed JSON, an empty intermediate page, duplicate keys, or an oversized response fails the run. -Every top-level bibliographic record receives exactly one proposed disposition. `NeedsStewardReview` also records a deterministic abstention reason so missing classification metadata, vocabulary unsupported by the current deterministic rules, and present-but-unmatched metadata are distinguishable. DOI duplicate identity normalization treats bare DOI values, `doi:`, `doi.org`, and legacy `dx.doi.org` resolver forms as the same identity when their normalized DOI is equal. +Every top-level bibliographic record receives exactly one proposed disposition. `NeedsStewardReview` also records a deterministic abstention reason so missing classification metadata, vocabulary unsupported by the current deterministic rules, and present-but-unmatched metadata are distinguishable. Its nonempty abstract is retained in the local report so a steward can decide from the same immutable snapshot; non-abstained items omit that review-only field. DOI duplicate identity normalization treats bare DOI values, `doi:`, `doi.org`, and legacy `dx.doi.org` resolver forms as the same identity when their normalized DOI is equal. Duplicate review is independent of subject classification. A reviewed decision set must match the exact raw-snapshot digest and complete item-key/item-version coordinates, cover every duplicate candidate exactly once, select one retained key from the connected duplicate component, and pass an external governance verifier. Every operation records all component item revisions, identity mappings before and after canonicalization, and the exact rollback mapping. These mappings affect only downstream identity resolution; Zotero records are neither mutated nor deleted. Golden-set evaluation accepts only a governance receipt verified with the complete reviewed set by a caller-owned authorization boundary. Its library version, rule revision, canonical SHA-256 content digest, and every observed parent/child item-key/item-version identity must bind the classification report. The digest covers every raw Zotero item in canonical key order. Blank, duplicate, unknown, stale, content-mismatched, label-mismatched, or abstention-as-truth inputs fail closed. The output retains the verified library version, rule revision, and opaque snapshot digest, but contains no item keys, reviewer identity, or bibliographic text. Production authorization remains Keyverse/governance-owned; this crate passes the complete reviewed labels to that boundary instead of minting authority. A successful classification report carries an `audit_summary` whose snapshot, bibliographic, proposed-disposition, provenance-complete, abstention, duplicate-candidate, failure, and per-disposition counts are derived from the same in-memory immutable snapshot. Zotero item version zero remains a valid observed coordinate for never-synced Zotero 9 records; provenance completeness rejects a missing item key rather than inventing a positive-only version invariant. Reader failures return an error instead of a partial report; therefore a returned report records `failure_count=0` rather than hiding partial failures. +The local report can contain titles, tags, matched metadata, and abstention abstracts. It is sensitive steward-review material, remains outside the repository, and is not a publication artifact. + The report is local JSON and contains proposals rather than governance decisions. CLI output is restricted to a new direct child of canonical `/tmp` or the operating system temporary directory; relative paths, nested paths, existing paths, and symlinks are rejected, and create-new file semantics prevent overwrite/path-swap writes. Reviewed collection/tag changes can produce a pure local plan whose default mode is dry-run. The plan requires exact report and item preconditions, complete before/after/rollback arrays, externally verified authority, and preserved Zotero tag types; its fields are externally read-only after validation. Zotero 9 execute mode fails closed. The execution core makes no call in dry-run mode; otherwise it preflights every item before the first write, advances the library precondition only from verified state, stops on the first adapter or response failure, and re-reads that item through the same boundary. A proven applied state receives reverse-ordered rollback evidence containing server identity, post-write item revision, expected post-write metadata, and the complete restoration state even when the write response was lost; a state matching neither the before nor after contract is marked indeterminate. The generic rollback core rejects operations spanning server identities before any read, then reads every receipt item at one current library version and verifies that evidence before writing. It follows receipt order, advances the library version only after a verified inverse write, and on failure re-reads the item to classify restored, unchanged, or indeterminate state. Its secret-free receipt separates restored, failed, indeterminate, not-attempted, and remaining work. Automatic retry evidence includes a failed current operation only when it is proven unchanged; an indeterminate operation and its complete metadata are retained separately for operator reconciliation. The delayed reconciliation boundary performs exactly one server-bound read and no write. It treats an exact item revision plus expected metadata as unchanged even if unrelated library changes advanced the library version, and treats restoration metadata as restored only at a newer item revision; every identity, metadata, or version ambiguity remains indeterminate. Already consumed evidence fails preflight on reuse. The Zotero 10+ transport is pinned to loopback, rejects redirects, and uses finite timeouts. A one-shot authorization POST to `/api/local/authorize` sends JSON `{ "appName": ... }`, `Content-Type: application/json`, and the expected `Zotero-Server-ID`. Application names must be nonblank and at most 128 bytes. Every authorization, read, and write response must repeat that exact server identity before its status is interpreted. A bounded `200 OK` authorization response contains a 32-byte visible-ASCII key plus the `remember` decision. A same-server `403` is classified as denial only when its bounded JSON body parses with `denied: true`; missing, malformed, oversized, or false denial evidence fails closed. `429` exposes only a safe integer `Retry-After` delta of at most one day. Neither condition retries or prompts again. The authorization wrapper is neither debug-printable nor serializable, keeps the key private, exposes only the remembered decision, and can be consumed into the existing adapter. Item responses remain capped at 1 MiB. Writes distinguish same-server `401` reauthorization from same-server `412` stale preconditions, while a different-server `412` on library, item, or write paths is a database switch; all errors remain static and secret-free. Narrow adapter functions reuse the generic write and rollback cores. Mock TCP evidence covers the wire contract, but no approved live Zotero 10 authorization, write, partial-failure, or rollback has been performed. diff --git a/docs/adr/0006-zotero-research-intake.md b/docs/adr/0006-zotero-research-intake.md index cbba8863..13f49b02 100644 --- a/docs/adr/0006-zotero-research-intake.md +++ b/docs/adr/0006-zotero-research-intake.md @@ -17,7 +17,7 @@ ConceptWeave owns a small read-only Anti-Corruption Layer from Zotero into resea The adapter links child records, emits exactly one deterministic proposed disposition per top-level bibliographic item, and abstains when evidence is weak or ambiguous. Every abstention preserves a deterministic reason distinguishing missing classification metadata, vocabulary outside the current deterministic rules, present-but-unmatched metadata, and conflicting specific disposition families. Specific rule families are evaluated together rather than by first-match priority. When evidence matches multiple families, the proposal becomes `NeedsStewardReview` and all matching evidence is retained. -Matched metadata values are copied into the local-only evidence receipt for replay. This is necessary for abstract-only matches because a later Zotero revision cannot reconstruct the exact text used for an earlier proposal from item key/version alone. DOI/title matches remain reversible duplicate candidates, including legacy `dx.doi.org` resolver forms. +Matched metadata values are copied into the local-only evidence receipt for replay. This is necessary for abstract-only matches because a later Zotero revision cannot reconstruct the exact text used for an earlier proposal from item key/version alone. An abstention likewise retains its nonempty abstract so a steward can resolve unsupported or unmatched vocabulary from the same immutable report; decided items omit that review-only copy. The report remains sensitive local material. DOI/title matches remain reversible duplicate candidates, including legacy `dx.doi.org` resolver forms. Duplicate candidates become canonical references only through externally verified steward decisions bound to the raw digest, complete item-key/item-version snapshot, and exact candidate membership. Overlapping candidates form one connected component and must select one component-level canonical item. Every resulting operation retains all component source revisions and complete before/after/rollback key mappings. It changes downstream identity resolution only; classification does not merge, delete, or mutate Zotero source records. diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index c361feb1..24a91b6b 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -46,6 +46,8 @@ The successor authorization slice adds one-shot Zotero 10 Local API authorizatio Local evidence on 2026-09-04 showed Zotero 9.0.6, Local API v3/schema 42, library version 12341, 8,326 total items, and 3,719 top-level items. The corrected read-only run observed all 8,326 records at that single version and classified all 3,715 top-level bibliographic records; four top-level note/attachment/annotation records were correctly excluded. It proposed 56 adjacent-evidence records, 1 semantic-consumption bridge, and 3,658 steward-review abstentions, linked children for 3,287 records, and surfaced 49 reversible duplicate groups (18 DOI, 31 title). No live record matched multiple specific disposition families; the tested conflict path still abstains fail-closed. Token-boundary matching prevents strings such as `knowledge` from becoming false OWL evidence. These are local aggregate observations, not reviewed truth or applied Zotero changes. The report stays outside the repository. +The 3,658-item abstention queue now preserves each nonempty abstract only in its sensitive local report entry, alongside the existing title, tags, collections, item revision, and abstention reason. Deterministically decided items omit this extra review copy. This makes the complete steward workload reviewable without creating a second workload model or weakening the snapshot-bound external approval requirement. + The golden-set evaluation contract now records aggregate precision/recall numerators and denominators, requires an externally verified governance receipt bound to the complete item-key/item-version snapshot, rejects abstention as expected truth, and retains verified revisions plus an opaque snapshot digest so detached metrics remain attributable. Item and reviewer identities stay out of its output. Successful classification reports also carry same-snapshot aggregate coverage, provenance, abstention, duplicate, disposition, and failure evidence. Connected duplicate components now produce a snapshot-bound local review manifest only after external steward verification; every operation retains all component source revisions and before/after/rollback canonical mappings while Zotero records remain unchanged. Reviewed collection/tag changes produce a default-dry-run plan bound to exact server, library, item, rule, digest, and complete metadata preconditions; externally read-only plan state prevents post-validation forgery, automatic-tag type is preserved, and Zotero 9 execute mode is rejected. The injected execution core calls nothing in dry-run mode, preflights every item before a write, stops at the first failure, reconciles a lost or invalid response with a same-boundary read, and emits rollback evidence for every item whose applied state is proven. The generic rollback executor rejects mixed-server evidence before reading, verifies all expected post-write states at one current library version before the first inverse write, follows the receipt's reverse order, advances only from verified writes, and stops with restored, failed, indeterminate, not-attempted, and remaining classifications. Unprovable state is reported as indeterminate with complete operation evidence retained separately and excluded from automatic retry until operator reconciliation. A later read-only reconciliation records the observed state, tolerates unrelated library-version advancement, and emits retry evidence only for an exact unchanged item; restoration metadata at a newer item revision proves current state but not causality. Reuse after restoration fails before writing. A fixed-loopback Zotero 10 adapter supplies stable server-pinned reads and authenticated one-item writes with atomic library/item preconditions, complete collection/tag replacement, and bounded verified responses; thin wrappers reuse both generic executors. Mock fixtures verify these contracts and secret-free failures. Korean, Japanese, Chinese, Vietnamese, Spanish, German, and French ontology-alignment metadata now have explicit fail-closed abstention coverage alongside the existing English positive case; this is safety evidence, not translated classification support. No real precision/recall, duplicate merge, write, or rollback claim exists until a steward supplies reviewed local decisions and a production authorization adapter verifies them. AC6 still requires approved live Zotero 10 write, partial-failure, and rollback evidence. Multilingual rule expansion remains a later evidence-driven change and must not reduce abstention safety. A dedicated utility repository remains unnecessary until an independently released cross-product contract exists. 1. **Concrete Source Observation adapter** — maintained Rust PostgreSQL driver behind `conceptweave-source-port`; adapter-local credential resolution; explicit read-only mode; statement timeout, cancellation, row/byte/concurrency budgets; complete immutable snapshot or fail closed; deterministic replay against a frozen anonymized GRC-shaped fixture. From 8ae81e260e1f6737e2927d488aad3919533288ef Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 02:04:55 +0900 Subject: [PATCH 05/14] style(zotero): format steward review test --- crates/conceptweave-zotero/tests/steward_review_context.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/conceptweave-zotero/tests/steward_review_context.rs b/crates/conceptweave-zotero/tests/steward_review_context.rs index 4588b696..f3f8d8eb 100644 --- a/crates/conceptweave-zotero/tests/steward_review_context.rs +++ b/crates/conceptweave-zotero/tests/steward_review_context.rs @@ -40,7 +40,10 @@ fn abstentions_retain_only_the_abstract_needed_for_local_steward_review() { .find(|item| item.item_key == "REVIEW01") .unwrap(); assert_eq!(review.proposed_disposition, Disposition::NeedsStewardReview); - assert_eq!(review.review_abstract_note.as_deref(), Some(review_abstract)); + assert_eq!( + review.review_abstract_note.as_deref(), + Some(review_abstract) + ); let decided = report .classified_items From a433e2f2e9e67a7b2a8fee3352162b51d9e280f8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 02:06:21 +0900 Subject: [PATCH 06/14] docs(zotero): record live steward context coverage --- docs/product-technical-gap-baseline.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 24a91b6b..fb7505e6 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -46,7 +46,7 @@ The successor authorization slice adds one-shot Zotero 10 Local API authorizatio Local evidence on 2026-09-04 showed Zotero 9.0.6, Local API v3/schema 42, library version 12341, 8,326 total items, and 3,719 top-level items. The corrected read-only run observed all 8,326 records at that single version and classified all 3,715 top-level bibliographic records; four top-level note/attachment/annotation records were correctly excluded. It proposed 56 adjacent-evidence records, 1 semantic-consumption bridge, and 3,658 steward-review abstentions, linked children for 3,287 records, and surfaced 49 reversible duplicate groups (18 DOI, 31 title). No live record matched multiple specific disposition families; the tested conflict path still abstains fail-closed. Token-boundary matching prevents strings such as `knowledge` from becoming false OWL evidence. These are local aggregate observations, not reviewed truth or applied Zotero changes. The report stays outside the repository. -The 3,658-item abstention queue now preserves each nonempty abstract only in its sensitive local report entry, alongside the existing title, tags, collections, item revision, and abstention reason. Deterministically decided items omit this extra review copy. This makes the complete steward workload reviewable without creating a second workload model or weakening the snapshot-bound external approval requirement. +The 3,658-item abstention queue now preserves each nonempty abstract only in its sensitive local report entry, alongside the existing title, tags, collections, item revision, and abstention reason. A live read-only rerun on 2026-09-05 retained review abstracts for 2,665 abstentions, copied none into the 57 deterministically decided entries, observed all 8,326 records at library version 12341, and reported zero read failures. The remaining 993 abstentions have no abstract and still retain their available title/tag/collection context and explicit reason. This makes the complete steward workload reviewable without creating a second workload model or weakening the snapshot-bound external approval requirement. The report remains outside the repository. The golden-set evaluation contract now records aggregate precision/recall numerators and denominators, requires an externally verified governance receipt bound to the complete item-key/item-version snapshot, rejects abstention as expected truth, and retains verified revisions plus an opaque snapshot digest so detached metrics remain attributable. Item and reviewer identities stay out of its output. Successful classification reports also carry same-snapshot aggregate coverage, provenance, abstention, duplicate, disposition, and failure evidence. Connected duplicate components now produce a snapshot-bound local review manifest only after external steward verification; every operation retains all component source revisions and before/after/rollback canonical mappings while Zotero records remain unchanged. Reviewed collection/tag changes produce a default-dry-run plan bound to exact server, library, item, rule, digest, and complete metadata preconditions; externally read-only plan state prevents post-validation forgery, automatic-tag type is preserved, and Zotero 9 execute mode is rejected. The injected execution core calls nothing in dry-run mode, preflights every item before a write, stops at the first failure, reconciles a lost or invalid response with a same-boundary read, and emits rollback evidence for every item whose applied state is proven. The generic rollback executor rejects mixed-server evidence before reading, verifies all expected post-write states at one current library version before the first inverse write, follows the receipt's reverse order, advances only from verified writes, and stops with restored, failed, indeterminate, not-attempted, and remaining classifications. Unprovable state is reported as indeterminate with complete operation evidence retained separately and excluded from automatic retry until operator reconciliation. A later read-only reconciliation records the observed state, tolerates unrelated library-version advancement, and emits retry evidence only for an exact unchanged item; restoration metadata at a newer item revision proves current state but not causality. Reuse after restoration fails before writing. A fixed-loopback Zotero 10 adapter supplies stable server-pinned reads and authenticated one-item writes with atomic library/item preconditions, complete collection/tag replacement, and bounded verified responses; thin wrappers reuse both generic executors. Mock fixtures verify these contracts and secret-free failures. Korean, Japanese, Chinese, Vietnamese, Spanish, German, and French ontology-alignment metadata now have explicit fail-closed abstention coverage alongside the existing English positive case; this is safety evidence, not translated classification support. No real precision/recall, duplicate merge, write, or rollback claim exists until a steward supplies reviewed local decisions and a production authorization adapter verifies them. AC6 still requires approved live Zotero 10 write, partial-failure, and rollback evidence. Multilingual rule expansion remains a later evidence-driven change and must not reduce abstention safety. A dedicated utility repository remains unnecessary until an independently released cross-product contract exists. From 6ea29094f226e326476f44a4d614b08be619456e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 02:08:00 +0900 Subject: [PATCH 07/14] test(zotero): reject duplicate abstract review context --- .../tests/steward_review_context.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/crates/conceptweave-zotero/tests/steward_review_context.rs b/crates/conceptweave-zotero/tests/steward_review_context.rs index f3f8d8eb..995fd5f6 100644 --- a/crates/conceptweave-zotero/tests/steward_review_context.rs +++ b/crates/conceptweave-zotero/tests/steward_review_context.rs @@ -31,6 +31,11 @@ fn abstentions_retain_only_the_abstract_needed_for_local_steward_review() { "This abstract must not be copied into review-only context.", ), item("EMPTY001", "Unmatched title", ""), + item( + "CONFLICT", + "Unmatched title", + "Ontology learning and ontology matching are compared.", + ), ], ); @@ -60,4 +65,18 @@ fn abstentions_retain_only_the_abstract_needed_for_local_steward_review() { .unwrap(); assert_eq!(empty.proposed_disposition, Disposition::NeedsStewardReview); assert!(empty.review_abstract_note.is_none()); + + let conflict = report + .classified_items + .iter() + .find(|item| item.item_key == "CONFLICT") + .unwrap(); + assert_eq!( + conflict.proposed_disposition, + Disposition::NeedsStewardReview + ); + assert!(conflict.evidence.field_values.contains_key("abstract_note")); + assert!(conflict.review_abstract_note.is_none()); + let serialized = serde_json::to_string(conflict).unwrap(); + assert_eq!(serialized.matches("Ontology learning").count(), 1); } From b63d775e807de17d85c24b7be8082482ba7b761e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 02:08:16 +0900 Subject: [PATCH 08/14] fix(zotero): retain abstention abstracts once --- crates/conceptweave-zotero/src/lib.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/conceptweave-zotero/src/lib.rs b/crates/conceptweave-zotero/src/lib.rs index 988ce4e7..66318bdf 100644 --- a/crates/conceptweave-zotero/src/lib.rs +++ b/crates/conceptweave-zotero/src/lib.rs @@ -2457,14 +2457,17 @@ fn classify_item(item: &ZoteroItem, child_item_keys: Vec) -> ClassifiedI ), }; + let review_abstract_note = (proposed_disposition == Disposition::NeedsStewardReview + && !item.data.abstract_note.trim().is_empty() + && !field_values.contains_key("abstract_note")) + .then(|| item.data.abstract_note.clone()); + ClassifiedItem { item_key: item.key.clone(), item_version: item.version, item_type: item.data.item_type.clone(), title: item.data.title.clone(), - review_abstract_note: (proposed_disposition == Disposition::NeedsStewardReview - && !item.data.abstract_note.trim().is_empty()) - .then(|| item.data.abstract_note.clone()), + review_abstract_note, collection_keys: item.data.collections.clone(), tags: item.data.tags.clone(), proposed_disposition, From 3fcd02d7245b81fa75afba0eea0e3c421bbe2bfd Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 02:09:37 +0900 Subject: [PATCH 09/14] docs(zotero): document single-copy review context --- CHANGELOG.md | 2 +- docs/PRD.md | 2 +- docs/TRD.md | 2 +- docs/adr/0006-zotero-research-intake.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e08e34d5..d89ce8c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ All notable changes to ConceptWeave are documented here. - Draft 2020-12 JSON Schema for the semantic-candidate public contract. - Standards and research doctoring covering stable W3C ontology standards, 2026 RDF/SHACL work in progress, Apache Ossie, and recent LLM ontology-engineering research. - Read-only delayed reconciliation receipts for indeterminate Zotero rollback operations. -- Minimal local abstract context for Zotero items that require steward classification. +- Minimal, nonduplicated local abstract context for Zotero items that require steward classification. ### Security diff --git a/docs/PRD.md b/docs/PRD.md index 147b0184..14d6346f 100644 --- a/docs/PRD.md +++ b/docs/PRD.md @@ -56,7 +56,7 @@ All LLM-backed induction uses `contextual-orchestrator`. Model output is untrust ### FR-9 Research evidence intake -Read one immutable Zotero Local API library-version snapshot and propose exactly one research disposition for every top-level bibliographic item. Each proposal retains the item key/version, exact matched metadata values, rule revision, linked child records, and any model receipt. Weak evidence and evidence that matches multiple specific disposition families must abstain into steward review. A local abstention retains its nonempty abstract as review context; decided items omit that extra copy. Duplicate DOI/title identities are review candidates only: intake never merges, deletes, or silently mutates Zotero records. +Read one immutable Zotero Local API library-version snapshot and propose exactly one research disposition for every top-level bibliographic item. Each proposal retains the item key/version, exact matched metadata values, rule revision, linked child records, and any model receipt. Weak evidence and evidence that matches multiple specific disposition families must abstain into steward review. A local abstention retains its nonempty abstract exactly once, as matched evidence when applicable or otherwise as review context; decided items omit the review-only copy. Duplicate DOI/title identities are review candidates only: intake never merges, deletes, or silently mutates Zotero records. For every connected duplicate component, accept externally verified steward decisions selecting one component-level canonical item. Produce a local-only manifest that binds the decisions to the raw snapshot, its complete item-key/item-version coordinates, and exact duplicate-candidate membership, and records every component source revision plus before, after, and rollback canonical-key mappings. Classification preserves every Zotero source record. diff --git a/docs/TRD.md b/docs/TRD.md index 0645c0c7..a60d360e 100644 --- a/docs/TRD.md +++ b/docs/TRD.md @@ -61,7 +61,7 @@ Evaluation must separate extraction recall, semantic correctness, structural cor `conceptweave-zotero` reads only the loopback Local API with at most 100 records per page, an 8 MiB page-body limit, a 50,000-item whole-snapshot limit, a 256 MiB cumulative body limit, redirects disabled, and finite connect/response/body/global timeouts. Every request pins `Zotero-API-Version: 3`; every response must report API version 3, while its schema version is recorded and must remain stable across the snapshot. Before another request is issued, exhausted whole-snapshot budgets fail closed. Before a parsed page is accumulated, checked item-count and byte arithmetic must remain within both the advertised total and the configured whole-snapshot budgets. `Total-Results`, `Last-Modified-Version`, Zotero version, and server identity must remain identical across all pages; contract drift, malformed JSON, an empty intermediate page, duplicate keys, or an oversized response fails the run. -Every top-level bibliographic record receives exactly one proposed disposition. `NeedsStewardReview` also records a deterministic abstention reason so missing classification metadata, vocabulary unsupported by the current deterministic rules, and present-but-unmatched metadata are distinguishable. Its nonempty abstract is retained in the local report so a steward can decide from the same immutable snapshot; non-abstained items omit that review-only field. DOI duplicate identity normalization treats bare DOI values, `doi:`, `doi.org`, and legacy `dx.doi.org` resolver forms as the same identity when their normalized DOI is equal. +Every top-level bibliographic record receives exactly one proposed disposition. `NeedsStewardReview` also records a deterministic abstention reason so missing classification metadata, vocabulary unsupported by the current deterministic rules, and present-but-unmatched metadata are distinguishable. Its nonempty abstract is retained exactly once in the local report: an abstract that triggered conflicting rules remains in matched evidence, while other abstention abstracts use the review-only field. Non-abstained items omit that field. DOI duplicate identity normalization treats bare DOI values, `doi:`, `doi.org`, and legacy `dx.doi.org` resolver forms as the same identity when their normalized DOI is equal. Duplicate review is independent of subject classification. A reviewed decision set must match the exact raw-snapshot digest and complete item-key/item-version coordinates, cover every duplicate candidate exactly once, select one retained key from the connected duplicate component, and pass an external governance verifier. Every operation records all component item revisions, identity mappings before and after canonicalization, and the exact rollback mapping. These mappings affect only downstream identity resolution; Zotero records are neither mutated nor deleted. diff --git a/docs/adr/0006-zotero-research-intake.md b/docs/adr/0006-zotero-research-intake.md index 13f49b02..54a827aa 100644 --- a/docs/adr/0006-zotero-research-intake.md +++ b/docs/adr/0006-zotero-research-intake.md @@ -17,7 +17,7 @@ ConceptWeave owns a small read-only Anti-Corruption Layer from Zotero into resea The adapter links child records, emits exactly one deterministic proposed disposition per top-level bibliographic item, and abstains when evidence is weak or ambiguous. Every abstention preserves a deterministic reason distinguishing missing classification metadata, vocabulary outside the current deterministic rules, present-but-unmatched metadata, and conflicting specific disposition families. Specific rule families are evaluated together rather than by first-match priority. When evidence matches multiple families, the proposal becomes `NeedsStewardReview` and all matching evidence is retained. -Matched metadata values are copied into the local-only evidence receipt for replay. This is necessary for abstract-only matches because a later Zotero revision cannot reconstruct the exact text used for an earlier proposal from item key/version alone. An abstention likewise retains its nonempty abstract so a steward can resolve unsupported or unmatched vocabulary from the same immutable report; decided items omit that review-only copy. The report remains sensitive local material. DOI/title matches remain reversible duplicate candidates, including legacy `dx.doi.org` resolver forms. +Matched metadata values are copied into the local-only evidence receipt for replay. This is necessary for abstract-only matches because a later Zotero revision cannot reconstruct the exact text used for an earlier proposal from item key/version alone. An abstention likewise retains its nonempty abstract so a steward can resolve unsupported or unmatched vocabulary from the same immutable report. If matched evidence already contains the abstract, the review-only field is omitted so sensitive text appears once; decided items also omit that extra copy. The report remains sensitive local material. DOI/title matches remain reversible duplicate candidates, including legacy `dx.doi.org` resolver forms. Duplicate candidates become canonical references only through externally verified steward decisions bound to the raw digest, complete item-key/item-version snapshot, and exact candidate membership. Overlapping candidates form one connected component and must select one component-level canonical item. Every resulting operation retains all component source revisions and complete before/after/rollback key mappings. It changes downstream identity resolution only; classification does not merge, delete, or mutate Zotero source records. From 0a606c65ba400ac41e172c9e4816de2beb39f9f8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 02:10:48 +0900 Subject: [PATCH 10/14] docs(gap): record single-copy Zotero evidence --- docs/product-technical-gap-baseline.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index fb7505e6..cac353fe 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -46,7 +46,7 @@ The successor authorization slice adds one-shot Zotero 10 Local API authorizatio Local evidence on 2026-09-04 showed Zotero 9.0.6, Local API v3/schema 42, library version 12341, 8,326 total items, and 3,719 top-level items. The corrected read-only run observed all 8,326 records at that single version and classified all 3,715 top-level bibliographic records; four top-level note/attachment/annotation records were correctly excluded. It proposed 56 adjacent-evidence records, 1 semantic-consumption bridge, and 3,658 steward-review abstentions, linked children for 3,287 records, and surfaced 49 reversible duplicate groups (18 DOI, 31 title). No live record matched multiple specific disposition families; the tested conflict path still abstains fail-closed. Token-boundary matching prevents strings such as `knowledge` from becoming false OWL evidence. These are local aggregate observations, not reviewed truth or applied Zotero changes. The report stays outside the repository. -The 3,658-item abstention queue now preserves each nonempty abstract only in its sensitive local report entry, alongside the existing title, tags, collections, item revision, and abstention reason. A live read-only rerun on 2026-09-05 retained review abstracts for 2,665 abstentions, copied none into the 57 deterministically decided entries, observed all 8,326 records at library version 12341, and reported zero read failures. The remaining 993 abstentions have no abstract and still retain their available title/tag/collection context and explicit reason. This makes the complete steward workload reviewable without creating a second workload model or weakening the snapshot-bound external approval requirement. The report remains outside the repository. +The 3,658-item abstention queue now preserves each nonempty abstract exactly once in its sensitive local report entry, alongside the existing title, tags, collections, item revision, and abstention reason. A live read-only rerun on 2026-09-05 retained review-only abstracts for 2,665 abstentions, found no live conflict whose matched evidence already carried an abstract, produced zero duplicate abstract copies, copied none into the 57 deterministically decided entries, observed all 8,326 records at library version 12341, and reported zero read failures. The remaining 993 abstentions have no abstract and still retain their available title/tag/collection context and explicit reason. The tested conflict path keeps its abstract only in matched evidence. This makes the complete steward workload reviewable without creating a second workload model or weakening the snapshot-bound external approval requirement. The report remains outside the repository. The golden-set evaluation contract now records aggregate precision/recall numerators and denominators, requires an externally verified governance receipt bound to the complete item-key/item-version snapshot, rejects abstention as expected truth, and retains verified revisions plus an opaque snapshot digest so detached metrics remain attributable. Item and reviewer identities stay out of its output. Successful classification reports also carry same-snapshot aggregate coverage, provenance, abstention, duplicate, disposition, and failure evidence. Connected duplicate components now produce a snapshot-bound local review manifest only after external steward verification; every operation retains all component source revisions and before/after/rollback canonical mappings while Zotero records remain unchanged. Reviewed collection/tag changes produce a default-dry-run plan bound to exact server, library, item, rule, digest, and complete metadata preconditions; externally read-only plan state prevents post-validation forgery, automatic-tag type is preserved, and Zotero 9 execute mode is rejected. The injected execution core calls nothing in dry-run mode, preflights every item before a write, stops at the first failure, reconciles a lost or invalid response with a same-boundary read, and emits rollback evidence for every item whose applied state is proven. The generic rollback executor rejects mixed-server evidence before reading, verifies all expected post-write states at one current library version before the first inverse write, follows the receipt's reverse order, advances only from verified writes, and stops with restored, failed, indeterminate, not-attempted, and remaining classifications. Unprovable state is reported as indeterminate with complete operation evidence retained separately and excluded from automatic retry until operator reconciliation. A later read-only reconciliation records the observed state, tolerates unrelated library-version advancement, and emits retry evidence only for an exact unchanged item; restoration metadata at a newer item revision proves current state but not causality. Reuse after restoration fails before writing. A fixed-loopback Zotero 10 adapter supplies stable server-pinned reads and authenticated one-item writes with atomic library/item preconditions, complete collection/tag replacement, and bounded verified responses; thin wrappers reuse both generic executors. Mock fixtures verify these contracts and secret-free failures. Korean, Japanese, Chinese, Vietnamese, Spanish, German, and French ontology-alignment metadata now have explicit fail-closed abstention coverage alongside the existing English positive case; this is safety evidence, not translated classification support. No real precision/recall, duplicate merge, write, or rollback claim exists until a steward supplies reviewed local decisions and a production authorization adapter verifies them. AC6 still requires approved live Zotero 10 write, partial-failure, and rollback evidence. Multilingual rule expansion remains a later evidence-driven change and must not reduce abstention safety. A dedicated utility repository remains unnecessary until an independently released cross-product contract exists. From 10eb8e1034b4ed174f47f254e21da85d7a1cd6bc Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 15:58:20 +0900 Subject: [PATCH 11/14] test(research): adopt captured-source review context fixture Signed-off-by: Seongho Bae --- crates/conceptweave-zotero/tests/steward_review_context.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/conceptweave-zotero/tests/steward_review_context.rs b/crates/conceptweave-zotero/tests/steward_review_context.rs index 995fd5f6..e0733047 100644 --- a/crates/conceptweave-zotero/tests/steward_review_context.rs +++ b/crates/conceptweave-zotero/tests/steward_review_context.rs @@ -2,6 +2,7 @@ use conceptweave_zotero::{Disposition, ItemData, ZoteroItem, classify_snapshot}; fn item(key: &str, title: &str, abstract_note: &str) -> ZoteroItem { ZoteroItem { + source_record: None, key: key.into(), version: 7, data: ItemData { From ebcf2a327e3f264c3873a4e8a3904003b6280c7d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 6 Sep 2026 23:21:16 +0900 Subject: [PATCH 12/14] test(zotero): bind steward context changes to approval --- .../tests/steward_review_context.rs | 56 ++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/crates/conceptweave-zotero/tests/steward_review_context.rs b/crates/conceptweave-zotero/tests/steward_review_context.rs index e0733047..d3499616 100644 --- a/crates/conceptweave-zotero/tests/steward_review_context.rs +++ b/crates/conceptweave-zotero/tests/steward_review_context.rs @@ -1,4 +1,7 @@ -use conceptweave_zotero::{Disposition, ItemData, ZoteroItem, classify_snapshot}; +use conceptweave_zotero::{ + Disposition, EvaluationError, GoldenLabel, GoldenSetApproval, ItemData, ReviewedGoldenSet, + ZoteroItem, classification_proposal_digest, classify_snapshot, evaluate_reviewed_golden_set, +}; fn item(key: &str, title: &str, abstract_note: &str) -> ZoteroItem { ZoteroItem { @@ -81,3 +84,54 @@ fn abstentions_retain_only_the_abstract_needed_for_local_steward_review() { let serialized = serde_json::to_string(conflict).unwrap(); assert_eq!(serialized.matches("Ontology learning").count(), 1); } + +#[test] +fn changed_review_context_invalidates_prior_approval_before_verification() { + for (original, replacement) in [ + ( + "Unmatched original abstract.", + Some("Changed review context."), + ), + ("Unmatched original abstract.", None), + ("", Some("Added review context.")), + ] { + let mut report = classify_snapshot( + "10.0.1".into(), + Some("synthetic-server".into()), + 42, + vec![item("REVIEW01", "Unmatched domain study", original)], + ); + let approved = ReviewedGoldenSet { + approval: GoldenSetApproval { + receipt_id: "synthetic-receipt".into(), + reviewer_subject: "synthetic-steward".into(), + library_version: report.library_version, + rule_revision: report.rule_revision.into(), + snapshot_digest: report.snapshot_digest.clone(), + proposal_digest: classification_proposal_digest(&report), + snapshot_items: report.snapshot_items.clone(), + }, + labels: vec![GoldenLabel::new("REVIEW01", Disposition::Generation)], + }; + let result = + evaluate_reviewed_golden_set(&report, &approved, |set| set == &approved).unwrap(); + let aggregate = serde_json::to_string(&result).unwrap(); + for private_value in ["REVIEW01", "synthetic-steward", "Unmatched domain study"] { + assert!(!aggregate.contains(private_value)); + } + if !original.is_empty() { + assert!(!aggregate.contains(original)); + } + report.classified_items[0].review_abstract_note = replacement.map(str::to_owned); + assert_ne!( + classification_proposal_digest(&report), + approved.approval.proposal_digest + ); + assert_eq!( + evaluate_reviewed_golden_set(&report, &approved, |_| { + panic!("changed review context must fail before governance") + }), + Err(EvaluationError::SnapshotMismatch) + ); + } +} From b44d60327d942db4261529661264e6cd7497a789 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 6 Sep 2026 23:22:28 +0900 Subject: [PATCH 13/14] test(zotero): preserve multilingual context and independent approval --- .../tests/steward_review_context.rs | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/crates/conceptweave-zotero/tests/steward_review_context.rs b/crates/conceptweave-zotero/tests/steward_review_context.rs index d3499616..73c27a94 100644 --- a/crates/conceptweave-zotero/tests/steward_review_context.rs +++ b/crates/conceptweave-zotero/tests/steward_review_context.rs @@ -35,6 +35,8 @@ fn abstentions_retain_only_the_abstract_needed_for_local_steward_review() { "This abstract must not be copied into review-only context.", ), item("EMPTY001", "Unmatched title", ""), + item("SPACE001", "Unmatched title", " \n\t "), + item("CJK00001", "Unmatched title", "地域固有の語彙を調査する。"), item( "CONFLICT", "Unmatched title", @@ -70,6 +72,32 @@ fn abstentions_retain_only_the_abstract_needed_for_local_steward_review() { assert_eq!(empty.proposed_disposition, Disposition::NeedsStewardReview); assert!(empty.review_abstract_note.is_none()); + let whitespace = report + .classified_items + .iter() + .find(|item| item.item_key == "SPACE001") + .unwrap(); + assert!(whitespace.review_abstract_note.is_none()); + assert!( + serde_json::to_value(whitespace) + .unwrap() + .get("review_abstract_note") + .is_none() + ); + let non_english = report + .classified_items + .iter() + .find(|item| item.item_key == "CJK00001") + .unwrap(); + assert_eq!( + non_english.proposed_disposition, + Disposition::NeedsStewardReview + ); + assert_eq!( + non_english.review_abstract_note.as_deref(), + Some("地域固有の語彙を調査する。") + ); + let conflict = report .classified_items .iter() @@ -133,5 +161,11 @@ fn changed_review_context_invalidates_prior_approval_before_verification() { }), Err(EvaluationError::SnapshotMismatch) ); + let mut rewritten = approved.clone(); + rewritten.approval.proposal_digest = classification_proposal_digest(&report); + assert_eq!( + evaluate_reviewed_golden_set(&report, &rewritten, |set| set == &approved), + Err(EvaluationError::UnverifiedApproval) + ); } } From 51d1682358b2a6c7ce6f24b7d884ddabf1857e57 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 6 Sep 2026 23:23:33 +0900 Subject: [PATCH 14/14] docs(research): record private steward context evidence --- docs/product-technical-gap-baseline.md | 34 ++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 205fddf8..a2778345 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -89,6 +89,40 @@ Remaining work: mandatory adoption by restoration, worksheet, duplicate and writ ## DDD fitness constraints +### PR #22 steward-context binding verification (2026-09-06) + +Untouched `7179d13b45d160682e4cce1473c145d465fe657b` passed 120 tests/23 suites. +Normal merge `49daf4c` retains that delta and parent PR #21 `425d8df`; integrated +tests passed 143/23. Documentation conflicts preserve both private abstract +minimization and corrected source-version/uncertainty contracts. The original +PR #22 production implementation is unchanged: an abstention retains a nonblank +abstract only when it is not already in matched evidence; decided items omit it. + +Independent review identified missing binding regression evidence, not a runtime +defect. Test `ebcf2a3` covers insertion, replacement and removal of review context: +each changes the inherited v2 proposal digest and rejects the original approval +before contacting governance. `b44d603` also rejects locally rewritten digests +against the independent original receipt, preserves whitespace omission and +verbatim Japanese context, and retains all four original minimization cases. +Evaluation aggregates exclude synthetic item/reviewer/title/abstract values. +This is context retention, not multilingual classification or real approval. + +Final 144 tests/23 suites including three doctests, strict Clippy, warnings-denied +rustdoc, format/CI-contract/diff and unchanged coverage pass. Coverage is 269/269 +functions, 2369/2369 normalized regions, 404/404 normalized branches; raw LLVM is +3083/3143 lines, 4625/4722 regions, 360/404 branches, not 100%. Logs use +`/tmp/conceptweave-pr22-scope-` with `baseline.log`, `integration.log`, +`complete.log`, `clippy-complete.log`, `rustdoc.log`, `coverage-complete.log`. +Independent final review found no remaining issue in the reviewed delta. + +Sensitive report context stays private; no fresh real-data capture or visual +evidence was collected. Latest native visual attempt encountered the locked Mac; +historical 3,719 displayed items are not classification evidence. Real decisions +and approvals remain 0/3,715 plus four unresolved sources. No real mutation, +authorization, recovery, protected merge or release occurred. Next successor is +PR #23 owner-only report permissions `2a3619f52e1d3e4f699c91be1fc2d0e9a6e234c8`; +root and authoritative full-text envelope adoption remain outstanding. + ### PR #21 delayed observation authority repair (2026-09-06) Untouched `09c84e4cdb1393a5e450f5200b87f292eeea956f` passed 119 tests/22 suites.