From 65bf49545ffd9f1dbaab259b5c2c450aea42d212 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 20:15:48 +0900 Subject: [PATCH 01/39] test: reproduce missing capture-bound review worksheet --- .../src/full_text_capture.rs | 3 ++ .../src/full_text_capture_tests.rs | 29 +++++++++++++++++++ .../src/full_text_review.rs | 25 ++++++++++++++++ crates/conceptweave-zotero/src/lib.rs | 1 + 4 files changed, 58 insertions(+) create mode 100644 crates/conceptweave-zotero/src/full_text_review.rs diff --git a/crates/conceptweave-zotero/src/full_text_capture.rs b/crates/conceptweave-zotero/src/full_text_capture.rs index 92e6ddc3..103172d3 100644 --- a/crates/conceptweave-zotero/src/full_text_capture.rs +++ b/crates/conceptweave-zotero/src/full_text_capture.rs @@ -6,6 +6,9 @@ use crate::{ local_agent, validate_item_key, verify_server_id, }; use serde::{Deserialize, Serialize}; +#[path = "full_text_review.rs"] +mod full_text_review; +pub use full_text_review::*; use sha2::{Digest, Sha256}; use std::collections::BTreeMap; use std::fmt; diff --git a/crates/conceptweave-zotero/src/full_text_capture_tests.rs b/crates/conceptweave-zotero/src/full_text_capture_tests.rs index 4047b48e..b15d5270 100644 --- a/crates/conceptweave-zotero/src/full_text_capture_tests.rs +++ b/crates/conceptweave-zotero/src/full_text_capture_tests.rs @@ -1,6 +1,35 @@ use super::*; use crate::{ZoteroItem, classify_snapshot}; +#[test] +fn full_text_worksheet_starts_blank_without_a_metadata_downcast() { + let report = report_fixture(); + let capture = capture_with(&report, 4096, &mut |request_path, _| { + Ok(response_fixture(request_path)) + }) + .unwrap(); + let worksheet = build_full_text_review_worksheet(&report, &capture).unwrap(); + let json = serde_json::to_value(&worksheet).unwrap(); + assert_eq!(json["artifact_kind"], "full_text_review_worksheet_v1"); + assert_eq!(json["capture_digest"], capture.capture_digest); + assert_eq!( + json["review_worksheet"]["decisions"] + .as_array() + .unwrap() + .len(), + 2 + ); + assert!( + json["review_worksheet"]["decisions"] + .as_array() + .unwrap() + .iter() + .all(|decision| decision["reviewed_disposition"].is_null()) + ); + assert!(serde_json::from_value::(json.clone()).is_err()); + assert!(serde_json::from_value::(json).is_ok()); +} + #[test] fn review_view_binds_exact_capture_and_keeps_missing_parents_without_decisions() { let report = report_fixture(); diff --git a/crates/conceptweave-zotero/src/full_text_review.rs b/crates/conceptweave-zotero/src/full_text_review.rs new file mode 100644 index 00000000..938b119f --- /dev/null +++ b/crates/conceptweave-zotero/src/full_text_review.rs @@ -0,0 +1,25 @@ +use super::*; + +/// Private single-capture review work, distinct from a metadata-only worksheet. +/// +/// Serialize this owner-only artifact for storage. Restoring it grants no +/// authority; each operation must revalidate the original report and capture. +#[derive(Deserialize, Serialize)] +#[serde( + tag = "artifact_kind", + rename = "full_text_review_worksheet_v1", + deny_unknown_fields +)] +pub struct FullTextReviewWorksheet { + capture_digest: String, + review_worksheet: StewardReviewWorksheet, +} + +/// Starts an entirely blank review bound to the verified retained full text. +/// Existing metadata decisions are deliberately not imported as text-reviewed. +pub fn build_full_text_review_worksheet( + _report: &ClassificationReport, + _capture: &FullTextCapture, +) -> Result { + Err(FullTextError(INVALID_EVIDENCE)) +} diff --git a/crates/conceptweave-zotero/src/lib.rs b/crates/conceptweave-zotero/src/lib.rs index 607c9bfd..203bdc97 100644 --- a/crates/conceptweave-zotero/src/lib.rs +++ b/crates/conceptweave-zotero/src/lib.rs @@ -15,6 +15,7 @@ pub use full_text_capture::{ FullTextCapture, FullTextError, build_full_text_review_json, read_local_full_text, verify_full_text_capture, }; +pub use full_text_capture::{FullTextReviewWorksheet, build_full_text_review_worksheet}; /// Classification rule revision recorded in every report. pub const RULE_REVISION: &str = "ontology-research-v2"; From 7cf80189dd95b56a847697b4f1100ae89fdf65f4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 20:17:32 +0900 Subject: [PATCH 02/39] test: repair imports before measuring review regression --- crates/conceptweave-zotero/src/full_text_capture_tests.rs | 2 +- crates/conceptweave-zotero/src/full_text_review.rs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/conceptweave-zotero/src/full_text_capture_tests.rs b/crates/conceptweave-zotero/src/full_text_capture_tests.rs index b15d5270..00e4d64f 100644 --- a/crates/conceptweave-zotero/src/full_text_capture_tests.rs +++ b/crates/conceptweave-zotero/src/full_text_capture_tests.rs @@ -1,5 +1,5 @@ use super::*; -use crate::{ZoteroItem, classify_snapshot}; +use crate::{StewardReviewWorksheet, ZoteroItem, classify_snapshot}; #[test] fn full_text_worksheet_starts_blank_without_a_metadata_downcast() { diff --git a/crates/conceptweave-zotero/src/full_text_review.rs b/crates/conceptweave-zotero/src/full_text_review.rs index 938b119f..7e89ee48 100644 --- a/crates/conceptweave-zotero/src/full_text_review.rs +++ b/crates/conceptweave-zotero/src/full_text_review.rs @@ -1,4 +1,5 @@ use super::*; +use crate::StewardReviewWorksheet; /// Private single-capture review work, distinct from a metadata-only worksheet. /// @@ -21,5 +22,5 @@ pub fn build_full_text_review_worksheet( _report: &ClassificationReport, _capture: &FullTextCapture, ) -> Result { - Err(FullTextError(INVALID_EVIDENCE)) + Err(INVALID_EVIDENCE) } From 063a99da6117d5bf38cb1352635b5aede0c1ef06 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 20:19:29 +0900 Subject: [PATCH 03/39] feat: validate a versioned capture-bound blank worksheet --- .../src/full_text_capture_tests.rs | 5 ++--- .../conceptweave-zotero/src/full_text_review.rs | 17 +++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/crates/conceptweave-zotero/src/full_text_capture_tests.rs b/crates/conceptweave-zotero/src/full_text_capture_tests.rs index 00e4d64f..0e571ce1 100644 --- a/crates/conceptweave-zotero/src/full_text_capture_tests.rs +++ b/crates/conceptweave-zotero/src/full_text_capture_tests.rs @@ -10,17 +10,16 @@ fn full_text_worksheet_starts_blank_without_a_metadata_downcast() { .unwrap(); let worksheet = build_full_text_review_worksheet(&report, &capture).unwrap(); let json = serde_json::to_value(&worksheet).unwrap(); - assert_eq!(json["artifact_kind"], "full_text_review_worksheet_v1"); assert_eq!(json["capture_digest"], capture.capture_digest); assert_eq!( - json["review_worksheet"]["decisions"] + json["full_text_worksheet_v1"]["decisions"] .as_array() .unwrap() .len(), 2 ); assert!( - json["review_worksheet"]["decisions"] + json["full_text_worksheet_v1"]["decisions"] .as_array() .unwrap() .iter() diff --git a/crates/conceptweave-zotero/src/full_text_review.rs b/crates/conceptweave-zotero/src/full_text_review.rs index 7e89ee48..ad413c81 100644 --- a/crates/conceptweave-zotero/src/full_text_review.rs +++ b/crates/conceptweave-zotero/src/full_text_review.rs @@ -6,21 +6,22 @@ use crate::StewardReviewWorksheet; /// Serialize this owner-only artifact for storage. Restoring it grants no /// authority; each operation must revalidate the original report and capture. #[derive(Deserialize, Serialize)] -#[serde( - tag = "artifact_kind", - rename = "full_text_review_worksheet_v1", - deny_unknown_fields -)] +#[serde(deny_unknown_fields)] pub struct FullTextReviewWorksheet { capture_digest: String, + #[serde(rename = "full_text_worksheet_v1")] review_worksheet: StewardReviewWorksheet, } /// Starts an entirely blank review bound to the verified retained full text. /// Existing metadata decisions are deliberately not imported as text-reviewed. pub fn build_full_text_review_worksheet( - _report: &ClassificationReport, - _capture: &FullTextCapture, + report: &ClassificationReport, + capture: &FullTextCapture, ) -> Result { - Err(INVALID_EVIDENCE) + verify_full_text_capture(capture, report)?; + Ok(FullTextReviewWorksheet { + capture_digest: capture.capture_digest.clone(), + review_worksheet: build_steward_review_worksheet(report).map_err(|_| INVALID_EVIDENCE)?, + }) } From 6bf92d2e418ac89481e6a532ed9f71271ae0c3b1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 20:21:34 +0900 Subject: [PATCH 04/39] test: require full-text context through external review verification --- .../src/full_text_capture_tests.rs | 77 +++++++++++++++++++ .../src/full_text_review.rs | 76 ++++++++++++++++++ crates/conceptweave-zotero/src/lib.rs | 6 +- 3 files changed, 158 insertions(+), 1 deletion(-) diff --git a/crates/conceptweave-zotero/src/full_text_capture_tests.rs b/crates/conceptweave-zotero/src/full_text_capture_tests.rs index 0e571ce1..c05d56fd 100644 --- a/crates/conceptweave-zotero/src/full_text_capture_tests.rs +++ b/crates/conceptweave-zotero/src/full_text_capture_tests.rs @@ -1,6 +1,83 @@ use super::*; use crate::{StewardReviewWorksheet, ZoteroItem, classify_snapshot}; +fn completed_full_text_view( + report: &ClassificationReport, + worksheet: &FullTextReviewWorksheet, + capture: &FullTextCapture, + limit: usize, +) -> Vec { + let mut json: serde_json::Value = serde_json::from_slice( + &build_bound_full_text_review_json(report, worksheet, capture, limit).unwrap(), + ) + .unwrap(); + for decision in json["review_batch"]["decisions"].as_array_mut().unwrap() { + decision["reviewed_disposition"] = serde_json::json!(crate::Disposition::OutOfScope); + } + serde_json::to_vec(&json).unwrap() +} + +fn full_text_approval_fixture( + report: &ClassificationReport, + capture: &FullTextCapture, +) -> FullTextReviewApproval { + serde_json::from_value(serde_json::json!({ + "capture_digest":capture.capture_digest, + "full_text_approval_v1":{ + "receipt_id":"synthetic-review-receipt", + "reviewer_subject":"synthetic-steward", + "library_version":report.library_version, + "rule_revision":report.rule_revision, + "snapshot_digest":report.snapshot_digest, + "proposal_digest":crate::classification_proposal_digest(report), + "snapshot_items":report.snapshot_items, + } + })) + .unwrap() +} + +#[test] +fn full_text_review_preserves_context_through_decisions_and_full_approval() { + let report = report_fixture(); + let capture = capture_with(&report, 4096, &mut |request_path, _| { + Ok(response_fixture(request_path)) + }) + .unwrap(); + let worksheet = build_full_text_review_worksheet(&report, &capture).unwrap(); + let unchanged = serde_json::to_vec(&worksheet).unwrap(); + let view = completed_full_text_view(&report, &worksheet, &capture, 2); + let decided = apply_full_text_review_view(&report, &worksheet, &capture, &view).unwrap(); + assert_eq!(serde_json::to_vec(&worksheet).unwrap(), unchanged); + let golden = finalize_full_text_review( + &report, + &decided, + &capture, + full_text_approval_fixture(&report, &capture), + ) + .unwrap(); + let expected = serde_json::to_value(&golden).unwrap(); + assert_eq!(expected["capture_digest"], capture.capture_digest); + assert!( + expected["full_text_golden_set_v1"]["labels"] + .as_array() + .unwrap() + .iter() + .all(|label| label["expected_disposition"] + == serde_json::json!(crate::Disposition::OutOfScope)) + ); + let evaluation = evaluate_full_text_review(&report, &capture, &golden, |received| { + serde_json::to_value(received).unwrap() == expected + }) + .unwrap(); + let result = serde_json::to_value(evaluation).unwrap(); + assert_eq!(result["capture_digest"], capture.capture_digest); + assert_eq!(result["full_text_evaluation_v1"]["reviewed_count"], 2); + assert!(evaluate_full_text_review(&report, &capture, &golden, |_| false).is_err()); + assert!(apply_full_text_review_view(&report, &decided, &capture, &view).is_err()); + assert!(serde_json::from_value::(expected.clone()).is_err()); + assert!(serde_json::from_value::(expected).is_err()); +} + #[test] fn full_text_worksheet_starts_blank_without_a_metadata_downcast() { let report = report_fixture(); diff --git a/crates/conceptweave-zotero/src/full_text_review.rs b/crates/conceptweave-zotero/src/full_text_review.rs index ad413c81..f1cf3b6b 100644 --- a/crates/conceptweave-zotero/src/full_text_review.rs +++ b/crates/conceptweave-zotero/src/full_text_review.rs @@ -1,6 +1,34 @@ use super::*; use crate::StewardReviewWorksheet; +/// Independently issued approval input for one full-text review context. +/// The issuer must bind the complete reviewed labels as well as this capture. +#[derive(Deserialize, Serialize)] +#[serde(deny_unknown_fields)] +pub struct FullTextReviewApproval { + capture_digest: String, + #[serde(rename = "full_text_approval_v1")] + review_approval: crate::GoldenSetApproval, +} + +/// Completed labels awaiting verification of the entire owner-only envelope. +/// Serialization is available to governance; no metadata-only downcast is offered. +#[derive(Deserialize, Serialize)] +#[serde(deny_unknown_fields)] +pub struct FullTextReviewedGoldenSet { + capture_digest: String, + #[serde(rename = "full_text_golden_set_v1")] + reviewed_golden_set: crate::ReviewedGoldenSet, +} + +/// Aggregate evaluation that retains the verified capture identity without text. +#[derive(Serialize)] +pub struct FullTextReviewEvaluation { + capture_digest: String, + #[serde(rename = "full_text_evaluation_v1")] + review_evaluation: crate::GoldenSetEvaluation, +} + /// Private single-capture review work, distinct from a metadata-only worksheet. /// /// Serialize this owner-only artifact for storage. Restoring it grants no @@ -25,3 +53,51 @@ pub fn build_full_text_review_worksheet( review_worksheet: build_steward_review_worksheet(report).map_err(|_| INVALID_EVIDENCE)?, }) } + +/// Shows the next pending rows only after revalidating the worksheet's capture. +pub fn build_bound_full_text_review_json( + _report: &ClassificationReport, + _worksheet: &FullTextReviewWorksheet, + _capture: &FullTextCapture, + _limit: usize, +) -> Result, FullTextError> { + Err(INVALID_EVIDENCE) +} + +/// Applies only completed decision slots in an otherwise unchanged evidence view. +/// Stale views fail without changing the input; preserve it and request a new view. +pub fn apply_full_text_review_view( + _report: &ClassificationReport, + _worksheet: &FullTextReviewWorksheet, + _capture: &FullTextCapture, + _completed_view: &[u8], +) -> Result { + Err(INVALID_EVIDENCE) +} + +/// Prepares a fully decided review for external verification, never issuing approval. +/// The approval must already name this capture; legacy receipts are not upgraded. +pub fn finalize_full_text_review( + _report: &ClassificationReport, + _worksheet: &FullTextReviewWorksheet, + _capture: &FullTextCapture, + _approval: FullTextReviewApproval, +) -> Result { + Err(INVALID_EVIDENCE) +} + +/// Evaluates all papers after local validation and verification of the whole envelope. +/// The verifier must authenticate capture identity and every label against an +/// independently issued receipt. JSON restoration or matching digests alone do +/// not prove human review. No result from this function authorizes Zotero writes. +pub fn evaluate_full_text_review( + _report: &ClassificationReport, + _capture: &FullTextCapture, + _reviewed: &FullTextReviewedGoldenSet, + _verify_approval: F, +) -> Result +where + F: FnOnce(&FullTextReviewedGoldenSet) -> bool, +{ + Err(INVALID_EVIDENCE) +} diff --git a/crates/conceptweave-zotero/src/lib.rs b/crates/conceptweave-zotero/src/lib.rs index 203bdc97..f7ed67e9 100644 --- a/crates/conceptweave-zotero/src/lib.rs +++ b/crates/conceptweave-zotero/src/lib.rs @@ -15,7 +15,11 @@ pub use full_text_capture::{ FullTextCapture, FullTextError, build_full_text_review_json, read_local_full_text, verify_full_text_capture, }; -pub use full_text_capture::{FullTextReviewWorksheet, build_full_text_review_worksheet}; +pub use full_text_capture::{ + FullTextReviewApproval, FullTextReviewEvaluation, FullTextReviewWorksheet, + FullTextReviewedGoldenSet, apply_full_text_review_view, build_bound_full_text_review_json, + build_full_text_review_worksheet, evaluate_full_text_review, finalize_full_text_review, +}; /// Classification rule revision recorded in every report. pub const RULE_REVISION: &str = "ontology-research-v2"; From d3676589b1b8143f2b33957773e31157e5ea3a83 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 20:22:54 +0900 Subject: [PATCH 05/39] test: expose duplicate keys before review JSON projection --- .../src/unique_review_json.rs | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 crates/conceptweave-zotero/src/unique_review_json.rs diff --git a/crates/conceptweave-zotero/src/unique_review_json.rs b/crates/conceptweave-zotero/src/unique_review_json.rs new file mode 100644 index 00000000..13370aec --- /dev/null +++ b/crates/conceptweave-zotero/src/unique_review_json.rs @@ -0,0 +1,100 @@ +//! Bounded JSON admission before a review object can discard duplicate keys. + +use serde_json::Value; + +const MAX_REVIEW_JSON_BYTES: usize = 16 * 1024 * 1024; +const INVALID_REVIEW_JSON: &str = "review JSON is invalid"; + +/// Parses one complete review value without accepting ambiguous object keys. +/// +/// The byte limit is inclusive. Invalid syntax, duplicate keys, excessive depth, +/// trailing data, and oversize inputs return one error without source content. +pub(crate) fn parse_review_json(bytes: &[u8]) -> Result { + if bytes.len() > MAX_REVIEW_JSON_BYTES { + return Err(INVALID_REVIEW_JSON); + } + serde_json::from_slice(bytes).map_err(|_| INVALID_REVIEW_JSON) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn review_json_rejects_duplicate_decoded_keys_at_every_depth() { + for bytes in [ + br#"{"decision":null,"decision":true}"#.as_slice(), + br#"{"decision":null,"\u0064ecision":true}"#, + br#"{"outer":{"decision":1,"decision":2}}"#, + br#"[{"decision":1,"decision":2}]"#, + br#"{"outer":[[{"decision":1,"decision":2}]]}"#, + br#"{"":null,"":false}"#, + r#"{"\ud834\udd1e":1,"𝄞":2}"#.as_bytes(), + ] { + assert_eq!(parse_review_json(bytes), Err(INVALID_REVIEW_JSON)); + } + } + + #[test] + fn review_json_preserves_normal_scalar_array_and_object_semantics() { + for text in [ + "null", + "true", + "false", + "0", + "-1", + "-9223372036854775808", + "18446744073709551615", + "1.25", + "-0.0", + "6.02e23", + r#""plain text""#, + r#""escaped \" text \n \uD834\uDD1E""#, + r#"{"first":{},"second":[],"third":[null,true,false,-2,3.5,"text"]}"#, + r#"[{"same":1},{"same":2}]"#, + r#"{"same":{"same":1},"Same":2}"#, + ] { + let expected: Value = serde_json::from_str(text).unwrap(); + assert_eq!(parse_review_json(text.as_bytes()).unwrap(), expected); + } + } + + #[test] + fn review_json_rejects_malformed_trailing_and_excessively_deep_inputs() { + for bytes in [ + b"".as_slice(), + b" ", + b"{}{}", + b"{} private-sentinel", + b"[0,]", + b"{0:true}", + b"{\"key\":}", + b"[", + b"\xff", + b"NaN", + b"1e9999", + br#""\uD800""#, + ] { + assert_eq!(parse_review_json(bytes), Err(INVALID_REVIEW_JSON)); + } + let within_depth = format!("{}null{}", "[".repeat(64), "]".repeat(64)); + assert_eq!( + parse_review_json(within_depth.as_bytes()).unwrap(), + serde_json::from_str::(&within_depth).unwrap(), + ); + let excessive_depth = format!("{}null{}", "[".repeat(256), "]".repeat(256)); + assert_eq!( + parse_review_json(excessive_depth.as_bytes()), + Err(INVALID_REVIEW_JSON) + ); + } + + #[test] + fn review_json_accepts_exact_size_limit_and_rejects_one_extra_byte() { + let mut bytes = b"null".to_vec(); + bytes.resize(MAX_REVIEW_JSON_BYTES, b' '); + assert_eq!(parse_review_json(&bytes), Ok(Value::Null)); + bytes.push(b' '); + assert_eq!(parse_review_json(&bytes), Err(INVALID_REVIEW_JSON)); + } +} From 9e510385bcb1ef91df23d6ba6eaef5e210156040 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 20:23:07 +0900 Subject: [PATCH 06/39] feat: retain capture binding through review and approval boundaries --- .../src/full_text_review.rs | 94 +++++++++++++++---- crates/conceptweave-zotero/src/lib.rs | 7 ++ 2 files changed, 81 insertions(+), 20 deletions(-) diff --git a/crates/conceptweave-zotero/src/full_text_review.rs b/crates/conceptweave-zotero/src/full_text_review.rs index f1cf3b6b..7b4b7505 100644 --- a/crates/conceptweave-zotero/src/full_text_review.rs +++ b/crates/conceptweave-zotero/src/full_text_review.rs @@ -56,34 +56,67 @@ pub fn build_full_text_review_worksheet( /// Shows the next pending rows only after revalidating the worksheet's capture. pub fn build_bound_full_text_review_json( - _report: &ClassificationReport, - _worksheet: &FullTextReviewWorksheet, - _capture: &FullTextCapture, - _limit: usize, + report: &ClassificationReport, + worksheet: &FullTextReviewWorksheet, + capture: &FullTextCapture, + limit: usize, ) -> Result, FullTextError> { - Err(INVALID_EVIDENCE) + validate_review_capture(report, capture, &worksheet.capture_digest)?; + build_full_text_review_json(report, &worksheet.review_worksheet, capture, limit) } /// Applies only completed decision slots in an otherwise unchanged evidence view. /// Stale views fail without changing the input; preserve it and request a new view. pub fn apply_full_text_review_view( - _report: &ClassificationReport, - _worksheet: &FullTextReviewWorksheet, - _capture: &FullTextCapture, - _completed_view: &[u8], + report: &ClassificationReport, + worksheet: &FullTextReviewWorksheet, + capture: &FullTextCapture, + completed_view: &[u8], ) -> Result { - Err(INVALID_EVIDENCE) + validate_review_capture(report, capture, &worksheet.capture_digest)?; + let mut completed = unique_review_json::parse_review_json(completed_view) + .map_err(|_| INVALID_EVIDENCE)?; + let batch: crate::StewardReviewBatch = serde_json::from_value( + completed.get("review_batch").ok_or(INVALID_EVIDENCE)?.clone(), + ).map_err(|_| INVALID_EVIDENCE)?; + let patch = crate::decision_patch_from_review_batch(report, &worksheet.review_worksheet, &batch) + .map_err(|_| INVALID_EVIDENCE)?; + let expected: serde_json::Value = serde_json::from_slice(&build_full_text_review_json( + report, &worksheet.review_worksheet, capture, batch.decisions.len(), + )?).map_err(|_| INVALID_EVIDENCE)?; + // The metadata boundary above already checked every displayed batch field. + // Replace only that verified batch before comparing the retained text envelope. + completed["review_batch"] = expected["review_batch"].clone(); + if completed != expected { + return Err(INVALID_EVIDENCE); + } + let review_worksheet = crate::apply_steward_decision_patch(report, &worksheet.review_worksheet, &patch) + .map_err(|_| INVALID_EVIDENCE)?; + Ok(FullTextReviewWorksheet { + capture_digest: worksheet.capture_digest.clone(), + review_worksheet, + }) } /// Prepares a fully decided review for external verification, never issuing approval. /// The approval must already name this capture; legacy receipts are not upgraded. pub fn finalize_full_text_review( - _report: &ClassificationReport, - _worksheet: &FullTextReviewWorksheet, - _capture: &FullTextCapture, - _approval: FullTextReviewApproval, + report: &ClassificationReport, + worksheet: &FullTextReviewWorksheet, + capture: &FullTextCapture, + approval: FullTextReviewApproval, ) -> Result { - Err(INVALID_EVIDENCE) + validate_review_capture(report, capture, &worksheet.capture_digest)?; + if approval.capture_digest != worksheet.capture_digest { + return Err(INVALID_EVIDENCE); + } + let reviewed_golden_set = crate::reviewed_golden_set_from_worksheet( + report, &worksheet.review_worksheet, approval.review_approval, + ).map_err(|_| INVALID_EVIDENCE)?; + Ok(FullTextReviewedGoldenSet { + capture_digest: worksheet.capture_digest.clone(), + reviewed_golden_set, + }) } /// Evaluates all papers after local validation and verification of the whole envelope. @@ -91,13 +124,34 @@ pub fn finalize_full_text_review( /// independently issued receipt. JSON restoration or matching digests alone do /// not prove human review. No result from this function authorizes Zotero writes. pub fn evaluate_full_text_review( - _report: &ClassificationReport, - _capture: &FullTextCapture, - _reviewed: &FullTextReviewedGoldenSet, - _verify_approval: F, + report: &ClassificationReport, + capture: &FullTextCapture, + reviewed: &FullTextReviewedGoldenSet, + verify_approval: F, ) -> Result where F: FnOnce(&FullTextReviewedGoldenSet) -> bool, { - Err(INVALID_EVIDENCE) + validate_review_capture(report, capture, &reviewed.capture_digest)?; + let review_evaluation = crate::evaluate_complete_reviewed_classification( + report, &reviewed.reviewed_golden_set, |_| verify_approval(reviewed), + ).map_err(|_| FullTextError("full-text review is invalid or unverified"))?; + Ok(FullTextReviewEvaluation { + capture_digest: reviewed.capture_digest.clone(), + review_evaluation, + }) +} + +fn validate_review_capture( + report: &ClassificationReport, + capture: &FullTextCapture, + capture_digest: &str, +) -> Result<(), FullTextError> { + if capture_digest != capture.capture_digest { + return Err(INVALID_EVIDENCE); + } + verify_full_text_capture(capture, report) } + +#[path = "unique_review_json.rs"] +mod unique_review_json; diff --git a/crates/conceptweave-zotero/src/lib.rs b/crates/conceptweave-zotero/src/lib.rs index f7ed67e9..e2b4c847 100644 --- a/crates/conceptweave-zotero/src/lib.rs +++ b/crates/conceptweave-zotero/src/lib.rs @@ -1089,6 +1089,7 @@ pub struct ClassificationAudit { /// One editable local steward decision without duplicated bibliographic text. #[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] pub struct StewardReviewDecision { /// Stable Zotero item key used to join the sensitive classification report. pub item_key: String, @@ -1104,6 +1105,7 @@ pub struct StewardReviewDecision { /// Snapshot-bound local worksheet for one decision per bibliographic item. #[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] pub struct StewardReviewWorksheet { /// Zotero library revision observed with the source snapshot. pub library_version: u64, @@ -1138,6 +1140,7 @@ pub struct StewardReviewProgress { /// One snapshot-bound steward decision supplied without rewriting a worksheet by hand. #[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] pub struct StewardDecisionUpdate { /// Stable Zotero item key of the reviewed bibliographic item. pub item_key: String, @@ -1552,6 +1555,7 @@ fn validate_steward_review_worksheet_against( /// One steward-reviewed expected disposition in a local golden set. #[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] pub struct GoldenLabel { /// Zotero item key used only to join the local report and local review set. pub item_key: String, @@ -1571,6 +1575,7 @@ impl GoldenLabel { /// Version-bound steward labels that remain outside the repository. #[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] pub struct ReviewedGoldenSet { /// Approval receipt verified by the caller's governance boundary. pub approval: GoldenSetApproval, @@ -1580,6 +1585,7 @@ pub struct ReviewedGoldenSet { /// One item revision in the exact reviewed classification snapshot. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] pub struct SnapshotItemRevision { /// Stable Zotero item key. pub item_key: String, @@ -1591,6 +1597,7 @@ pub struct SnapshotItemRevision { /// Governance receipt binding a steward approval to exact input and proposals. #[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] pub struct GoldenSetApproval { /// Opaque receipt identifier. pub receipt_id: String, From fc882ef8ed1b46062dacbc0873b518ff4fc2594f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 20:26:06 +0900 Subject: [PATCH 07/39] fix: reject ambiguous review JSON and exercise full-text trust boundaries --- .../src/full_text_capture_tests.rs | 337 ++++++++++++++++++ .../src/full_text_review.rs | 48 ++- .../src/unique_review_json.rs | 82 ++++- 3 files changed, 450 insertions(+), 17 deletions(-) diff --git a/crates/conceptweave-zotero/src/full_text_capture_tests.rs b/crates/conceptweave-zotero/src/full_text_capture_tests.rs index c05d56fd..93e7f697 100644 --- a/crates/conceptweave-zotero/src/full_text_capture_tests.rs +++ b/crates/conceptweave-zotero/src/full_text_capture_tests.rs @@ -78,6 +78,343 @@ fn full_text_review_preserves_context_through_decisions_and_full_approval() { assert!(serde_json::from_value::(expected).is_err()); } +#[test] +fn full_text_review_rejects_modified_display_and_never_overwrites_work() { + let report = report_fixture(); + let capture = capture_with(&report, 4096, &mut |request_path, _| { + Ok(response_fixture(request_path)) + }) + .unwrap(); + let worksheet = build_full_text_review_worksheet(&report, &capture).unwrap(); + let completed = completed_full_text_view(&report, &worksheet, &capture, 1); + let original: serde_json::Value = serde_json::from_slice(&completed).unwrap(); + let before = serde_json::to_vec(&worksheet).unwrap(); + for (pointer, value) in [ + ("/capture_digest", serde_json::json!("changed")), + ("/metadata_report_digest", serde_json::json!("changed")), + ("/proposal_digest", serde_json::json!("changed")), + ("/view_kind", serde_json::json!("changed")), + ("/bibliographic_item_count", serde_json::json!(1)), + ("/review_batch/remaining_count", serde_json::json!(1)), + ( + "/review_batch/decisions/0/title", + serde_json::json!("changed"), + ), + ( + "/review_batch/decisions/0/reviewed_disposition", + serde_json::Value::Null, + ), + ( + "/review_batch/decisions/0/reviewed_disposition", + serde_json::json!(crate::Disposition::NeedsStewardReview), + ), + ( + "/attachment_evidence/ABCD2345/0/content_response/body", + serde_json::json!("changed"), + ), + ( + "/attachment_evidence/ABCD2345/0/content_response/status", + serde_json::json!(404), + ), + ( + "/attachment_evidence/ABCD2345/0/content_response/version", + serde_json::json!(42), + ), + ( + "/attachment_evidence/ABCD2345/0/metadata_version", + serde_json::json!(42), + ), + ("/attachment_evidence/ABCD2345", serde_json::json!([])), + ("/review_batch/decisions", serde_json::json!([])), + ] { + let mut invalid = original.clone(); + *invalid.pointer_mut(pointer).unwrap() = value; + assert!( + apply_full_text_review_view( + &report, + &worksheet, + &capture, + &serde_json::to_vec(&invalid).unwrap() + ) + .is_err(), + "{pointer}" + ); + } + for pointer in [ + "", + "/review_batch", + "/review_batch/decisions/0", + "/review_batch/decisions/0/evidence", + "/attachment_evidence/ABCD2345/0", + "/attachment_evidence/ABCD2345/0/content_response", + ] { + let mut invalid = original.clone(); + invalid + .pointer_mut(pointer) + .unwrap() + .as_object_mut() + .unwrap() + .insert("unexpected_field".into(), serde_json::json!(true)); + assert!( + apply_full_text_review_view( + &report, + &worksheet, + &capture, + &serde_json::to_vec(&invalid).unwrap() + ) + .is_err() + ); + } + for bytes in [b"null".as_slice(), b"{}", b"{", b"{\"review_batch\":true}"] { + assert!(apply_full_text_review_view(&report, &worksheet, &capture, bytes).is_err()); + } + let advanced = apply_full_text_review_view(&report, &worksheet, &capture, &completed).unwrap(); + assert!(apply_full_text_review_view(&report, &advanced, &capture, &completed).is_err()); + assert!( + finalize_full_text_review( + &report, + &advanced, + &capture, + full_text_approval_fixture(&report, &capture) + ) + .is_err() + ); + let next: serde_json::Value = serde_json::from_slice( + &build_bound_full_text_review_json(&report, &advanced, &capture, 1).unwrap(), + ) + .unwrap(); + assert_eq!(next["review_batch"]["remaining_count"], 1); + assert_eq!( + next["attachment_evidence"], + serde_json::json!({"DEFG5678":[]}) + ); + let completed_next = completed_full_text_view(&report, &advanced, &capture, 1); + let complete = + apply_full_text_review_view(&report, &advanced, &capture, &completed_next).unwrap(); + assert!( + finalize_full_text_review( + &report, + &complete, + &capture, + full_text_approval_fixture(&report, &capture) + ) + .is_ok() + ); + assert_eq!(serde_json::to_vec(&worksheet).unwrap(), before); +} + +#[test] +fn full_text_review_rejects_duplicate_keys_before_comparing_presented_context() { + let report = report_fixture(); + let capture = capture_with(&report, 4096, &mut |request_path, _| { + Ok(response_fixture(request_path)) + }) + .unwrap(); + let worksheet = build_full_text_review_worksheet(&report, &capture).unwrap(); + let completed = + String::from_utf8(completed_full_text_view(&report, &worksheet, &capture, 2)).unwrap(); + for (original, duplicated) in [ + ( + "\"capture_digest\":", + "\"capture_digest\":\"changed\",\"capture_digest\":", + ), + ("\"item_key\":", "\"item_key\":\"changed\",\"item_key\":"), + ("\"ABCD2345\":", "\"ABCD2345\":[],\"ABCD2345\":"), + ("\"body\":", "\"body\":\"changed\",\"body\":"), + ] { + assert!(completed.contains(original)); + let invalid = completed.replacen(original, duplicated, 1); + assert!( + apply_full_text_review_view(&report, &worksheet, &capture, invalid.as_bytes()).is_err() + ); + } +} + +#[test] +fn full_text_review_revalidates_restored_bindings_before_governance() { + let report = report_fixture(); + let capture = capture_with(&report, 4096, &mut |request_path, _| { + Ok(response_fixture(request_path)) + }) + .unwrap(); + let worksheet = build_full_text_review_worksheet(&report, &capture).unwrap(); + let completed = completed_full_text_view(&report, &worksheet, &capture, 2); + let decided = apply_full_text_review_view(&report, &worksheet, &capture, &completed).unwrap(); + let golden = finalize_full_text_review( + &report, + &decided, + &capture, + full_text_approval_fixture(&report, &capture), + ) + .unwrap(); + let expected = serde_json::to_value(&golden).unwrap(); + let mut changed_report = report_fixture(); + changed_report.classified_items[0] + .title + .push_str(" changed"); + assert!( + finalize_full_text_review( + &changed_report, + &decided, + &capture, + full_text_approval_fixture(&changed_report, &capture) + ) + .is_err() + ); + let mut calls = 0; + assert!( + evaluate_full_text_review(&changed_report, &capture, &golden, |_| { + calls += 1; + true + }) + .is_err() + ); + assert_eq!(calls, 0); + for (pointer, replacement) in [ + ("/capture_digest", serde_json::json!("changed")), + ("/full_text_golden_set_v1/labels", serde_json::json!([])), + ( + "/full_text_golden_set_v1/approval/proposal_digest", + serde_json::json!("changed"), + ), + ] { + let mut invalid = expected.clone(); + *invalid.pointer_mut(pointer).unwrap() = replacement; + let invalid: FullTextReviewedGoldenSet = serde_json::from_value(invalid).unwrap(); + assert!( + evaluate_full_text_review(&report, &capture, &invalid, |_| { + calls += 1; + true + }) + .is_err() + ); + } + assert_eq!(calls, 0); + let mut relabeled = expected.clone(); + relabeled["full_text_golden_set_v1"]["labels"][0]["expected_disposition"] = + serde_json::json!(crate::Disposition::Generation); + let relabeled = serde_json::from_value(relabeled).unwrap(); + assert!( + evaluate_full_text_review( + &report, + &capture, + &relabeled, + |received| serde_json::to_value(received).unwrap() == expected + ) + .is_err() + ); + let mut wrong_approval = + serde_json::to_value(full_text_approval_fixture(&report, &capture)).unwrap(); + wrong_approval["capture_digest"] = serde_json::json!("changed"); + assert!( + finalize_full_text_review( + &report, + &decided, + &capture, + serde_json::from_value(wrong_approval).unwrap() + ) + .is_err() + ); + let mut invalid_worksheet = serde_json::to_value(&worksheet).unwrap(); + invalid_worksheet["capture_digest"] = serde_json::json!("changed"); + let invalid_worksheet = serde_json::from_value(invalid_worksheet).unwrap(); + assert!(build_bound_full_text_review_json(&report, &invalid_worksheet, &capture, 1).is_err()); + assert!( + apply_full_text_review_view(&report, &invalid_worksheet, &capture, &completed).is_err() + ); + assert!( + finalize_full_text_review( + &report, + &invalid_worksheet, + &capture, + full_text_approval_fixture(&report, &capture) + ) + .is_err() + ); + let mut invalid_report = report_fixture(); + invalid_report.audit_summary.failure_count = 1; + assert!(build_full_text_review_worksheet(&invalid_report, &capture).is_err()); + assert!(build_full_text_review_worksheet(&changed_report, &capture).is_err()); +} + +#[test] +fn full_text_owned_artifacts_reject_unknown_fields_without_changing_legacy_validity() { + let report = report_fixture(); + let capture = capture_with(&report, 4096, &mut |request_path, _| { + Ok(response_fixture(request_path)) + }) + .unwrap(); + let worksheet = build_full_text_review_worksheet(&report, &capture).unwrap(); + let worksheet_json = serde_json::to_value(&worksheet).unwrap(); + for pointer in [ + "", + "/full_text_worksheet_v1", + "/full_text_worksheet_v1/decisions/0", + "/full_text_worksheet_v1/snapshot_items/0", + ] { + let mut invalid = worksheet_json.clone(); + invalid + .pointer_mut(pointer) + .unwrap() + .as_object_mut() + .unwrap() + .insert("unexpected_field".into(), serde_json::json!(true)); + assert!(serde_json::from_value::(invalid).is_err()); + } + let approval_json = + serde_json::to_value(full_text_approval_fixture(&report, &capture)).unwrap(); + assert!(serde_json::from_value::(approval_json.clone()).is_err()); + assert!( + serde_json::from_value::( + approval_json["full_text_approval_v1"].clone() + ) + .is_err() + ); + assert!( + serde_json::from_value::( + approval_json["full_text_approval_v1"].clone() + ) + .is_ok() + ); + for pointer in [ + "", + "/full_text_approval_v1", + "/full_text_approval_v1/snapshot_items/0", + ] { + let mut invalid = approval_json.clone(); + invalid + .pointer_mut(pointer) + .unwrap() + .as_object_mut() + .unwrap() + .insert("unexpected_field".into(), serde_json::json!(true)); + assert!(serde_json::from_value::(invalid).is_err()); + } + let completed = completed_full_text_view(&report, &worksheet, &capture, 2); + let decided = apply_full_text_review_view(&report, &worksheet, &capture, &completed).unwrap(); + let golden = finalize_full_text_review( + &report, + &decided, + &capture, + full_text_approval_fixture(&report, &capture), + ) + .unwrap(); + for pointer in [ + "", + "/full_text_golden_set_v1", + "/full_text_golden_set_v1/labels/0", + ] { + let mut invalid = serde_json::to_value(&golden).unwrap(); + invalid + .pointer_mut(pointer) + .unwrap() + .as_object_mut() + .unwrap() + .insert("unexpected_field".into(), serde_json::json!(true)); + assert!(serde_json::from_value::(invalid).is_err()); + } +} + #[test] fn full_text_worksheet_starts_blank_without_a_metadata_downcast() { let report = report_fixture(); diff --git a/crates/conceptweave-zotero/src/full_text_review.rs b/crates/conceptweave-zotero/src/full_text_review.rs index 7b4b7505..b7d16d99 100644 --- a/crates/conceptweave-zotero/src/full_text_review.rs +++ b/crates/conceptweave-zotero/src/full_text_review.rs @@ -47,10 +47,11 @@ pub fn build_full_text_review_worksheet( report: &ClassificationReport, capture: &FullTextCapture, ) -> Result { + let review_worksheet = build_steward_review_worksheet(report).map_err(|_| INVALID_EVIDENCE)?; verify_full_text_capture(capture, report)?; Ok(FullTextReviewWorksheet { capture_digest: capture.capture_digest.clone(), - review_worksheet: build_steward_review_worksheet(report).map_err(|_| INVALID_EVIDENCE)?, + review_worksheet, }) } @@ -74,24 +75,35 @@ pub fn apply_full_text_review_view( completed_view: &[u8], ) -> Result { validate_review_capture(report, capture, &worksheet.capture_digest)?; - let mut completed = unique_review_json::parse_review_json(completed_view) - .map_err(|_| INVALID_EVIDENCE)?; + let mut completed = + unique_review_json::parse_review_json(completed_view).map_err(|_| INVALID_EVIDENCE)?; let batch: crate::StewardReviewBatch = serde_json::from_value( - completed.get("review_batch").ok_or(INVALID_EVIDENCE)?.clone(), - ).map_err(|_| INVALID_EVIDENCE)?; - let patch = crate::decision_patch_from_review_batch(report, &worksheet.review_worksheet, &batch) - .map_err(|_| INVALID_EVIDENCE)?; + completed + .get("review_batch") + .ok_or(INVALID_EVIDENCE)? + .clone(), + ) + .map_err(|_| INVALID_EVIDENCE)?; + let patch = + crate::decision_patch_from_review_batch(report, &worksheet.review_worksheet, &batch) + .map_err(|_| INVALID_EVIDENCE)?; let expected: serde_json::Value = serde_json::from_slice(&build_full_text_review_json( - report, &worksheet.review_worksheet, capture, batch.decisions.len(), - )?).map_err(|_| INVALID_EVIDENCE)?; + report, + &worksheet.review_worksheet, + capture, + batch.decisions.len(), + )?) + .expect("generated full-text view contains valid JSON"); // The metadata boundary above already checked every displayed batch field. // Replace only that verified batch before comparing the retained text envelope. completed["review_batch"] = expected["review_batch"].clone(); if completed != expected { return Err(INVALID_EVIDENCE); } - let review_worksheet = crate::apply_steward_decision_patch(report, &worksheet.review_worksheet, &patch) - .map_err(|_| INVALID_EVIDENCE)?; + let review_worksheet = + crate::apply_steward_decision_patch(report, &worksheet.review_worksheet, &patch).expect( + "a validated pending review patch cannot conflict with its unchanged worksheet", + ); Ok(FullTextReviewWorksheet { capture_digest: worksheet.capture_digest.clone(), review_worksheet, @@ -111,8 +123,11 @@ pub fn finalize_full_text_review( return Err(INVALID_EVIDENCE); } let reviewed_golden_set = crate::reviewed_golden_set_from_worksheet( - report, &worksheet.review_worksheet, approval.review_approval, - ).map_err(|_| INVALID_EVIDENCE)?; + report, + &worksheet.review_worksheet, + approval.review_approval, + ) + .map_err(|_| INVALID_EVIDENCE)?; Ok(FullTextReviewedGoldenSet { capture_digest: worksheet.capture_digest.clone(), reviewed_golden_set, @@ -134,8 +149,11 @@ where { validate_review_capture(report, capture, &reviewed.capture_digest)?; let review_evaluation = crate::evaluate_complete_reviewed_classification( - report, &reviewed.reviewed_golden_set, |_| verify_approval(reviewed), - ).map_err(|_| FullTextError("full-text review is invalid or unverified"))?; + report, + &reviewed.reviewed_golden_set, + |_| verify_approval(reviewed), + ) + .map_err(|_| FullTextError("full-text review is invalid or unverified"))?; Ok(FullTextReviewEvaluation { capture_digest: reviewed.capture_digest.clone(), review_evaluation, diff --git a/crates/conceptweave-zotero/src/unique_review_json.rs b/crates/conceptweave-zotero/src/unique_review_json.rs index 13370aec..6a64da20 100644 --- a/crates/conceptweave-zotero/src/unique_review_json.rs +++ b/crates/conceptweave-zotero/src/unique_review_json.rs @@ -1,6 +1,8 @@ //! Bounded JSON admission before a review object can discard duplicate keys. -use serde_json::Value; +use serde::de::{DeserializeSeed, MapAccess, SeqAccess, Visitor}; +use serde_json::{Map, Value}; +use std::fmt; const MAX_REVIEW_JSON_BYTES: usize = 16 * 1024 * 1024; const INVALID_REVIEW_JSON: &str = "review JSON is invalid"; @@ -13,7 +15,74 @@ pub(crate) fn parse_review_json(bytes: &[u8]) -> Result { if bytes.len() > MAX_REVIEW_JSON_BYTES { return Err(INVALID_REVIEW_JSON); } - serde_json::from_slice(bytes).map_err(|_| INVALID_REVIEW_JSON) + let mut deserializer = serde_json::Deserializer::from_slice(bytes); + let value = UniqueReviewValue + .deserialize(&mut deserializer) + .map_err(|_| INVALID_REVIEW_JSON)?; + deserializer.end().map_err(|_| INVALID_REVIEW_JSON)?; + Ok(value) +} + +/// Builds values recursively while retaining each object's decoded key boundary. +struct UniqueReviewValue; + +impl<'de> DeserializeSeed<'de> for UniqueReviewValue { + type Value = Value; + + fn deserialize>(self, deserializer: D) -> Result { + deserializer.deserialize_any(self) + } +} + +impl<'de> Visitor<'de> for UniqueReviewValue { + type Value = Value; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("a JSON value with unique object keys") + } + + fn visit_unit(self) -> Result { + Ok(Value::Null) + } + + fn visit_bool(self, value: bool) -> Result { + Ok(Value::Bool(value)) + } + + fn visit_i64(self, value: i64) -> Result { + Ok(Value::from(value)) + } + + fn visit_u64(self, value: u64) -> Result { + Ok(Value::from(value)) + } + + fn visit_f64(self, value: f64) -> Result { + Ok(Value::from(value)) + } + + fn visit_str(self, value: &str) -> Result { + Ok(Value::String(value.to_owned())) + } + + fn visit_seq>(self, mut sequence: A) -> Result { + let mut values = Vec::new(); + while let Some(value) = sequence.next_element_seed(UniqueReviewValue)? { + values.push(value); + } + Ok(Value::Array(values)) + } + + fn visit_map>(self, mut object: A) -> Result { + let mut values = Map::new(); + while let Some(key) = object.next_key::()? { + if values.contains_key(&key) { + return Err(serde::de::Error::custom("duplicate review key")); + } + values.insert(key, object.next_value_seed(UniqueReviewValue)?); + } + Ok(Value::Object(values)) + } } #[cfg(test)] @@ -61,6 +130,15 @@ mod tests { #[test] fn review_json_rejects_malformed_trailing_and_excessively_deep_inputs() { + let expectation = ::invalid_type( + serde::de::Unexpected::Bytes(b""), + &UniqueReviewValue, + ); + assert!( + expectation + .to_string() + .contains("a JSON value with unique object keys") + ); for bytes in [ b"".as_slice(), b" ", From 2a77a33452699989766917adc82d425a744bc1aa Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 20:28:39 +0900 Subject: [PATCH 08/39] test: cover oversized context rejection during decision admission --- .../conceptweave-zotero/src/full_text_capture_tests.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/crates/conceptweave-zotero/src/full_text_capture_tests.rs b/crates/conceptweave-zotero/src/full_text_capture_tests.rs index 93e7f697..81268fe6 100644 --- a/crates/conceptweave-zotero/src/full_text_capture_tests.rs +++ b/crates/conceptweave-zotero/src/full_text_capture_tests.rs @@ -708,6 +708,15 @@ fn review_view_rejects_json_expansion_without_truncating_valid_captured_text() { let error = build_full_text_review_json(&report, &worksheet, &capture, 1).unwrap_err(); assert!(error.to_string().contains("16 MiB output limit")); assert_eq!(worksheet.decisions.len(), 2); + let bound = build_full_text_review_worksheet(&report, &capture).unwrap(); + assert!(build_bound_full_text_review_json(&report, &bound, &capture, 1).is_err()); + let mut batch = crate::build_steward_review_batch(&report, &worksheet, 1).unwrap(); + batch.decisions[0].reviewed_disposition = Some(crate::Disposition::OutOfScope); + let input = serde_json::to_vec(&serde_json::json!({"review_batch":batch})).unwrap(); + let error = apply_full_text_review_view(&report, &bound, &capture, &input) + .err() + .unwrap(); + assert!(error.to_string().contains("16 MiB output limit")); } fn report_fixture() -> ClassificationReport { From 50dc154978ae0cdf9b4d4d5dfafebcea4c26198e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 20:28:59 +0900 Subject: [PATCH 09/39] test(zotero): reproduce missing full-text worksheet initializer --- crates/conceptweave-zotero/src/main.rs | 27 +++ .../tests/full_text_worksheet_cli.rs | 193 ++++++++++++++++++ 2 files changed, 220 insertions(+) create mode 100644 crates/conceptweave-zotero/tests/full_text_worksheet_cli.rs diff --git a/crates/conceptweave-zotero/src/main.rs b/crates/conceptweave-zotero/src/main.rs index 9ebfa917..20aafef6 100644 --- a/crates/conceptweave-zotero/src/main.rs +++ b/crates/conceptweave-zotero/src/main.rs @@ -755,6 +755,33 @@ mod full_text_review_cli_reader_tests; mod tests { use super::*; + #[test] + fn full_text_worksheet_mode_requires_three_distinct_paths() { + assert!( + parse_output_request([ + "--full-text-worksheet", + "/tmp/report.json", + "/tmp/capture.json", + "/tmp/worksheet.json", + ]) + .is_ok() + ); + for length in 1..4 { + let arguments = ["--full-text-worksheet", "r", "c", "o"]; + assert!(parse_output_request(arguments[..length].iter().copied()).is_err()); + } + for paths in [["r", "r", "o"], ["r", "c", "r"], ["r", "c", "c"]] { + assert!( + parse_output_request(["--full-text-worksheet", paths[0], paths[1], paths[2]]) + .is_err() + ); + } + assert!( + parse_output_request(["--full-text-worksheet", "r", "c", "o", "metadata-worksheet"]) + .is_err() + ); + } + #[test] fn full_text_review_mode_requires_five_distinct_bounded_arguments() { assert!( diff --git a/crates/conceptweave-zotero/tests/full_text_worksheet_cli.rs b/crates/conceptweave-zotero/tests/full_text_worksheet_cli.rs new file mode 100644 index 00000000..618b46df --- /dev/null +++ b/crates/conceptweave-zotero/tests/full_text_worksheet_cli.rs @@ -0,0 +1,193 @@ +#![cfg(unix)] + +use conceptweave_zotero::{ + ClassificationReport, FullTextCapture, ZoteroItem, build_full_text_review_worksheet, + classify_snapshot, +}; +use sha2::{Digest, Sha256}; +use std::fs::{self, OpenOptions}; +use std::io::Write; +use std::os::unix::fs::{OpenOptionsExt, PermissionsExt}; +use std::path::{Path, PathBuf}; +use std::process::{Command, Output}; + +#[test] +fn full_text_worksheet_cli_creates_blank_private_capture_bound_rows() { + let files = FixtureFiles::new("blank"); + let original_report = fs::read(&files.report).unwrap(); + let original_capture = fs::read(&files.capture).unwrap(); + let command = run(&files.report, &files.capture, &files.output); + assert!(command.status.success(), "{:?}", command.stderr); + assert!(command.stdout.is_empty()); + let bytes = fs::read(&files.output).unwrap(); + let worksheet: serde_json::Value = serde_json::from_slice(&bytes).unwrap(); + let capture: serde_json::Value = serde_json::from_slice(&original_capture).unwrap(); + assert_eq!(worksheet["capture_digest"], capture["capture_digest"]); + let rows = worksheet["full_text_worksheet_v1"]["decisions"] + .as_array() + .unwrap(); + assert_eq!(rows.len(), 2); + assert!(rows.iter().all(|row| row["reviewed_disposition"].is_null())); + assert_eq!( + fs::metadata(&files.output).unwrap().permissions().mode() & 0o777, + 0o600 + ); + assert!( + !run(&files.report, &files.capture, &files.output) + .status + .success() + ); + assert_eq!(fs::read(&files.output).unwrap(), bytes); + assert_eq!(fs::read(&files.report).unwrap(), original_report); + assert_eq!(fs::read(&files.capture).unwrap(), original_capture); +} + +#[test] +fn full_text_worksheet_cli_rejects_wrong_capture_or_report_without_output() { + let files = FixtureFiles::new("mismatch"); + let original_report = fs::read(&files.report).unwrap(); + let mut report: ClassificationReport = serde_json::from_slice(&original_report).unwrap(); + report.zotero_version = "10.0.2".into(); + fs::write(&files.report, serde_json::to_vec(&report).unwrap()).unwrap(); + let command = run(&files.report, &files.capture, &files.output); + assert!(!command.status.success()); + assert!( + String::from_utf8(command.stderr) + .unwrap() + .contains("full-text capture evidence is invalid") + ); + assert!(!files.output.exists()); + fs::write(&files.report, original_report).unwrap(); + let mut capture: serde_json::Value = + serde_json::from_slice(&fs::read(&files.capture).unwrap()).unwrap(); + capture["capture_digest"] = "synthetic-wrong-digest".into(); + fs::write(&files.capture, serde_json::to_vec(&capture).unwrap()).unwrap(); + let command = run(&files.report, &files.capture, &files.output); + assert!(!command.status.success()); + assert!( + String::from_utf8(command.stderr) + .unwrap() + .contains("full-text capture evidence is invalid") + ); + assert!(command.stdout.is_empty()); + assert!(!files.output.exists()); +} + +#[test] +fn full_text_worksheet_cli_rejects_aliases_and_unsafe_inputs() { + let files = FixtureFiles::new("alias"); + for (report, capture, output) in [ + (&files.report, &files.report, &files.output), + (&files.report, &files.capture, &files.report), + (&files.report, &files.capture, &files.capture), + ] { + assert!(!run(report, capture, output).status.success()); + } + let alias = files + .report + .parent() + .unwrap() + .join(".") + .join(files.report.file_name().unwrap()); + assert!(!run(&files.report, &alias, &files.output).status.success()); + let hard_link = files.output.with_extension("alias.json"); + fs::hard_link(&files.capture, &hard_link).unwrap(); + assert!( + !run(&files.report, &hard_link, &files.output) + .status + .success() + ); + fs::remove_file(hard_link).unwrap(); + fs::set_permissions(&files.capture, fs::Permissions::from_mode(0o644)).unwrap(); + assert!( + !run(&files.report, &files.capture, &files.output) + .status + .success() + ); + assert!(!files.output.exists()); +} + +struct FixtureFiles { + report: PathBuf, + capture: PathBuf, + output: PathBuf, +} + +impl FixtureFiles { + fn new(case: &str) -> Self { + let prefix = format!( + "conceptweave-fulltext-worksheet-{}-{case}", + std::process::id() + ); + let files = Self { + report: std::env::temp_dir().join(format!("{prefix}-report.json")), + capture: std::env::temp_dir().join(format!("{prefix}-capture.json")), + output: std::env::temp_dir().join(format!("{prefix}-worksheet.json")), + }; + let (report, capture) = synthetic_fixture(); + for (path, bytes) in [ + (&files.report, serde_json::to_vec(&report).unwrap()), + (&files.capture, serde_json::to_vec(&capture).unwrap()), + ] { + OpenOptions::new() + .write(true) + .create_new(true) + .mode(0o600) + .open(path) + .unwrap() + .write_all(&bytes) + .unwrap(); + } + files + } +} + +impl Drop for FixtureFiles { + fn drop(&mut self) { + for path in [&self.report, &self.capture, &self.output] { + let _ = fs::remove_file(path); + } + } +} + +fn run(report: &Path, capture: &Path, output: &Path) -> Output { + Command::new(env!("CARGO_BIN_EXE_conceptweave-zotero")) + .arg("--full-text-worksheet") + .args([report, capture, output]) + .output() + .unwrap() +} + +fn synthetic_fixture() -> (ClassificationReport, FullTextCapture) { + let items: Vec = serde_json::from_value(serde_json::json!([ + {"key":"ABCD2345","version":2,"data":{"itemType":"journalArticle","title":"synthetic ontology engineering"}}, + {"key":"BCDE3456","version":2,"data":{"itemType":"book","title":"synthetic unmatched paper"}} + ])).unwrap(); + let mut report = classify_snapshot("10.0.1".into(), Some("synthetic-server".into()), 2, items); + report.api_version = Some(3); + report.schema_version = Some(44); + let library = r#"{"status":200,"version":2,"body":"[]"}"#; + let manifest = r#"{"status":200,"version":null,"body":"{}"}"#; + let evidence = format!( + concat!( + "{{\"capture_kind\":\"non_atomic_fulltext_sweep_v1\",", + "\"metadata_report_digest\":\"sha256:{:x}\",\"metadata_snapshot_digest\":\"{}\",", + "\"bibliographic_item_count\":2,\"started_unix_ms\":0,\"finished_unix_ms\":1,", + "\"library_before\":{},\"manifest_before\":{},\"records\":[],", + "\"manifest_after\":{},\"library_after\":{}}}" + ), + Sha256::digest(serde_json::to_vec(&report).unwrap()), + report.snapshot_digest, + library, + manifest, + manifest, + library, + ); + let capture: FullTextCapture = serde_json::from_str(&format!( + "{{\"capture_digest\":\"sha256:{:x}\",\"capture_evidence\":{evidence}}}", + Sha256::digest(evidence.as_bytes()) + )) + .unwrap(); + build_full_text_review_worksheet(&report, &capture).unwrap(); + (report, capture) +} From 21ef42dfc2a9f0dee1f81ca1e2a7c595f3f8958c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 20:30:28 +0900 Subject: [PATCH 10/39] feat(zotero): initialize private blank full-text worksheets --- crates/conceptweave-zotero/src/main.rs | 47 +++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/crates/conceptweave-zotero/src/main.rs b/crates/conceptweave-zotero/src/main.rs index 20aafef6..250104df 100644 --- a/crates/conceptweave-zotero/src/main.rs +++ b/crates/conceptweave-zotero/src/main.rs @@ -4,9 +4,9 @@ use conceptweave_zotero::{ ClassificationReport, FullTextCapture, GoldenSetApproval, MAX_REVIEW_BATCH_ITEMS, StewardDecisionPatch, StewardReviewBatch, StewardReviewWorksheet, apply_steward_decision_patch, - assess_steward_review_progress, build_full_text_review_json, build_steward_review_batch, - build_steward_review_worksheet, decision_patch_from_review_batch, read_local_full_text, - read_local_snapshot, reviewed_golden_set_from_worksheet, + assess_steward_review_progress, build_full_text_review_json, build_full_text_review_worksheet, + build_steward_review_batch, build_steward_review_worksheet, decision_patch_from_review_batch, + read_local_full_text, read_local_snapshot, reviewed_golden_set_from_worksheet, }; use serde::de::DeserializeOwned; use std::collections::BTreeSet; @@ -15,7 +15,7 @@ use std::fs::{self, File, OpenOptions}; use std::io::{self, BufReader, BufWriter, Read, Write}; use std::path::{Path, PathBuf}; -const USAGE: &str = "usage: conceptweave-zotero /tmp/REPORT.json | --capture-full-text /tmp/REPORT.json /tmp/CAPTURE.json | --full-text-review /tmp/REPORT.json /tmp/WORKSHEET.json /tmp/CAPTURE.json LIMIT /tmp/VIEW.json | --worksheet /tmp/REPORT.json /tmp/WORKSHEET.json | --review-progress /tmp/REPORT.json /tmp/WORKSHEET.json /tmp/PROGRESS.json | --review-batch /tmp/REPORT.json /tmp/CURRENT_WORKSHEET.json LIMIT /tmp/BATCH.json | --apply-review-batch /tmp/REPORT.json /tmp/CURRENT_WORKSHEET.json /tmp/COMPLETED_BATCH.json /tmp/UPDATED_WORKSHEET.json | --apply-decision-patch /tmp/REPORT.json /tmp/CURRENT_WORKSHEET.json /tmp/PATCH.json /tmp/UPDATED_WORKSHEET.json | --finalize /tmp/REPORT.json /tmp/WORKSHEET.json /tmp/APPROVAL.json /tmp/GOLDEN.json"; +const USAGE: &str = "usage: conceptweave-zotero /tmp/REPORT.json | --capture-full-text /tmp/REPORT.json /tmp/CAPTURE.json | --full-text-worksheet /tmp/REPORT.json /tmp/CAPTURE.json /tmp/WORKSHEET.json | --full-text-review /tmp/REPORT.json /tmp/WORKSHEET.json /tmp/CAPTURE.json LIMIT /tmp/VIEW.json | --worksheet /tmp/REPORT.json /tmp/WORKSHEET.json | --review-progress /tmp/REPORT.json /tmp/WORKSHEET.json /tmp/PROGRESS.json | --review-batch /tmp/REPORT.json /tmp/CURRENT_WORKSHEET.json LIMIT /tmp/BATCH.json | --apply-review-batch /tmp/REPORT.json /tmp/CURRENT_WORKSHEET.json /tmp/COMPLETED_BATCH.json /tmp/UPDATED_WORKSHEET.json | --apply-decision-patch /tmp/REPORT.json /tmp/CURRENT_WORKSHEET.json /tmp/PATCH.json /tmp/UPDATED_WORKSHEET.json | --finalize /tmp/REPORT.json /tmp/WORKSHEET.json /tmp/APPROVAL.json /tmp/GOLDEN.json"; const MAX_ARTIFACT_BYTES: u64 = 16 * 1024 * 1024; // JSON escaping and envelope bytes are separate from the capture's raw-body budget. const MAX_CAPTURE_FILE_BYTES: u64 = 512 * 1024 * 1024; @@ -27,6 +27,11 @@ enum OutputRequest { report: String, output: String, }, + FullTextWorksheet { + report: String, + capture: String, + output: String, + }, FullTextReview { report: String, worksheet: String, @@ -88,6 +93,24 @@ where return Err("full-text report and output paths must differ"); } OutputRequest::FullTextCapture { report, output } + } else if first == "--full-text-worksheet" { + let report = args + .next() + .ok_or("--full-text-worksheet requires report, capture, and output paths")?; + let capture = args + .next() + .ok_or("--full-text-worksheet requires report, capture, and output paths")?; + let output = args + .next() + .ok_or("--full-text-worksheet requires report, capture, and output paths")?; + if BTreeSet::from([&report, &capture, &output]).len() != 3 { + return Err("full-text worksheet paths must be distinct"); + } + OutputRequest::FullTextWorksheet { + report, + capture, + output, + } } else if first == "--full-text-review" { let report = args .next() @@ -577,6 +600,22 @@ fn create_report_file_with( /// Reads one Zotero snapshot and writes its sensitive local proposal report. fn main() -> Result<(), Box> { match parse_output_request(env::args().skip(1))? { + OutputRequest::FullTextWorksheet { + report, + capture, + output, + } => { + let output_path = validate_output_path(&output)?; + let (report, report_identity): (ClassificationReport, _) = + read_private_json(&report).map_err(|error| label_input("report", error))?; + let (capture, capture_identity) = + read_private_capture(&capture).map_err(|error| label_input("capture", error))?; + if report_identity == capture_identity { + return Err("full-text worksheet inputs must be distinct files".into()); + } + let worksheet = build_full_text_review_worksheet(&report, &capture)?; + write_private_output(&output_path, &serde_json::to_vec_pretty(&worksheet)?)?; + } OutputRequest::FullTextReview { report, worksheet, From 35b7816b4f7e8f11d8ee0faffbae6a643e7b8956 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 20:32:32 +0900 Subject: [PATCH 11/39] test: reproduce completed review overflow at exact size boundary --- .../src/full_text_capture_tests.rs | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/crates/conceptweave-zotero/src/full_text_capture_tests.rs b/crates/conceptweave-zotero/src/full_text_capture_tests.rs index 81268fe6..b893dcd9 100644 --- a/crates/conceptweave-zotero/src/full_text_capture_tests.rs +++ b/crates/conceptweave-zotero/src/full_text_capture_tests.rs @@ -443,6 +443,71 @@ fn full_text_worksheet_starts_blank_without_a_metadata_downcast() { assert!(serde_json::from_value::(json).is_ok()); } +#[test] +fn bound_review_view_reserves_space_for_every_valid_decision() { + let report = report_fixture(); + let metadata = build_steward_review_worksheet(&report).unwrap(); + let make_capture = |text: String| { + capture_with(&report, MAX_SNAPSHOT_BYTES, &mut |request_path, _| { + let mut response = response_fixture(request_path); + if request_path == "items/BCDE3456/fulltext" { + response.body = serde_json::json!({"content":text}).to_string(); + } + Ok(response) + }) + .unwrap() + }; + let empty = make_capture(String::new()); + let base_bytes = build_full_text_review_json(&report, &metadata, &empty, 1) + .unwrap() + .len(); + let max_bytes = 16 * 1024 * 1024; + let full_size = max_bytes - base_bytes; + let full_capture = make_capture(format!( + "{}{}", + "\"".repeat(full_size / 4), + "a".repeat(full_size % 4) + )); + assert_eq!( + build_full_text_review_json(&report, &metadata, &full_capture, 1) + .unwrap() + .len(), + max_bytes + ); + let worksheet = build_full_text_review_worksheet(&report, &full_capture).unwrap(); + assert!(build_bound_full_text_review_json(&report, &worksheet, &full_capture, 1).is_err()); + + let growth = serde_json::to_vec(&crate::Disposition::SemanticConsumptionBridge) + .unwrap() + .len() + - 4; + let admitted_size = full_size - growth; + let capture = make_capture(format!( + "{}{}", + "\"".repeat(admitted_size / 4), + "a".repeat(admitted_size % 4) + )); + let worksheet = build_full_text_review_worksheet(&report, &capture).unwrap(); + let bytes = build_bound_full_text_review_json(&report, &worksheet, &capture, 1).unwrap(); + assert_eq!(bytes.len() + growth, max_bytes); + for disposition in [ + crate::Disposition::Generation, + crate::Disposition::AlignmentVersioning, + crate::Disposition::SemanticConsumptionBridge, + crate::Disposition::EvaluationGovernance, + crate::Disposition::AdjacentEvidence, + crate::Disposition::OutOfScope, + ] { + assert!(serde_json::to_vec(&disposition).unwrap().len() - 4 <= growth); + } + let mut view: serde_json::Value = serde_json::from_slice(&bytes).unwrap(); + view["review_batch"]["decisions"][0]["reviewed_disposition"] = + serde_json::json!(crate::Disposition::SemanticConsumptionBridge); + let completed = serde_json::to_vec(&view).unwrap(); + assert_eq!(completed.len(), max_bytes); + assert!(apply_full_text_review_view(&report, &worksheet, &capture, &completed).is_ok()); +} + #[test] fn review_view_binds_exact_capture_and_keeps_missing_parents_without_decisions() { let report = report_fixture(); From e0744e0e11461c2f20a05d104f73f7ca32549b4d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 20:33:42 +0900 Subject: [PATCH 12/39] fix: reserve decision headroom in actionable full-text views --- .../src/full_text_review.rs | 23 ++++++++++++++++++- .../src/unique_review_json.rs | 2 +- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/crates/conceptweave-zotero/src/full_text_review.rs b/crates/conceptweave-zotero/src/full_text_review.rs index b7d16d99..185d80be 100644 --- a/crates/conceptweave-zotero/src/full_text_review.rs +++ b/crates/conceptweave-zotero/src/full_text_review.rs @@ -56,6 +56,8 @@ pub fn build_full_text_review_worksheet( } /// Shows the next pending rows only after revalidating the worksheet's capture. +/// Reserves room for the longest v1 decision so compact completed JSON still fits +/// the 16 MiB input limit. Extra formatting whitespace is not reserved. pub fn build_bound_full_text_review_json( report: &ClassificationReport, worksheet: &FullTextReviewWorksheet, @@ -63,7 +65,26 @@ pub fn build_bound_full_text_review_json( limit: usize, ) -> Result, FullTextError> { validate_review_capture(report, capture, &worksheet.capture_digest)?; - build_full_text_review_json(report, &worksheet.review_worksheet, capture, limit) + let bytes = build_full_text_review_json(report, &worksheet.review_worksheet, capture, limit)?; + let editable_count = worksheet + .review_worksheet + .decisions + .iter() + .filter(|decision| decision.reviewed_disposition.is_none()) + .take(limit) + .count(); + // The v1 disposition contract's longest label; all valid labels are checked + // by the generation -> edit -> admission boundary regression. + let maximum_growth = serde_json::to_vec(&crate::Disposition::SemanticConsumptionBridge) + .expect("review dispositions are JSON-compatible") + .len() + - b"null".len(); + if bytes.len() + editable_count * maximum_growth > unique_review_json::MAX_REVIEW_JSON_BYTES { + return Err(FullTextError( + "full-text review exceeds the 16 MiB limit including decisions", + )); + } + Ok(bytes) } /// Applies only completed decision slots in an otherwise unchanged evidence view. diff --git a/crates/conceptweave-zotero/src/unique_review_json.rs b/crates/conceptweave-zotero/src/unique_review_json.rs index 6a64da20..715ba728 100644 --- a/crates/conceptweave-zotero/src/unique_review_json.rs +++ b/crates/conceptweave-zotero/src/unique_review_json.rs @@ -4,7 +4,7 @@ use serde::de::{DeserializeSeed, MapAccess, SeqAccess, Visitor}; use serde_json::{Map, Value}; use std::fmt; -const MAX_REVIEW_JSON_BYTES: usize = 16 * 1024 * 1024; +pub(super) const MAX_REVIEW_JSON_BYTES: usize = 16 * 1024 * 1024; const INVALID_REVIEW_JSON: &str = "review JSON is invalid"; /// Parses one complete review value without accepting ambiguous object keys. From 656b8c469f110a94fc4045ab25ce090acebfa350 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 20:38:56 +0900 Subject: [PATCH 13/39] docs: bind full-text review contracts and measured private evidence --- AGENTS.md | 1 + ARCHITECTURE.md | 2 + CHANGELOG.md | 4 ++ CLAUDE.md | 2 + THREAT_MODEL.md | 2 + docs/CONTEXT_MAP.md | 2 +- docs/PRD.md | 4 +- docs/TRD.md | 10 ++++- docs/UBIQUITOUS_LANGUAGE.md | 2 + docs/UML.md | 18 ++++++++ docs/adr/0006-zotero-research-intake.md | 14 ++++++ docs/doctoring/REFERENCES.md | 6 +++ .../zotero_fulltext_contract_audit.md | 10 +++++ ...tero_fulltext_review_binding_evidence.json | 43 +++++++++++++++++++ docs/product-technical-gap-baseline.md | 16 +++++++ 15 files changed, 133 insertions(+), 3 deletions(-) create mode 100644 docs/doctoring/zotero_fulltext_review_binding_evidence.json diff --git a/AGENTS.md b/AGENTS.md index 8983336e..c320cb18 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -18,6 +18,7 @@ ConceptWeave owns automatic, evidence-bound **Semantic Model Engineering**. Do n - Preserve source evidence, truth status, and publication state separately. - Keep Zotero full-text captures separate from metadata reports and approval receipts; restored captures require bounded verification, and local HTTP continuity is not peer authentication. - Full-text review views are read-only evidence; never strip their outer binding into a metadata-only patch and claim full-text decision or approval provenance. +- Full-text decisions use the separate blank-start worksheet and atomic exact-view application; reverify the capture/report relation through finalization and whole-envelope governance. No reviewed-set downcast grants Zotero write authority. - Published semantic truth is immutable; correction uses supersession/new release. - Public Rust APIs require beginner-readable documentation. - Owned production coverage target is 100% line/function/region/branch where tooling exposes it. diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 3d38bb37..9f80a1e4 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -26,6 +26,8 @@ Research Intake's Zotero adapter retains optional full-text observations in a se Its Full-Text Review View reuses capture verification and canonical pending selection to make exact text inspectable without changing earlier proposals. This bounded read projection is not an aggregate or a decision-application API; the metadata-only approval chain does not acquire full-text provenance from it. +The separate Full-Text Review Worksheet starts blank and retains one capture identity through atomic completed-view application, finalization and whole-envelope governance verification. Every boundary rechecks the capture against the complete report. It reuses existing review cores rather than introducing another service or source of semantic truth. The CLI only initializes this work; the remaining operations are library APIs. Verified review still does not admit Zotero writes, whose exact change set requires independent authorization. + | Context | Type | Owns | Does not own | | --- | --- | --- | --- | | Source Observation | Supporting | immutable observations, parser receipts, evidence locations | source-system business truth | diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c96d907..bf822f59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ All notable changes to ConceptWeave are documented here. ### Added +- A separate private review worksheet and library review path that keeps saved-text evidence attached to decisions and approval verification. Command-line support currently initializes blank review work; it does not complete or approve a review. + - Private inspection of pending papers alongside their saved text, preserving missing material and leaving previous reports and decisions unchanged. - Private, replayable paper-text capture for later research review, preserving unavailable material and leaving earlier reports and approvals unchanged. @@ -28,6 +30,8 @@ All notable changes to ConceptWeave are documented here. ### Security +- Completed text-review files reject changed evidence, stale decisions and duplicate fields before updating local work. Earlier approvals cannot silently acquire later text evidence. + - Local research requests bypass environment-configured proxies. This prevents unintended proxy forwarding; local peer authentication remains an explicit release limitation. - Source receipts bind complete captured metadata and actual classifier inputs; earlier report and review artifacts require regeneration under the versioned digest representation. diff --git a/CLAUDE.md b/CLAUDE.md index 0cfb8135..b854da77 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -9,3 +9,5 @@ Keep domain logic in bounded domain modules, LLM/provider logic behind ports/ada Zotero source capture must not alter the metadata report or renew its approval. Preserve private-file protections and the full bibliographic denominator, including missing and partial text. The separate full-text review view does not authorize decisions or writes. Keep its evidence binding intact; existing metadata-only apply/finalization cannot establish full-text-reviewed approval. + +Use the capture-bound blank-start worksheet for full-text decision work. Only the dedicated atomic view path may apply completed slots; finalization/evaluation reverify capture/report bindings and require whole-envelope external approval. CLI initialization alone is not an end-to-end review interface or Zotero write admission. diff --git a/THREAT_MODEL.md b/THREAT_MODEL.md index 42f9ed6f..f44dc19f 100644 --- a/THREAT_MODEL.md +++ b/THREAT_MODEL.md @@ -77,4 +77,6 @@ PR #30 commit `9733d28` reproduced the inode-preserving final-component symlink The optional full-text review view must not be confused with the metadata-only decision or approval artifacts. A required versioned outer envelope and strict legacy parsers prevent silent direct application; an intentional caller can still strip fields, so no full-text-reviewed approval is claimed. The complete capture is reverified before selecting text, unrelated parent text is excluded, and output serialization itself is bounded. The larger serialized-capture input ceiling is separate from the unchanged report/worksheet limit. Source text is untrusted data, never instructions, and source content must not be included in input-error diagnostics. +Capture-bound review uses separate required versioned payloads, with no import of previous metadata decisions. Atomic application permits only completed decision slots in the exact current view; recursive duplicate-key rejection precedes projection so a changed first field cannot hide behind a canonical later field. Finalization and evaluation revalidate the capture against the complete current report, including proposal records, before authority is contacted. Governance must authenticate the entire outer reviewed set and every label. Owner-only storage, private fields and hashes do not defeat a malicious local replacement or prove human review. Preserve old artifacts; stale views fail rather than overwrite concurrent work. These APIs neither implement authenticated governance nor authorize the independent Zotero write contract. Restoring report/capture/worksheet/approval JSON still requires caller-owned bounded private-file admission. + A capability is not release-ready while a valid security finding lacks a deterministic test or equivalent machine-verifiable contract, while required exact-head checks are non-terminal, or while the implemented transport cannot satisfy the advertised security claim. Documentation must describe residual risk without upgrading provider guarantees by inference. diff --git a/docs/CONTEXT_MAP.md b/docs/CONTEXT_MAP.md index 0a9ea806..e28d49ce 100644 --- a/docs/CONTEXT_MAP.md +++ b/docs/CONTEXT_MAP.md @@ -10,7 +10,7 @@ Research Intake also owns the optional private Full-Text Capture bound to a metadata report. It preserves provider observations while rejecting mixed-origin counters as a reliable incremental cursor. This is an adapter responsibility, not a new bounded context, shared catalog or approval owner; downstream proposal adoption remains a separate evidence/review transition. -The Full-Text Review View is an in-context read projection over that verified capture and the current pending worksheet. It introduces no new aggregate or authority owner. Governance cannot accept it through the existing metadata-only patch path; full-text decision provenance needs a subsequent explicit contract. +The Full-Text Review View is an in-context read projection over that verified capture and the current pending worksheet. It introduces no new aggregate or authority owner. The separate Full-Text Review Worksheet carries one verified capture through atomic completed-view application and finalization. Governance receives the complete capture-bound reviewed set for independent verification; no metadata-only downcast or approval renewal is provided. The classification-write review remains a separate authority contract, with no full-text evaluation-to-write conversion. ## External relationships diff --git a/docs/PRD.md b/docs/PRD.md index 3c3c7013..ebff7023 100644 --- a/docs/PRD.md +++ b/docs/PRD.md @@ -60,7 +60,9 @@ Read one immutable Zotero Local API library-version snapshot and propose exactly Full-text enrichment must distinguish listed attachments, returned nonempty text, complete or partial indexing, and reviewed meaning. Missing abstracts or unavailable text never remove papers from the campaign denominator or prove irrelevance. Newly retrieved text requires its own immutable evidence capture and renewed review of any changed proposal; it cannot silently replace evidence beneath an earlier approval. The [full-text audit](doctoring/zotero_fulltext_contract_audit.md) establishes availability only. A separate proposed local capture now preserves the observed text for later review, with missing material still visible. Retained text is neither completed classification nor approved meaning. -Stewards must be able to inspect saved text alongside the next pending review rows without rereading Zotero or rewriting the report. A separate private evidence view binds the capture, original report and unchanged proposals, includes only those rows' attachment content, and retains missing and partial material explicitly. If the complete view exceeds its size limit, generation fails instead of truncating papers or text. Generating or reading this view does not fill decisions. Existing metadata-only apply commands must reject it; a later full-text decision path must carry its reviewed evidence identity into finalization and independently verified approval. +Stewards must be able to inspect saved text alongside the next pending review rows without rereading Zotero or rewriting the report. A separate private evidence view binds the capture, original report and unchanged proposals, includes only those rows' attachment content, and retains missing and partial material explicitly. If the complete view exceeds its size limit, generation fails instead of truncating papers or text. Generating or reading this view does not fill decisions. Existing metadata-only apply commands must reject it. + +A full-text review starts with blank decisions for every paper; previous metadata decisions cannot silently acquire a claim that the text was reviewed. Accept a completed view only while its displayed evidence and pending selection still match the current review. Changed or ambiguous content, incomplete decisions and stale views fail without replacing prior work. Each new worksheet, completed review and verified aggregate result retains the same captured-evidence identity. Finalization and evaluation recheck the capture against the original report; approval must authenticate that identity and every reviewed label. This is review provenance, not proof that a person read a file, and not permission to change Zotero. Command-line initialization is supplied separately from the library's decision/finalization/evaluation operations; those operations are not yet an end-to-end steward interface. 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 9fe1cfa0..43013e48 100644 --- a/docs/TRD.md +++ b/docs/TRD.md @@ -73,7 +73,15 @@ The proposed offline `--full-text-review /tmp/REPORT.json /tmp/WORKSHEET.json /t `build_full_text_review_json` reuses canonical pending-batch selection and verifies the complete capture against the report before projecting any text. Its non-flattened, Serialize-only `full_text_review_view_v1` envelope records the capture digest, metadata-report digest, current proposal digest, full bibliographic denominator, nested pending batch and attachment evidence keyed by the selected bibliographic parent. Every selected parent has an entry, including an empty list when the capture has no associated attachment. Only verified direct-parent attachments enter that entry; unrelated or standalone attachments remain in the original capture and are not copied into the view. Metadata item versions and opaque content versions remain separate. Raw content response bodies preserve 404, empty text, partial/unknown index counters and unknown provider fields without inferring relevance or completeness. -Output serialization uses a fixed 16 MiB byte slice, so JSON escaping cannot silently grow the buffer; overflow fails before file creation and never emits a truncated view. Original artifacts and proposal/decision counts remain unchanged. The envelope cannot deserialize as `StewardReviewBatch` or `StewardDecisionPatch`; no conversion API is provided. Deliberately extracting its nested metadata batch does not preserve full-text review provenance. Carrying a review-context digest through decision application, worksheet finalization and a freshly verified approval remains a required follow-up, not a capability of this read-only view. +Output serialization uses a fixed 16 MiB byte slice, so JSON escaping cannot silently grow the buffer; overflow fails before file creation and never emits a truncated view. Original artifacts and proposal/decision counts remain unchanged. The envelope cannot deserialize as `StewardReviewBatch` or `StewardDecisionPatch`; no metadata-only conversion is provided. Deliberately extracting its nested metadata batch does not preserve full-text review provenance. + +The separate full-text review contract uses required, non-flattened versioned payload fields (`full_text_worksheet_v1`, `full_text_approval_v1`, `full_text_golden_set_v1`, `full_text_evaluation_v1`) beside a required `capture_digest`. Owned worksheet, decision, update, snapshot, approval, golden-set and label objects reject unknown fields. Rust fields remain private and no downcast into metadata-only review types is offered. A matching digest or restored JSON is not authenticated review history. The capture itself already binds the complete report, including all proposal records; every new operation verifies that relation rather than accepting an opaque marker alone. + +`build_full_text_review_worksheet` creates only blank work from report and capture, never importing previous decisions. `build_bound_full_text_review_json` produces the same bounded view after checking that worksheet's capture. It reserves 25 bytes per selected blank decision for the longest v1 disposition, so replacing every `null` slot with a valid label still fits the 16 MiB compact JSON limit; arbitrary added formatting whitespace is not reserved. A view that cannot leave that space fails without truncating evidence. The read-only legacy view retains its original ceiling. `apply_full_text_review_view` parses at most 16 MiB, rejects decoded duplicate keys recursively before JSON projection, validates the canonical pending batch and compares the complete retained-text envelope with only its verified decision slots removed. It immediately reuses the existing atomic patch core and returns a new worksheet. No independently deserializable patch can bypass this comparison. A stale/replayed completed view fails; preserve earlier artifacts and regenerate the next pending view. This differs deliberately from idempotent replay of legacy metadata decision patches. + +`finalize_full_text_review` revalidates report/capture/worksheet/approval bindings and requires every bibliographic decision, returning only an input for governance verification. `evaluate_full_text_review` repeats local capture/report and complete-label validation before passing the entire outer reviewed set to a caller-owned verifier. That verifier must authenticate both capture identity and every label against an independently issued receipt; accepting an identifier or recomputed digest is insufficient. The aggregate result retains the capture identity without source text or item/reviewer identities. Legacy metadata evaluation remains available but cannot establish full-text-reviewed approval. + +The `--full-text-worksheet /tmp/REPORT.json /tmp/CAPTURE.json /tmp/WORKSHEET.json` entry point initializes a separate owner-only file using existing bounded readers and create-new output protection. The other full-text decision/finalization/evaluation operations are library contracts, not CLI commands or deployed governance. Write planning still has its separate `ReviewedClassificationWriteSet` and approval verifier; there is no conversion or authority transfer from a full-text evaluation to a Zotero write plan. A future write admission must explicitly preserve this context and authenticate the exact proposed collection/tag changes. `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. diff --git a/docs/UBIQUITOUS_LANGUAGE.md b/docs/UBIQUITOUS_LANGUAGE.md index fdd347ed..28a21741 100644 --- a/docs/UBIQUITOUS_LANGUAGE.md +++ b/docs/UBIQUITOUS_LANGUAGE.md @@ -5,6 +5,8 @@ | Source Snapshot | Immutable revision of source evidence observed by ConceptWeave. | | Full-Text Capture | Separate private record of exact text/metadata responses, missing results and read interval, bound to an earlier metadata report; not an atomic Source Snapshot or an Authority Receipt. | | Full-Text Review View | Bounded private projection of verified captured text beside current pending metadata decisions; keeps missing evidence visible and is neither an applicable decision patch nor an Authority Receipt. | +| Full-Text Review Worksheet | Separate private work that starts with blank decisions for every bibliographic item and retains one verified capture identity through decision application; not authenticated history or approval. | +| Capture-Bound Reviewed Set | Complete non-abstention labels and a capture-bound approval input, all presented together for independent governance verification; never a classification-write authorization. | | Observation | Deterministically extracted fact from a Source Snapshot. | | Evidence Reference | Stable source identity, digest, and location supporting a candidate. | | Semantic Candidate | Evidence-bound proposal for a concept, relation, constraint, dimension, measure, or physical mapping. | diff --git a/docs/UML.md b/docs/UML.md index 97fa9a5f..27f06219 100644 --- a/docs/UML.md +++ b/docs/UML.md @@ -27,6 +27,7 @@ sequenceDiagram participant Discovery participant Validator participant Steward + participant Governance as External approval verifier participant Publisher Source->>Observation: immutable snapshot @@ -75,6 +76,23 @@ sequenceDiagram Intake-->>Steward: create-new bounded evidence view with missing text visible Note over Intake,Steward: read-only view; legacy apply commands reject it end + opt separate capture-bound review campaign + Report->>Intake: original report, without importing metadata decisions + Capture->>Intake: exact retained capture + Intake->>Intake: create blank capture-bound worksheet + loop next pending evidence view + Intake-->>Steward: exact text and pending decisions + Steward->>Intake: completed decision slots only + Intake->>Intake: reject duplicate keys, stale selection and changed evidence + Intake->>Report: new capture-bound worksheet; prior work preserved + end + Steward->>Intake: complete worksheet and independently issued approval input + Intake->>Intake: reverify capture/report and finalize complete labels + Intake->>Governance: entire capture-bound reviewed set after local validation + Governance-->>Intake: authenticated receipt decision or rejection + Intake-->>Steward: capture-bound aggregate result or failure + Note over Intake,Governance: no transfer to the independent Zotero write authority + end Report->>Steward: review dispositions and merge candidates Steward->>Intake: save partially completed worksheet Intake->>Report: validate original binding; emit aggregate progress only diff --git a/docs/adr/0006-zotero-research-intake.md b/docs/adr/0006-zotero-research-intake.md index ae94bd4c..479ed5a7 100644 --- a/docs/adr/0006-zotero-research-intake.md +++ b/docs/adr/0006-zotero-research-intake.md @@ -47,6 +47,20 @@ No dedicated utility repository or Zotero mutation path is created. A future Zot ## Consequences +### 2026-09-05 capture-bound review application amendment (Proposed) + +In the context of stewards deciding papers after inspecting retained text, facing metadata-only review receipts that cannot establish which later capture was reviewed, we decided for required non-flattened single-capture review artifacts and atomic completed-view application and against optional fields on legacy receipts or a separately restorable decision patch, to preserve evidence identity through finalization and external verification, accepting another private worksheet, rejection of stale views, and no current end-to-end steward interface or write admission. + +Research Intake remains the single implementation owner; Governance & Publication remains the approval authority. Each campaign begins entirely blank, including papers with missing or partial text. No earlier metadata decision is imported as text-reviewed. The complete capture/report relation is reverified during view creation, application, finalization and evaluation. Its content digest already binds every proposal through the complete report, so another redundant context hash is unnecessary. Version-named required payload fields distinguish the new artifact types without flattening or optional fallbacks. Strict owned nested objects reject accidental context stripping; private Rust fields prevent an API downcast but cannot authenticate deliberately replaced JSON. + +Only decision slots may change in a completed view. Local admission rejects malformed, oversized, duplicate-key, unknown-field, stale or altered evidence before atomically reusing existing decision validation. A recursive Serde visitor is necessary because ordinary JSON value projection discards duplicate object keys; comparison after that loss could accept an altered first occurrence. Existing dependencies and the standard library cover the requirement. A separately deserializable patch was rejected because restoration would not prove the view comparison ran. Replayed views are rejected rather than treated as fresh work; previous artifacts remain available and the next view is regenerated. This chooses a smaller single-process validation path over an assignment service, event store or new repository. + +Independent review found that a blank view at the 16 MiB ceiling could not be submitted after replacing `null` with a longer valid label. The actionable view therefore reserves the longest v1 decision's 25-byte growth for each selected row. A generated compact view remains completable within the unchanged admission ceiling; arbitrary added whitespace is not covered. Reserving space was selected over silently raising the completed-input budget. An exact-limit generation/edit/application regression records the failure and repair, including the largest valid label. Too-large text still fails visibly and never reduces the campaign denominator. + +Finalization requires a new externally issued capture-bound approval input and complete non-abstention labels; it does not verify authority. Evaluation checks local structure first, then provides governance the entire capture-bound reviewed set. The verifier must authenticate all labels and the capture, not merely recognize a receipt identifier. For example, a report title changed under the same snapshot coordinate must fail against its earlier capture before governance is called; a syntactically valid relabeling must fail the independently issued receipt. Matching hashes and readable text do not prove that a human reviewed a paper. + +Positive consequences are retained source identity, no retroactive approval migration, and deterministic local failure without overwriting work. Negative consequences are repeated full-capture verification costs, additional sensitive retained artifacts and rejection of stale completed views. No exact-time performance target or concurrency guarantee is claimed. CLI support starts with blank initialization only; application/finalization/evaluation remain library APIs pending a private steward workflow. Zotero write planning is an independent reviewed-change authority boundary, not a caller of golden-set evaluation. It receives no new conversion, release, execution authority or transport guarantee here. The existing Proposed ADR 0007 remains necessary, and full-text-aware write admission remains a subsequent gap. + ### 2026-09-05 private full-text review view amendment (Proposed) In the context of stewards inspecting retained text for pending papers, facing a metadata-only decision and approval chain that cannot retain a later capture's identity, we decided for a separate verified, bounded evidence view and against enriching the earlier report or silently converting the view into a legacy decision patch, to preserve source and review boundaries while making the saved text inspectable, accepting that full-text decision application and approval binding still require a subsequent contract change. diff --git a/docs/doctoring/REFERENCES.md b/docs/doctoring/REFERENCES.md index 2e3a58de..f847284a 100644 --- a/docs/doctoring/REFERENCES.md +++ b/docs/doctoring/REFERENCES.md @@ -2,6 +2,12 @@ This file records the evidence basis for ConceptWeave architecture decisions. Stable Recommendations and in-progress specifications are deliberately distinguished. The current paper-by-paper capability, rejection/adoption, owner, limitation, and benchmark mapping is maintained in `docs/doctoring/RESEARCH_CAPABILITY_TRACEABILITY.md`; bibliography alone does not count as product use. +## Implementation contract references + +Bray, T. (Ed.). (2017). *The JavaScript Object Notation (JSON) data interchange format* (RFC 8259, Section 4). Internet Engineering Task Force. https://www.rfc-editor.org/rfc/rfc8259#section-4 + +Serde contributors. (n.d.). *Container attributes*. Serde. Retrieved September 5, 2026, from https://serde.rs/container-attrs.html + ## Stable standards / recommendations Corporation for Digital Scholarship. (2026). *Zotero Local API*. Zotero Documentation. https://www.zotero.org/support/dev/web_api/v3/local_api diff --git a/docs/doctoring/zotero_fulltext_contract_audit.md b/docs/doctoring/zotero_fulltext_contract_audit.md index a91c473a..7288fb8f 100644 --- a/docs/doctoring/zotero_fulltext_contract_audit.md +++ b/docs/doctoring/zotero_fulltext_contract_audit.md @@ -122,8 +122,18 @@ The later #19 coverage failure, 333/334 normalized branch outcomes, exposed nond The final non-force cascade reaches #34 `b0119a57047e7b1fe5ddfbbf4b973de0f15de172`, with 156 workspace tests and its existing coverage gate passing. Root full-text integration `75da75cf01704d9aae47f1e5573e3bbe3fb42bb0` passes 186 workspace tests across 37 unfiltered suites, strict Clippy, formatting, rustdoc, CI contract and the existing coverage gate. It preserves one unchanged shared reader, inherited regression modules and all earlier full-text safeguards. Source-normalized coverage is 3,710/3,710 regions and 674/674 branches; functions are 347/347. Raw LLVM totals remain 4,159/4,255 lines, 6,129/6,274 regions and 603/674 branches. Independent source review found no actionable merge finding, not approval. No predecessor delta was discarded; every head still requires its own protected acceptance evidence. +### Review binding follow-up, 2026-09-05 + +The [blank initialization evidence](zotero_fulltext_review_binding_evidence.json) and [current baseline](../product-technical-gap-baseline.md#capture-bound-review-application-and-blank-initialization) record the separate capture-bound review contract at `da406cf6110888808fa530592bbce2b774b73f33`. The original source files remain unchanged, 3,715 new review slots are blank, and no approval, model call or Zotero write occurred. Its CLI only initializes work; completed-view application and whole-envelope governance verification are library APIs. This does not resolve the provider version-space defect or supply released orchestration. + +For restored owned review artifacts, unknown fields must fail instead of disappearing during deserialization. Serde documents that distinction and disallows combining its strict container attribute with flattening (Serde contributors, n.d.). JSON object-name duplication has inconsistent receiver behavior under RFC 8259; ConceptWeave rejects duplicate decoded keys recursively before comparing completed evidence views, rather than accepting last-key-wins projection (Bray, 2017, Section 4). These primary references support the input-contract choice, not semantic-label correctness. Context7's monthly quota was exhausted and DeepWiki did not index this repository during this follow-up; direct official documentation and current source/tests supplied the evidence. + ## References +Bray, T. (Ed.). (2017). *The JavaScript Object Notation (JSON) data interchange format* (RFC 8259, Section 4). Internet Engineering Task Force. https://www.rfc-editor.org/rfc/rfc8259#section-4 + +Serde contributors. (n.d.). *Container attributes*. Serde. Retrieved September 5, 2026, from https://serde.rs/container-attrs.html + Zotero. (2026a, July 29). *Zotero local API*. https://www.zotero.org/support/dev/web_api/v3/local_api Zotero. (2026b, July 29). *Zotero Web API full-text content requests*. https://www.zotero.org/support/dev/web_api/v3/fulltext_content diff --git a/docs/doctoring/zotero_fulltext_review_binding_evidence.json b/docs/doctoring/zotero_fulltext_review_binding_evidence.json new file mode 100644 index 00000000..2417314c --- /dev/null +++ b/docs/doctoring/zotero_fulltext_review_binding_evidence.json @@ -0,0 +1,43 @@ +{ + "evidence_kind": "private_blank_fulltext_review_initialization_v1", + "observed_at_utc": "2026-09-05T11:35:47Z", + "independently_audited_at_utc": "2026-09-05T11:36:18.331Z", + "source_commit": "da406cf6110888808fa530592bbce2b774b73f33", + "output": { + "bytes": 1776354, + "mode": "0600", + "link_count": 1, + "file_sha256": "36bcf0679de068a744fd4ae8709cd192d99cb556591420d7a43c2b5f0e633a46", + "capture_digest": "sha256:429d98dc90e172b4f0bb4e3e1c493feb33b61664793d02a53d8183fb76f76a50" + }, + "input_file_sha256": { + "metadata_report": "bf45248413f433a537fe8fc62c02b93eef3c7e47ff6245f31610e9ba72031d8d", + "original_blank_metadata_worksheet": "2093aeffd3907e71d310715889b87e3fbc189cfba620338bf9a81b53ced26f87", + "fulltext_capture": "56d385398c8da559aa597a4e3783d946638855bba19ac808ce81d917bf06f94d" + }, + "measures": { + "bibliographic_denominator": 3715, + "capture_bound_blank_decisions": 3715, + "authentic_steward_decisions": 0, + "independently_approved_labels": 0, + "new_text_bound_proposals": 0, + "zotero_requests": 0, + "model_requests": 0, + "command_elapsed_seconds": 1.50, + "maximum_resident_bytes": 286375936, + "peak_memory_footprint_bytes": 285344320 + }, + "independent_read_only_checks": { + "all_input_hashes_unchanged": true, + "all_artifacts_private_single_link_regular_files": true, + "required_outer_keys_only": true, + "capture_marker_matches_saved_capture_identity": true, + "nested_worksheet_exactly_matches_prior_blank_worksheet": true + }, + "limits": [ + "One observed offline initialization, not a load or latency SLO.", + "No completed real review, independent approval, release, or write admission.", + "Application, finalization and evaluation remain library APIs; CLI only initializes blank work.", + "This JSON is aggregate evidence, not the sensitive worksheet or a governance receipt." + ] +} diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index d23874ea..83bbe783 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -6,6 +6,8 @@ This file records code-current product and technical gaps. Exact PR/check/run co ## Protected truth and active stack +The 11:13:09–11:15:04 UTC refresh found 31 open PRs, 30 Draft, unchanged existing heads/bases, 36 unresolved threads and no new approvals or terminal check results since 10:48. PR #35 has a new COMMENTED review following the counterevidence, not a changed approval verdict. Central `.github/main` advanced to `f250638827f8252b0d9e5cb2601f4d333f96162f` through #1922; its trigger/test-isolation change does not repair Noema/CodeQL or retroactively rebind queued CodeQL successors from `7fcada597d5b79bdb14445f24322b2c9f6ed4b19`. Current-main hosted GREEN was not established. This paragraph is a dated observation, not transferable acceptance evidence for the new child work below. + Protected/default `main` remains `f4f440dd58c77d7cd90dff8a1eb2eeb9a9940425`; only the bootstrap state is shipped there and no immutable ConceptWeave release exists. The active roots observed immediately before this baseline refresh are: @@ -124,6 +126,20 @@ At `54383ea`, 201 workspace tests across 38 unfiltered suites, including three d PRD FR-9, TRD, the Proposed ADR 0006 amendment, Context Map, Ubiquitous Language, sequence diagram, architecture, threat model and contributor rules preserve the same boundary. The next product gap is exact full-text review-context identity through decision application, worksheet history, finalization and fresh external approval. The existing approval contract has no capture digest; a readable file does not close that gap. Hosted current-head checks, prerequisite protected merges and released orchestration evidence remain independent requirements. +### Capture-bound review application and blank initialization + +The child of private review view #37 preserves capture identity through blank worksheet creation, exact completed-view application, finalization and whole-envelope external verification. Integrated runtime `da406cf6110888808fa530592bbce2b774b73f33` contains ordinary merge `21ef42d` for CLI initialization and the preceding root core repairs. No parent delta or PR was discarded. CLI support is deliberately limited to `--full-text-worksheet`; the remaining full-text operations are library APIs, not an end-to-end steward interface or a live approval service. + +RED `7cf8018` selected one failing blank-initialization test before GREEN `063a99d`; earlier `65bf495` compile errors and the invalid tagged-struct round trip are retained rather than called passing evidence. End-to-end library RED `6bf92d2` failed one selected test. Duplicate-key RED source `d367658` plus registration `9e51038` produced three passes and one failure before recursive admission repair `fc882ef`. The same integrated source adds unknown-field, changed-display, stale-view, incomplete-review, altered-label, no-downcast and zero-governance-call regressions. These are synthetic unit checks, not authentic steward input. + +Independent review found that a blank view exactly at 16 MiB became impossible to submit after filling a longer decision. RED `35b7816` reproduces it. GREEN `e0744e0` reserves 25 bytes for each selected decision under the current v1 label contract, with no parser-budget increase or text truncation. The regression verifies all six valid labels and successful compact completed input at exactly 16 MiB. A preceding coverage failure at `fc882ef` exposed the oversized expected-view rejection path; test-only `2a77a33` exercises it. Independent follow-up found no new actionable finding; this is source review, not protected approval. + +At `da406cf`, 216 workspace tests across 39 unfiltered suites, including three doctests, pass; one nested filtered subprocess is not counted twice. Strict Clippy, formatting, rustdoc with warnings denied, CI contract and the existing coverage gate pass. Functions are 383/383, source-normalized regions 4,215/4,215 and branches 712/712. Raw LLVM lines are 4,606/4,714, regions 6,693/6,884 and branches 630/712, not 100%. No dependency, coverage exclusion, provider call or authority bypass was added. + +The [real blank-initialization receipt](doctoring/zotero_fulltext_review_binding_evidence.json) records a new 1,776,354-byte single-link `0600` file, SHA-256 `36bcf0679de068a744fd4ae8709cd192d99cb556591420d7a43c2b5f0e633a46`, created offline in 1.50 seconds with 286,375,936 bytes maximum resident memory. An independent read-only audit checked original input hashes, exact capture marker and equality of the nested worksheet with the prior blank metadata worksheet. There are now 3,715 explicitly capture-bound blank slots, but authentic decisions and independently approved labels remain 0/3,715. The earlier 25-row view remains 21 nonempty-text rows and four missing-text rows; no new text-bound proposal was generated. + +Lifecycle capability metric advances from 25 to 26 for the tested single-capture review chain; it is not a count of approved papers or shipped capabilities. Next gaps are a bounded private steward workflow for these library APIs, authentic decisions and independently issued approval, and separately governed full-text-aware write admission. Released contextual-orchestrator evidence, protected prerequisite acceptance, the remaining ontology repository census and a real deployment/release remain outstanding. No separate Utility Repository has an evidenced independent responsibility/consumer need. + ### Historical pre-repair Zotero 10 transition The following schema-44 observations were captured before integrity repair and are retained for provenance, not reused as current approval inputs. From 6f80c8d57a21e9837268b9bdce5adfd755ab65de Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 20:43:19 +0900 Subject: [PATCH 14/39] docs: distinguish review binding progress from historical gaps --- docs/product-technical-gap-baseline.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 83bbe783..39b7f6fa 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -45,7 +45,7 @@ Acceptance remains stricter than the RCA. HTTP 200 availability alone is not GRE ## Central control-plane evidence -The current central branch ref is `.github/main@7fcada597d5b79bdb14445f24322b2c9f6ed4b19`, refreshed at 10:46–10:48 UTC. This is evidence only, not a mutable ConceptWeave dependency or a new audit of central protection. The earlier `8aea81323d93e90c79b71d7718de2798919fa1df` checkpoint followed admission-coverage and echo-only-review-job repairs; its nine-commit range to the current ref changes governance documents while preserving the previously read master-context and product-directive blobs. Already-created consumer runs retain their own exact workflow revisions. +The historical 10:46–10:48 UTC central branch ref was `.github/main@7fcada597d5b79bdb14445f24322b2c9f6ed4b19`; the 11:13 refresh above supersedes it with `f250638827f8252b0d9e5cb2601f4d333f96162f`. These are evidence coordinates, not mutable ConceptWeave dependencies or a new audit of central protection. The earlier `8aea81323d93e90c79b71d7718de2798919fa1df` checkpoint followed admission-coverage and echo-only-review-job repairs; its nine-commit range to `7fcada59` changed governance documents while preserving the previously read master-context and product-directive blobs. Already-created consumer runs retain their own exact workflow revisions. - The current central source includes queue/admission and changed-scope/review-runtime repairs already integrated through ordinary protected history. - `.github#1873@41935494aa234eb458f1cc08f006daaa278b9760` is closed/unmerged because repository dependency-graph initialization, not its retry/sleep source delta, was the verified root cause of the observed public-repository 403. @@ -124,11 +124,11 @@ An independent aggregate-only audit recomputed the saved capture, complete repor At `54383ea`, 201 workspace tests across 38 unfiltered suites, including three doctests, pass; the nested subprocess result is not counted twice. Strict Clippy, formatting, rustdoc with warnings denied, CI contract and the existing coverage gate pass. Functions are 359/359, source-normalized regions 3,982/3,982 and branch outcomes 692/692. Raw LLVM lines remain 4,322/4,430, regions 6,314/6,500 and branches 612/692, not 100%. An earlier CLI-only coverage run exposed an unreachable test panic arm, repaired without exclusions, and core cases already covered by the integrated newer tests. Independent documentation and integrated source reviews found no actionable regression; neither is independent protected approval. -PRD FR-9, TRD, the Proposed ADR 0006 amendment, Context Map, Ubiquitous Language, sequence diagram, architecture, threat model and contributor rules preserve the same boundary. The next product gap is exact full-text review-context identity through decision application, worksheet history, finalization and fresh external approval. The existing approval contract has no capture digest; a readable file does not close that gap. Hosted current-head checks, prerequisite protected merges and released orchestration evidence remain independent requirements. +At the historical `54383ea` view checkpoint, PRD FR-9, TRD, Proposed ADR 0006, DDD/UML, architecture and threat-model documents identified capture-bound decision/finalization/approval propagation as the next gap. The legacy metadata-only approval contract had no capture digest. The following separate contract implements that propagation; authentic decisions and fresh independently verified approval still remain absent. Hosted current-head checks, prerequisite protected merges and released orchestration evidence remain independent requirements. ### Capture-bound review application and blank initialization -The child of private review view #37 preserves capture identity through blank worksheet creation, exact completed-view application, finalization and whole-envelope external verification. Integrated runtime `da406cf6110888808fa530592bbce2b774b73f33` contains ordinary merge `21ef42d` for CLI initialization and the preceding root core repairs. No parent delta or PR was discarded. CLI support is deliberately limited to `--full-text-worksheet`; the remaining full-text operations are library APIs, not an end-to-end steward interface or a live approval service. +[Capture-bound review PR #38](https://github.com/ContextualWisdomLab/ConceptWeave/pull/38) is an open Draft child of private review view #37. It preserves capture identity through blank worksheet creation, exact completed-view application, finalization and whole-envelope external verification. Integrated runtime `da406cf6110888808fa530592bbce2b774b73f33` contains ordinary merge `21ef42d` for CLI initialization and the preceding root core repairs. No parent delta or PR was discarded. CLI support is deliberately limited to `--full-text-worksheet`; the remaining full-text operations are library APIs, not an end-to-end steward interface or a live approval service. RED `7cf8018` selected one failing blank-initialization test before GREEN `063a99d`; earlier `65bf495` compile errors and the invalid tagged-struct round trip are retained rather than called passing evidence. End-to-end library RED `6bf92d2` failed one selected test. Duplicate-key RED source `d367658` plus registration `9e51038` produced three passes and one failure before recursive admission repair `fc882ef`. The same integrated source adds unknown-field, changed-display, stale-view, incomplete-review, altered-label, no-downcast and zero-governance-call regressions. These are synthetic unit checks, not authentic steward input. From 4aabc6ab59fc16447bfe9b78cb12aeb2997a0a9d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 20:54:15 +0900 Subject: [PATCH 15/39] test(zotero): reject oversized private metadata output --- .../src/full_text_review_cli_reader_tests.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/crates/conceptweave-zotero/src/full_text_review_cli_reader_tests.rs b/crates/conceptweave-zotero/src/full_text_review_cli_reader_tests.rs index e8c55ce7..0c92fca3 100644 --- a/crates/conceptweave-zotero/src/full_text_review_cli_reader_tests.rs +++ b/crates/conceptweave-zotero/src/full_text_review_cli_reader_tests.rs @@ -69,3 +69,21 @@ fn private_capture_reader_reuses_regular_owner_only_file_boundary() { assert_eq!(error.to_string(), "full-text capture input is invalid"); fs::remove_file(file_path).unwrap(); } + +#[test] +fn metadata_writer_rejects_oversized_output_before_creating_file() { + let output = tests::unique_temp_path("oversized-private-output"); + let _ = fs::remove_file(&output); + let content = vec![b'x'; MAX_ARTIFACT_BYTES as usize + 1]; + + let result = write_private_output(&output, &content); + let output_was_created = output.exists(); + if output_was_created { + fs::remove_file(&output).unwrap(); + } + + let error = result.expect_err("oversized metadata output must fail closed"); + assert_eq!(error.kind(), io::ErrorKind::InvalidData); + assert_eq!(error.to_string(), "metadata output exceeds the artifact size limit"); + assert!(!output_was_created); +} From 351a0c88368e7ea07330de16ef3f5daa83661fd3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 20:57:49 +0900 Subject: [PATCH 16/39] fix(zotero): bound private metadata writes --- crates/conceptweave-zotero/src/main.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/conceptweave-zotero/src/main.rs b/crates/conceptweave-zotero/src/main.rs index 250104df..cb67d187 100644 --- a/crates/conceptweave-zotero/src/main.rs +++ b/crates/conceptweave-zotero/src/main.rs @@ -476,8 +476,14 @@ fn label_input(name: &str, error: io::Error) -> io::Error { io::Error::new(error.kind(), format!("{name}: {error}")) } -/// Writes one create-new owner-only artifact and removes a failed partial write. +/// Writes one bounded create-new owner-only metadata artifact and removes failed partial writes. fn write_private_output(path: &Path, content: &[u8]) -> io::Result<()> { + if content.len() as u64 > MAX_ARTIFACT_BYTES { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "metadata output exceeds the artifact size limit", + )); + } write_private_output_with(path, content, &mut write_all_and_flush) } @@ -1158,7 +1164,7 @@ mod tests { let valid = unique_temp_path("valid-input"); let _ = fs::remove_file(&valid); - fs::write(&valid, br#"{"accepted":true}"#).unwrap(); + fs::write(&valid, br#"{\"accepted\":true}"#).unwrap(); fs::set_permissions(&valid, fs::Permissions::from_mode(0o600)).unwrap(); fs::set_permissions(&valid, fs::Permissions::from_mode(0o000)).unwrap(); From 0672ebe1144de9b27dcc778065904089a5931208 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 21:01:04 +0900 Subject: [PATCH 17/39] test(zotero): restore valid permission-boundary JSON fixture --- crates/conceptweave-zotero/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/conceptweave-zotero/src/main.rs b/crates/conceptweave-zotero/src/main.rs index cb67d187..eb78ebd1 100644 --- a/crates/conceptweave-zotero/src/main.rs +++ b/crates/conceptweave-zotero/src/main.rs @@ -1164,7 +1164,7 @@ mod tests { let valid = unique_temp_path("valid-input"); let _ = fs::remove_file(&valid); - fs::write(&valid, br#"{\"accepted\":true}"#).unwrap(); + fs::write(&valid, br#"{"accepted":true}"#).unwrap(); fs::set_permissions(&valid, fs::Permissions::from_mode(0o600)).unwrap(); fs::set_permissions(&valid, fs::Permissions::from_mode(0o000)).unwrap(); From ee9e263b59a7249460fc797f0d8a11203b3fa932 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 21:03:40 +0900 Subject: [PATCH 18/39] fix(zotero): restore private JSON fixture --- crates/conceptweave-zotero/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/conceptweave-zotero/src/main.rs b/crates/conceptweave-zotero/src/main.rs index cb67d187..eb78ebd1 100644 --- a/crates/conceptweave-zotero/src/main.rs +++ b/crates/conceptweave-zotero/src/main.rs @@ -1164,7 +1164,7 @@ mod tests { let valid = unique_temp_path("valid-input"); let _ = fs::remove_file(&valid); - fs::write(&valid, br#"{\"accepted\":true}"#).unwrap(); + fs::write(&valid, br#"{"accepted":true}"#).unwrap(); fs::set_permissions(&valid, fs::Permissions::from_mode(0o600)).unwrap(); fs::set_permissions(&valid, fs::Permissions::from_mode(0o000)).unwrap(); From 0a70da9c756b6f8c75c15ba505a69e69f02dc1d7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 21:04:50 +0900 Subject: [PATCH 19/39] test(zotero): specify bound full-text CLI review lifecycle --- crates/conceptweave-zotero/src/main.rs | 38 ++ .../tests/bound_full_text_commands_cli.rs | 494 ++++++++++++++++++ 2 files changed, 532 insertions(+) create mode 100644 crates/conceptweave-zotero/tests/bound_full_text_commands_cli.rs diff --git a/crates/conceptweave-zotero/src/main.rs b/crates/conceptweave-zotero/src/main.rs index eb78ebd1..efa0e597 100644 --- a/crates/conceptweave-zotero/src/main.rs +++ b/crates/conceptweave-zotero/src/main.rs @@ -800,6 +800,44 @@ mod full_text_review_cli_reader_tests; mod tests { use super::*; + #[test] + fn bound_full_text_modes_require_complete_distinct_paths_and_limits() { + for mode in [ + "--bound-full-text-review", + "--apply-full-text-review", + "--finalize-full-text-review", + ] { + let fourth = if mode == "--bound-full-text-review" { + "1" + } else { + "input" + }; + let args = [mode, "report", "worksheet", "capture", fourth, "output"]; + assert!(parse_output_request(args).is_ok()); + for count in 1..6 { + assert!(parse_output_request(args[..count].iter().copied()).is_err()); + } + assert!(parse_output_request([mode, "r", "r", "c", fourth, "o"]).is_err()); + assert!(parse_output_request([mode, "r", "w", "c", fourth, "c"]).is_err()); + assert!(parse_output_request([mode, "r", "w", "c", fourth, "o", "extra"]).is_err()); + } + for limit in [ + "", + "0", + "101", + "-1", + "+1", + " 1", + "one", + "999999999999999999999999", + ] { + assert!( + parse_output_request(["--bound-full-text-review", "r", "w", "c", limit, "o"]) + .is_err() + ); + } + } + #[test] fn full_text_worksheet_mode_requires_three_distinct_paths() { assert!( diff --git a/crates/conceptweave-zotero/tests/bound_full_text_commands_cli.rs b/crates/conceptweave-zotero/tests/bound_full_text_commands_cli.rs new file mode 100644 index 00000000..5ac996c3 --- /dev/null +++ b/crates/conceptweave-zotero/tests/bound_full_text_commands_cli.rs @@ -0,0 +1,494 @@ +#![cfg(unix)] + +use conceptweave_zotero::{ + ClassificationReport, Disposition, FullTextCapture, FullTextReviewApproval, + FullTextReviewWorksheet, ZoteroItem, apply_full_text_review_view, + build_bound_full_text_review_json, build_full_text_review_worksheet, + classification_proposal_digest, classify_snapshot, finalize_full_text_review, +}; +use serde_json::{Value, json}; +use sha2::{Digest, Sha256}; +use std::cell::RefCell; +use std::fs::{self, OpenOptions}; +use std::io::Write; +use std::os::unix::fs::{OpenOptionsExt, PermissionsExt, symlink}; +use std::path::{Path, PathBuf}; +use std::process::{Command, Output}; + +#[test] +fn library_two_batch_flow_keeps_the_full_review_and_approval_envelopes() { + let case = Case::new("library"); + let original = build_full_text_review_worksheet(&case.report, &case.capture).unwrap(); + let mut worksheet = original; + for _ in 0..2 { + let view = + build_bound_full_text_review_json(&case.report, &worksheet, &case.capture, 1).unwrap(); + worksheet = + apply_full_text_review_view(&case.report, &worksheet, &case.capture, &complete(&view)) + .unwrap(); + } + assert!(build_bound_full_text_review_json(&case.report, &worksheet, &case.capture, 1).is_err()); + let approval = case.approval(); + let golden = + finalize_full_text_review(&case.report, &worksheet, &case.capture, approval).unwrap(); + assert_full_golden(&serde_json::to_value(golden).unwrap()); +} + +#[test] +fn cli_two_batches_finalize_without_issuing_or_rewriting_approval() { + let case = Case::new("flow"); + let [report, mut worksheet, capture] = case.inputs(); + let first_worksheet = worksheet.clone(); + let original = fs::read(&worksheet).unwrap(); + let approval = case.json("approval", &case.approval()); + let original_approval = fs::read(&approval).unwrap(); + for index in 0..2 { + let view = case.path(&format!("view-{index}")); + assert_success(run( + "--bound-full-text-review", + &[&report, &worksheet, &capture, Path::new("1"), &view], + )); + let displayed: Value = serde_json::from_slice(&fs::read(&view).unwrap()).unwrap(); + assert_eq!(displayed["review_batch"]["remaining_count"], 2 - index); + let completed = case.bytes( + &format!("completed-{index}"), + &complete(&fs::read(&view).unwrap()), + ); + let next = case.path(&format!("next-{index}")); + assert_success(run( + "--apply-full-text-review", + &[&report, &worksheet, &capture, &completed, &next], + )); + let restored: FullTextReviewWorksheet = + serde_json::from_slice(&fs::read(&next).unwrap()).unwrap(); + let restored = serde_json::to_value(restored).unwrap(); + assert!(restored.get("full_text_worksheet_v1").is_some()); + assert_private(&view); + assert_private(&next); + assert!( + !run( + "--apply-full-text-review", + &[&report, &worksheet, &capture, &completed, &next] + ) + .status + .success() + ); + worksheet = next; + } + let golden = case.path("golden"); + assert_success(run( + "--finalize-full-text-review", + &[&report, &worksheet, &capture, &approval, &golden], + )); + assert_full_golden(&serde_json::from_slice(&fs::read(&golden).unwrap()).unwrap()); + assert_private(&golden); + assert_eq!(fs::read(&approval).unwrap(), original_approval); + assert_eq!(fs::read(&first_worksheet).unwrap(), original); + let missing_approval = case.path("missing-approval"); + let rejected = case.path("missing-approval-output"); + assert!( + !run( + "--finalize-full-text-review", + &[&report, &worksheet, &capture, &missing_approval, &rejected] + ) + .status + .success() + ); + assert!(!missing_approval.exists()); + assert!(!rejected.exists()); +} + +#[test] +fn cli_rejects_replay_stale_tampered_duplicate_and_legacy_review_inputs() { + let case = Case::new("reject"); + let [report, worksheet, capture] = case.inputs(); + let blank: FullTextReviewWorksheet = + serde_json::from_slice(&fs::read(&worksheet).unwrap()).unwrap(); + let view = build_bound_full_text_review_json(&case.report, &blank, &case.capture, 1).unwrap(); + let completed = complete(&view); + let completed_path = case.bytes("completed", &completed); + let next = case.path("next"); + assert_success(run( + "--apply-full-text-review", + &[&report, &worksheet, &capture, &completed_path, &next], + )); + let saved_next = fs::read(&next).unwrap(); + let replay = case.path("replay"); + assert!( + !run( + "--apply-full-text-review", + &[&report, &next, &capture, &completed_path, &replay] + ) + .status + .success() + ); + assert!(!replay.exists()); + assert_eq!(fs::read(&next).unwrap(), saved_next); + + let mut tampered: Value = serde_json::from_slice(&completed).unwrap(); + tampered["attachment_evidence"]["ABCD2345"][0]["content_response"]["body"] = + "synthetic tampering".into(); + let original = String::from_utf8(completed.clone()).unwrap(); + let root_duplicate = format!("{{\"view_kind\":\"wrong\",{}", &original[1..]); + let nested_duplicate = original.replacen( + "\"reviewed_disposition\":", + "\"reviewed_disposition\":null,\"reviewed_disposition\":", + 1, + ); + let legacy: Value = serde_json::from_slice(&completed).unwrap(); + for (index, bytes) in [ + view, + serde_json::to_vec(&tampered).unwrap(), + root_duplicate.into_bytes(), + nested_duplicate.into_bytes(), + serde_json::to_vec(&legacy["review_batch"]).unwrap(), + ] + .into_iter() + .enumerate() + { + let invalid = case.bytes(&format!("invalid-{index}"), &bytes); + let rejected = case.path(&format!("rejected-{index}")); + assert!( + !run( + "--apply-full-text-review", + &[&report, &worksheet, &capture, &invalid, &rejected] + ) + .status + .success() + ); + assert!(!rejected.exists()); + } + let legacy_worksheet = case.json( + "legacy-worksheet", + &serde_json::to_value(blank).unwrap()["full_text_worksheet_v1"], + ); + let rejected = case.path("legacy-output"); + assert!( + !run( + "--bound-full-text-review", + &[ + &report, + &legacy_worksheet, + &capture, + Path::new("1"), + &rejected + ] + ) + .status + .success() + ); + assert!(!rejected.exists()); +} + +#[test] +fn cli_finalization_rejects_incomplete_wrong_capture_and_legacy_approval() { + let case = Case::new("finalize"); + let [report, worksheet, capture] = case.inputs(); + let approval = case.json("approval", &case.approval()); + let output = case.path("incomplete"); + assert!( + !run( + "--finalize-full-text-review", + &[&report, &worksheet, &capture, &approval, &output] + ) + .status + .success() + ); + assert!(!output.exists()); + let blank: FullTextReviewWorksheet = + serde_json::from_slice(&fs::read(&worksheet).unwrap()).unwrap(); + let view = build_bound_full_text_review_json(&case.report, &blank, &case.capture, 2).unwrap(); + let decided = + apply_full_text_review_view(&case.report, &blank, &case.capture, &complete(&view)).unwrap(); + let decided = case.json("decided", &decided); + let mut wrong_capture = serde_json::to_value(&case.capture).unwrap(); + wrong_capture["capture_digest"] = "wrong-capture".into(); + let wrong_capture = case.json("wrong-capture", &wrong_capture); + let mut wrong_approval = serde_json::to_value(case.approval()).unwrap(); + wrong_approval["capture_digest"] = "wrong-capture".into(); + let wrong_approval = case.json("wrong-approval", &wrong_approval); + let legacy_approval = case.json( + "legacy-approval", + &serde_json::to_value(case.approval()).unwrap()["full_text_approval_v1"], + ); + for (index, (capture_path, approval_path)) in [ + (&wrong_capture, &approval), + (&capture, &wrong_approval), + (&capture, &legacy_approval), + ] + .into_iter() + .enumerate() + { + let output = case.path(&format!("wrong-{index}")); + assert!( + !run( + "--finalize-full-text-review", + &[&report, &decided, capture_path, approval_path, &output] + ) + .status + .success() + ); + assert!(!output.exists()); + } +} + +#[test] +fn cli_commands_reuse_private_permissions_alias_and_size_boundaries() { + let case = Case::new("privacy"); + let [report, worksheet, capture] = case.inputs(); + let blank = build_full_text_review_worksheet(&case.report, &case.capture).unwrap(); + let completed = case.bytes( + "completed", + &complete( + &build_bound_full_text_review_json(&case.report, &blank, &case.capture, 1).unwrap(), + ), + ); + let output = case.path("output"); + fs::set_permissions(&completed, fs::Permissions::from_mode(0o644)).unwrap(); + assert!( + !run( + "--apply-full-text-review", + &[&report, &worksheet, &capture, &completed, &output] + ) + .status + .success() + ); + fs::set_permissions(&completed, fs::Permissions::from_mode(0o600)).unwrap(); + let symlink_path = case.path("symlink"); + symlink(&completed, &symlink_path).unwrap(); + assert!( + !run( + "--apply-full-text-review", + &[&report, &worksheet, &capture, &symlink_path, &output] + ) + .status + .success() + ); + let hard_link = case.path("hard-link"); + fs::hard_link(&completed, &hard_link).unwrap(); + assert!( + !run( + "--apply-full-text-review", + &[&report, &worksheet, &capture, &hard_link, &output] + ) + .status + .success() + ); + fs::remove_file(hard_link).unwrap(); + let alias = worksheet + .parent() + .unwrap() + .join(".") + .join(worksheet.file_name().unwrap()); + assert!( + !run( + "--apply-full-text-review", + &[&report, &worksheet, &capture, &alias, &output] + ) + .status + .success() + ); + assert!( + !run( + "--apply-full-text-review", + &[&report, &worksheet, &capture, &completed, &worksheet] + ) + .status + .success() + ); + OpenOptions::new() + .write(true) + .open(&completed) + .unwrap() + .set_len(16 * 1024 * 1024 + 1) + .unwrap(); + let oversized = run( + "--apply-full-text-review", + &[&report, &worksheet, &capture, &completed, &output], + ); + assert!(!oversized.status.success()); + assert!( + String::from_utf8(oversized.stderr) + .unwrap() + .contains("size limit") + ); + assert!(!output.exists()); +} + +fn assert_success(output: Output) { + assert!(output.status.success(), "{:?}", output.stderr); + assert!(output.stdout.is_empty()); +} + +fn assert_private(path: &Path) { + assert_eq!( + fs::metadata(path).unwrap().permissions().mode() & 0o777, + 0o600 + ); +} + +fn assert_full_golden(golden: &Value) { + assert!(golden.get("capture_digest").is_some()); + assert!(golden.get("labels").is_none()); + assert!(golden.get("evaluation").is_none()); + let reviewed = &golden["full_text_golden_set_v1"]; + assert_eq!(reviewed["labels"].as_array().unwrap().len(), 2); + assert!( + reviewed["labels"] + .as_array() + .unwrap() + .iter() + .all(|label| label["expected_disposition"] == json!(Disposition::OutOfScope)) + ); + assert_eq!( + reviewed["approval"]["receipt_id"], + "synthetic-existing-receipt" + ); +} + +fn complete(view: &[u8]) -> Vec { + let mut value: Value = serde_json::from_slice(view).unwrap(); + for row in value["review_batch"]["decisions"].as_array_mut().unwrap() { + row["reviewed_disposition"] = json!(Disposition::OutOfScope); + } + serde_json::to_vec(&value).unwrap() +} + +fn run(mode: &str, arguments: &[&Path]) -> Output { + Command::new(env!("CARGO_BIN_EXE_conceptweave-zotero")) + .arg(mode) + .args(arguments) + .output() + .unwrap() +} + +struct Case { + name: &'static str, + report: ClassificationReport, + capture: FullTextCapture, + paths: RefCell>, +} + +impl Case { + fn new(name: &'static str) -> Self { + let (report, capture) = synthetic_fixture(); + Self { + name, + report, + capture, + paths: RefCell::new(Vec::new()), + } + } + fn path(&self, suffix: &str) -> PathBuf { + let path = std::env::temp_dir().join(format!( + "conceptweave-bound-review-{}-{}-{suffix}.json", + std::process::id(), + self.name + )); + self.paths.borrow_mut().push(path.clone()); + path + } + fn bytes(&self, suffix: &str, bytes: &[u8]) -> PathBuf { + let path = self.path(suffix); + OpenOptions::new() + .write(true) + .create_new(true) + .mode(0o600) + .open(&path) + .unwrap() + .write_all(bytes) + .unwrap(); + path + } + fn json(&self, suffix: &str, value: &impl serde::Serialize) -> PathBuf { + self.bytes(suffix, &serde_json::to_vec(value).unwrap()) + } + fn inputs(&self) -> [PathBuf; 3] { + [ + self.json("report", &self.report), + self.json( + "worksheet", + &build_full_text_review_worksheet(&self.report, &self.capture).unwrap(), + ), + self.json("capture", &self.capture), + ] + } + fn approval(&self) -> FullTextReviewApproval { + serde_json::from_value(json!({ + "capture_digest":serde_json::to_value(&self.capture).unwrap()["capture_digest"], + "full_text_approval_v1":{ + "receipt_id":"synthetic-existing-receipt", "reviewer_subject":"synthetic-steward", + "library_version":self.report.library_version, "rule_revision":self.report.rule_revision, + "snapshot_digest":self.report.snapshot_digest, "proposal_digest":classification_proposal_digest(&self.report), + "snapshot_items":self.report.snapshot_items, + } + })).unwrap() + } +} + +impl Drop for Case { + fn drop(&mut self) { + for path in self.paths.get_mut() { + let _ = fs::remove_file(path); + } + } +} + +fn synthetic_fixture() -> (ClassificationReport, FullTextCapture) { + let items: Vec = serde_json::from_value(json!([ + {"key":"ABCD2345","version":2,"data":{"itemType":"journalArticle","title":"synthetic paper"}}, + {"key":"BCDE3456","version":1,"data":{"itemType":"attachment","parentItem":"ABCD2345"}}, + {"key":"CDEF4567","version":2,"data":{"itemType":"book","title":"synthetic unattached paper"}} + ])).unwrap(); + let mut report = classify_snapshot("10.0.1".into(), Some("synthetic-server".into()), 2, items); + report.api_version = Some(3); + report.schema_version = Some(44); + let evidence = format!( + concat!( + "{{\"capture_kind\":\"non_atomic_fulltext_sweep_v1\",", + "\"metadata_report_digest\":\"sha256:{:x}\",\"metadata_snapshot_digest\":\"{}\",", + "\"bibliographic_item_count\":2,\"started_unix_ms\":0,\"finished_unix_ms\":1,", + "\"library_before\":{},\"manifest_before\":{},\"records\":[{{", + "\"item_key\":\"BCDE3456\",\"metadata_response\":{},\"content_response\":{}}}],", + "\"manifest_after\":{},\"library_after\":{}}}" + ), + Sha256::digest(serde_json::to_vec(&report).unwrap()), + report.snapshot_digest, + response(200, Some(2), "[]"), + response(200, None, r#"{"BCDE3456":3}"#), + response( + 200, + Some(1), + r#"{"key":"BCDE3456","version":1,"data":{"itemType":"attachment","parentItem":"ABCD2345"}}"# + ), + response( + 200, + Some(3), + r#"{"content":"synthetic private text","providerExtra":true}"# + ), + response(200, None, r#"{"BCDE3456":3}"#), + response(200, Some(2), "[]"), + ); + let capture: FullTextCapture = serde_json::from_str(&format!( + "{{\"capture_digest\":\"sha256:{:x}\",\"capture_evidence\":{evidence}}}", + Sha256::digest(evidence.as_bytes()) + )) + .unwrap(); + build_full_text_review_worksheet(&report, &capture).unwrap(); + (report, capture) +} + +fn response(status: u16, version: Option, body: &str) -> String { + #[derive(serde::Serialize)] + struct Response<'a> { + status: u16, + version: Option, + body: &'a str, + } + serde_json::to_string(&Response { + status, + version, + body, + }) + .unwrap() +} From 3c08a44919d4018da6da3d1e8f1cbf2b65007bbc Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 21:09:20 +0900 Subject: [PATCH 20/39] docs: bind metadata output ceiling to current source --- docs/product-technical-gap-baseline.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 39b7f6fa..a2428605 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -8,6 +8,8 @@ This file records code-current product and technical gaps. Exact PR/check/run co The 11:13:09–11:15:04 UTC refresh found 31 open PRs, 30 Draft, unchanged existing heads/bases, 36 unresolved threads and no new approvals or terminal check results since 10:48. PR #35 has a new COMMENTED review following the counterevidence, not a changed approval verdict. Central `.github/main` advanced to `f250638827f8252b0d9e5cb2601f4d333f96162f` through #1922; its trigger/test-isolation change does not repair Noema/CodeQL or retroactively rebind queued CodeQL successors from `7fcada597d5b79bdb14445f24322b2c9f6ed4b19`. Current-main hosted GREEN was not established. This paragraph is a dated observation, not transferable acceptance evidence for the new child work below. +The 12:05 UTC #38 review found one additional bounded-artifact invariant: metadata input is capped at 16 MiB, but the shared private metadata writer could create a larger artifact that the same owner could not read back. Synthetic RED `4aabc6ab59fc16447bfe9b78cb12aeb2997a0a9d` requires fail-closed rejection before file creation. Runtime repair `351a0c88368e7ea07330de16ef3f5daa83661fd3`, followed by one-line fixture restoration `ee9e263b59a7249460fc797f0d8a11203b3fa932`, applies that bound only to metadata output; the independently bounded 512 MiB streaming full-text capture path remains unchanged. No pull-request workflow run existed for `ee9e263b59a7249460fc797f0d8a11203b3fa932`, so this is source/test repair evidence rather than hosted current-head GREEN. + Protected/default `main` remains `f4f440dd58c77d7cd90dff8a1eb2eeb9a9940425`; only the bootstrap state is shipped there and no immutable ConceptWeave release exists. The active roots observed immediately before this baseline refresh are: @@ -100,7 +102,7 @@ Next: revalidate current-head protected gates after the completed transport casc ### Canonical transport repair and released-owner audit -PR #9's committed RED `31b507ae9feaf58688cf62ddcb597a88d2223366` reproduces six environment-proxy routes and rejection of valid JSON exactly at the 8 MiB response bound. GREEN `a2a84884f67dcac6f6892c958d55450aea6d6c88` disables inherited proxies and introduces the same strict UTF-8 inclusive reader at the original metadata owner. Oversized, invalid-UTF-8 and truncated responses remain rejected. Its 38 workspace tests, strict Clippy, formatting and existing coverage gate pass; source-normalized regions are 686/686 and branches 90/90. Raw LLVM functions are 97/97, lines 981/982 and branches 89/90, not raw 100% coverage. Root review independently reran the four transport tests before the non-force push. Subsequent authenticated adapters must reuse this reader at their own introduction points; full-text feature commits are not reverse-merged into the earlier owner. +PR #9's committed RED `31b507ae9feaf58688cf62ddcb597a88d2223366` reproduces six environment-proxy routes and rejection of valid JSON exactly at the 8 MiB response bound. GREEN `a2a84884f67dcac6f6892c958d55450aea6d6c88` disables inherited proxies and introduces the same strict UTF-8 inclusive reader at the original metadata owner. Oversized, invalid-UTF8 and truncated responses remain rejected. Its 38 workspace tests, strict Clippy, formatting and existing coverage gate pass; source-normalized regions are 686/686 and branches 90/90. Raw LLVM functions are 97/97, lines 981/982 and branches 89/90, not raw 100% coverage. Root review independently reran the four transport tests before the non-force push. Subsequent authenticated adapters must reuse this reader at their own introduction points; full-text feature commits are not reverse-merged into the earlier owner. Authenticated PR #17's production repair `53bd1fe16c43adc5cb0e7a052183e80b8c6c2e25` and authorization PR #18's `ba1bbd203c2f90afbf97ac3d7eab989982e8bc09` preserve committed RED cases for actual synthetic proxy forwarding and inclusive 1 MiB/512-byte boundaries. The existing synthetic server also needed POST-framing repair `7bcb791853ffa794529418ee9de1337fea4e1b15`; 91/99 workspace tests respectively, focused repetition and strict Clippy/formatting passed at those historical heads. A subsequent #19 coverage run correctly failed at 333/334 normalized branch outcomes because TCP fragmentation did not reliably exercise the helper's full-read loop. Canonical test-only #17 `b388810be8bceb3a4f81c336708cf1c56a20d057` sends an 8 KiB header and 8 KiB body through the unchanged 4 KiB buffer. It closes that gap without a new helper or coverage exclusion: #17 passes 92 workspace tests and 318/318 normalized branch outcomes; restacked #18 `21a7ee8f8b4b0988c13bb45aecbb016242c21308` passes 100 tests; #19 `aa74f8642e9e8c3804996ce443650df29f08bf5f` passes 101 tests and 334/334 branch outcomes. @@ -136,6 +138,8 @@ Independent review found that a blank view exactly at 16 MiB became impossible t At `da406cf`, 216 workspace tests across 39 unfiltered suites, including three doctests, pass; one nested filtered subprocess is not counted twice. Strict Clippy, formatting, rustdoc with warnings denied, CI contract and the existing coverage gate pass. Functions are 383/383, source-normalized regions 4,215/4,215 and branches 712/712. Raw LLVM lines are 4,606/4,714, regions 6,693/6,884 and branches 630/712, not 100%. No dependency, coverage exclusion, provider call or authority bypass was added. +Current-head review then found that `write_private_output` could create metadata larger than the 16 MiB ceiling enforced by `read_private_json`. RED `4aabc6ab59fc16447bfe9b78cb12aeb2997a0a9d` uses only synthetic bytes and requires `InvalidData` before output creation. Repair `351a0c88368e7ea07330de16ef3f5daa83661fd3` adds that pre-create check to the shared metadata writer; `ee9e263b59a7249460fc797f0d8a11203b3fa932` restores one unrelated fixture line accidentally escaped during the contents-API edit. Comparison shows the corrective commit is one line and the net runtime delta after RED is the writer guard/comment only. The full-text capture still streams through its separate 512 MiB boundary. No current-head workflow run was present after this source repair, so the historical `da406cf` execution evidence is not promoted to current-head GREEN. + The [real blank-initialization receipt](doctoring/zotero_fulltext_review_binding_evidence.json) records a new 1,776,354-byte single-link `0600` file, SHA-256 `36bcf0679de068a744fd4ae8709cd192d99cb556591420d7a43c2b5f0e633a46`, created offline in 1.50 seconds with 286,375,936 bytes maximum resident memory. An independent read-only audit checked original input hashes, exact capture marker and equality of the nested worksheet with the prior blank metadata worksheet. There are now 3,715 explicitly capture-bound blank slots, but authentic decisions and independently approved labels remain 0/3,715. The earlier 25-row view remains 21 nonempty-text rows and four missing-text rows; no new text-bound proposal was generated. Lifecycle capability metric advances from 25 to 26 for the tested single-capture review chain; it is not a count of approved papers or shipped capabilities. Next gaps are a bounded private steward workflow for these library APIs, authentic decisions and independently issued approval, and separately governed full-text-aware write admission. Released contextual-orchestrator evidence, protected prerequisite acceptance, the remaining ontology repository census and a real deployment/release remain outstanding. No separate Utility Repository has an evidenced independent responsibility/consumer need. From 2a92b710c574b1f79cb8f2cc0047fd3870c09b16 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 21:09:47 +0900 Subject: [PATCH 21/39] feat(zotero): connect bound full-text review application and finalization --- .../src/full_text_review_cli_reader_tests.rs | 34 +++- crates/conceptweave-zotero/src/main.rs | 190 ++++++++++++++++-- 2 files changed, 205 insertions(+), 19 deletions(-) diff --git a/crates/conceptweave-zotero/src/full_text_review_cli_reader_tests.rs b/crates/conceptweave-zotero/src/full_text_review_cli_reader_tests.rs index 0c92fca3..5dac95f1 100644 --- a/crates/conceptweave-zotero/src/full_text_review_cli_reader_tests.rs +++ b/crates/conceptweave-zotero/src/full_text_review_cli_reader_tests.rs @@ -78,12 +78,38 @@ fn metadata_writer_rejects_oversized_output_before_creating_file() { let result = write_private_output(&output, &content); let output_was_created = output.exists(); - if output_was_created { - fs::remove_file(&output).unwrap(); - } + let _ = fs::remove_file(&output); let error = result.expect_err("oversized metadata output must fail closed"); assert_eq!(error.kind(), io::ErrorKind::InvalidData); - assert_eq!(error.to_string(), "metadata output exceeds the artifact size limit"); + assert_eq!( + error.to_string(), + "metadata output exceeds the artifact size limit" + ); assert!(!output_was_created); } + +#[test] +fn metadata_writer_accepts_exact_limit_and_capture_writer_keeps_its_separate_budget() { + let output = tests::unique_temp_path("exact-limit-private-output"); + let mut content = vec![b'x'; MAX_ARTIFACT_BYTES as usize]; + write_private_output(&output, &content).unwrap(); + assert_eq!(fs::metadata(&output).unwrap().len(), MAX_ARTIFACT_BYTES); + fs::remove_file(&output).unwrap(); + content.push(b'x'); + write_private_output_with(&output, &content, &mut write_all_and_flush).unwrap(); + assert_eq!(fs::metadata(&output).unwrap().len(), MAX_ARTIFACT_BYTES + 1); + fs::remove_file(output).unwrap(); +} + +#[test] +fn completed_view_reader_preserves_duplicate_keys_byte_for_byte() { + let input = tests::unique_temp_path("exact-completed-view"); + let bytes = br#"{"decision":false,"decision":true}"#; + let mut file = create_report_file(&input).unwrap(); + file.write_all(bytes).unwrap(); + drop(file); + let (restored, _) = read_private_bytes(input.to_str().unwrap()).unwrap(); + assert_eq!(restored, bytes); + fs::remove_file(input).unwrap(); +} diff --git a/crates/conceptweave-zotero/src/main.rs b/crates/conceptweave-zotero/src/main.rs index efa0e597..ce286adb 100644 --- a/crates/conceptweave-zotero/src/main.rs +++ b/crates/conceptweave-zotero/src/main.rs @@ -2,11 +2,13 @@ #![cfg_attr(coverage_nightly, feature(coverage_attribute))] use conceptweave_zotero::{ - ClassificationReport, FullTextCapture, GoldenSetApproval, MAX_REVIEW_BATCH_ITEMS, - StewardDecisionPatch, StewardReviewBatch, StewardReviewWorksheet, apply_steward_decision_patch, - assess_steward_review_progress, build_full_text_review_json, build_full_text_review_worksheet, - build_steward_review_batch, build_steward_review_worksheet, decision_patch_from_review_batch, - read_local_full_text, read_local_snapshot, reviewed_golden_set_from_worksheet, + ClassificationReport, FullTextCapture, FullTextReviewApproval, FullTextReviewWorksheet, + GoldenSetApproval, MAX_REVIEW_BATCH_ITEMS, StewardDecisionPatch, StewardReviewBatch, + StewardReviewWorksheet, apply_full_text_review_view, apply_steward_decision_patch, + assess_steward_review_progress, build_bound_full_text_review_json, build_full_text_review_json, + build_full_text_review_worksheet, build_steward_review_batch, build_steward_review_worksheet, + decision_patch_from_review_batch, finalize_full_text_review, read_local_full_text, + read_local_snapshot, reviewed_golden_set_from_worksheet, }; use serde::de::DeserializeOwned; use std::collections::BTreeSet; @@ -15,7 +17,7 @@ use std::fs::{self, File, OpenOptions}; use std::io::{self, BufReader, BufWriter, Read, Write}; use std::path::{Path, PathBuf}; -const USAGE: &str = "usage: conceptweave-zotero /tmp/REPORT.json | --capture-full-text /tmp/REPORT.json /tmp/CAPTURE.json | --full-text-worksheet /tmp/REPORT.json /tmp/CAPTURE.json /tmp/WORKSHEET.json | --full-text-review /tmp/REPORT.json /tmp/WORKSHEET.json /tmp/CAPTURE.json LIMIT /tmp/VIEW.json | --worksheet /tmp/REPORT.json /tmp/WORKSHEET.json | --review-progress /tmp/REPORT.json /tmp/WORKSHEET.json /tmp/PROGRESS.json | --review-batch /tmp/REPORT.json /tmp/CURRENT_WORKSHEET.json LIMIT /tmp/BATCH.json | --apply-review-batch /tmp/REPORT.json /tmp/CURRENT_WORKSHEET.json /tmp/COMPLETED_BATCH.json /tmp/UPDATED_WORKSHEET.json | --apply-decision-patch /tmp/REPORT.json /tmp/CURRENT_WORKSHEET.json /tmp/PATCH.json /tmp/UPDATED_WORKSHEET.json | --finalize /tmp/REPORT.json /tmp/WORKSHEET.json /tmp/APPROVAL.json /tmp/GOLDEN.json"; +const USAGE: &str = "usage: conceptweave-zotero /tmp/REPORT.json | --capture-full-text /tmp/REPORT.json /tmp/CAPTURE.json | --full-text-worksheet /tmp/REPORT.json /tmp/CAPTURE.json /tmp/WORKSHEET.json | --bound-full-text-review /tmp/REPORT.json /tmp/WORKSHEET.json /tmp/CAPTURE.json LIMIT /tmp/VIEW.json | --apply-full-text-review /tmp/REPORT.json /tmp/WORKSHEET.json /tmp/CAPTURE.json /tmp/COMPLETED_VIEW.json /tmp/UPDATED.json | --finalize-full-text-review /tmp/REPORT.json /tmp/WORKSHEET.json /tmp/CAPTURE.json /tmp/APPROVAL.json /tmp/GOLDEN.json | --full-text-review /tmp/REPORT.json /tmp/WORKSHEET.json /tmp/CAPTURE.json LIMIT /tmp/VIEW.json | --worksheet /tmp/REPORT.json /tmp/WORKSHEET.json | --review-progress /tmp/REPORT.json /tmp/WORKSHEET.json /tmp/PROGRESS.json | --review-batch /tmp/REPORT.json /tmp/CURRENT_WORKSHEET.json LIMIT /tmp/BATCH.json | --apply-review-batch /tmp/REPORT.json /tmp/CURRENT_WORKSHEET.json /tmp/COMPLETED_BATCH.json /tmp/UPDATED_WORKSHEET.json | --apply-decision-patch /tmp/REPORT.json /tmp/CURRENT_WORKSHEET.json /tmp/PATCH.json /tmp/UPDATED_WORKSHEET.json | --finalize /tmp/REPORT.json /tmp/WORKSHEET.json /tmp/APPROVAL.json /tmp/GOLDEN.json"; const MAX_ARTIFACT_BYTES: u64 = 16 * 1024 * 1024; // JSON escaping and envelope bytes are separate from the capture's raw-body budget. const MAX_CAPTURE_FILE_BYTES: u64 = 512 * 1024 * 1024; @@ -39,6 +41,27 @@ enum OutputRequest { limit: usize, output: String, }, + BoundFullTextReview { + report: String, + worksheet: String, + capture: String, + limit: usize, + output: String, + }, + ApplyFullTextReview { + report: String, + worksheet: String, + capture: String, + completed_view: String, + output: String, + }, + FinalizeFullTextReview { + report: String, + worksheet: String, + capture: String, + approval: String, + output: String, + }, Worksheet { report: String, worksheet: String, @@ -111,7 +134,43 @@ where capture, output, } - } else if first == "--full-text-review" { + } else if first == "--apply-full-text-review" || first == "--finalize-full-text-review" { + let report = args + .next() + .ok_or("full-text review application and finalization require five paths")?; + let worksheet = args + .next() + .ok_or("full-text review application and finalization require five paths")?; + let capture = args + .next() + .ok_or("full-text review application and finalization require five paths")?; + let input = args + .next() + .ok_or("full-text review application and finalization require five paths")?; + let output = args + .next() + .ok_or("full-text review application and finalization require five paths")?; + if BTreeSet::from([&report, &worksheet, &capture, &input, &output]).len() != 5 { + return Err("full-text review paths must be distinct"); + } + if first == "--apply-full-text-review" { + OutputRequest::ApplyFullTextReview { + report, + worksheet, + capture, + completed_view: input, + output, + } + } else { + OutputRequest::FinalizeFullTextReview { + report, + worksheet, + capture, + approval: input, + output, + } + } + } else if first == "--full-text-review" || first == "--bound-full-text-review" { let report = args .next() .ok_or("full-text review mode requires a report path")?; @@ -139,12 +198,22 @@ where if BTreeSet::from([&report, &worksheet, &capture, &output]).len() != 4 { return Err("full-text review paths must be distinct"); } - OutputRequest::FullTextReview { - report, - worksheet, - capture, - limit, - output, + if first == "--bound-full-text-review" { + OutputRequest::BoundFullTextReview { + report, + worksheet, + capture, + limit, + output, + } + } else { + OutputRequest::FullTextReview { + report, + worksheet, + capture, + limit, + output, + } } } else if first == "--worksheet" { let report = args @@ -316,6 +385,11 @@ fn read_private_json(raw: &str) -> io::Result<(T, ArtifactI read_private_input(raw, |file, length| read_bounded_json(file, length)) } +/// Retains exact completed-view bytes for the duplicate-key-aware review parser. +fn read_private_bytes(raw: &str) -> io::Result<(Vec, ArtifactIdentity)> { + read_private_input(raw, |file, length| read_bounded_bytes(file, length)) +} + /// Restores a private capture without allocating a second full-file byte buffer. fn read_private_capture(raw: &str) -> io::Result<(FullTextCapture, ArtifactIdentity)> { read_private_input(raw, |file, length| { @@ -415,6 +489,12 @@ fn read_bounded_json( reader: &mut dyn Read, advertised_len: u64, ) -> io::Result { + serde_json::from_slice(&read_bounded_bytes(reader, advertised_len)?) + .map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error)) +} + +/// Shares the metadata byte ceiling without normalizing completed review JSON. +fn read_bounded_bytes(reader: &mut dyn Read, advertised_len: u64) -> io::Result> { if advertised_len > MAX_ARTIFACT_BYTES { return Err(io::Error::new( io::ErrorKind::InvalidData, @@ -431,8 +511,7 @@ fn read_bounded_json( "review input grew beyond the artifact size limit", )); } - serde_json::from_slice(&content) - .map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error)) + Ok(content) } /// Parses capture JSON through a fixed buffer and rejects size drift or trailing data. @@ -606,6 +685,87 @@ fn create_report_file_with( /// Reads one Zotero snapshot and writes its sensitive local proposal report. fn main() -> Result<(), Box> { match parse_output_request(env::args().skip(1))? { + OutputRequest::BoundFullTextReview { + report, + worksheet, + capture, + limit, + output, + } => { + let output_path = validate_output_path(&output)?; + let (report, report_identity): (ClassificationReport, _) = + read_private_json(&report).map_err(|error| label_input("report", error))?; + let (worksheet, worksheet_identity): (FullTextReviewWorksheet, _) = + read_private_json(&worksheet).map_err(|error| label_input("worksheet", error))?; + let (capture, capture_identity) = + read_private_capture(&capture).map_err(|error| label_input("capture", error))?; + if BTreeSet::from([report_identity, worksheet_identity, capture_identity]).len() != 3 { + return Err("full-text review inputs must be distinct files".into()); + } + let content = build_bound_full_text_review_json(&report, &worksheet, &capture, limit)?; + write_private_output(&output_path, &content)?; + } + OutputRequest::ApplyFullTextReview { + report, + worksheet, + capture, + completed_view, + output, + } => { + let output_path = validate_output_path(&output)?; + let (report, report_identity): (ClassificationReport, _) = + read_private_json(&report).map_err(|error| label_input("report", error))?; + let (worksheet, worksheet_identity): (FullTextReviewWorksheet, _) = + read_private_json(&worksheet).map_err(|error| label_input("worksheet", error))?; + let (capture, capture_identity) = + read_private_capture(&capture).map_err(|error| label_input("capture", error))?; + let (completed_view, view_identity) = read_private_bytes(&completed_view) + .map_err(|error| label_input("completed view", error))?; + if BTreeSet::from([ + report_identity, + worksheet_identity, + capture_identity, + view_identity, + ]) + .len() + != 4 + { + return Err("full-text review inputs must be distinct files".into()); + } + let updated = + apply_full_text_review_view(&report, &worksheet, &capture, &completed_view)?; + write_private_output(&output_path, &serde_json::to_vec_pretty(&updated)?)?; + } + OutputRequest::FinalizeFullTextReview { + report, + worksheet, + capture, + approval, + output, + } => { + let output_path = validate_output_path(&output)?; + let (report, report_identity): (ClassificationReport, _) = + read_private_json(&report).map_err(|error| label_input("report", error))?; + let (worksheet, worksheet_identity): (FullTextReviewWorksheet, _) = + read_private_json(&worksheet).map_err(|error| label_input("worksheet", error))?; + let (capture, capture_identity) = + read_private_capture(&capture).map_err(|error| label_input("capture", error))?; + let (approval, approval_identity): (FullTextReviewApproval, _) = + read_private_json(&approval).map_err(|error| label_input("approval", error))?; + if BTreeSet::from([ + report_identity, + worksheet_identity, + capture_identity, + approval_identity, + ]) + .len() + != 4 + { + return Err("full-text review inputs must be distinct files".into()); + } + let golden = finalize_full_text_review(&report, &worksheet, &capture, approval)?; + write_private_output(&output_path, &serde_json::to_vec_pretty(&golden)?)?; + } OutputRequest::FullTextWorksheet { report, capture, From d3fdaa5c84b9eb0062f8fc89f91dbadfb8201222 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 21:15:42 +0900 Subject: [PATCH 22/39] test(zotero): reproduce private JSON diagnostic disclosure --- .../src/full_text_review_cli_reader_tests.rs | 25 ++++++++ .../tests/bound_full_text_commands_cli.rs | 61 +++++++++++++++++++ 2 files changed, 86 insertions(+) diff --git a/crates/conceptweave-zotero/src/full_text_review_cli_reader_tests.rs b/crates/conceptweave-zotero/src/full_text_review_cli_reader_tests.rs index 5dac95f1..22a05a3c 100644 --- a/crates/conceptweave-zotero/src/full_text_review_cli_reader_tests.rs +++ b/crates/conceptweave-zotero/src/full_text_review_cli_reader_tests.rs @@ -1,5 +1,30 @@ use super::*; +#[test] +fn private_json_reader_does_not_echo_unknown_keys_or_invalid_values() { + let unknown_field = br#"{"synthetic-private-field-sentinel":true}"#; + let invalid_disposition = br#""synthetic-private-enum-sentinel""#; + let unknown_error = read_bounded_json::( + &mut unknown_field.as_slice(), + unknown_field.len() as u64, + ) + .err() + .unwrap(); + let value_error = read_bounded_json::( + &mut invalid_disposition.as_slice(), + invalid_disposition.len() as u64, + ) + .unwrap_err(); + for error in [unknown_error, value_error] { + assert_eq!(error.kind(), io::ErrorKind::InvalidData); + assert_eq!(error.to_string(), "review input is invalid"); + assert_eq!( + label_input("worksheet", error).to_string(), + "worksheet: review input is invalid" + ); + } +} + #[test] fn capture_reader_accepts_exact_budget_without_truncating_trailing_bytes() { let bytes = b"{\"synthetic\":true} "; diff --git a/crates/conceptweave-zotero/tests/bound_full_text_commands_cli.rs b/crates/conceptweave-zotero/tests/bound_full_text_commands_cli.rs index 5ac996c3..26992182 100644 --- a/crates/conceptweave-zotero/tests/bound_full_text_commands_cli.rs +++ b/crates/conceptweave-zotero/tests/bound_full_text_commands_cli.rs @@ -15,6 +15,67 @@ use std::os::unix::fs::{OpenOptionsExt, PermissionsExt, symlink}; use std::path::{Path, PathBuf}; use std::process::{Command, Output}; +#[test] +fn cli_private_json_errors_never_echo_source_keys_or_dispositions() { + let case = Case::new("diagnostic-privacy"); + let [report, worksheet, capture] = case.inputs(); + let original = fs::read(&worksheet).unwrap(); + let original_value: Value = serde_json::from_slice(&original).unwrap(); + let mut unknown_field = original_value.clone(); + unknown_field["synthetic-private-field-sentinel"] = true.into(); + let mut invalid_value = original_value.clone(); + invalid_value["full_text_worksheet_v1"]["decisions"][0]["reviewed_disposition"] = + "synthetic-private-enum-sentinel".into(); + for (index, invalid) in [unknown_field, invalid_value.clone()] + .into_iter() + .enumerate() + { + let invalid = case.json(&format!("invalid-worksheet-{index}"), &invalid); + let output = case.path(&format!("rejected-{index}")); + let command = run( + "--bound-full-text-review", + &[&report, &invalid, &capture, Path::new("1"), &output], + ); + assert_private_parse_failure(command, "worksheet"); + assert!(!output.exists()); + } + let mut approval = serde_json::to_value(case.approval()).unwrap(); + approval["synthetic-private-approval-sentinel"] = true.into(); + let approval = case.json("invalid-approval", &approval); + let golden = case.path("rejected-golden"); + assert_private_parse_failure( + run( + "--finalize-full-text-review", + &[&report, &worksheet, &capture, &approval, &golden], + ), + "approval", + ); + assert!(!golden.exists()); + // Metadata-only siblings share the same parser and must not retain the leak. + let legacy = case.json( + "invalid-legacy-worksheet", + &invalid_value["full_text_worksheet_v1"], + ); + let progress = case.path("rejected-progress"); + assert_private_parse_failure( + run("--review-progress", &[&report, &legacy, &progress]), + "worksheet", + ); + assert!(!progress.exists()); + assert_eq!(fs::read(&worksheet).unwrap(), original); +} + +fn assert_private_parse_failure(output: Output, role: &str) { + assert!(!output.status.success()); + assert!(output.stdout.is_empty()); + let stderr = String::from_utf8(output.stderr).unwrap(); + assert!(!stderr.contains("synthetic-private-"), "{stderr}"); + assert!( + stderr.contains(&format!("{role}: review input is invalid")), + "{stderr}" + ); +} + #[test] fn library_two_batch_flow_keeps_the_full_review_and_approval_envelopes() { let case = Case::new("library"); From c23ac82398e258b335017ce2ae643cc4f7768342 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 21:16:24 +0900 Subject: [PATCH 23/39] docs: trace private review commands and twenty audited owner contracts --- AGENTS.md | 1 + ARCHITECTURE.md | 2 +- CHANGELOG.md | 4 +- CLAUDE.md | 2 +- THREAT_MODEL.md | 2 + docs/CONTEXT_MAP.md | 2 + docs/PRD.md | 2 +- docs/TRD.md | 13 +++++- docs/UML.md | 1 + docs/adr/0006-zotero-research-intake.md | 10 ++++- .../RESEARCH_CAPABILITY_TRACEABILITY.md | 4 ++ .../cwl_ontology_capability_inventory.md | 42 ++++++++++++++++--- .../zotero_fulltext_contract_audit.md | 2 + docs/product-technical-gap-baseline.md | 6 +-- 14 files changed, 79 insertions(+), 14 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index c320cb18..6bc51ac3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -19,6 +19,7 @@ ConceptWeave owns automatic, evidence-bound **Semantic Model Engineering**. Do n - Keep Zotero full-text captures separate from metadata reports and approval receipts; restored captures require bounded verification, and local HTTP continuity is not peer authentication. - Full-text review views are read-only evidence; never strip their outer binding into a metadata-only patch and claim full-text decision or approval provenance. - Full-text decisions use the separate blank-start worksheet and atomic exact-view application; reverify the capture/report relation through finalization and whole-envelope governance. No reviewed-set downcast grants Zotero write authority. +- Offline full-text commands reuse the private-file boundary and pass completed-view bytes unchanged into atomic validation. Finalized files await external approval verification; no CLI command issues approval or writes Zotero. - Published semantic truth is immutable; correction uses supersession/new release. - Public Rust APIs require beginner-readable documentation. - Owned production coverage target is 100% line/function/region/branch where tooling exposes it. diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 9f80a1e4..5f120796 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -26,7 +26,7 @@ Research Intake's Zotero adapter retains optional full-text observations in a se Its Full-Text Review View reuses capture verification and canonical pending selection to make exact text inspectable without changing earlier proposals. This bounded read projection is not an aggregate or a decision-application API; the metadata-only approval chain does not acquire full-text provenance from it. -The separate Full-Text Review Worksheet starts blank and retains one capture identity through atomic completed-view application, finalization and whole-envelope governance verification. Every boundary rechecks the capture against the complete report. It reuses existing review cores rather than introducing another service or source of semantic truth. The CLI only initializes this work; the remaining operations are library APIs. Verified review still does not admit Zotero writes, whose exact change set requires independent authorization. +The separate Full-Text Review Worksheet starts blank and retains one capture identity through atomic completed-view application, finalization and whole-envelope governance verification. Every boundary rechecks the capture against the complete report. The offline CLI exposes initialization, evidence-view creation, atomic application and finalization using those existing review cores and private-file helpers. Completed JSON bytes enter the owner parser unchanged, preserving duplicate-key detection. External approval verification remains a caller-owned library boundary; no CLI verifier, service or new source of semantic truth is introduced. Verified review still does not admit Zotero writes, whose exact change set requires independent authorization. | Context | Type | Owns | Does not own | | --- | --- | --- | --- | diff --git a/CHANGELOG.md b/CHANGELOG.md index bf822f59..1674a85d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ All notable changes to ConceptWeave are documented here. ### Added -- A separate private review worksheet and library review path that keeps saved-text evidence attached to decisions and approval verification. Command-line support currently initializes blank review work; it does not complete or approve a review. +- Private review commands now show saved text, accept completed decisions without replacing earlier work, and prepare a complete review for independent approval verification. They do not supply decisions or approve a review. - Private inspection of pending papers alongside their saved text, preserving missing material and leaving previous reports and decisions unchanged. @@ -41,4 +41,6 @@ All notable changes to ConceptWeave are documented here. ### Fixed +- Oversized private review outputs fail before creating a file, so a successful save stays within the corresponding reader's size limit. Large saved-text captures retain their separate limit. + - Research reads accept valid responses exactly at their documented size limit while still rejecting oversized, incomplete or invalidly encoded responses. diff --git a/CLAUDE.md b/CLAUDE.md index b854da77..3259b7f3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,4 +10,4 @@ Zotero source capture must not alter the metadata report or renew its approval. The separate full-text review view does not authorize decisions or writes. Keep its evidence binding intact; existing metadata-only apply/finalization cannot establish full-text-reviewed approval. -Use the capture-bound blank-start worksheet for full-text decision work. Only the dedicated atomic view path may apply completed slots; finalization/evaluation reverify capture/report bindings and require whole-envelope external approval. CLI initialization alone is not an end-to-end review interface or Zotero write admission. +Use the capture-bound blank-start worksheet for full-text decision work. Only the dedicated atomic view path may apply completed slots; finalization/evaluation reverify capture/report bindings and require whole-envelope external approval. Offline CLI view/application/finalization preserve that envelope and earlier files. They do not supply a reviewer, authenticate approval, or admit Zotero writes. diff --git a/THREAT_MODEL.md b/THREAT_MODEL.md index f44dc19f..648522ae 100644 --- a/THREAT_MODEL.md +++ b/THREAT_MODEL.md @@ -79,4 +79,6 @@ The optional full-text review view must not be confused with the metadata-only d Capture-bound review uses separate required versioned payloads, with no import of previous metadata decisions. Atomic application permits only completed decision slots in the exact current view; recursive duplicate-key rejection precedes projection so a changed first field cannot hide behind a canonical later field. Finalization and evaluation revalidate the capture against the complete current report, including proposal records, before authority is contacted. Governance must authenticate the entire outer reviewed set and every label. Owner-only storage, private fields and hashes do not defeat a malicious local replacement or prove human review. Preserve old artifacts; stale views fail rather than overwrite concurrent work. These APIs neither implement authenticated governance nor authorize the independent Zotero write contract. Restoring report/capture/worksheet/approval JSON still requires caller-owned bounded private-file admission. +The offline full-text commands reuse those private readers and pass completed-view bytes unchanged into the duplicate-key-aware application boundary. Distinct argument strings do not replace opened-file identity checks. Metadata/review serialization must fit the inclusive 16 MiB reader limit before a new file is created; the larger capture remains a separate bounded artifact. Finalization writes only an input for independent verification, never an approval result. Test-only labels and receipts cannot be substituted for genuine campaign decisions or authority. + A capability is not release-ready while a valid security finding lacks a deterministic test or equivalent machine-verifiable contract, while required exact-head checks are non-terminal, or while the implemented transport cannot satisfy the advertised security claim. Documentation must describe residual risk without upgrading provider guarantees by inference. diff --git a/docs/CONTEXT_MAP.md b/docs/CONTEXT_MAP.md index e28d49ce..12ba0f10 100644 --- a/docs/CONTEXT_MAP.md +++ b/docs/CONTEXT_MAP.md @@ -20,3 +20,5 @@ The Full-Text Review View is an in-context read projection over that verified ca - context-graph-contracts <-> Interoperability: **Shared Kernel only for versioned public contracts**, kept minimal. - semantic-data-portal <- Interoperability: **Published Language**. SDP consumes releases; ConceptWeave does not read SDP application tables. - Keyverse -> future delivery layer: **Anti-Corruption Layer** for verified identity/tenant context. +- governance-risk-compliance -> Source Observation / Client Consumption: **Proposed Anti-Corruption Layer**, pending a released contract and exact-consumer proof. External requirements, internal controls, evidence links and effectiveness remain distinct GRC-owned meanings; ConceptWeave cannot infer assurance from evidence presence. +- Orgmetra -> Source Observation: **Proposed Anti-Corruption Layer**, pending a released evidence contract. Job/Task/KSAO relationships remain product-owned; tenant, source version and review status must survive observation without moving employment authority or ordinal scoring into ConceptWeave. diff --git a/docs/PRD.md b/docs/PRD.md index ebff7023..af4f3a43 100644 --- a/docs/PRD.md +++ b/docs/PRD.md @@ -62,7 +62,7 @@ Full-text enrichment must distinguish listed attachments, returned nonempty text Stewards must be able to inspect saved text alongside the next pending review rows without rereading Zotero or rewriting the report. A separate private evidence view binds the capture, original report and unchanged proposals, includes only those rows' attachment content, and retains missing and partial material explicitly. If the complete view exceeds its size limit, generation fails instead of truncating papers or text. Generating or reading this view does not fill decisions. Existing metadata-only apply commands must reject it. -A full-text review starts with blank decisions for every paper; previous metadata decisions cannot silently acquire a claim that the text was reviewed. Accept a completed view only while its displayed evidence and pending selection still match the current review. Changed or ambiguous content, incomplete decisions and stale views fail without replacing prior work. Each new worksheet, completed review and verified aggregate result retains the same captured-evidence identity. Finalization and evaluation recheck the capture against the original report; approval must authenticate that identity and every reviewed label. This is review provenance, not proof that a person read a file, and not permission to change Zotero. Command-line initialization is supplied separately from the library's decision/finalization/evaluation operations; those operations are not yet an end-to-end steward interface. +A full-text review starts with blank decisions for every paper; previous metadata decisions cannot silently acquire a claim that the text was reviewed. Accept a completed view only while its displayed evidence and pending selection still match the current review. Changed or ambiguous content, incomplete decisions and stale views fail without replacing prior work. Each new worksheet, completed review and verified aggregate result retains the same captured-evidence identity. Finalization and evaluation recheck the capture against the original report; approval must authenticate that identity and every reviewed label. This is review provenance, not proof that a person read a file, and not permission to change Zotero. The offline command-line workflow initializes blank work, shows the next pending text, saves accepted decisions to a new worksheet and prepares a fully decided review using a separately supplied approval input. It neither supplies decisions nor authenticates that input; independent governance verification remains required. A successful private review output must fit the matching reader's size limit, without truncation or overwriting earlier work. 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 43013e48..be00805c 100644 --- a/docs/TRD.md +++ b/docs/TRD.md @@ -81,7 +81,18 @@ The separate full-text review contract uses required, non-flattened versioned pa `finalize_full_text_review` revalidates report/capture/worksheet/approval bindings and requires every bibliographic decision, returning only an input for governance verification. `evaluate_full_text_review` repeats local capture/report and complete-label validation before passing the entire outer reviewed set to a caller-owned verifier. That verifier must authenticate both capture identity and every label against an independently issued receipt; accepting an identifier or recomputed digest is insufficient. The aggregate result retains the capture identity without source text or item/reviewer identities. Legacy metadata evaluation remains available but cannot establish full-text-reviewed approval. -The `--full-text-worksheet /tmp/REPORT.json /tmp/CAPTURE.json /tmp/WORKSHEET.json` entry point initializes a separate owner-only file using existing bounded readers and create-new output protection. The other full-text decision/finalization/evaluation operations are library contracts, not CLI commands or deployed governance. Write planning still has its separate `ReviewedClassificationWriteSet` and approval verifier; there is no conversion or authority transfer from a full-text evaluation to a Zotero write plan. A future write admission must explicitly preserve this context and authenticate the exact proposed collection/tag changes. +The private commands reuse those library contracts without rereading Zotero: + +| Command | Arguments after the command | Result | +| --- | --- | --- | +| `--full-text-worksheet` | `REPORT CAPTURE WORKSHEET` | Separate blank capture-bound worksheet. | +| `--bound-full-text-review` | `REPORT WORKSHEET CAPTURE LIMIT VIEW` | Compact exact pending evidence view, including reserved decision space. | +| `--apply-full-text-review` | `REPORT WORKSHEET CAPTURE COMPLETED_VIEW UPDATED` | New worksheet after atomic validation; the previous worksheet remains unchanged. | +| `--finalize-full-text-review` | `REPORT WORKSHEET CAPTURE APPROVAL GOLDEN` | Complete capture-bound reviewed set awaiting independent verification. | + +All file arguments must be distinct direct children of a canonical temporary directory. Every opened input must also have a distinct device/inode identity and satisfy the existing regular-file, single-link, exact-`0600`, no-follow and bounded-read policy. Completed-view bytes reach `apply_full_text_review_view` unchanged; projecting them to a JSON value in the CLI would erase duplicate keys before the owner can reject them. Metadata/review reads and writes have an inclusive 16 MiB ceiling. The shared metadata writer rejects overflow before file creation. Captures keep their separate buffered 512 MiB input ceiling and streaming writer. Outputs use create-new `0600` protection, never replacing existing files. The legacy `--full-text-review` remains a read-only metadata-worksheet view, not a substitute for these capture-bound commands. + +There is no CLI approval issuer or evaluator. Finalization accepts an independently supplied approval input but cannot authenticate it. Whole-envelope external verification remains a library boundary, not deployed governance. Write planning still has its separate `ReviewedClassificationWriteSet` and approval verifier; there is no conversion or authority transfer from a full-text evaluation to a Zotero write plan. A future write admission must explicitly preserve this context and authenticate the exact proposed collection/tag changes. `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. diff --git a/docs/UML.md b/docs/UML.md index 27f06219..96bcf405 100644 --- a/docs/UML.md +++ b/docs/UML.md @@ -77,6 +77,7 @@ sequenceDiagram Note over Intake,Steward: read-only view; legacy apply commands reject it end opt separate capture-bound review campaign + Note over Intake,Steward: offline CLI through finalization; external verification is a separate library boundary Report->>Intake: original report, without importing metadata decisions Capture->>Intake: exact retained capture Intake->>Intake: create blank capture-bound worksheet diff --git a/docs/adr/0006-zotero-research-intake.md b/docs/adr/0006-zotero-research-intake.md index 479ed5a7..bda94169 100644 --- a/docs/adr/0006-zotero-research-intake.md +++ b/docs/adr/0006-zotero-research-intake.md @@ -47,6 +47,14 @@ No dedicated utility repository or Zotero mutation path is created. A future Zot ## Consequences +### 2026-09-05 private review command amendment (Proposed) + +In the context of an operator reviewing retained paper text without accessing mutable Zotero state, facing tested library operations that were not callable as one private review workflow, we decided for three thin commands reusing the existing view/application/finalization APIs and against another review engine, service, repository or local approval issuer, to preserve source bindings and prior work at each saved-file transition, accepting repeated capture verification and separately supplied decisions and approval evidence. + +The command sequence is blank initialization, bounded pending view, exact completed-view application to a new worksheet, and complete-review finalization to a new reviewed set. Each command reuses the existing owner-only readers, opened identity comparisons and create-new output. Completed JSON crosses the CLI as bytes rather than a projected value, because projection would discard duplicate keys before atomic application sees them. Finalization receives a separately issued approval input but does not authenticate it; the existing whole-envelope library verifier and independent Zotero write-admission contract remain outside the CLI. Neither a successfully saved file nor a test receipt counts as completed classification or governance approval. + +The concurrent owner regression at `4aabc6a` showed that the shared metadata writer could save a file larger than its 16 MiB reader accepted. Its `351a0c8` guard is retained in normal history and rejects oversized output before creation. Captures use the separate streaming writer and are not reduced to the metadata ceiling. A malformed JSON permission-test fixture introduced in that same commit was reproduced and corrected separately, preserving the guard and failure history. Positive effects are callable offline review and consistent save/read limits. Costs are additional private artifacts, explicit output-path management and repeated validation of a potentially large capture. Authenticated review service, full-text-aware write admission and a released orchestration integration remain separate prerequisites; this amendment does not make their status Accepted. + ### 2026-09-05 capture-bound review application amendment (Proposed) In the context of stewards deciding papers after inspecting retained text, facing metadata-only review receipts that cannot establish which later capture was reviewed, we decided for required non-flattened single-capture review artifacts and atomic completed-view application and against optional fields on legacy receipts or a separately restorable decision patch, to preserve evidence identity through finalization and external verification, accepting another private worksheet, rejection of stale views, and no current end-to-end steward interface or write admission. @@ -59,7 +67,7 @@ Independent review found that a blank view at the 16 MiB ceiling could not be su Finalization requires a new externally issued capture-bound approval input and complete non-abstention labels; it does not verify authority. Evaluation checks local structure first, then provides governance the entire capture-bound reviewed set. The verifier must authenticate all labels and the capture, not merely recognize a receipt identifier. For example, a report title changed under the same snapshot coordinate must fail against its earlier capture before governance is called; a syntactically valid relabeling must fail the independently issued receipt. Matching hashes and readable text do not prove that a human reviewed a paper. -Positive consequences are retained source identity, no retroactive approval migration, and deterministic local failure without overwriting work. Negative consequences are repeated full-capture verification costs, additional sensitive retained artifacts and rejection of stale completed views. No exact-time performance target or concurrency guarantee is claimed. CLI support starts with blank initialization only; application/finalization/evaluation remain library APIs pending a private steward workflow. Zotero write planning is an independent reviewed-change authority boundary, not a caller of golden-set evaluation. It receives no new conversion, release, execution authority or transport guarantee here. The existing Proposed ADR 0007 remains necessary, and full-text-aware write admission remains a subsequent gap. +Positive consequences are retained source identity, no retroactive approval migration, and deterministic local failure without overwriting work. Negative consequences are repeated full-capture verification costs, additional sensitive retained artifacts and rejection of stale completed views. No exact-time performance target or concurrency guarantee is claimed. At this amendment's initial checkpoint CLI support only initialized blank work; the subsequent command amendment above exposes view/application/finalization without adding authenticated evaluation. Zotero write planning is an independent reviewed-change authority boundary, not a caller of golden-set evaluation. It receives no new conversion, release, execution authority or transport guarantee here. The existing Proposed ADR 0007 remains necessary, and full-text-aware write admission remains a subsequent gap. ### 2026-09-05 private full-text review view amendment (Proposed) diff --git a/docs/doctoring/RESEARCH_CAPABILITY_TRACEABILITY.md b/docs/doctoring/RESEARCH_CAPABILITY_TRACEABILITY.md index 2731e2ca..5ede0b7d 100644 --- a/docs/doctoring/RESEARCH_CAPABILITY_TRACEABILITY.md +++ b/docs/doctoring/RESEARCH_CAPABILITY_TRACEABILITY.md @@ -49,6 +49,10 @@ This register turns the accepted research set into product decisions. A paper is ## GRC reference flow +The [2026-09-05 exact-default-source audit](cwl_ontology_capability_inventory.md#further-domain-contract-audits-2026-09-05) found GRC evidence-to-requirement binding and a Proposed internal-control mapping design, not a released semantic round trip. NIST IR 8477 and the OSCAL mapping documentation distinguish relationship semantics and source/target context; use these to test subset direction, explicit no-relationship versus missing mapping, and source-edition drift. Evidence presence must never satisfy an effectiveness assertion. These are future evaluation requirements, not executed conformance or steward-approved labels. + +Orgmetra's inspected Task–KSAO contract is a second domain-specific observation candidate. A future consumer test must preserve Job versus Position/Assignment, tenant and source-version boundaries, reject LLM-origin material presented as validated, and avoid treating an ordinal relationship rating as calibrated ontology confidence. No Orgmetra package or application table is imported. Its absence of a release keeps this a cultivation hypothesis. + `ContextualWisdomLab/governance-risk-compliance` is the first enterprise golden/reference scenario, not a special-case algorithm. The same immutable GRC fixture must exercise both tracks: `GRC source contract -> observed facts -> generation candidates -> validation/steward review -> semantic_release -> client validation/resolution/diff/query-plan -> GRC deterministic calculation`. diff --git a/docs/doctoring/cwl_ontology_capability_inventory.md b/docs/doctoring/cwl_ontology_capability_inventory.md index e1556371..a4759f99 100644 --- a/docs/doctoring/cwl_ontology_capability_inventory.md +++ b/docs/doctoring/cwl_ontology_capability_inventory.md @@ -4,11 +4,11 @@ Evidence snapshot: 2026-09-05. Status: research inventory, not dependency-adopti ## Scope and evidence limits -The paginated organization metadata census returned 76 repositories, including one archived repository and 11 forks. Name/description screening for ontology, semantic, knowledge graph, schema, RDF, SHACL, provenance and lineage was combined with the existing Context Map; metadata matches alone miss owners whose descriptions do not use those words. This expanded the original eight-candidate audit to 12 selected candidates. The organization product-goal directive then identified DiskSage's actual OWL use, expanding the audit to 13. Subsequent naruon sender-ontology and pg-erd-cloud schema-engineering audits bring the selected total to 15. The other 61 repositories have not received a source-level capability audit, so this is not proof that all relevant implementations have been found. +The paginated organization metadata census returned 76 repositories, including one archived repository and 11 forks. Name/description screening for ontology, semantic, knowledge graph, schema, RDF, SHACL, provenance and lineage was combined with the existing Context Map; metadata matches alone miss owners whose descriptions do not use those words. This expanded the original eight-candidate audit to 12 selected candidates. The organization product-goal directive then identified DiskSage's actual OWL use, expanding the audit to 13. naruon and pg-erd-cloud brought the count to 15. The subsequent five domain/interoperability audits below bring selected source-level coverage to 20/76, leaving 56 repositories unaudited at that depth. This does not prove that all relevant implementations have been found. -Each row separates responsibility, exact default-head documentation or tree evidence, and published GitHub release evidence. A name, open PR or protected branch alone does not prove a usable released API. This is not a complete package-registry, deployment, attestation or consumer-conformance audit; bounded follow-up attempts and their limitations are recorded below. CalendarWeave and four-pillars metadata also matched, but their descriptions identify domain-specific calendar/calculation consumers, not a demonstrated shared ontology library; this is a screening disposition, not an architectural exclusion. +Each row separates responsibility, exact default-head documentation or tree evidence, and published GitHub release evidence. A name, open PR or protected branch alone does not prove a usable released API. This is not a complete package-registry, deployment, attestation or consumer-conformance audit; bounded follow-up attempts and their limitations are recorded below. CalendarWeave and four-pillars were initially screened from metadata only; their subsequent source audits now distinguish a bootstrap owner from an implemented product-domain model without excluding either by description alone. -GitHub repository, branch, README-at-SHA, complete-tree and release endpoints supplied these observations. All 15 selected default branches reported protected at their recorded observations. `context-graph-contracts`, `enterprise-architecture-core` and naruon default to `develop`; do not substitute a branch named `main` for the actual default. For the original 13 candidates, GitHub detected MIT for semantic-data-portal, TEPP, LineageWeave, fast-mlsirm and disksage; Apache-2.0 for RankWeave, graphify, Veilpick and mhtml-etl-gateway; and no SPDX identifier for the other four. An undetected license is unresolved evidence, not permission to adopt. The additional naruon audit read an explicitly proprietary license despite public visibility and NOASSERTION metadata; pg-erd-cloud's exact license contains Apache-2.0 without an appended noncommercial restriction. DeepWiki had no indexed evidence for graphify, Veilpick, disksage or pg-erd-cloud, so their exact GitHub source/tree was used instead. +GitHub repository, branch, README-at-SHA, complete-tree and release endpoints supplied these observations. All 20 selected default branches reported protected at their recorded observations. Several use `develop`; do not substitute a branch named `main` for the actual default shown in each row. For the original 13 candidates, GitHub detected MIT for semantic-data-portal, TEPP, LineageWeave, fast-mlsirm and disksage; Apache-2.0 for RankWeave, graphify, Veilpick and mhtml-etl-gateway; and no SPDX identifier for the other four. An undetected license is unresolved evidence, not permission to adopt. The naruon audit read an explicitly proprietary license despite public visibility and NOASSERTION metadata; pg-erd-cloud's exact license contains Apache-2.0 without an appended noncommercial restriction. DeepWiki had no indexed evidence for graphify, Veilpick, disksage, pg-erd-cloud or the five further candidates below, so exact GitHub source/tree was used instead. ## Owner and maturity evidence @@ -38,6 +38,24 @@ pg-erd-cloud returns relational snapshots, diffs, dictionary/export material and No runtime dependency, repository, service or database was added. The [Context Map](../CONTEXT_MAP.md) and [ADR 0006](../adr/0006-zotero-research-intake.md) still place Research Intake in ConceptWeave. A separate utility owner needs an evidenced independent consumer and deployment contract. The separate [orchestration release audit](zotero_fulltext_contract_audit.md#released-orchestration-evidence) tracks the existing model owner; it does not count that support service as another ontology implementation. +## Further domain-contract audits (2026-09-05) + +The 11:59–12:07 UTC source audits added five bounded domain/interoperability observations. These are candidates for future evidence interchange, not additional shared ontology generators. Each actual default branch reported protected; effective ruleset 18156473 requires one approval and seven workflows. Complete trees were not truncated. Paginated GitHub release and tag queries returned no entries for four repositories; four-pillars returned six releases and six commit-resolved tags. This audit inspected source and test definitions, not runtime, protected-merge verdicts or asset provenance. + +| Candidate | Exact default source and demonstrated boundary | Cultivation requirement before consumption | +| --- | --- | --- | +| Orgmetra | `develop@eb9757f8649aaad026a9865508d9aad50c1a7a4f`; the [job-analysis contract](https://github.com/ContextualWisdomLab/Orgmetra/blob/eb9757f8649aaad026a9865508d9aad50c1a7a4f/packages/hris-kernel/src/orgmetra_hris_kernel/job_analysis.py#L265) carries tenant/Job identity, source versions/digests, Task–KSAO links and explicit draft/validated states. Validated input requires complete links, a review reference and non-LLM origins. The [read boundary](https://github.com/ContextualWisdomLab/Orgmetra/blob/eb9757f8649aaad026a9865508d9aad50c1a7a4f/services/job-analysis-api/src/orgmetra_job_analysis_api/snapshot.py#L456) authorizes the exact target before persistence access. [Apache-2.0 license](https://github.com/ContextualWisdomLab/Orgmetra/blob/eb9757f8649aaad026a9865508d9aad50c1a7a4f/LICENSE). | Release a versioned evidence-observation contract with exact-consumer conformance, authorization and tenant isolation. Canonical JSON and SHA-256 prove content identity, not independent review or an authenticated occupational source. Job, Position, Assignment, KSAO requirements and employment decisions remain Orgmetra-owned; do not copy its Python kernel or interpret its 1–5 ordinal inputs as calibrated semantic weights. | +| governance-risk-compliance | `develop@529cf321f134e26c0cd379ee53c06ab5297363b6`; [evidence binding](https://github.com/ContextualWisdomLab/governance-risk-compliance/blob/529cf321f134e26c0cd379ee53c06ab5297363b6/cwl_grc/evidence.py) links artifacts to catalog requirements. [Coverage](https://github.com/ContextualWisdomLab/governance-risk-compliance/blob/529cf321f134e26c0cd379ee53c06ab5297363b6/cwl_grc/coverage.py) tests binding presence, not control effectiveness. The [HTTP boundary](https://github.com/ContextualWisdomLab/governance-risk-compliance/blob/529cf321f134e26c0cd379ee53c06ab5297363b6/cwl_grc/remote_access.py) is a loopback-only unauthenticated preview. No license file was found in the complete 50-entry tree; repository license metadata is absent. | Resolve licensing and release an authenticated, tenant-scoped observation/consumption contract. Its [Proposed ADR 0011](https://github.com/ContextualWisdomLab/governance-risk-compliance/blob/529cf321f134e26c0cd379ee53c06ab5297363b6/docs/adr/0011-separate-external-requirements-and-internal-controls.md) separates external requirements, internal controls, mapping and effectiveness; do not present that future model as implemented default-source behavior. GRC retains policy, control, risk and audit truth. | +| CalendarWeave | `main@d972ccae6225716bdff7210a1fed808c01d32689`; the [complete tree](https://github.com/ContextualWisdomLab/CalendarWeave/tree/d972ccae6225716bdff7210a1fed808c01d32689) contains only README.md. Calendar ownership and endpoint instructions are proposals, not implemented APIs, tests or an ontology library. No license file or license metadata is present. | Implement and release a licensed bounded calendar contract and conformance tests before integration. Named consumers in a README do not establish active connectivity. Preserve calendar truth with its owner. | +| four-pillars | `main@8c6a2fa76af1cb7f6bb7f56ceb4e7ce92d2f7897`; [calculation](https://github.com/ContextualWisdomLab/four-pillars/blob/8c6a2fa76af1cb7f6bb7f56ceb4e7ce92d2f7897/src/four_pillars/calendar.py#L165) and [API](https://github.com/ContextualWisdomLab/four-pillars/blob/8c6a2fa76af1cb7f6bb7f56ceb4e7ce92d2f7897/src/four_pillars/api.py#L126) implement a Python traditional-symbol calculation/report model, not general ontology generation or publication. [MIT license](https://github.com/ContextualWisdomLab/four-pillars/blob/8c6a2fa76af1cb7f6bb7f56ceb4e7ce92d2f7897/LICENSE). Latest [v0.8.0](https://github.com/ContextualWisdomLab/four-pillars/releases/tag/v0.8.0) resolves to `8853b46a064d7ec260bfbc5810e370760f8e90d3`, with wheel, source archive and checksum assets enumerated. | Preserve traditional-symbol rules as product meaning, not scientifically validated prediction or semantic approval. Any export needs explicit source/status/version contracts and exact-consumer conformance. Rust-first computation and canonical orchestration alignment belong in this owner; enumerated assets are not downloaded, authenticated or adopted artifacts. | +| learning-interoperability-contracts | `develop@ba2948de245448eab739329f1131a36b4e59a54d`; the [complete tree](https://github.com/ContextualWisdomLab/learning-interoperability-contracts/tree/ba2948de245448eab739329f1131a36b4e59a54d) contains only a bootstrap README.md, without implemented schemas, tests or a license. | Establish licensing, versioned interchange schemas and consumer conformance before release. Product learning meanings remain with the learning-domain owners; a bootstrap anchor is not a shared implementation. | + +Orgmetra's source validation is useful evidence of a domain-specific relationship contract, but it does not authenticate a self-supplied review reference. Its [ADR 0007](https://github.com/ContextualWisdomLab/Orgmetra/blob/eb9757f8649aaad026a9865508d9aad50c1a7a4f/docs/adr/0007-governed-job-analysis-evidence.md) also still says “Accepted on stacked implementation branch”; that status wording is not a release or current protected-review receipt. The audit did not execute the kernel, service, database, occupational-source retrieval or any employment decision. + +The GRC reference scenario must preserve the distinction between a documented relationship and assurance. NIST IR 8477 describes explicit concept-mapping approaches; OSCAL's mapping model preserves relation types and source/target context without copying source control text (Scarfone et al., 2024; National Institute of Standards and Technology, n.d.). These sources guide future mapping fixtures; they neither supply authentic Zotero labels nor turn an evidence link into an effective control. No GRC service, private evidence or database was accessed. + +four-pillars' current default is three commits ahead of v0.8.0. Its [12 KASI Jie-term test cases](https://github.com/ContextualWisdomLab/four-pillars/blob/8c6a2fa76af1cb7f6bb7f56ceb4e7ce92d2f7897/tests/test_solar_term_golden.py) specify a 120-second tolerance, but that test file is absent from the released tree and calendar/model implementation differs. The tests were not executed here. [Report quality validation](https://github.com/ContextualWisdomLab/four-pillars/blob/8c6a2fa76af1cb7f6bb7f56ceb4e7ce92d2f7897/src/four_pillars/quality.py) checks fingerprints, supplied pillars, completeness and prohibited language, not scientific predictive validity. Fixed element weights and [direct-NIM default configuration](https://github.com/ContextualWisdomLab/four-pillars/blob/8c6a2fa76af1cb7f6bb7f56ceb4e7ce92d2f7897/src/four_pillars/settings.py), despite optional CO integration, are owner alignment findings. No model call, release installation or cross-product source adoption was performed. + ## Actual Zotero evidence The repaired read-only executable at `a359c5b9d1013e84f5832506f5a57aec364e6493` captured a new report/worksheet pair from Zotero 10.0.1, API 3, schema 44, library version 2. It binds captured provider JSON values and the actual typed classifier inputs with the versioned snapshot digest. The earlier `22030ae6c8510d9eb8f7b07d98959bb69d2bd286` schema-44 capture is pre-repair observation; the 9.0.6/schema-42/library-12341 pair is also historical. Neither was overwritten. Zotero documents that pre-10 synced revisions could remain unchanged after local edits; version 10 metadata uses instance-local revisions (Zotero, 2026). These version spaces must not be compared or merged. Equal record totals do not prove unchanged content. The separate full-text endpoints have an observed version-contract mismatch and must not inherit this metadata-version assumption. @@ -54,8 +72,8 @@ The PR #10 findings for [provider metadata lost before hashing](https://github.c | Measure | Observation | Required next evidence | | --- | --- | --- | -| Metadata census / bounded capability audit | 76 metadata records; 15/15 selected candidates have exact default-head documentation/source/tree and release-query evidence | Audit actual contracts/consumers; metadata coverage is not full source-level discovery, and 61 repositories remain unaudited at that depth. | -| GitHub release with resolved source commit | 4/15 selected candidates, including proprietary naruon | License, artifact/provenance and consumer conformance; a release count is not permissive-library or adoption evidence. | +| Metadata census / bounded capability audit | 76 metadata records; 20/20 selected candidates have exact default-head documentation/source/tree and release-query evidence | Audit actual contracts/consumers; metadata coverage is not full source-level discovery, and 56 repositories remain unaudited at that depth. | +| GitHub release with resolved source commit | 5/20 selected candidates, including proprietary naruon and product-domain four-pillars | License, artifact/provenance and consumer conformance; a release count is not permissive-library or adoption evidence. | | Verified ConceptWeave adoption | 0 demonstrated in this audit | Released owner contract, exact consumer revision and passing contract/runtime evidence. | | Privately retained nonempty full text | 3,203/3,715 parents in the separate [capture evidence](zotero_fulltext_capture_evidence.json); 512 remain without demonstrated nonempty text | New content-bound proposals and authentic review with partial/unknown indexing. The earlier sweep's 800/1,000 abstract-missing subgroup is availability evidence, not a remeasured retained-capture subgroup. | | Unverified steward decisions | 0/3,715 on the repaired snapshot; first pending batch has 0/25 decisions | Authentic snapshot-bound decisions; batch generation is not review progress. | @@ -65,6 +83,20 @@ Review the repaired current snapshot, including missing-abstract and unsupported ## References +ContextualWisdomLab. (2026). *CalendarWeave* (Commit d972ccae6225716bdff7210a1fed808c01d32689) [Source repository]. GitHub. https://github.com/ContextualWisdomLab/CalendarWeave/tree/d972ccae6225716bdff7210a1fed808c01d32689 + +ContextualWisdomLab. (2026). *four-pillars* (Commit 8c6a2fa76af1cb7f6bb7f56ceb4e7ce92d2f7897) [Computer software]. GitHub. https://github.com/ContextualWisdomLab/four-pillars/tree/8c6a2fa76af1cb7f6bb7f56ceb4e7ce92d2f7897 + +ContextualWisdomLab. (2026). *learning-interoperability-contracts* (Commit ba2948de245448eab739329f1131a36b4e59a54d) [Source repository]. GitHub. https://github.com/ContextualWisdomLab/learning-interoperability-contracts/tree/ba2948de245448eab739329f1131a36b4e59a54d + +ContextualWisdomLab. (2026). *Orgmetra* (Commit eb9757f8649aaad026a9865508d9aad50c1a7a4f) [Computer software]. GitHub. https://github.com/ContextualWisdomLab/Orgmetra/tree/eb9757f8649aaad026a9865508d9aad50c1a7a4f + +ContextualWisdomLab. (2026). *CWL GRC* (Commit 529cf321f134e26c0cd379ee53c06ab5297363b6) [Computer software]. GitHub. https://github.com/ContextualWisdomLab/governance-risk-compliance/tree/529cf321f134e26c0cd379ee53c06ab5297363b6 + +National Institute of Standards and Technology. (n.d.). *OSCAL control mapping model*. Retrieved September 5, 2026, from https://pages.nist.gov/OSCAL/learn/concepts/layer/control/mapping/ + +Scarfone, K., Souppaya, M., & Fagan, M. (2024). *Mapping relationships between documentary standards, regulations, frameworks, and guidelines: Developing cybersecurity and privacy concept mappings* (NIST IR 8477). National Institute of Standards and Technology. https://doi.org/10.6028/NIST.IR.8477 + Groth, P., & Moreau, L. (Eds.). (2013, April 30). *PROV-overview: An overview of the PROV family of documents* (W3C Working Group Note). World Wide Web Consortium. https://www.w3.org/TR/2013/NOTE-prov-overview-20130430/ Knublauch, H., & Kontokostas, D. (Eds.). (2017, July 20). *Shapes constraint language (SHACL)* (W3C Recommendation). World Wide Web Consortium. https://www.w3.org/TR/2017/REC-shacl-20170720/ diff --git a/docs/doctoring/zotero_fulltext_contract_audit.md b/docs/doctoring/zotero_fulltext_contract_audit.md index 7288fb8f..caf5cd4b 100644 --- a/docs/doctoring/zotero_fulltext_contract_audit.md +++ b/docs/doctoring/zotero_fulltext_contract_audit.md @@ -85,6 +85,8 @@ GitHub does contain deployment records: 66 observed records had 57 failure, one The existing owner [PR #1030](https://github.com/ContextualWisdomLab/contextual-orchestrator/pull/1030), Draft at `f753f453ce4fc3dbc612bb9bdbb8db4cbfd93c16`, already owns immutable release work under ADR 0129. The contacted CO integration task reported release artifacts, schema digest and gateway deployed-version evidence as unproved. It later clarified that its single-writer scope is #1067/#1074, not #1030, and will identify the existing release owner's exact evidence. Confirmation from that actual release owner remains pending; no duplicate release machinery was requested. At the subsequent branch check, both branch and Git-ref endpoints still returned `a080297d2546bb61e89520d637cabc202db331ec`, while PR #1030's base object returned `2e414d15ba58f28597751b625a8a2f00fc9fadcf`. The PR base observation is not substituted for the default-branch ref. +The 12:05:30–12:07:20 UTC refresh confirmed the same protected default SHA, zero releases/tags and unchanged Draft #1030. In the new-artifact window since 08:58, the newest-100 listing added three SBOMs and one Strix report, not a released client/schema bundle; this was not an audit of all 7,026 historical artifacts. Deployment `6277953573` became successful at 11:27:06 through [Provider catalog sync run 33948079376](https://github.com/ContextualWisdomLab/contextual-orchestrator/actions/runs/33948079376). New queued deployment `6280414175` likewise points to a [provider-catalog job](https://github.com/ContextualWisdomLab/contextual-orchestrator/actions/runs/33961185349/job/101299036078), with no gateway environment URL. The earlier 66-record state partition is historical, not the new total. The existing integration task was idle with no new release evidence and was not restarted or reassigned release ownership. + Admission requires an immutable owner artifact and schema digest, protected-source provenance, an identified deployed gateway version and exact-consumer contract evidence. Until then, no model-assisted proposal is generated through copied source, a temporary branch or a direct provider. Catalog-sync success, source documentation and a Draft release PR cannot satisfy this gate. Review labels must not be invented to compensate for unavailable model assistance. ## Follow-up: privately retained content, not reclassification diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 39b7f6fa..b021f4ee 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -6,7 +6,7 @@ This file records code-current product and technical gaps. Exact PR/check/run co ## Protected truth and active stack -The 11:13:09–11:15:04 UTC refresh found 31 open PRs, 30 Draft, unchanged existing heads/bases, 36 unresolved threads and no new approvals or terminal check results since 10:48. PR #35 has a new COMMENTED review following the counterevidence, not a changed approval verdict. Central `.github/main` advanced to `f250638827f8252b0d9e5cb2601f4d333f96162f` through #1922; its trigger/test-isolation change does not repair Noema/CodeQL or retroactively rebind queued CodeQL successors from `7fcada597d5b79bdb14445f24322b2c9f6ed4b19`. Current-main hosted GREEN was not established. This paragraph is a dated observation, not transferable acceptance evidence for the new child work below. +The 11:58:19–12:00:15 UTC refresh found 32 open PRs, 31 Draft, 36 unresolved threads and no new submitted reviews, thread comments or terminal check results since 11:15:04. No current-head approval exists. Every named head/base matched its actual ref. PR #38 alone advanced with the preserved writer-size regression and repair to `351a0c88368e7ea07330de16ef3f5daa83661fd3`; its only check was an explicit CodeRabbit Draft skip. #35's CHANGES_REQUESTED and later COMMENTED reviews were unchanged. Central `.github/main` remains `f250638827f8252b0d9e5cb2601f4d333f96162f`; its earlier #1922 trigger/test-isolation change does not repair Noema/CodeQL or retroactively rebind queued successors from `7fcada597d5b79bdb14445f24322b2c9f6ed4b19`. The three previously recorded CodeQL successors remain queued at attempt one. Effective ruleset 18156473 still requires one approval, stale-review dismissal, resolved threads and seven workflows, with deletion/non-fast-forward protection. This is a dated checkpoint, not acceptance evidence for subsequent commits below. Protected/default `main` remains `f4f440dd58c77d7cd90dff8a1eb2eeb9a9940425`; only the bootstrap state is shipped there and no immutable ConceptWeave release exists. @@ -78,7 +78,7 @@ All four new artifacts remain private mode `0600`, outside the repository: The repaired worksheet has 0/3,715 decisions and the first batch has 0/25; externally approved full-review coverage remains 0/3,715. No authorization prompt, approval, Zotero write, record merge/deletion or rollback was performed. The three historical Zotero 9 artifact hashes below were rechecked unchanged. The pre-repair schema-44 artifacts are also preserved. Stronger source binding is not classification correctness, business approval or loopback peer authentication. -The [CWL ontology capability inventory](doctoring/cwl_ontology_capability_inventory.md) now separates a 76-repository metadata census from a 15-candidate exact-default-head capability audit, up from 13. Four selected candidates have GitHub releases with resolved source commits: RankWeave, mhtml-etl-gateway, fast-mlsirm and naruon. naruon owns product-specific sender relationships and is explicitly proprietary, so its release does not satisfy permissive-library adoption. pg-erd-cloud implements adjacent relational-schema evidence and heuristic relation proposals under Apache-2.0, with no returned GitHub release or tag. DiskSage owns its implemented Rust filesystem ontology subset, not general semantic publication. Veilpick's protected tree contains only a license; graphify is an upstream fork without a returned CWL release. These are bounded maturity observations, not adoption receipts. Source-level discovery remains incomplete for 61 repositories, and actual ConceptWeave adoption remains unproved. No additional utility owner is justified yet. +The [CWL ontology capability inventory](doctoring/cwl_ontology_capability_inventory.md) now separates the 76-repository metadata census from 20 exact-default-source audits, up from 15. The five additions distinguish Orgmetra's Task–KSAO evidence contract, GRC's evidence-binding preview, four-pillars' product-domain calculation model, and README-only CalendarWeave/learning-interoperability-contracts. Five selected candidates have GitHub releases with resolved source commits: RankWeave, mhtml-etl-gateway, fast-mlsirm, proprietary naruon and four-pillars. Current-main tests in four-pillars are not attributed to its older v0.8.0 release. Missing licenses, unverified artifacts, absent releases and optional/direct-provider configuration remain owner cultivation gaps. NIST IR 8477 and OSCAL mapping references guide future relation-semantics tests without granting assurance or labels. Source-level discovery remains incomplete for 56 repositories; exact-consumer adoption remains unproved, and no additional utility owner is justified. Next: apply only authentic snapshot-bound steward decisions to the repaired batch, cover all 3,715 items and independently verify full-review approval; continue owner contract discovery and protected Foundation work while external checks/reviews are pending. The current body notes for PRs #11–#33 distinguish inherited source repair from historical head/check claims. No PR was closed, approved, merged or retargeted by this repair. @@ -96,7 +96,7 @@ Replayable retained-text coverage has progressed from 0 to 3,203/3,715 parents. Committed regressions repaired inherited environment proxies, exact-byte-limit rejection and replay checks occurring after digest allocation; clock fault injection verifies late/invalid-clock failures without changing the deadline. Final source verification at `733425df01511d894277fb8682e070f3dde03689` passed 173 tests across 37 suites including documentation tests, strict Clippy, formatting, rustdoc with warnings denied, the CI contract and the existing coverage gate. Coverage is 347/347 functions, 3,710/3,710 source-normalized regions and 674/674 source-normalized branch outcomes. Raw LLVM totals remain 4,159/4,255 lines, 6,129/6,274 regions and 603/674 branch outcomes; those are not 100%. The only source delta after the live run is a writer type alias resolving strict Clippy's complexity finding without changing runtime behavior. Hosted checks and independent protected approval remain separate gates. -Next: revalidate current-head protected gates after the completed transport cascade below; carry the new read-only view's exact context into a separately verified decision and approval contract without stripping its capture binding; continue the 61 remaining repository capability audits and the upstream version-contract repair. Unchanged predictions do not require a new proposal run merely to display retained evidence. A released contextual-orchestrator integration artifact remains unverified at the audited protected owner head, so source documentation alone does not authorize model-provider bypass. No new utility repository is justified by this one intake seam. +At this earlier capture checkpoint, the next gap was the context-bound review chain implemented below. Remaining work now includes authentic decisions and independent approval, full-text-aware write admission, the 56 unaudited repository sources and the upstream version-contract repair. Unchanged predictions do not require a new proposal run merely to display retained evidence. A released contextual-orchestrator integration artifact remains unverified at the audited protected owner head, so source documentation alone does not authorize model-provider bypass. No new utility repository is justified by this one intake seam. ### Canonical transport repair and released-owner audit From 3059c0ed69e669965e3ffac2d32c100896987179 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 21:16:59 +0900 Subject: [PATCH 24/39] test(zotero): independently expose enum diagnostic disclosure --- .../tests/bound_full_text_commands_cli.rs | 50 ++++++++++++------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/crates/conceptweave-zotero/tests/bound_full_text_commands_cli.rs b/crates/conceptweave-zotero/tests/bound_full_text_commands_cli.rs index 26992182..9886ebac 100644 --- a/crates/conceptweave-zotero/tests/bound_full_text_commands_cli.rs +++ b/crates/conceptweave-zotero/tests/bound_full_text_commands_cli.rs @@ -16,29 +16,20 @@ use std::path::{Path, PathBuf}; use std::process::{Command, Output}; #[test] -fn cli_private_json_errors_never_echo_source_keys_or_dispositions() { +fn cli_private_json_errors_never_echo_source_keys_or_approval_fields() { let case = Case::new("diagnostic-privacy"); let [report, worksheet, capture] = case.inputs(); let original = fs::read(&worksheet).unwrap(); - let original_value: Value = serde_json::from_slice(&original).unwrap(); - let mut unknown_field = original_value.clone(); + let mut unknown_field: Value = serde_json::from_slice(&original).unwrap(); unknown_field["synthetic-private-field-sentinel"] = true.into(); - let mut invalid_value = original_value.clone(); - invalid_value["full_text_worksheet_v1"]["decisions"][0]["reviewed_disposition"] = - "synthetic-private-enum-sentinel".into(); - for (index, invalid) in [unknown_field, invalid_value.clone()] - .into_iter() - .enumerate() - { - let invalid = case.json(&format!("invalid-worksheet-{index}"), &invalid); - let output = case.path(&format!("rejected-{index}")); - let command = run( - "--bound-full-text-review", - &[&report, &invalid, &capture, Path::new("1"), &output], - ); - assert_private_parse_failure(command, "worksheet"); - assert!(!output.exists()); - } + let invalid = case.json("invalid-worksheet", &unknown_field); + let output = case.path("rejected"); + let command = run( + "--bound-full-text-review", + &[&report, &invalid, &capture, Path::new("1"), &output], + ); + assert_private_parse_failure(command, "worksheet"); + assert!(!output.exists()); let mut approval = serde_json::to_value(case.approval()).unwrap(); approval["synthetic-private-approval-sentinel"] = true.into(); let approval = case.json("invalid-approval", &approval); @@ -51,6 +42,27 @@ fn cli_private_json_errors_never_echo_source_keys_or_dispositions() { "approval", ); assert!(!golden.exists()); + assert_eq!(fs::read(&worksheet).unwrap(), original); +} + +#[test] +fn cli_private_json_errors_never_echo_disposition_values() { + let case = Case::new("enum-diagnostic-privacy"); + let [report, worksheet, capture] = case.inputs(); + let original = fs::read(&worksheet).unwrap(); + let mut invalid_value: Value = serde_json::from_slice(&original).unwrap(); + invalid_value["full_text_worksheet_v1"]["decisions"][0]["reviewed_disposition"] = + "synthetic-private-enum-sentinel".into(); + let invalid = case.json("invalid-worksheet", &invalid_value); + let output = case.path("rejected"); + assert_private_parse_failure( + run( + "--bound-full-text-review", + &[&report, &invalid, &capture, Path::new("1"), &output], + ), + "worksheet", + ); + assert!(!output.exists()); // Metadata-only siblings share the same parser and must not retain the leak. let legacy = case.json( "invalid-legacy-worksheet", From d5fda2028dbf1ef1d5b919c9e30d3b808ce88887 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 21:17:43 +0900 Subject: [PATCH 25/39] fix(zotero): keep private JSON parse diagnostics source-free --- crates/conceptweave-zotero/src/main.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/crates/conceptweave-zotero/src/main.rs b/crates/conceptweave-zotero/src/main.rs index ce286adb..6345cde1 100644 --- a/crates/conceptweave-zotero/src/main.rs +++ b/crates/conceptweave-zotero/src/main.rs @@ -484,13 +484,13 @@ fn validate_opened_identity( }) } -/// Reads JSON without allowing the input to exceed or grow past the artifact limit. +/// Reads bounded JSON without exposing rejected field names or values in diagnostics. fn read_bounded_json( reader: &mut dyn Read, advertised_len: u64, ) -> io::Result { serde_json::from_slice(&read_bounded_bytes(reader, advertised_len)?) - .map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error)) + .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "review input is invalid")) } /// Shares the metadata byte ceiling without normalizing completed review JSON. @@ -1464,6 +1464,10 @@ mod tests { read_bounded_json::(&mut io::empty(), MAX_ARTIFACT_BYTES + 1) .unwrap_err(); assert_eq!(oversized.kind(), io::ErrorKind::InvalidData); + assert_eq!( + oversized.to_string(), + "review input exceeds the artifact size limit" + ); let grown = read_bounded_json::( &mut io::repeat(b' ').take(MAX_ARTIFACT_BYTES + 1), @@ -1471,10 +1475,15 @@ mod tests { ) .unwrap_err(); assert_eq!(grown.kind(), io::ErrorKind::InvalidData); + assert_eq!( + grown.to_string(), + "review input grew beyond the artifact size limit" + ); let read_failure = read_bounded_json::(&mut FailingReader, 0).unwrap_err(); assert_eq!(read_failure.kind(), io::ErrorKind::Other); + assert_eq!(read_failure.to_string(), "injected read failure"); let labeled = label_input( "worksheet", From 6dc5f3ca09729b5594130722d37af47d1bf30462 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 21:32:18 +0900 Subject: [PATCH 26/39] docs: record verified private review command evidence --- CHANGELOG.md | 2 + THREAT_MODEL.md | 2 +- docs/TRD.md | 2 + docs/adr/0006-zotero-research-intake.md | 2 + ...zotero_bound_review_commands_evidence.json | 53 +++++++++++++++++++ .../zotero_fulltext_contract_audit.md | 4 +- docs/product-technical-gap-baseline.md | 22 ++++++-- 7 files changed, 81 insertions(+), 6 deletions(-) create mode 100644 docs/doctoring/zotero_bound_review_commands_evidence.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 1674a85d..56a009a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,8 @@ All notable changes to ConceptWeave are documented here. ### Security +- Invalid private review files no longer expose rejected field names or values in error messages. File-role, size and access errors remain distinguishable. + - Completed text-review files reject changed evidence, stale decisions and duplicate fields before updating local work. Earlier approvals cannot silently acquire later text evidence. - Local research requests bypass environment-configured proxies. This prevents unintended proxy forwarding; local peer authentication remains an explicit release limitation. diff --git a/THREAT_MODEL.md b/THREAT_MODEL.md index 648522ae..21fc3ca6 100644 --- a/THREAT_MODEL.md +++ b/THREAT_MODEL.md @@ -79,6 +79,6 @@ The optional full-text review view must not be confused with the metadata-only d Capture-bound review uses separate required versioned payloads, with no import of previous metadata decisions. Atomic application permits only completed decision slots in the exact current view; recursive duplicate-key rejection precedes projection so a changed first field cannot hide behind a canonical later field. Finalization and evaluation revalidate the capture against the complete current report, including proposal records, before authority is contacted. Governance must authenticate the entire outer reviewed set and every label. Owner-only storage, private fields and hashes do not defeat a malicious local replacement or prove human review. Preserve old artifacts; stale views fail rather than overwrite concurrent work. These APIs neither implement authenticated governance nor authorize the independent Zotero write contract. Restoring report/capture/worksheet/approval JSON still requires caller-owned bounded private-file admission. -The offline full-text commands reuse those private readers and pass completed-view bytes unchanged into the duplicate-key-aware application boundary. Distinct argument strings do not replace opened-file identity checks. Metadata/review serialization must fit the inclusive 16 MiB reader limit before a new file is created; the larger capture remains a separate bounded artifact. Finalization writes only an input for independent verification, never an approval result. Test-only labels and receipts cannot be substituted for genuine campaign decisions or authority. +The offline full-text commands reuse those private readers and pass completed-view bytes unchanged into the duplicate-key-aware application boundary. Distinct argument strings do not replace opened-file identity checks. Metadata/review serialization must fit the inclusive 16 MiB reader limit before a new file is created; the larger capture remains a separate bounded artifact. Shared private JSON deserialization replaces syntax/type errors with static diagnostics, preventing unknown-field names or invalid enum values from echoing private material. Role, size and I/O errors remain distinguishable. Finalization writes only an input for independent verification, never an approval result. Test-only labels and receipts cannot be substituted for genuine campaign decisions or authority. A capability is not release-ready while a valid security finding lacks a deterministic test or equivalent machine-verifiable contract, while required exact-head checks are non-terminal, or while the implemented transport cannot satisfy the advertised security claim. Documentation must describe residual risk without upgrading provider guarantees by inference. diff --git a/docs/TRD.md b/docs/TRD.md index be00805c..6a0b68ee 100644 --- a/docs/TRD.md +++ b/docs/TRD.md @@ -92,6 +92,8 @@ The private commands reuse those library contracts without rereading Zotero: All file arguments must be distinct direct children of a canonical temporary directory. Every opened input must also have a distinct device/inode identity and satisfy the existing regular-file, single-link, exact-`0600`, no-follow and bounded-read policy. Completed-view bytes reach `apply_full_text_review_view` unchanged; projecting them to a JSON value in the CLI would erase duplicate keys before the owner can reject them. Metadata/review reads and writes have an inclusive 16 MiB ceiling. The shared metadata writer rejects overflow before file creation. Captures keep their separate buffered 512 MiB input ceiling and streaming writer. Outputs use create-new `0600` protection, never replacing existing files. The legacy `--full-text-review` remains a read-only metadata-worksheet view, not a substitute for these capture-bound commands. +Private JSON syntax/type failures return one static invalid-input error rather than Serde's rejected field names or values. This shared reader rule covers existing metadata commands as well as the new full-text commands. Artifact-role prefixes, size/growth failures and filesystem I/O errors remain distinct; completed-view and capture parsers retain their own source-free errors. + There is no CLI approval issuer or evaluator. Finalization accepts an independently supplied approval input but cannot authenticate it. Whole-envelope external verification remains a library boundary, not deployed governance. Write planning still has its separate `ReviewedClassificationWriteSet` and approval verifier; there is no conversion or authority transfer from a full-text evaluation to a Zotero write plan. A future write admission must explicitly preserve this context and authenticate the exact proposed collection/tag changes. `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. diff --git a/docs/adr/0006-zotero-research-intake.md b/docs/adr/0006-zotero-research-intake.md index bda94169..00ef86b2 100644 --- a/docs/adr/0006-zotero-research-intake.md +++ b/docs/adr/0006-zotero-research-intake.md @@ -53,6 +53,8 @@ In the context of an operator reviewing retained paper text without accessing mu The command sequence is blank initialization, bounded pending view, exact completed-view application to a new worksheet, and complete-review finalization to a new reviewed set. Each command reuses the existing owner-only readers, opened identity comparisons and create-new output. Completed JSON crosses the CLI as bytes rather than a projected value, because projection would discard duplicate keys before atomic application sees them. Finalization receives a separately issued approval input but does not authenticate it; the existing whole-envelope library verifier and independent Zotero write-admission contract remain outside the CLI. Neither a successfully saved file nor a test receipt counts as completed classification or governance approval. +Independent review also found that Serde's ordinary syntax/type diagnostics could echo private unknown-field names or invalid disposition values. Separate synthetic REDs `d3fdaa5` and `3059c0e` reproduced both paths through actual commands before shared-reader repair `d5fda20`. We chose one static parse diagnostic at the common private JSON boundary over per-command sanitizers or regular-expression filtering. This covers existing metadata callers too, while preserving actionable artifact roles, explicit size/growth errors and I/O failures. The cost is less detailed private JSON troubleshooting; preventing source content from reaching terminal/log output takes precedence. Earlier open predecessor branches require the same minimal repair before their own acceptance, without reverse-merging later features. + The concurrent owner regression at `4aabc6a` showed that the shared metadata writer could save a file larger than its 16 MiB reader accepted. Its `351a0c8` guard is retained in normal history and rejects oversized output before creation. Captures use the separate streaming writer and are not reduced to the metadata ceiling. A malformed JSON permission-test fixture introduced in that same commit was reproduced and corrected separately, preserving the guard and failure history. Positive effects are callable offline review and consistent save/read limits. Costs are additional private artifacts, explicit output-path management and repeated validation of a potentially large capture. Authenticated review service, full-text-aware write admission and a released orchestration integration remain separate prerequisites; this amendment does not make their status Accepted. ### 2026-09-05 capture-bound review application amendment (Proposed) diff --git a/docs/doctoring/zotero_bound_review_commands_evidence.json b/docs/doctoring/zotero_bound_review_commands_evidence.json new file mode 100644 index 00000000..fa80e44b --- /dev/null +++ b/docs/doctoring/zotero_bound_review_commands_evidence.json @@ -0,0 +1,53 @@ +{ + "evidence_kind": "offline_capture_bound_review_view_v1", + "source_commit": "2fadbdaba3cd546f6c345ed4a950baef79325982", + "preflight_at": "2026-09-05T12:19:52.551Z", + "output_created_at": "2026-09-05T12:20:49.714Z", + "artifact_audited_at": "2026-09-05T12:21:47.264Z", + "command": "--bound-full-text-review REPORT WORKSHEET CAPTURE 25 VIEW", + "input_file_sha256": { + "metadata_report": "bf45248413f433a537fe8fc62c02b93eef3c7e47ff6245f31610e9ba72031d8d", + "capture_bound_blank_worksheet": "36bcf0679de068a744fd4ae8709cd192d99cb556591420d7a43c2b5f0e633a46", + "full_text_capture": "56d385398c8da559aa597a4e3783d946638855bba19ac808ce81d917bf06f94d" + }, + "capture_digest": "sha256:429d98dc90e172b4f0bb4e3e1c493feb33b61664793d02a53d8183fb76f76a50", + "output": { + "file_name": "conceptweave-zotero-20260905-bound-review-batch-001-v1.json", + "outside_repository": true, + "bytes": 1590742, + "file_sha256": "e99a1963f3b5d7adfb62070785f2b69f1d9efd1d4882d365a5ca6f6b8d70f34a", + "mode": "0600", + "regular_single_link": true, + "byte_equal_prior_read_only_view": true + }, + "measurement": { + "elapsed_seconds": 2.22, + "user_seconds": 1.72, + "system_seconds": 0.09, + "maximum_resident_bytes": 288505856, + "peak_memory_footprint_bytes": 287343168 + }, + "audit": { + "all_three_input_hashes_unchanged": true, + "all_inputs_regular_single_link_mode_0600": true, + "worksheet_rows": 3715, + "blank_worksheet_decisions": 3715, + "selected_pending_rows": 25, + "remaining_papers": 3715, + "selected_parents_with_nonempty_text": 21, + "selected_parents_without_nonempty_text": 4, + "selected_attachment_records": 21, + "selected_attachment_http_200": 21, + "zotero_requests": 0, + "model_requests": 0, + "new_proposals": 0, + "authentic_decisions": 0, + "independently_approved_labels": 0 + }, + "limits": [ + "This verifies offline pending-view creation, not real decision application or approval.", + "Application and finalization were exercised with synthetic test-only inputs, never fabricated campaign labels.", + "Byte equality with the earlier view confirms reuse; it is not additional retained-text coverage.", + "Protected integration, external governance verification and Zotero write admission remain separate gates." + ] +} diff --git a/docs/doctoring/zotero_fulltext_contract_audit.md b/docs/doctoring/zotero_fulltext_contract_audit.md index caf5cd4b..d45d9c6b 100644 --- a/docs/doctoring/zotero_fulltext_contract_audit.md +++ b/docs/doctoring/zotero_fulltext_contract_audit.md @@ -126,7 +126,9 @@ The final non-force cascade reaches #34 `b0119a57047e7b1fe5ddfbbf4b973de0f15de17 ### Review binding follow-up, 2026-09-05 -The [blank initialization evidence](zotero_fulltext_review_binding_evidence.json) and [current baseline](../product-technical-gap-baseline.md#capture-bound-review-application-and-blank-initialization) record the separate capture-bound review contract at `da406cf6110888808fa530592bbce2b774b73f33`. The original source files remain unchanged, 3,715 new review slots are blank, and no approval, model call or Zotero write occurred. Its CLI only initializes work; completed-view application and whole-envelope governance verification are library APIs. This does not resolve the provider version-space defect or supply released orchestration. +The [blank initialization evidence](zotero_fulltext_review_binding_evidence.json) and [baseline](../product-technical-gap-baseline.md#capture-bound-review-application-and-blank-initialization) record the initial separate review contract at `da406cf6110888808fa530592bbce2b774b73f33`. Its CLI then only initialized work. The original source files remain unchanged, 3,715 new review slots are blank, and no approval, model call or Zotero write occurred. + +The subsequent [private command evidence](zotero_bound_review_commands_evidence.json), at `2fadbdaba3cd546f6c345ed4a950baef79325982`, verifies the new capture-bound pending-view command against those real saved files. It created a separate 1,590,742-byte `0600` single-link output in 2.22 seconds, with maximum resident memory 288,505,856 bytes. A separate read-only audit at 12:21:47.264 UTC confirmed all input hashes, the exact capture marker, 25 blank pending rows, 21 parents with nonempty text and four without, and byte equality with the earlier read-only view. Equality proves reuse, not more reviewed papers or additional text coverage. The same CLI now exposes atomic application and complete-review finalization, exercised only with test inputs; authenticated governance verification remains outside it. Authentic decisions and independently approved labels remain 0/3,715. Neither this work nor source-free error diagnostics resolve the provider version-space defect or supply released orchestration. For restored owned review artifacts, unknown fields must fail instead of disappearing during deserialization. Serde documents that distinction and disallows combining its strict container attribute with flattening (Serde contributors, n.d.). JSON object-name duplication has inconsistent receiver behavior under RFC 8259; ConceptWeave rejects duplicate decoded keys recursively before comparing completed evidence views, rather than accepting last-key-wins projection (Bray, 2017, Section 4). These primary references support the input-contract choice, not semantic-label correctness. Context7's monthly quota was exhausted and DeepWiki did not index this repository during this follow-up; direct official documentation and current source/tests supplied the evidence. diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index d4030c3b..bec97835 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -8,7 +8,7 @@ This file records code-current product and technical gaps. Exact PR/check/run co The 11:58:19–12:00:15 UTC refresh found 32 open PRs, 31 Draft, 36 unresolved threads and no new submitted reviews, thread comments or terminal check results since 11:15:04. No current-head approval exists. Every named head/base matched its actual ref. PR #38 alone advanced with the preserved writer-size regression and repair to `351a0c88368e7ea07330de16ef3f5daa83661fd3`; its only check was an explicit CodeRabbit Draft skip. #35's CHANGES_REQUESTED and later COMMENTED reviews were unchanged. Central `.github/main` remains `f250638827f8252b0d9e5cb2601f4d333f96162f`; its earlier #1922 trigger/test-isolation change does not repair Noema/CodeQL or retroactively rebind queued successors from `7fcada597d5b79bdb14445f24322b2c9f6ed4b19`. The three previously recorded CodeQL successors remain queued at attempt one. Effective ruleset 18156473 still requires one approval, stale-review dismissal, resolved threads and seven workflows, with deletion/non-fast-forward protection. This is a dated checkpoint, not acceptance evidence for subsequent commits below. -The 12:05 UTC #38 review found one additional bounded-artifact invariant: metadata input is capped at 16 MiB, but the shared private metadata writer could create a larger artifact that the same owner could not read back. Synthetic RED `4aabc6ab59fc16447bfe9b78cb12aeb2997a0a9d` requires fail-closed rejection before file creation. Runtime repair `351a0c88368e7ea07330de16ef3f5daa83661fd3`, followed by one-line fixture restoration `ee9e263b59a7249460fc797f0d8a11203b3fa932`, applies that bound only to metadata output; the independently bounded 512 MiB streaming full-text capture path remains unchanged. No pull-request workflow run existed for `ee9e263b59a7249460fc797f0d8a11203b3fa932`, so this is source/test repair evidence rather than hosted current-head GREEN. +The 12:05 UTC #38 review found one additional bounded-artifact invariant: metadata input is capped at 16 MiB, but the shared private metadata writer could create a larger artifact that the same owner could not read back. Synthetic RED `4aabc6ab59fc16447bfe9b78cb12aeb2997a0a9d` requires fail-closed rejection before file creation. Runtime repair `351a0c88368e7ea07330de16ef3f5daa83661fd3`, followed by one-line fixture restoration `ee9e263b59a7249460fc797f0d8a11203b3fa932`, applies that bound only to metadata output. Full-text capture retains its streaming writer and separate 512 MiB input ceiling; the metadata guard adds no serialized-capture output ceiling. No pull-request workflow run existed for `ee9e263b59a7249460fc797f0d8a11203b3fa932`, so this is source/test repair evidence rather than hosted current-head GREEN. Protected/default `main` remains `f4f440dd58c77d7cd90dff8a1eb2eeb9a9940425`; only the bootstrap state is shipped there and no immutable ConceptWeave release exists. @@ -130,7 +130,7 @@ At the historical `54383ea` view checkpoint, PRD FR-9, TRD, Proposed ADR 0006, D ### Capture-bound review application and blank initialization -[Capture-bound review PR #38](https://github.com/ContextualWisdomLab/ConceptWeave/pull/38) is an open Draft child of private review view #37. It preserves capture identity through blank worksheet creation, exact completed-view application, finalization and whole-envelope external verification. Integrated runtime `da406cf6110888808fa530592bbce2b774b73f33` contains ordinary merge `21ef42d` for CLI initialization and the preceding root core repairs. No parent delta or PR was discarded. CLI support is deliberately limited to `--full-text-worksheet`; the remaining full-text operations are library APIs, not an end-to-end steward interface or a live approval service. +[Capture-bound review PR #38](https://github.com/ContextualWisdomLab/ConceptWeave/pull/38) is an open Draft child of private review view #37. It preserves capture identity through blank worksheet creation, exact completed-view application, finalization and whole-envelope external verification. Its initial integrated runtime `da406cf6110888808fa530592bbce2b774b73f33` contains ordinary merge `21ef42d` for CLI initialization and the preceding core repairs. CLI support at that historical checkpoint was limited to `--full-text-worksheet`; the subsequent private-command checkpoint below adds view/application/finalization. No parent delta or PR was discarded, and no live approval service is claimed. RED `7cf8018` selected one failing blank-initialization test before GREEN `063a99d`; earlier `65bf495` compile errors and the invalid tagged-struct round trip are retained rather than called passing evidence. End-to-end library RED `6bf92d2` failed one selected test. Duplicate-key RED source `d367658` plus registration `9e51038` produced three passes and one failure before recursive admission repair `fc882ef`. The same integrated source adds unknown-field, changed-display, stale-view, incomplete-review, altered-label, no-downcast and zero-governance-call regressions. These are synthetic unit checks, not authentic steward input. @@ -138,11 +138,25 @@ Independent review found that a blank view exactly at 16 MiB became impossible t At `da406cf`, 216 workspace tests across 39 unfiltered suites, including three doctests, pass; one nested filtered subprocess is not counted twice. Strict Clippy, formatting, rustdoc with warnings denied, CI contract and the existing coverage gate pass. Functions are 383/383, source-normalized regions 4,215/4,215 and branches 712/712. Raw LLVM lines are 4,606/4,714, regions 6,693/6,884 and branches 630/712, not 100%. No dependency, coverage exclusion, provider call or authority bypass was added. -Current-head review then found that `write_private_output` could create metadata larger than the 16 MiB ceiling enforced by `read_private_json`. RED `4aabc6ab59fc16447bfe9b78cb12aeb2997a0a9d` uses only synthetic bytes and requires `InvalidData` before output creation. Repair `351a0c88368e7ea07330de16ef3f5daa83661fd3` adds that pre-create check to the shared metadata writer; `ee9e263b59a7249460fc797f0d8a11203b3fa932` restores one unrelated fixture line accidentally escaped during the contents-API edit. Comparison shows the corrective commit is one line and the net runtime delta after RED is the writer guard/comment only. The full-text capture still streams through its separate 512 MiB boundary. No current-head workflow run was present after this source repair, so the historical `da406cf` execution evidence is not promoted to current-head GREEN. +Subsequent review found that `write_private_output` could create metadata larger than the 16 MiB ceiling enforced by `read_private_json`. RED `4aabc6ab59fc16447bfe9b78cb12aeb2997a0a9d` uses only synthetic bytes and requires `InvalidData` before output creation. Repair `351a0c88368e7ea07330de16ef3f5daa83661fd3` adds that pre-create check to the shared metadata writer; `ee9e263b59a7249460fc797f0d8a11203b3fa932` restores one unrelated fixture line accidentally escaped during the contents-API edit. Comparison shows the corrective commit is one line and the net runtime delta after RED is the writer guard/comment only. Full-text capture keeps a separate streaming writer and 512 MiB input ceiling, not a new output-size guarantee. No current-head workflow run was present after this source repair, so historical `da406cf` execution evidence is not promoted to that head's GREEN. The [real blank-initialization receipt](doctoring/zotero_fulltext_review_binding_evidence.json) records a new 1,776,354-byte single-link `0600` file, SHA-256 `36bcf0679de068a744fd4ae8709cd192d99cb556591420d7a43c2b5f0e633a46`, created offline in 1.50 seconds with 286,375,936 bytes maximum resident memory. An independent read-only audit checked original input hashes, exact capture marker and equality of the nested worksheet with the prior blank metadata worksheet. There are now 3,715 explicitly capture-bound blank slots, but authentic decisions and independently approved labels remain 0/3,715. The earlier 25-row view remains 21 nonempty-text rows and four missing-text rows; no new text-bound proposal was generated. -Lifecycle capability metric advances from 25 to 26 for the tested single-capture review chain; it is not a count of approved papers or shipped capabilities. Next gaps are a bounded private steward workflow for these library APIs, authentic decisions and independently issued approval, and separately governed full-text-aware write admission. Released contextual-orchestrator evidence, protected prerequisite acceptance, the remaining ontology repository census and a real deployment/release remain outstanding. No separate Utility Repository has an evidenced independent responsibility/consumer need. +At that initial checkpoint the lifecycle capability metric advanced from 25 to 26 for the tested single-capture review chain; it was not a count of approved papers or shipped capabilities. The following checkpoint closes its private command-access gap. Authentic decisions, independently issued approval and separately governed full-text-aware write admission remain absent. + +### Private review command flow and source-free diagnostics + +Integrated runtime `2fadbdaba3cd546f6c345ed4a950baef79325982` normally merges command GREEN `2a92b710c574b1f79cb8f2cc0047fd3870c09b16`, concurrent owner guard/fixture/evidence through `3c08a44919d4018da6da3d1e8f1cbf2b65007bbc`, and shared private-JSON diagnostic repair `d5fda2028dbf1ef1d5b919c9e30d3b808ce88887`. The independent same-line fixture repair `0672ebe1144de9b27dcc778065904089a5931208` is also retained; normal merges converge their content without dropping either history. No extra PR, dependency, service, force push or predecessor closure was introduced. + +CLI RED `0a70da9c756b6f8c75c15ba505a69e69f02dc1d7` reproduced three command failures and a parser failure while the existing two-batch library path passed. The new `--bound-full-text-review`, `--apply-full-text-review` and `--finalize-full-text-review` commands call that owner implementation with full envelopes. Compact view bytes preserve decision headroom; completed input reaches duplicate-key-aware validation unchanged. Shared private readers enforce path, opened-identity, single-link, permission and size contracts; create-new outputs never replace prior work. Finalization cannot issue or authenticate approval, and no evaluator CLI or write-admission conversion was added. + +Independent review identified private source disclosure in Serde syntax/type errors. REDs `d3fdaa5c84b9eb0062f8fc89f91dbadfb8201222` and `3059c0ed69e669965e3ffac2d32c100896987179` reproduce unknown-field and invalid-disposition content in actual CLI diagnostics. GREEN `d5fda20` changes the shared parser once to static invalid-input text, preserving role, size/growth and I/O diagnostics for every current caller. Tests cover the exact 16 MiB writer limit, one-byte overflow, separate capture writer, two batches, replay, altered text, duplicate keys, wrong/legacy envelopes, incomplete finalization, missing approval, file aliasing and no overwrite. These use test-only data, not campaign decisions. The original reader and verbatim error were introduced at `ffa115018f957842a7b9f102e5d975ded806b930`; `5299219` subsequently extracted the existing parsing into the shared bounded helper. Minimal predecessor repair and forward propagation remain required before those older PRs' own acceptance, without reverse-merging full-text features. + +At `2fadbdab`, the workspace test run completed successfully and its exact test inventory contains 228 tests across 40 suites, including three doctests; the nested filtered subprocess is not counted twice. Strict Clippy, formatting, warnings-denied rustdoc, CI contract, release build and the existing coverage gate pass. Functions are 387/387, source-normalized regions 4,394/4,394 and branches 728/728. Raw LLVM lines remain 4,696/4,804, regions 6,824/7,020 and branches 644/728, not 100%. No coverage exclusion was added. + +The [real private-command receipt](doctoring/zotero_bound_review_commands_evidence.json) records a new 1,590,742-byte single-link `0600` pending view, generated offline in 2.22 seconds with maximum resident memory 288,505,856 bytes. The separate audit at 12:21:47.264 UTC checked unchanged input hashes, capture identity, 25 blank pending rows, 21 parents with nonempty text and four without, and byte equality with the earlier read-only view. All 3,715 worksheet slots remain blank. No Zotero/model request, new prediction, authentic decision, external approval or write occurred. Byte equality is reuse evidence, not increased full-text coverage. + +The lifecycle capability metric advances from 26 to 27 for callable private review through finalization. This is local verified functionality, not protected-main shipment. Next work is the minimal predecessor diagnostic repair, remaining 56 repository audits, authentic full-denominator decisions and governance, full-text-aware write admission, upstream full-text version semantics and released CO integration. Current protected checks/approval and a real immutable release remain independently necessary; no Utility Repository is justified by the current single-consumer seam. ### Historical pre-repair Zotero 10 transition From 2f28d5dafc805b031641f91751a83abef6b57d63 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 21:55:17 +0900 Subject: [PATCH 27/39] docs: audit five additional domain and document contract owners --- docs/CONTEXT_MAP.md | 2 + .../RESEARCH_CAPABILITY_TRACEABILITY.md | 6 +++ .../cwl_ontology_capability_inventory.md | 41 +++++++++++++++++-- docs/product-technical-gap-baseline.md | 2 +- 4 files changed, 46 insertions(+), 5 deletions(-) diff --git a/docs/CONTEXT_MAP.md b/docs/CONTEXT_MAP.md index 12ba0f10..24ee95fb 100644 --- a/docs/CONTEXT_MAP.md +++ b/docs/CONTEXT_MAP.md @@ -22,3 +22,5 @@ The Full-Text Review View is an in-context read projection over that verified ca - Keyverse -> future delivery layer: **Anti-Corruption Layer** for verified identity/tenant context. - governance-risk-compliance -> Source Observation / Client Consumption: **Proposed Anti-Corruption Layer**, pending a released contract and exact-consumer proof. External requirements, internal controls, evidence links and effectiveness remain distinct GRC-owned meanings; ConceptWeave cannot infer assurance from evidence presence. - Orgmetra -> Source Observation: **Proposed Anti-Corruption Layer**, pending a released evidence contract. Job/Task/KSAO relationships remain product-owned; tenant, source version and review status must survive observation without moving employment authority or ordinal scoring into ConceptWeave. +- DiagramWeave -> future model-review delivery: **Proposed Anti-Corruption Layer**, pending a released edit contract and consumer conformance. Diagram revisions, scope expansion and host persistence remain distinct from ConceptWeave's semantic review/publication authority. +- newsdom-api -> Source Observation: **Proposed Anti-Corruption Layer**, pending release/default reconciliation and a bounded source-digest/parser-version contract. Parsed pages and sections remain observations; parser status, filenames and extracted relationships do not authorize semantic publication or private-data upload. diff --git a/docs/doctoring/RESEARCH_CAPABILITY_TRACEABILITY.md b/docs/doctoring/RESEARCH_CAPABILITY_TRACEABILITY.md index 5ede0b7d..4d8930c8 100644 --- a/docs/doctoring/RESEARCH_CAPABILITY_TRACEABILITY.md +++ b/docs/doctoring/RESEARCH_CAPABILITY_TRACEABILITY.md @@ -59,6 +59,12 @@ Orgmetra's inspected Task–KSAO contract is a second domain-specific observatio Acceptance must prove that ConceptWeave never becomes the GRC system of record, that proposed/inferred relations do not mutate authoritative GRC records, that release validation works offline, and that release upgrades identify affected GRC queries explicitly. Public OAEI/RODI/LLMs4OL-style benchmarks remain necessary because one enterprise fixture cannot establish general matching or learning performance. +## Adjacent evidence and edit contracts + +The [learning/measurement audit](cwl_ontology_capability_inventory.md#further-learning-and-measurement-contracts-2026-09-05) adds Psychometrics Commons as an unreleased product-domain evidence candidate. Future conformance must preserve construct/instrument/item versions, locale, consent, evidence validity and distinct observation clocks. Caller-supplied membership shares are not estimated weights, and reference checks cannot authenticate a reviewer or scientifically validate an instrument. README-only supply-chain and learning-record-store owners remain prerequisites rather than implementations to copy. + +The [document-contract audit](cwl_ontology_capability_inventory.md#adjacent-document-contract-audits-2026-09-05) identifies two additional cultivation hypotheses. Future DiagramWeave conformance should reject stale source revisions, a mismatched caller document and unapproved scope expansion while keeping diagram source and semantic approval separate. Future NewsDOM observation conformance should bind original bytes and parser version, retain missing coordinates/warnings, and reject an incompatible or unauthenticated parser before private upload. Neither an editable diagram nor parsed section text establishes an ontology relation. These are unimplemented consumer evaluation families, not authentic paper labels or tested integrations; no source or service is copied into ConceptWeave. + ## Consensus records used in the prior accepted set The following canonical Consensus records were fetched before recording the corresponding product implications: diff --git a/docs/doctoring/cwl_ontology_capability_inventory.md b/docs/doctoring/cwl_ontology_capability_inventory.md index a4759f99..e450ec10 100644 --- a/docs/doctoring/cwl_ontology_capability_inventory.md +++ b/docs/doctoring/cwl_ontology_capability_inventory.md @@ -4,11 +4,11 @@ Evidence snapshot: 2026-09-05. Status: research inventory, not dependency-adopti ## Scope and evidence limits -The paginated organization metadata census returned 76 repositories, including one archived repository and 11 forks. Name/description screening for ontology, semantic, knowledge graph, schema, RDF, SHACL, provenance and lineage was combined with the existing Context Map; metadata matches alone miss owners whose descriptions do not use those words. This expanded the original eight-candidate audit to 12 selected candidates. The organization product-goal directive then identified DiskSage's actual OWL use, expanding the audit to 13. naruon and pg-erd-cloud brought the count to 15. The subsequent five domain/interoperability audits below bring selected source-level coverage to 20/76, leaving 56 repositories unaudited at that depth. This does not prove that all relevant implementations have been found. +The paginated organization metadata census returned 76 repositories, including one archived repository and 11 forks; the 12:52:47 UTC refresh confirmed the same counts. Name/description screening for ontology, semantic, knowledge graph, schema, RDF, SHACL, provenance and lineage was combined with the existing Context Map; metadata matches alone miss owners whose descriptions do not use those words. This expanded the original eight-candidate audit to 12 selected candidates. The organization product-goal directive then identified DiskSage's actual OWL use, expanding the audit to 13. naruon and pg-erd-cloud brought the count to 15. Five domain/interoperability audits brought coverage to 20; the latest three domain and two document-contract audits below bring it to 25/76, leaving 51 repositories unaudited at that depth. This does not prove that all relevant implementations have been found. Each row separates responsibility, exact default-head documentation or tree evidence, and published GitHub release evidence. A name, open PR or protected branch alone does not prove a usable released API. This is not a complete package-registry, deployment, attestation or consumer-conformance audit; bounded follow-up attempts and their limitations are recorded below. CalendarWeave and four-pillars were initially screened from metadata only; their subsequent source audits now distinguish a bootstrap owner from an implemented product-domain model without excluding either by description alone. -GitHub repository, branch, README-at-SHA, complete-tree and release endpoints supplied these observations. All 20 selected default branches reported protected at their recorded observations. Several use `develop`; do not substitute a branch named `main` for the actual default shown in each row. For the original 13 candidates, GitHub detected MIT for semantic-data-portal, TEPP, LineageWeave, fast-mlsirm and disksage; Apache-2.0 for RankWeave, graphify, Veilpick and mhtml-etl-gateway; and no SPDX identifier for the other four. An undetected license is unresolved evidence, not permission to adopt. The naruon audit read an explicitly proprietary license despite public visibility and NOASSERTION metadata; pg-erd-cloud's exact license contains Apache-2.0 without an appended noncommercial restriction. DeepWiki had no indexed evidence for graphify, Veilpick, disksage, pg-erd-cloud or the five further candidates below, so exact GitHub source/tree was used instead. +GitHub repository, branch, README-at-SHA, complete-tree and release endpoints supplied these observations. All 25 selected default branches reported protected at their recorded observations. Several use `develop`; do not substitute a branch named `main` for the actual default shown in each row. For the original 13 candidates, GitHub detected MIT for semantic-data-portal, TEPP, LineageWeave, fast-mlsirm and disksage; Apache-2.0 for RankWeave, graphify, Veilpick and mhtml-etl-gateway; and no SPDX identifier for the other four. An undetected license is unresolved evidence, not permission to adopt. The naruon audit read an explicitly proprietary license despite public visibility and NOASSERTION metadata; pg-erd-cloud's exact license contains Apache-2.0 without an appended noncommercial restriction. DeepWiki had no indexed evidence for graphify, Veilpick, disksage, pg-erd-cloud or the ten further candidates below, so exact GitHub source/tree was used instead. ## Owner and maturity evidence @@ -56,6 +56,29 @@ The GRC reference scenario must preserve the distinction between a documented re four-pillars' current default is three commits ahead of v0.8.0. Its [12 KASI Jie-term test cases](https://github.com/ContextualWisdomLab/four-pillars/blob/8c6a2fa76af1cb7f6bb7f56ceb4e7ce92d2f7897/tests/test_solar_term_golden.py) specify a 120-second tolerance, but that test file is absent from the released tree and calendar/model implementation differs. The tests were not executed here. [Report quality validation](https://github.com/ContextualWisdomLab/four-pillars/blob/8c6a2fa76af1cb7f6bb7f56ceb4e7ce92d2f7897/src/four_pillars/quality.py) checks fingerprints, supplied pillars, completeness and prohibited language, not scientific predictive validity. Fixed element weights and [direct-NIM default configuration](https://github.com/ContextualWisdomLab/four-pillars/blob/8c6a2fa76af1cb7f6bb7f56ceb4e7ce92d2f7897/src/four_pillars/settings.py), despite optional CO integration, are owner alignment findings. No model call, release installation or cross-product source adoption was performed. +## Further learning and measurement contracts (2026-09-05) + +The 12:39–12:45 UTC audit added three domain owners. Each default head was rechecked unchanged and protected, with effective organization ruleset 18156473. Complete trees were untruncated and paginated release/tag queries were empty for all three. DeepWiki was unindexed; CodeGraph reported its queue busy, so exact GitHub source supplied the audit. No tests, runtime, model or private data were accessed. + +| Candidate | Exact source-level contract | Cultivation requirement before consumption | +| --- | --- | --- | +| supply-chain-control-plane | `main@11f3e0f191d7f5a30e1bb0512d26e0db323f38e2`; the [complete tree](https://github.com/ContextualWisdomLab/supply-chain-control-plane/tree/11f3e0f191d7f5a30e1bb0512d26e0db323f38e2) contains one bootstrap README. Temporal/evidence-graph repository metadata is not implemented ontology or supply-chain inference. No license file or detected license. | Establish licensed, versioned event/relationship contracts and conformance before integration. Supply-chain meanings and decisions remain product-owned. A bootstrap result stays in the audit denominator. | +| psychometrics-commons | `main@e81441ce70c676992470afe2be469dd891ad3eb5`; the complete 374-entry tree includes Rust [instrument manifests](https://github.com/ContextualWisdomLab/psychometrics-commons/blob/e81441ce70c676992470afe2be469dd891ad3eb5/src/instrument.rs#L164) binding constructs, ordered item versions, locale, calibration, norms, consent and intended use. [Transitions](https://github.com/ContextualWisdomLab/psychometrics-commons/blob/e81441ce70c676992470afe2be469dd891ad3eb5/src/instrument.rs#L810) require matching evidence, approved status and effective validity. [Apache-2.0 license](https://github.com/ContextualWisdomLab/psychometrics-commons/blob/e81441ce70c676992470afe2be469dd891ad3eb5/LICENSE); the package declares 0.1.0 and publish=false. | Release a domain-observation contract with exact-consumer conformance. Preserve psychometric construct, instrument, consent and interpretation authority in this product. These source-level evidence checks do not authenticate reviewer references, establish scientific validity or supply a released shared ontology generator. | +| learning-record-store | `develop@6c888a30152dc9e258a9338397e2cb064096b370`; the [complete tree](https://github.com/ContextualWisdomLab/learning-record-store/tree/6c888a30152dc9e258a9338397e2cb064096b370) contains one bootstrap README. Metadata proposes xAPI persistence, but no implemented statement schema, endpoint, persistence or test exists. No license file or detected license. | Establish licensed, versioned learning-record contracts with provenance, idempotency, authorization and conformance tests before release. A proposed record-store role does not establish shared ontology ownership. | + +Psychometrics Commons' [research-release gate](https://github.com/ContextualWisdomLab/psychometrics-commons/blob/e81441ce70c676992470afe2be469dd891ad3eb5/src/research_release.rs#L209) checks references, digest syntax, declared blocker counts and distinct administrator/approver references. It does not authenticate those references, recompute scientific evidence or publish to the catalog. Its [longitudinal observations](https://github.com/ContextualWisdomLab/psychometrics-commons/blob/e81441ce70c676992470afe2be469dd891ad3eb5/src/longitudinal_observation.rs#L48) retain validity, recorded, received and ingested clocks and caller-supplied membership shares; shares are not estimated statistical weights. [Replay/rebinding tests](https://github.com/ContextualWisdomLab/psychometrics-commons/blob/e81441ce70c676992470afe2be469dd891ad3eb5/tests/longitudinal_observation_time_contract.rs#L188) and [publication-provenance tests](https://github.com/ContextualWisdomLab/psychometrics-commons/blob/e81441ce70c676992470afe2be469dd891ad3eb5/tests/instrument_publication_evidence_provenance.rs#L135) were read, not executed. Its traceability file still names older evaluated source `4499d9c`; reconcile that checkpoint before claiming current maturity. A product research-release type is neither a GitHub software release nor an independent approval receipt. + +## Adjacent document-contract audits (2026-09-05) + +The 12:40–12:43 UTC audit inspected DiagramWeave and newsdom-api as adjacent evidence/editing owners. Both complete default trees were untruncated (271 and 243 entries respectively), and both exact licenses are MIT. DeepWiki had no indexed repository evidence. DiagramWeave's protected main is `2f95243534778dbab69bf957aeaaeacfa253f6f0`; its paginated GitHub release/tag lists are empty. newsdom-api's protected default is `develop@e06b1f3fb10903569124af011da213951e6e2473`, with three releases and three commit-resolved tags. Organization ruleset 18156473 applies to both; newsdom-api additionally has repository rules requiring last-push approval, strict pytest checks and linear history. No tests, renderer, parser, model, package installation or live service were executed in this audit. + +| Candidate | Exact source-level contract | Cultivation requirement before consumption | +| --- | --- | --- | +| DiagramWeave | The [revision primitive](https://github.com/ContextualWisdomLab/DiagramWeave/blob/2f95243534778dbab69bf957aeaaeacfa253f6f0/packages/core/src/revision.js) hashes source text, and [edit admission](https://github.com/ContextualWisdomLab/DiagramWeave/blob/2f95243534778dbab69bf957aeaaeacfa253f6f0/packages/core/src/edit-proposal.js) validates schema version, exact revision, bounded requested/effective scopes and explicit scope expansion before computing a new source string. [Core tests](https://github.com/ContextualWisdomLab/DiagramWeave/blob/2f95243534778dbab69bf957aeaaeacfa253f6f0/packages/core/test/edit-proposal.test.js) define stale-revision, scope and malformed-input cases; they were inspected, not run. | Release a versioned package and prove caller identity/document binding, scope consent and stale-revision conformance at the consumer. The current package declares 0.0.0; source hashing and frozen JavaScript objects do not establish semantic approval or durable publication. Source text and host save/commit authority remain with DiagramWeave's caller, consistent with its Accepted ADRs 0001/0002. Do not extract this module or turn a diagram edge into approved ontology truth. | +| newsdom-api | [Canonical response schemas](https://github.com/ContextualWisdomLab/newsdom-api/blob/e06b1f3fb10903569124af011da213951e6e2473/src/newsdom_api/schemas.py) retain page/section text, coordinates, media and parser-warning observations. The [service](https://github.com/ContextualWisdomLab/newsdom-api/blob/e06b1f3fb10903569124af011da213951e6e2473/src/newsdom_api/service.py) derives document identity from a sanitized filename stem, not a source digest. The [current HTTP boundary](https://github.com/ContextualWisdomLab/newsdom-api/blob/e06b1f3fb10903569124af011da213951e6e2473/src/newsdom_api/main.py) checks configured authentication before multipart parsing, caps uploaded bytes at 20 MiB and separates readiness from liveness. | Release a bounded, source-digest/parser-version observation contract with consumer conformance and an explicit Rust-first/Python-only parser-runtime rationale. Parsed structure and a default success status are not semantic confidence or source authority. Resolve release/default divergence before adoption; do not copy its Python service or send private research files merely because a release exists. | + +Latest NewsDOM [v0.2.0](https://github.com/ContextualWisdomLab/newsdom-api/releases/tag/v0.2.0), published April 24, resolves to `c26f3db7e9176b6e698b4e686aeda79b15a010b9`. Wheel, source archive, attestation, manifest and checksum assets were enumerated, not downloaded or authenticated. GitHub reports that default and release source diverged, with 163 default-only and seven release-only commits. The [release-source parser endpoint](https://github.com/ContextualWisdomLab/newsdom-api/blob/c26f3db7e9176b6e698b4e686aeda79b15a010b9/src/newsdom_api/main.py) has an unbounded whole-upload read, no application authentication and exception-string responses, unlike current default source. Current and released schema blobs also differ. These are source-level release-compatibility findings, not an assertion about a deployed gateway or the actual contents of a downloaded distribution. Existing fleet coordination was asked to locate the canonical NewsDOM release owner; ConceptWeave added no parser dependency or bypass. + ## Actual Zotero evidence The repaired read-only executable at `a359c5b9d1013e84f5832506f5a57aec364e6493` captured a new report/worksheet pair from Zotero 10.0.1, API 3, schema 44, library version 2. It binds captured provider JSON values and the actual typed classifier inputs with the versioned snapshot digest. The earlier `22030ae6c8510d9eb8f7b07d98959bb69d2bd286` schema-44 capture is pre-repair observation; the 9.0.6/schema-42/library-12341 pair is also historical. Neither was overwritten. Zotero documents that pre-10 synced revisions could remain unchanged after local edits; version 10 metadata uses instance-local revisions (Zotero, 2026). These version spaces must not be compared or merged. Equal record totals do not prove unchanged content. The separate full-text endpoints have an observed version-contract mismatch and must not inherit this metadata-version assumption. @@ -72,8 +95,8 @@ The PR #10 findings for [provider metadata lost before hashing](https://github.c | Measure | Observation | Required next evidence | | --- | --- | --- | -| Metadata census / bounded capability audit | 76 metadata records; 20/20 selected candidates have exact default-head documentation/source/tree and release-query evidence | Audit actual contracts/consumers; metadata coverage is not full source-level discovery, and 56 repositories remain unaudited at that depth. | -| GitHub release with resolved source commit | 5/20 selected candidates, including proprietary naruon and product-domain four-pillars | License, artifact/provenance and consumer conformance; a release count is not permissive-library or adoption evidence. | +| Metadata census / bounded capability audit | 76 metadata records; 25/25 selected candidates have exact default-head documentation/source/tree and release-query evidence | Audit actual contracts/consumers; metadata coverage is not full source-level discovery, and 51 repositories remain unaudited at that depth. | +| GitHub release with resolved source commit | 6/25 selected candidates, including proprietary naruon, product-domain four-pillars and release-diverged newsdom-api | License, artifact/provenance and consumer conformance; a release count is not permissive-library or adoption evidence. | | Verified ConceptWeave adoption | 0 demonstrated in this audit | Released owner contract, exact consumer revision and passing contract/runtime evidence. | | Privately retained nonempty full text | 3,203/3,715 parents in the separate [capture evidence](zotero_fulltext_capture_evidence.json); 512 remain without demonstrated nonempty text | New content-bound proposals and authentic review with partial/unknown indexing. The earlier sweep's 800/1,000 abstract-missing subgroup is availability evidence, not a remeasured retained-capture subgroup. | | Unverified steward decisions | 0/3,715 on the repaired snapshot; first pending batch has 0/25 decisions | Authentic snapshot-bound decisions; batch generation is not review progress. | @@ -83,6 +106,16 @@ Review the repaired current snapshot, including missing-abstract and unsupported ## References +ContextualWisdomLab. (2026). *Supply Chain Control Plane* (Commit 11f3e0f191d7f5a30e1bb0512d26e0db323f38e2) [Source repository]. GitHub. https://github.com/ContextualWisdomLab/supply-chain-control-plane/tree/11f3e0f191d7f5a30e1bb0512d26e0db323f38e2 + +ContextualWisdomLab. (2026). *Psychometrics Commons* (Commit e81441ce70c676992470afe2be469dd891ad3eb5) [Computer software]. GitHub. https://github.com/ContextualWisdomLab/psychometrics-commons/tree/e81441ce70c676992470afe2be469dd891ad3eb5 + +ContextualWisdomLab. (2026). *Learning Record Store* (Commit 6c888a30152dc9e258a9338397e2cb064096b370) [Source repository]. GitHub. https://github.com/ContextualWisdomLab/learning-record-store/tree/6c888a30152dc9e258a9338397e2cb064096b370 + +ContextualWisdomLab. (2026). *DiagramWeave* (Commit 2f95243534778dbab69bf957aeaaeacfa253f6f0) [Computer software]. GitHub. https://github.com/ContextualWisdomLab/DiagramWeave/tree/2f95243534778dbab69bf957aeaaeacfa253f6f0 + +ContextualWisdomLab. (2026). *NewsDOM API* (Commit e06b1f3fb10903569124af011da213951e6e2473) [Computer software]. GitHub. https://github.com/ContextualWisdomLab/newsdom-api/tree/e06b1f3fb10903569124af011da213951e6e2473 + ContextualWisdomLab. (2026). *CalendarWeave* (Commit d972ccae6225716bdff7210a1fed808c01d32689) [Source repository]. GitHub. https://github.com/ContextualWisdomLab/CalendarWeave/tree/d972ccae6225716bdff7210a1fed808c01d32689 ContextualWisdomLab. (2026). *four-pillars* (Commit 8c6a2fa76af1cb7f6bb7f56ceb4e7ce92d2f7897) [Computer software]. GitHub. https://github.com/ContextualWisdomLab/four-pillars/tree/8c6a2fa76af1cb7f6bb7f56ceb4e7ce92d2f7897 diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index bec97835..4edf77d4 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -80,7 +80,7 @@ All four new artifacts remain private mode `0600`, outside the repository: The repaired worksheet has 0/3,715 decisions and the first batch has 0/25; externally approved full-review coverage remains 0/3,715. No authorization prompt, approval, Zotero write, record merge/deletion or rollback was performed. The three historical Zotero 9 artifact hashes below were rechecked unchanged. The pre-repair schema-44 artifacts are also preserved. Stronger source binding is not classification correctness, business approval or loopback peer authentication. -The [CWL ontology capability inventory](doctoring/cwl_ontology_capability_inventory.md) now separates the 76-repository metadata census from 20 exact-default-source audits, up from 15. The five additions distinguish Orgmetra's Task–KSAO evidence contract, GRC's evidence-binding preview, four-pillars' product-domain calculation model, and README-only CalendarWeave/learning-interoperability-contracts. Five selected candidates have GitHub releases with resolved source commits: RankWeave, mhtml-etl-gateway, fast-mlsirm, proprietary naruon and four-pillars. Current-main tests in four-pillars are not attributed to its older v0.8.0 release. Missing licenses, unverified artifacts, absent releases and optional/direct-provider configuration remain owner cultivation gaps. NIST IR 8477 and OSCAL mapping references guide future relation-semantics tests without granting assurance or labels. Source-level discovery remains incomplete for 56 repositories; exact-consumer adoption remains unproved, and no additional utility owner is justified. +The [CWL ontology capability inventory](doctoring/cwl_ontology_capability_inventory.md) now separates the 76-repository metadata census from 25 exact-default-source audits, up from 20. The latest five distinguish Psychometrics Commons' instrument/temporal domain contracts, DiagramWeave's revision-bound edits, NewsDOM's parser observations, and README-only supply-chain-control-plane/learning-record-store. Six selected candidates have GitHub releases with resolved source commits: RankWeave, mhtml-etl-gateway, fast-mlsirm, proprietary naruon, four-pillars and newsdom-api. Current four-pillars tests are not attributed to older v0.8.0; NewsDOM's v0.2.0 source lacks current-default authentication/upload bounds and has diverged history. Enumerated assets were not authenticated or installed. Missing licenses, absent releases, reference-only approval checks and source/default divergence remain owner cultivation gaps. NIST IR 8477 and OSCAL mapping references guide future relation-semantics tests without granting assurance or labels. Source-level discovery remains incomplete for 51 repositories; exact-consumer adoption remains unproved, and no additional utility owner is justified. Next: apply only authentic snapshot-bound steward decisions to the repaired batch, cover all 3,715 items and independently verify full-review approval; continue owner contract discovery and protected Foundation work while external checks/reviews are pending. The current body notes for PRs #11–#33 distinguish inherited source repair from historical head/check claims. No PR was closed, approved, merged or retargeted by this repair. From bbb9a8b6c9726294fd4c7c03880e558ea18fc83a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 22:08:22 +0900 Subject: [PATCH 28/39] docs: preserve earliest-owner private artifact repair rationale --- docs/adr/0006-zotero-research-intake.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/adr/0006-zotero-research-intake.md b/docs/adr/0006-zotero-research-intake.md index 00ef86b2..3cd5ff6a 100644 --- a/docs/adr/0006-zotero-research-intake.md +++ b/docs/adr/0006-zotero-research-intake.md @@ -47,6 +47,18 @@ No dedicated utility repository or Zotero mutation path is created. A future Zot ## Consequences +### 2026-09-05 earliest-owner private artifact repair amendment (Proposed) + +In the context of independently reviewed stacked pull requests, facing a shared private JSON defect fixed only in a later child, we decided for a minimal repair at the original reader boundary and ordinary parent-to-child merges and against accepting vulnerable predecessors, reverse-merging later full-text features or discarding predecessor deltas, to make each proposed slice independently verifiable, accepting repeated checks at every changed head. + +The private reader and verbatim Serde error entered at `ffa115018f957842a7b9f102e5d975ded806b930` in PR #29; `5299219c1049701b80d9e9f743499b6d7adaeae3` extracted an existing helper rather than introducing the defect. At that earlier contract, test-only strict input reproduces rejected field disclosure and the actual finalization command reproduces invalid disposition and scalar disclosure. RED `4c0c8f0` retains those failures alongside a compatible successful finalization. GREEN `25d4a78` replaces only the shared parse error with static text; artifact-role, size/growth and I/O diagnostics remain distinct. Adding later strict DTO rules to that predecessor was rejected because it would change its accepted contract rather than repair disclosure. + +The metadata writer predates that reader, but PR #29 establishes the 16 MiB saved-artifact round trip. Separate RED `cdf8e12` proves exact-limit success and one-byte overflow creating an unreadable output. GREEN `0837c6f` rejects overflow before creating any file. The later capture-specific streaming writer remains outside this metadata guard. No dependency, approval issuer, capture feature or new output ceiling is backported. + +Verification also reproduced an inherited strict-Clippy failure on PR #28's unchanged head `d6e7b3570ae91832a8da658c31cd71d45361840a`. Merge `1623aad` had reintroduced an unnecessary fixture conversion already removed by `1ca8a79`. Repair `5c95bb7` restores that one line on PR #28 before forward integration; test fixtures are not a reason to suppress warnings. PR #29's first coverage run then exposed a test-cleanup branch reachable only during the preserved RED failure. Test-only `fec97ac` makes cleanup unconditional while retaining the file-created assertion and existing-file protection. The unchanged coverage gate passes after that repair; raw LLVM coverage remains below 100% and is recorded separately in the Gap baseline. + +Positive consequences are source-free diagnostics and save/read compatibility on the original owner slice, with both old heads retained as ancestors through normal merges. Costs are additional regression history and repeated validation across the stack. Local GREEN and an independent source review do not constitute protected approval, release, completed paper review or authority to modify Zotero. The next governance and write-admission gaps remain open; ADR status remains Proposed. + ### 2026-09-05 private review command amendment (Proposed) In the context of an operator reviewing retained paper text without accessing mutable Zotero state, facing tested library operations that were not callable as one private review workflow, we decided for three thin commands reusing the existing view/application/finalization APIs and against another review engine, service, repository or local approval issuer, to preserve source bindings and prior work at each saved-file transition, accepting repeated capture verification and separately supplied decisions and approval evidence. From 6db1ca4ef909394a799b425ebaf48eab760b7b44 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 22:18:06 +0900 Subject: [PATCH 29/39] docs: record verified earliest-owner repair cascade and audit gaps --- docs/adr/0006-zotero-research-intake.md | 2 +- docs/product-technical-gap-baseline.md | 39 ++++++++++++++++++++++--- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/docs/adr/0006-zotero-research-intake.md b/docs/adr/0006-zotero-research-intake.md index 3cd5ff6a..9fb5e6ce 100644 --- a/docs/adr/0006-zotero-research-intake.md +++ b/docs/adr/0006-zotero-research-intake.md @@ -53,7 +53,7 @@ In the context of independently reviewed stacked pull requests, facing a shared The private reader and verbatim Serde error entered at `ffa115018f957842a7b9f102e5d975ded806b930` in PR #29; `5299219c1049701b80d9e9f743499b6d7adaeae3` extracted an existing helper rather than introducing the defect. At that earlier contract, test-only strict input reproduces rejected field disclosure and the actual finalization command reproduces invalid disposition and scalar disclosure. RED `4c0c8f0` retains those failures alongside a compatible successful finalization. GREEN `25d4a78` replaces only the shared parse error with static text; artifact-role, size/growth and I/O diagnostics remain distinct. Adding later strict DTO rules to that predecessor was rejected because it would change its accepted contract rather than repair disclosure. -The metadata writer predates that reader, but PR #29 establishes the 16 MiB saved-artifact round trip. Separate RED `cdf8e12` proves exact-limit success and one-byte overflow creating an unreadable output. GREEN `0837c6f` rejects overflow before creating any file. The later capture-specific streaming writer remains outside this metadata guard. No dependency, approval issuer, capture feature or new output ceiling is backported. +The metadata writer predates that reader, but PR #29 establishes the 16 MiB saved-artifact round trip. Separate RED `cdf8e12` proves exact-limit success and one-byte overflow creating an unreadable output. GREEN `0837c6f` rejects overflow before creating any file. The later capture-specific streaming writer remains outside this metadata guard. No dependency, approval issuer, capture feature or capture-output ceiling is backported. Verification also reproduced an inherited strict-Clippy failure on PR #28's unchanged head `d6e7b3570ae91832a8da658c31cd71d45361840a`. Merge `1623aad` had reintroduced an unnecessary fixture conversion already removed by `1ca8a79`. Repair `5c95bb7` restores that one line on PR #28 before forward integration; test fixtures are not a reason to suppress warnings. PR #29's first coverage run then exposed a test-cleanup branch reachable only during the preserved RED failure. Test-only `fec97ac` makes cleanup unconditional while retaining the file-created assertion and existing-file protection. The unchanged coverage gate passes after that repair; raw LLVM coverage remains below 100% and is recorded separately in the Gap baseline. diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 4edf77d4..56927538 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -4,7 +4,38 @@ This file records code-current product and technical gaps. Exact PR/check/run coordinates are evidence snapshots, not mutable-head dependencies. Live protected-branch, PR, issue and workflow state wins whenever it advances after this snapshot. Refresh the changed PR's metadata after each documentation commit; evidence from another PR or an earlier head does not transfer. -## Protected truth and active stack +## Latest local repair checkpoint + +Integrated runtime `b9c250632d04794773d4436665299610f6f9892d` preserves the original private-artifact fixes at their earliest owner and normally merges them forward through PR #38. The original reader/error is `ffa1150` in #29, not the later `5299219` helper extraction. Separate diagnostic RED/GREEN `4c0c8f0` → `25d4a78` and writer RED/GREEN `cdf8e12` → `0837c6f` repair source disclosure and the inclusive 16 MiB metadata save/read contract. PR #28's unchanged head also reproduced an inherited strict-Clippy fixture warning; `5c95bb7` restores the original clone-only intent before parent-to-child integration. [Proposed ADR 0006](adr/0006-zotero-research-intake.md) records the failure scenarios, rejected alternatives and propagation boundary. + +Each listed head passed locked Rust 1.98.0 workspace tests, strict all-target Clippy, formatting, warnings-denied rustdoc, CI contract and diff checks before normal push. Counts include doctests and exclude duplicate nested filtered subprocess totals. Every normal merge retains both the old child head and verified parent as ancestors; no predecessor delta was discarded or later full-text feature reverse-merged. + +| PR | Verified source head | Tests / unfiltered suites | +| --- | --- | ---: | +| #28 | `5c95bb77ac12d25477ef278f7a23976700ceac2b` | 130 / 26 | +| #29 | `fec97acb06b55bcb9c1e7dfe9d2d942bf0f5e9d2` | 141 / 28 | +| #30 | `88d4931f12942c3d2e0a1114fde9c6f97a73afac` | 148 / 32 | +| #31 | `1c67d1e7b9ff90a84314d4e4bbf227d199771589` | 150 / 33 | +| #32 | `bd7669d28abfd3476106362b790198f6dd9d2e2c` | 153 / 34 | +| #33 | `e6a94178c929b47a23a2ef3d5b00afba5a997363` | 158 / 36 | +| #34 | `b69f97792a5b3f48f627b50ede200c45eccf6050` | 163 / 37 | +| #36 | `6b33c071a6e1ded610fc9634db51f7a56ed90868` | 193 / 38 | +| #37 | `a41306a96f67388998c8c8bfdf24d70049bcf15a` | 208 / 39 | +| #38 | `b9c250632d04794773d4436665299610f6f9892d` | 235 / 41 | + +At #29, the first integrated coverage run exposed a RED-only test-cleanup conditional: 521/522 source-normalized branch outcomes. Test-only `fec97ac` makes cleanup unconditional while retaining the file-created and no-overwrite assertions. The unchanged gate then passed 293/293 functions, 2,791/2,791 source-normalized regions and 520/520 normalized branch outcomes; raw LLVM remained 3,375/3,461 lines, 5,039/5,170 regions and 452/520 branches. No exclusion or coverage threshold changed. This coverage belongs to #29, not every intervening head. + +Final #38 source verification passed the release build and exact test inventory of 235 tests across 41 suites, including three doctests. Its existing coverage gate passed 391/391 functions, 4,394/4,394 source-normalized regions and 728/728 normalized branch outcomes. Raw LLVM is 4,746/4,854 lines, 6,938/7,134 regions and 644/728 branches, not 100%. Independent read-only review found no source regression in either merge resolution; one ADR wording contradiction was corrected to distinguish the metadata output ceiling from the absent capture-output ceiling. These are local test/source results, not hosted current-head GREEN, protected approval or release. + +The 12:52:47 UTC census remains 76 repositories (one archived, 11 forks); exact-default source audits advanced from 20 to 25, leaving 51. The five new audits and six release-bearing candidates are detailed in the [capability inventory](doctoring/cwl_ontology_capability_inventory.md). No release asset was authenticated or installed, and no consumer adoption is claimed. NewsDOM v0.2.0 source and protected default differ; its release/default transport mismatch was sent to the existing Naruon task. That task explicitly owns Naruon work, not the NewsDOM release. Existing candidate PRs #665/#682 were identified from only the newest 100 open NewsDOM PRs; their implementation, checks and actual writer remain unverified, so no duplicate owner repair was started. + +No real private artifact was read or modified during this repair continuation. The earlier retained evidence remains the last real aggregate measurement: 3,715 blank capture-bound slots, a 25-row pending view with 21 nonempty-text and four missing-text rows, and separately 0/3,715 authentic decisions and independently approved labels. No Zotero/model request, new proposal, label, approval, write or rollback occurred. The lifecycle capability metric remains 27; regression hardening and source discovery do not increase completed classification. + +Repository-effective protection and bootstrap main `f4f440dd58c77d7cd90dff8a1eb2eeb9a9940425` were rechecked at 13:11 UTC. Ruleset 18156473 was independently rechecked at 13:12–13:13 UTC. The subsequent all-PR GraphQL audit failed with `RATE_LIMIT / graphql_rate_limit` before returning PR data; its contradictory quota diagnostic is not evidence of access. Therefore the last complete all-PR review/thread census remains the dated 12:39:25–12:40:46 UTC observation: 32 open, 31 Draft, 36 unresolved threads, zero current-head approvals. Those counts are not asserted current after the new pushes. #28–#37's heads, bases, Draft state and updated bodies were individually read back, but that is not an all-PR review/check audit. No protected merge, closure, retarget, self-approval, dismissal or protection change was performed. + +Next: resume changed-head review/check verification when the API permits, obtain prerequisite protected integration, continue the remaining 51 owner audits, and close the authentic-decision/independent-approval and full-text-aware write-admission gaps. Released CO adoption and upstream full-text version semantics remain separate owner prerequisites. Do not reimplement the completed private CLI or earliest-owner repairs, infer labels from metadata, strip full-text bindings into legacy write input, or create an unneeded Utility Repository. + +## Historical protected truth and stack checkpoints The 11:58:19–12:00:15 UTC refresh found 32 open PRs, 31 Draft, 36 unresolved threads and no new submitted reviews, thread comments or terminal check results since 11:15:04. No current-head approval exists. Every named head/base matched its actual ref. PR #38 alone advanced with the preserved writer-size regression and repair to `351a0c88368e7ea07330de16ef3f5daa83661fd3`; its only check was an explicit CodeRabbit Draft skip. #35's CHANGES_REQUESTED and later COMMENTED reviews were unchanged. Central `.github/main` remains `f250638827f8252b0d9e5cb2601f4d333f96162f`; its earlier #1922 trigger/test-isolation change does not repair Noema/CodeQL or retroactively rebind queued successors from `7fcada597d5b79bdb14445f24322b2c9f6ed4b19`. The three previously recorded CodeQL successors remain queued at attempt one. Effective ruleset 18156473 still requires one approval, stale-review dismissal, resolved threads and seven workflows, with deletion/non-fast-forward protection. This is a dated checkpoint, not acceptance evidence for subsequent commits below. @@ -12,7 +43,7 @@ The 12:05 UTC #38 review found one additional bounded-artifact invariant: metada Protected/default `main` remains `f4f440dd58c77d7cd90dff8a1eb2eeb9a9940425`; only the bootstrap state is shipped there and no immutable ConceptWeave release exists. -The active roots observed immediately before this baseline refresh are: +The following roots retain the earlier audit observations. The latest local repair table above supersedes changed #28–#38 head coordinates; unchanged review/check status must still be refreshed before acceptance: 1. Foundation PR #1 — exact head `b538470c963e6524ddc0c3f652a46a4fc8265150`, Draft/open. The current Foundation makes Product CI draft-aware while keeping Ready/non-Draft quality requirements intact. Product CI still cannot materialize from protected `main` because that branch does not yet contain `.github/workflows/product.yml`. 2. Product-CI bootstrap PR #35 — exact head `a31ae0c2df920f2794f7ddb456795b04797ab472`, open/non-Draft. It adds the pull-request form of Product CI and removes no-op closed/converted-to-Draft triggers. At 10:46–10:48 UTC, Semgrep, Noema execution, admission, Trivy and Scorecard had completed successfully, coverage was queued and Strix running. CodeQL's new red dispatch job handed off to a queued successor; this is not a completed scan verdict. Noema submitted CHANGES_REQUESTED for an unsupported-flag claim contradicted by pinned Cargo help and official documentation, as detailed below. The workflow-only diff skips Dependency Review and OSV; these skips do not prove Foundation's dependency-changing checks. No independent approval exists for this head. @@ -98,7 +129,7 @@ Replayable retained-text coverage has progressed from 0 to 3,203/3,715 parents. Committed regressions repaired inherited environment proxies, exact-byte-limit rejection and replay checks occurring after digest allocation; clock fault injection verifies late/invalid-clock failures without changing the deadline. Final source verification at `733425df01511d894277fb8682e070f3dde03689` passed 173 tests across 37 suites including documentation tests, strict Clippy, formatting, rustdoc with warnings denied, the CI contract and the existing coverage gate. Coverage is 347/347 functions, 3,710/3,710 source-normalized regions and 674/674 source-normalized branch outcomes. Raw LLVM totals remain 4,159/4,255 lines, 6,129/6,274 regions and 603/674 branch outcomes; those are not 100%. The only source delta after the live run is a writer type alias resolving strict Clippy's complexity finding without changing runtime behavior. Hosted checks and independent protected approval remain separate gates. -At this earlier capture checkpoint, the next gap was the context-bound review chain implemented below. Remaining work now includes authentic decisions and independent approval, full-text-aware write admission, the 56 unaudited repository sources and the upstream version-contract repair. Unchanged predictions do not require a new proposal run merely to display retained evidence. A released contextual-orchestrator integration artifact remains unverified at the audited protected owner head, so source documentation alone does not authorize model-provider bypass. No new utility repository is justified by this one intake seam. +At this earlier capture checkpoint, the next gap was the context-bound review chain implemented below, with 56 repository sources then unaudited. The latest checkpoint above reduces that remaining inventory to 51 while retaining authentic decisions, independent approval, full-text-aware write admission and the upstream version-contract repair as gaps. Unchanged predictions do not require a new proposal run merely to display retained evidence. A released contextual-orchestrator integration artifact remains unverified at the audited protected owner head, so source documentation alone does not authorize model-provider bypass. No new utility repository is justified by this one intake seam. ### Canonical transport repair and released-owner audit @@ -156,7 +187,7 @@ At `2fadbdab`, the workspace test run completed successfully and its exact test The [real private-command receipt](doctoring/zotero_bound_review_commands_evidence.json) records a new 1,590,742-byte single-link `0600` pending view, generated offline in 2.22 seconds with maximum resident memory 288,505,856 bytes. The separate audit at 12:21:47.264 UTC checked unchanged input hashes, capture identity, 25 blank pending rows, 21 parents with nonempty text and four without, and byte equality with the earlier read-only view. All 3,715 worksheet slots remain blank. No Zotero/model request, new prediction, authentic decision, external approval or write occurred. Byte equality is reuse evidence, not increased full-text coverage. -The lifecycle capability metric advances from 26 to 27 for callable private review through finalization. This is local verified functionality, not protected-main shipment. Next work is the minimal predecessor diagnostic repair, remaining 56 repository audits, authentic full-denominator decisions and governance, full-text-aware write admission, upstream full-text version semantics and released CO integration. Current protected checks/approval and a real immutable release remain independently necessary; no Utility Repository is justified by the current single-consumer seam. +At this private-command checkpoint the lifecycle capability metric advanced from 26 to 27 for callable review through finalization. This is local verified functionality, not protected-main shipment. The latest checkpoint above completes the then-pending minimal predecessor repair and advances repository audits from 20 to 25 of 76. Authentic full-denominator decisions and governance, full-text-aware write admission, upstream full-text version semantics and released CO integration remain gaps. Current protected checks/approval and a real immutable release remain independently necessary; no Utility Repository is justified by the current single-consumer seam. ### Historical pre-repair Zotero 10 transition From 4fe8800f6ae793e37cedec077be954346bc73b47 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 6 Sep 2026 15:52:34 +0900 Subject: [PATCH 30/39] docs: specify approval ordering and audited identity evidence owners --- CHANGELOG.md | 2 ++ docs/PRD.md | 2 ++ docs/TRD.md | 4 ++- docs/UML.md | 14 ++++++-- docs/adr/0007-reviewed-zotero-write-plan.md | 20 ++++++++++- .../cwl_ontology_capability_inventory.md | 33 ++++++++++++++++--- 6 files changed, 67 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 56a009a9..f3abf4cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,8 @@ All notable changes to ConceptWeave are documented here. ### Fixed +- Invalid or stale classification changes are rejected before redeeming approval, preserving it when a request cannot produce a valid plan. + - Oversized private review outputs fail before creating a file, so a successful save stays within the corresponding reader's size limit. Large saved-text captures retain their separate limit. - Research reads accept valid responses exactly at their documented size limit while still rejecting oversized, incomplete or invalidly encoded responses. diff --git a/docs/PRD.md b/docs/PRD.md index af4f3a43..321cc59a 100644 --- a/docs/PRD.md +++ b/docs/PRD.md @@ -68,6 +68,8 @@ For every connected duplicate component, accept externally verified steward deci Reviewed collection and tag changes default to a local dry-run plan. Each operation binds the authority receipt, server/library/item revisions, raw-snapshot digest, and complete before/after/rollback metadata. Execution-critical plan state is immutable outside the owner crate, so callers cannot turn a dry run into execution or alter validated operations. Zotero 9 execute requests fail closed. No plan contains credentials or permits `NeedsStewardReview`, source-record deletion, or attachment deletion. +An invalid or stale change request must be rejected before approval is redeemed, including when an earlier item in the same request is valid. A locally valid request reaches approval verification exactly once. For future writes based on full-text review, approval must cover the captured evidence, every reviewed label, the explicitly chosen collections and tags, and whether execution was requested. Reviewing meaning does not choose a destination or grant permission to change it. The same approval and evidence identity must remain attached to partial outcomes and recovery. Full-text-aware write admission remains a gap, distinct from the implemented metadata-planning safeguards. + For execute-mode plans, the runtime must preflight every item before the first write, stop at the first failed or unverifiable response, reconcile that item through the same server before declaring its state, and emit a secret-free receipt bound to the exact reviewed plan coordinates. Dry-run receipts enumerate every planned item as untouched. Execution receipts identify verified writes, the failed item, any indeterminate item, untouched items, and reverse-ordered rollback operations bound to the server identity, proven post-write item revision, and complete expected post-write metadata, including an identity- and version-confirmed unexpected mutation. Rollback must reject mixed-server evidence before reading, preflight every receipt item at one current library version before its first inverse write, consume the existing receipt order, advance the library precondition only from a verified response, and reconcile a failed response as restored, unchanged, or indeterminate. Only proven unchanged and untouched operations remain eligible for automatic retry; an indeterminate operation is retained separately with complete reconciliation evidence for an operator. Delayed reconciliation performs one read and no write, preserves the observed state, ignores unrelated library-version advancement, and emits retry evidence only when the exact item revision and expected metadata remain unchanged. A second use of consumed evidence must fail before writing. Cross-item atomicity is not claimed. The Zotero 10+ adapter can accept a caller-owned API key and server identity at runtime or consume one successful, user-approved Local API authorization. Authorization sends one bounded application name and the expected server identity to the fixed loopback endpoint; only a same-server bounded response that explicitly reports denial is classified as the user's decision. Denial and rate limiting return immediately without another prompt or automatic retry. The private 32-character key is neither serializable nor printable. Authorization, read, and write responses bind to the expected server before status classification; writes name expired authorization and matching-server stale preconditions separately. Thin public execution boundaries connect the adapter to the reviewed write and rollback cores without duplicating mutation logic. Synthetic transport evidence does not satisfy AC6's approved live Zotero 10 authorization, write, and rollback requirement. diff --git a/docs/TRD.md b/docs/TRD.md index 6a0b68ee..8c832b71 100644 --- a/docs/TRD.md +++ b/docs/TRD.md @@ -94,7 +94,9 @@ All file arguments must be distinct direct children of a canonical temporary dir Private JSON syntax/type failures return one static invalid-input error rather than Serde's rejected field names or values. This shared reader rule covers existing metadata commands as well as the new full-text commands. Artifact-role prefixes, size/growth failures and filesystem I/O errors remain distinct; completed-view and capture parsers retain their own source-free errors. -There is no CLI approval issuer or evaluator. Finalization accepts an independently supplied approval input but cannot authenticate it. Whole-envelope external verification remains a library boundary, not deployed governance. Write planning still has its separate `ReviewedClassificationWriteSet` and approval verifier; there is no conversion or authority transfer from a full-text evaluation to a Zotero write plan. A future write admission must explicitly preserve this context and authenticate the exact proposed collection/tag changes. +There is no CLI approval issuer or evaluator. Finalization accepts an independently supplied approval input but cannot authenticate it. Whole-envelope external verification remains a library boundary, not deployed governance. Write planning still has its separate `ReviewedClassificationWriteSet` and approval verifier; there is no conversion or authority transfer from a full-text evaluation to a Zotero write plan. The shared planner now finishes all existing local identity, mode, membership, revision and metadata checks before invoking its caller-owned verifier exactly once on valid input. Invalid input makes zero verifier calls, preserving a potentially one-use approval; local validation errors intentionally precede approval denial. + +Future full-text write admission must require the complete reviewed golden set, an explicit independently approved write set and the requested mode. Changed dispositions must equal the corresponding golden labels; destinations are supplied and authorized, never inferred from those labels. Both local validation paths must finish before either real verifier. Reuse the current evaluator and planner without a permissive verification bridge. An opaque plan and every execution, rollback, retry and reconciliation outcome must retain the same versioned capture/proposal/label/authority/write-scope binding, with no executable legacy-plan downcast. Legacy nested write DTOs are permissive JSON; the first increment must remain typed-only unless an explicit compatibility change supplies recursive strict admission. This full-text-aware write contract remains unimplemented; [Proposed ADR 0007](adr/0007-reviewed-zotero-write-plan.md) records its acceptance cases and limits. `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. diff --git a/docs/UML.md b/docs/UML.md index 96bcf405..58954917 100644 --- a/docs/UML.md +++ b/docs/UML.md @@ -49,6 +49,7 @@ sequenceDiagram participant Report as Local proposal report participant Capture as Private text capture participant Steward + participant Governance as External approval verifier loop bounded pages Intake->>Zotero: read items at one library version @@ -104,8 +105,17 @@ sequenceDiagram Steward->>Intake: verified canonical-item decisions Intake->>Report: before/after/rollback identity manifest Report-->>Steward: reversible local mapping; source records preserved - Steward->>Intake: verified collection/tag changes - Intake->>Report: dry-run write plan with exact rollback state + Steward->>Intake: collection/tag changes and independent approval + Intake->>Intake: validate complete local request and every changed item + alt invalid or stale request + Intake-->>Steward: reject without redeeming approval + else locally valid request + Intake->>Governance: verify complete write set exactly once + Governance-->>Intake: approval or denial + opt approved + Intake->>Report: dry-run write plan with exact rollback state + end + end Intake-->>Zotero: Zotero 9 execute rejected opt caller supplies authenticated Zotero 10+ adapter Intake->>Zotero: preflight every planned item diff --git a/docs/adr/0007-reviewed-zotero-write-plan.md b/docs/adr/0007-reviewed-zotero-write-plan.md index 6808508b..8f7032c0 100644 --- a/docs/adr/0007-reviewed-zotero-write-plan.md +++ b/docs/adr/0007-reviewed-zotero-write-plan.md @@ -10,12 +10,30 @@ Issue #8 requires classification changes to default to dry-run, preserve complet ## Decision -ConceptWeave builds a local-only `ClassificationWritePlan` from an externally verified complete review set. Dry-run is the default. The review must match the exact Zotero version, server identity, library version, classifier revision, raw-snapshot digest, complete item-key/item-version coordinates, and observed collection/tag state. The plan retains the reviewed Zotero version used for execute eligibility, while private fields and read-only accessors prevent external callers from mutating validated execution state. Every receipt copies the plan's review and snapshot coordinates so an outcome cannot be detached from its authority or evidence. It rejects unknown or duplicate items, detached item revisions, blank or duplicate metadata, unsupported tag types, no-op changes, and `NeedsStewardReview` as a write decision. Operations are deterministic and retain complete before, after, and rollback states. Manual tag markers `None` and `0` are canonicalized to `None`; automatic tag type `1` is preserved. +ConceptWeave builds a local-only `ClassificationWritePlan` from an externally verified complete review set. Dry-run is the default. The review must match the exact Zotero version, server identity, library version, classifier revision, raw-snapshot digest, complete item-key/item-version coordinates, and observed collection/tag state. The plan retains the reviewed Zotero version used for execute eligibility, while private fields and read-only accessors prevent external callers from mutating validated execution state. Write-execution receipts copy the plan's review and snapshot coordinates. Legacy rollback receipts retain conditional restoration evidence but do not carry those review/authority coordinates; they must not be advertised as full-text-bound approval evidence. It rejects unknown or duplicate items, detached item revisions, blank or duplicate metadata, unsupported tag types, no-op changes, and `NeedsStewardReview` as a write decision. Operations are deterministic and retain complete before, after, and rollback states. Manual tag markers `None` and `0` are canonicalized to `None`; automatic tag type `1` is preserved. Execute planning fails closed for Zotero versions below 10. The plan contains no API key and performs no network call. Dry-run enumerates every operation as not attempted. The execution core accepts caller-owned preflight and write functions, preflights the complete plan before the first mutation, and verifies server, library, item revision, collection, and typed-tag responses. After a failed or invalid write response, it reuses the same read boundary to distinguish unchanged, applied, and indeterminate state. A reconciled applied item receives a rollback operation. An unexpected mutation remains indeterminate but retains an inverse operation only when the same server/item identity and newer library/item revisions establish a safe conditional rollback target. An unprovable state is named explicitly and requires operator reconciliation. The authenticated Zotero 10+ adapter is a narrow loopback transport for those injected functions. A caller may supply credentials directly or perform one official `/api/local/authorize` request with a bounded nonblank application name and expected server identity. Every authorization, read, and write response must repeat that identity before status classification. Success returns an exact 32-character header-safe key plus the remembered decision; denial requires same-server bounded JSON with `denied: true`. The private authorization wrapper can only disclose the remembered decision or be consumed into the existing adapter; neither value is debuggable or serializable. Denial and rate limiting never trigger an automatic retry or repeated prompt, and only a bounded integer retry delay is retained. Writes distinguish an expired authorization from a matching-server stale precondition, while a different-server `412` invalidates the read/write partition as a database switch. Thin public adapter boundaries delegate to the generic write and rollback cores rather than creating parallel mutation logic. Rollback evidence binds the server, post-write item revision, complete expected current metadata, and complete restoration metadata. Before its first read, rollback rejects evidence spanning server identities; before its first write, it verifies every item at one current library version. It then follows the already reversed receipt order and advances that version only from a verified write. A failed or unverifiable response is re-read once and classified as restored, unchanged, or indeterminate. Only a proven unchanged current operation and untouched later operations remain eligible for automatic retry; an indeterminate operation retains the complete evidence required for operator reconciliation in a separate field. A delayed reconciliation reads once without writing and records the current state. Library-level advancement alone is not item-change evidence: unchanged requires the exact item revision and expected metadata, while restored requires restoration metadata at a newer item revision. Reusing consumed evidence fails preflight before writing. Static errors and serializable receipts cannot echo a credential, response body, or URL. Cross-item transactionality is not claimed, and source records and attachments are never deleted. +## Local validation before external approval (2026-09-05) + +The original planner introduced at `53b1d4dd046727d345fa2032d9426b2ba697b9df` in [PR #13](https://github.com/ContextualWisdomLab/ConceptWeave/pull/13) called the external verifier after matching top-level review coordinates but before checking execute eligibility, report membership and each operation's revisions and metadata. A caller may redeem a one-use approval inside that verifier. For example, a valid first change followed by a stale second change could consume approval and still return no plan. Repeating the request would then lose an otherwise usable approval without any Zotero write. Checking only the newer full-text caller would leave every existing planner caller exposed. + +Regression commit `505e111c993d8269e5b7b9e17a25a5ce20f8606e` preserves the failure at the original owner: four new negative test groups failed because the verifier ran once instead of zero times; the valid-input control passed. Repair `8a684882005085d8b3cb47812e185975084e0475` moves the existing verifier block after operation validation and sorting. It adds no alternate planner or dependency. Twenty-two invalid-input scenarios, each with accepting and denying verifiers, require zero calls; four valid dry-run/execute and accept/deny controls require exactly one call with the unchanged complete review. Local validation errors now intentionally take precedence over `UnverifiedApproval`. A valid rejected approval still returns that error, and successful plans retain deterministic operations and complete rollback metadata. + +The exact repaired #13 head passed 72 tests across 18 unfiltered suites, including two doctests, strict Clippy, formatting, warnings-denied rustdoc and the CI contract. Its unchanged coverage gate passed 143/143 functions, 1,357/1,357 source-normalized regions and 244/244 normalized branch outcomes. Raw LLVM remained 1,508/1,530 lines, 2,126/2,161 regions and 206/244 branches. These are local source measurements, not protected acceptance. The [gap baseline](../product-technical-gap-baseline.md) records normal parent-to-child propagation without discarding predecessor commits. + +Rejected alternatives were per-caller guards, which duplicate validation and miss sibling callers, and accepting invalid input before approval as harmless, which ignores caller-owned receipt consumption. The remaining limit is explicit: this planner validates its existing metadata-write contract, not the complete full-text review envelope, authority revocation, or live execution. + +## Required full-text write admission (not implemented) + +A full-text-reviewed golden set and its aggregate evaluation are not authority to replace Zotero collections or tags. The next increment must combine the complete capture-bound golden set, a separately approved explicit write set, and the requested mode in a required, non-flattened input. Every changed item's disposition must match its approved golden label. All local capture/report/proposal/full-denominator and write-state checks must finish before either external authority verifier runs. Reuse the existing full-text evaluator and repaired planner; do not insert a permissive verifier bridge, derive destinations from disposition names, backfill receipts or convert aggregate evaluation into approval. + +The returned opaque, serialize-only plan must retain a versioned binding for the complete labels, capture/proposal coordinates, approvals, destinations and mode. Write execution, partial failure, rollback, retry and delayed reconciliation must preserve that same binding. No executable legacy-plan downcast or freely mixed rollback operations may detach it. Existing legacy write DTOs accept unknown nested JSON fields, so strict outer deserialization alone is insufficient. Begin with typed-only admission, or separately document and test an intentional owned-DTO compatibility change before claiming strict persisted JSON admission. + +The failure analysis must cover relabeling, destination/mode substitution under old authority, denial by either verifier, missing full-denominator labels, stale preflight, mixed receipts and indeterminate outcomes. Dry-run must make no reads or writes. Paper text and authority secrets must stay out of errors and receipts. This is a bounded extension of ConceptWeave's existing intake context, not a new Utility Repository, transport, approval issuer or live-write CLI. Published owner contracts, authentic decisions, independently verified authority and approved live write/rollback evidence remain separate prerequisites. + ## Consequences - Review and rollback semantics can be tested on Zotero 9 without changing the library. diff --git a/docs/doctoring/cwl_ontology_capability_inventory.md b/docs/doctoring/cwl_ontology_capability_inventory.md index e450ec10..a63d15aa 100644 --- a/docs/doctoring/cwl_ontology_capability_inventory.md +++ b/docs/doctoring/cwl_ontology_capability_inventory.md @@ -4,11 +4,11 @@ Evidence snapshot: 2026-09-05. Status: research inventory, not dependency-adopti ## Scope and evidence limits -The paginated organization metadata census returned 76 repositories, including one archived repository and 11 forks; the 12:52:47 UTC refresh confirmed the same counts. Name/description screening for ontology, semantic, knowledge graph, schema, RDF, SHACL, provenance and lineage was combined with the existing Context Map; metadata matches alone miss owners whose descriptions do not use those words. This expanded the original eight-candidate audit to 12 selected candidates. The organization product-goal directive then identified DiskSage's actual OWL use, expanding the audit to 13. naruon and pg-erd-cloud brought the count to 15. Five domain/interoperability audits brought coverage to 20; the latest three domain and two document-contract audits below bring it to 25/76, leaving 51 repositories unaudited at that depth. This does not prove that all relevant implementations have been found. +The paginated organization metadata census returned 76 repositories, including one archived repository and 11 forks; the 12:52:47 UTC refresh confirmed the same counts. Name/description screening for ontology, semantic, knowledge graph, schema, RDF, SHACL, provenance and lineage was combined with the existing Context Map; metadata matches alone miss owners whose descriptions do not use those words. This expanded the original eight-candidate audit to 12 selected candidates. The organization product-goal directive then identified DiskSage's actual OWL use, expanding the audit to 13. naruon and pg-erd-cloud brought the count to 15. Five domain/interoperability audits brought coverage to 20; three further domain and two document-contract audits brought it to 25/76, with 51 repositories then unaudited at that depth. The subsequent keyverse and inkspan audits below advance the current count to 27/76, leaving 49. This does not prove that all relevant implementations have been found. Each row separates responsibility, exact default-head documentation or tree evidence, and published GitHub release evidence. A name, open PR or protected branch alone does not prove a usable released API. This is not a complete package-registry, deployment, attestation or consumer-conformance audit; bounded follow-up attempts and their limitations are recorded below. CalendarWeave and four-pillars were initially screened from metadata only; their subsequent source audits now distinguish a bootstrap owner from an implemented product-domain model without excluding either by description alone. -GitHub repository, branch, README-at-SHA, complete-tree and release endpoints supplied these observations. All 25 selected default branches reported protected at their recorded observations. Several use `develop`; do not substitute a branch named `main` for the actual default shown in each row. For the original 13 candidates, GitHub detected MIT for semantic-data-portal, TEPP, LineageWeave, fast-mlsirm and disksage; Apache-2.0 for RankWeave, graphify, Veilpick and mhtml-etl-gateway; and no SPDX identifier for the other four. An undetected license is unresolved evidence, not permission to adopt. The naruon audit read an explicitly proprietary license despite public visibility and NOASSERTION metadata; pg-erd-cloud's exact license contains Apache-2.0 without an appended noncommercial restriction. DeepWiki had no indexed evidence for graphify, Veilpick, disksage, pg-erd-cloud or the ten further candidates below, so exact GitHub source/tree was used instead. +GitHub repository, branch, README-at-SHA, complete-tree and release endpoints supplied these observations. All 27 selected default branches reported protected at their recorded observations. Several use `develop`; do not substitute a branch named `main` for the actual default shown in each row. For the original 13 candidates, GitHub detected MIT for semantic-data-portal, TEPP, LineageWeave, fast-mlsirm and disksage; Apache-2.0 for RankWeave, graphify, Veilpick and mhtml-etl-gateway; and no SPDX identifier for the other four. An undetected license is unresolved evidence, not permission to adopt. The naruon audit read an explicitly proprietary license despite public visibility and NOASSERTION metadata; pg-erd-cloud's exact license contains Apache-2.0 without an appended noncommercial restriction. DeepWiki had no indexed evidence for graphify, Veilpick, disksage, pg-erd-cloud or the twelve further candidates below, so exact GitHub source/tree was used instead. ## Owner and maturity evidence @@ -79,6 +79,21 @@ The 12:40–12:43 UTC audit inspected DiagramWeave and newsdom-api as adjacent e Latest NewsDOM [v0.2.0](https://github.com/ContextualWisdomLab/newsdom-api/releases/tag/v0.2.0), published April 24, resolves to `c26f3db7e9176b6e698b4e686aeda79b15a010b9`. Wheel, source archive, attestation, manifest and checksum assets were enumerated, not downloaded or authenticated. GitHub reports that default and release source diverged, with 163 default-only and seven release-only commits. The [release-source parser endpoint](https://github.com/ContextualWisdomLab/newsdom-api/blob/c26f3db7e9176b6e698b4e686aeda79b15a010b9/src/newsdom_api/main.py) has an unbounded whole-upload read, no application authentication and exception-string responses, unlike current default source. Current and released schema blobs also differ. These are source-level release-compatibility findings, not an assertion about a deployed gateway or the actual contents of a downloaded distribution. Existing fleet coordination was asked to locate the canonical NewsDOM release owner; ConceptWeave added no parser dependency or bypass. +## Identity and editor-evidence contracts (2026-09-05) + +The keyverse and inkspan source audits completed their exact-head verification at 13:37:09 UTC. Both actual default branches are `main`, reported protected and inherit effective organization ruleset 18156473. Their complete recursive trees contain 221 and 934 entries respectively, neither truncated. DeepWiki returned no repository evidence; CodeGraph was consulted before source exploration, but differing local heads were not substituted for the exact GitHub source. These observations add two adjacent contract owners, not two ontology generators. No tests, application runtime, identity session, private data, model, package-registry lookup or consumer adoption was exercised. + +| Candidate | Exact default source and demonstrated boundary | Release evidence / next cultivation requirement | +| --- | --- | --- | +| keyverse | `main@7d9151cd2da260e118020c938c7358e2ee75d541`; the [relying-party contract](https://github.com/ContextualWisdomLab/keyverse/blob/7d9151cd2da260e118020c938c7358e2ee75d541/services/account_unification/app/relying_party.py#L104) defines closed registration/validation DTOs. The same module pins [audiences](https://github.com/ContextualWisdomLab/keyverse/blob/7d9151cd2da260e118020c938c7358e2ee75d541/services/account_unification/app/relying_party.py#L396) to the client identity, defines a [claim allowlist](https://github.com/ContextualWisdomLab/keyverse/blob/7d9151cd2da260e118020c938c7358e2ee75d541/services/account_unification/app/relying_party.py#L436) and exposes the [relying-party validation route](https://github.com/ContextualWisdomLab/keyverse/blob/7d9151cd2da260e118020c938c7358e2ee75d541/services/account_unification/app/relying_party.py#L570). The [root license](https://github.com/ContextualWisdomLab/keyverse/blob/7d9151cd2da260e118020c938c7358e2ee75d541/LICENSE) and account-unification service license are identical Apache-2.0 blobs (`d645695673349e3947e8e5ae42332d0ac3164cd7`). | Release and tag queries returned no entries. The service manifest's 0.1.0 is a source declaration, not a software release. Release a versioned identity/client/claim-schema contract with fixtures and provenance, then prove consumer audience, tenant and fail-closed authentication behavior. Identity claims may identify a reviewer; they do not establish approval of particular paper labels, a capture or Zotero changes. Keep semantic-review and publication permissions with their product/governance owner rather than adding them to Keyverse or copying its Python service. | +| inkspan | `main@0b88c16f14f51b54a87eb7164f0edfb06dd60902`; [document envelopes](https://github.com/ContextualWisdomLab/inkspan/blob/0b88c16f14f51b54a87eb7164f0edfb06dd60902/src/documentEnvelope.ts#L7) define versioned schema and bounded content, with a [duplicate/unsupported-version rejecting parser](https://github.com/ContextualWisdomLab/inkspan/blob/0b88c16f14f51b54a87eb7164f0edfb06dd60902/src/documentEnvelope.ts#L88). [Text-position evidence](https://github.com/ContextualWisdomLab/inkspan/blob/0b88c16f14f51b54a87eb7164f0edfb06dd60902/src/textPositionSelectorEvidence.ts#L19) retains Unicode code-point coordinates, document revision and projection identity. [Host ownership](https://github.com/ContextualWisdomLab/inkspan/blob/0b88c16f14f51b54a87eb7164f0edfb06dd60902/docs/CONTRACTS.md#L66) covers annotation identity, authorization, persistence, reanchoring and semantic publication. The package declares 0.6.0; [MIT licensing](https://github.com/ContextualWisdomLab/inkspan/blob/0b88c16f14f51b54a87eb7164f0edfb06dd60902/LICENSE#L25) includes an explicit SIL OFL 1.1 font carve-out. | The sole GitHub release/tag, [v0.3.1](https://github.com/ContextualWisdomLab/inkspan/releases/tag/v0.3.1), resolves to `67afc7099cc0e5711a9cc9476bf3be5bb820e229` and has no attached assets. Its 574-entry tree lacks the current document-envelope/text-position contract files, CONTRACTS.md and corresponding subpath exports. Release those contracts with exact-consumer Unicode, revision and projection conformance before relying on them for research evidence. Preserve font-specific obligations; a declared package version, historical release or editor selection is neither authenticated artifact provenance nor semantic approval. | + +Keyverse's [Accepted ADR 0008](https://github.com/ContextualWisdomLab/keyverse/blob/7d9151cd2da260e118020c938c7358e2ee75d541/docs/adr/0008-keyverse-rp-authorization-boundary.md#L56) separates identity-claim validation from relying-product ABAC/RBAC; its [coarse operator-bearer limitation](https://github.com/ContextualWisdomLab/keyverse/blob/7d9151cd2da260e118020c938c7358e2ee75d541/docs/adr/0008-keyverse-rp-authorization-boundary.md#L127) remains an owner gap. The ADR's downstream PR table is historical, not freshly verified consumer adoption. [Claim-mapper cases](https://github.com/ContextualWisdomLab/keyverse/blob/7d9151cd2da260e118020c938c7358e2ee75d541/services/account_unification/tests/test_relying_party_claim_mappers.py#L253) cover unsafe audiences and duplicate claim inputs, while [authentication cases](https://github.com/ContextualWisdomLab/keyverse/blob/7d9151cd2da260e118020c938c7358e2ee75d541/services/account_unification/tests/test_auth.py#L119) specify failure when a required service token is not configured. These definitions were read, not run. They do not demonstrate an active ConceptWeave connector or independently authenticated steward decisions. + +inkspan computes [canonical local revision evidence](https://github.com/ContextualWisdomLab/inkspan/blob/0b88c16f14f51b54a87eb7164f0edfb06dd60902/src/documentEnvelopeRevision.ts#L39). Its [selection-evidence tests](https://github.com/ContextualWisdomLab/inkspan/blob/0b88c16f14f51b54a87eb7164f0edfb06dd60902/src/components/CwlEditor.textPositionSelectorEvidence.test.tsx#L9) cover Unicode, bidirectional text, graphemes and unavailable segmentation, including [delayed hashing](https://github.com/ContextualWisdomLab/inkspan/blob/0b88c16f14f51b54a87eb7164f0edfb06dd60902/src/components/CwlEditor.textPositionSelectorEvidence.test.tsx#L151); the [package-boundary test](https://github.com/ContextualWisdomLab/inkspan/blob/0b88c16f14f51b54a87eb7164f0edfb06dd60902/src/textPositionSelectorPackage.test.ts#L22) checks React-free consumption. None was executed here. The immutable v0.3.1 release was published on 2026-08-03 at 09:51:11 UTC; its [resolved source tree](https://github.com/ContextualWisdomLab/inkspan/tree/67afc7099cc0e5711a9cc9476bf3be5bb820e229) cannot substitute for the newer contracts. [ADR 0010](https://github.com/ContextualWisdomLab/inkspan/blob/0b88c16f14f51b54a87eb7164f0edfb06dd60902/docs/adr/0010-release-evidence-authority.md#L3) remains Proposed, and CONTRACTS.md's release-inventory description is not actual publication evidence. The Office package has its own [plain MIT license](https://github.com/ContextualWisdomLab/inkspan/blob/0b88c16f14f51b54a87eb7164f0edfb06dd60902/packages/inkspan-office/LICENSE); bundled fonts retain their separate obligations. No release asset was downloaded or authenticated, and no npm distribution was inspected. + +The Web Annotation Data Model supplies interoperable annotation and selector semantics, while RFC 8785 specifies canonical JSON serialization (World Wide Web Consortium, 2017; Rundgren et al., 2020). They are primary references for future consumer fixtures, not proof that these repositories conform fully or that content hashing authenticates a source, reviewer or approval. Research Intake should consume a released owner contract through its own boundary; it must not import editor annotations as published ontology truth or substitute identity claims for independent review. + ## Actual Zotero evidence The repaired read-only executable at `a359c5b9d1013e84f5832506f5a57aec364e6493` captured a new report/worksheet pair from Zotero 10.0.1, API 3, schema 44, library version 2. It binds captured provider JSON values and the actual typed classifier inputs with the versioned snapshot digest. The earlier `22030ae6c8510d9eb8f7b07d98959bb69d2bd286` schema-44 capture is pre-repair observation; the 9.0.6/schema-42/library-12341 pair is also historical. Neither was overwritten. Zotero documents that pre-10 synced revisions could remain unchanged after local edits; version 10 metadata uses instance-local revisions (Zotero, 2026). These version spaces must not be compared or merged. Equal record totals do not prove unchanged content. The separate full-text endpoints have an observed version-contract mismatch and must not inherit this metadata-version assumption. @@ -95,8 +110,8 @@ The PR #10 findings for [provider metadata lost before hashing](https://github.c | Measure | Observation | Required next evidence | | --- | --- | --- | -| Metadata census / bounded capability audit | 76 metadata records; 25/25 selected candidates have exact default-head documentation/source/tree and release-query evidence | Audit actual contracts/consumers; metadata coverage is not full source-level discovery, and 51 repositories remain unaudited at that depth. | -| GitHub release with resolved source commit | 6/25 selected candidates, including proprietary naruon, product-domain four-pillars and release-diverged newsdom-api | License, artifact/provenance and consumer conformance; a release count is not permissive-library or adoption evidence. | +| Metadata census / bounded capability audit | 76 metadata records; 27/27 selected candidates have exact default-head documentation/source/tree and release-query evidence | Audit actual contracts/consumers; metadata coverage is not full source-level discovery, and 49 repositories remain unaudited at that depth. | +| GitHub release with resolved source commit | 7/27 selected candidates, including proprietary naruon, product-domain four-pillars, release-diverged newsdom-api and inkspan's historical release without the relevant current contracts | License, artifact/provenance and consumer conformance; a release count is not permissive-library or adoption evidence. | | Verified ConceptWeave adoption | 0 demonstrated in this audit | Released owner contract, exact consumer revision and passing contract/runtime evidence. | | Privately retained nonempty full text | 3,203/3,715 parents in the separate [capture evidence](zotero_fulltext_capture_evidence.json); 512 remain without demonstrated nonempty text | New content-bound proposals and authentic review with partial/unknown indexing. The earlier sweep's 800/1,000 abstract-missing subgroup is availability evidence, not a remeasured retained-capture subgroup. | | Unverified steward decisions | 0/3,715 on the repaired snapshot; first pending batch has 0/25 decisions | Authentic snapshot-bound decisions; batch generation is not review progress. | @@ -106,6 +121,16 @@ Review the repaired current snapshot, including missing-abstract and unsupported ## References +ContextualWisdomLab. (2026). *Keyverse* (Commit 7d9151cd2da260e118020c938c7358e2ee75d541) [Computer software]. GitHub. https://github.com/ContextualWisdomLab/keyverse/tree/7d9151cd2da260e118020c938c7358e2ee75d541 + +ContextualWisdomLab. (2026). *inkspan* (Commit 0b88c16f14f51b54a87eb7164f0edfb06dd60902) [Computer software]. GitHub. https://github.com/ContextualWisdomLab/inkspan/tree/0b88c16f14f51b54a87eb7164f0edfb06dd60902 + +ContextualWisdomLab. (2026, August 3). *inkspan v0.3.1* [Software release]. GitHub. https://github.com/ContextualWisdomLab/inkspan/releases/tag/v0.3.1 + +Rundgren, A., Jordan, B., & Erdtman, S. (2020, June). *JSON canonicalization scheme (JCS)* (RFC 8785). RFC Editor. https://www.rfc-editor.org/rfc/rfc8785 + +World Wide Web Consortium. (2017, February 23). *Web annotation data model* (W3C Recommendation). https://www.w3.org/TR/2017/REC-annotation-model-20170223/ + ContextualWisdomLab. (2026). *Supply Chain Control Plane* (Commit 11f3e0f191d7f5a30e1bb0512d26e0db323f38e2) [Source repository]. GitHub. https://github.com/ContextualWisdomLab/supply-chain-control-plane/tree/11f3e0f191d7f5a30e1bb0512d26e0db323f38e2 ContextualWisdomLab. (2026). *Psychometrics Commons* (Commit e81441ce70c676992470afe2be469dd891ad3eb5) [Computer software]. GitHub. https://github.com/ContextualWisdomLab/psychometrics-commons/tree/e81441ce70c676992470afe2be469dd891ad3eb5 From 8e057652ee7784b373beeeec865d80dd3db773be Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 6 Sep 2026 15:59:16 +0900 Subject: [PATCH 31/39] docs: record verified approval-order cascade and current protected gates --- docs/product-technical-gap-baseline.md | 53 ++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 56927538..896c25be 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -1,11 +1,56 @@ # Product / Technical Gap Baseline -**Snapshot:** 2026-09-05 +**Snapshot:** 2026-09-06 This file records code-current product and technical gaps. Exact PR/check/run coordinates are evidence snapshots, not mutable-head dependencies. Live protected-branch, PR, issue and workflow state wins whenever it advances after this snapshot. Refresh the changed PR's metadata after each documentation commit; evidence from another PR or an earlier head does not transfer. ## Latest local repair checkpoint +Runtime `61bb211f798e9b91e921e65bc12d988e4b080dee` integrates the earliest-owner approval-order repair through the full dependent stack. Original planner `53b1d4dd046727d345fa2032d9426b2ba697b9df` in #13 could invoke an external approval verifier before rejecting locally invalid item, metadata or execute input. A one-use approval could therefore be consumed without a plan. Committed RED `505e111c993d8269e5b7b9e17a25a5ce20f8606e` demonstrated four failing negative test groups and a passing valid control. Minimal repair `8a684882005085d8b3cb47812e185975084e0475` moves the unchanged verifier block after all existing local checks. [Proposed ADR 0007](adr/0007-reviewed-zotero-write-plan.md) records 22 invalid-input scenarios with zero verifier calls, four valid controls with exactly one call, the intentional local-error precedence, alternatives and remaining authority limits. + +Every head below passed locked Rust 1.98.0 workspace tests, strict all-target Clippy, formatting, warnings-denied rustdoc, CI contract and diff checks. Counts include doctests and exclude nested filtered subprocess duplicates. #13–#37 were normally pushed and their exact heads matched the September 6 live audit. The final #38 integration retains both its original `6db1ca4` and verified parent `692cb58`; no later feature was reverse-merged into an earlier owner, and no predecessor was closed or discarded. + +| PR | Verified source head | Tests / unfiltered suites | +| --- | --- | ---: | +| #13 | `8a684882005085d8b3cb47812e185975084e0475` | 72 / 18 | +| #15 | `a07dd9a433c7211c2f95065031622d51dadf2cb6` | 84 / 19 | +| #16 | `873c46b7dcf2930a98cf7ef7ff8bdcbcf04f17d5` | 84 / 19 | +| #17 | `cf93f5323d97e718c8ff986c8e780bfaa26fb765` | 97 / 19 | +| #18 | `5e33981ccb0691e0a24260652c44fe8e28afb8d9` | 105 / 20 | +| #19 | `de9df48d727fe22a3f1efb881d7d17ef0566b620` | 106 / 20 | +| #20 | `4fb1ad073cfbe25d269d063c90914636f27619da` | 113 / 21 | +| #21 | `9302f8525aa3b3aba2f68576a97b6d2c853f2819` | 116 / 22 | +| #22 | `0b0691ee264264a7c50f894ef0190d12d97dea0d` | 117 / 23 | +| #23 | `1912c21d9fbe895db62cd9735b44f36fc2b19221` | 118 / 23 | +| #24 | `ba3a691bb246258d2a27bc83c308be21031e310e` | 120 / 23 | +| #25 | `6af51119f434035c9e8fc2743e8327c0199a8c92` | 123 / 24 | +| #26 | `f60c07ff0dc0b6933bbf5fa097b5bb72b0b24fea` | 125 / 24 | +| #27 | `9c4ecf5fc8bc3e16c3aaffc10ba0498e59128f9d` | 130 / 25 | +| #28 | `b411b66c2ec34c39bb0cceb27f96221a1fda4416` | 135 / 26 | +| #29 | `7af6881fc567fbec671f91d3590b4d4d47cf9f50` | 146 / 28 | +| #30 | `a43ddceaf5dfaa4daba5270b954bda9f42a59cdb` | 153 / 32 | +| #31 | `0dad7ca52bd2932e82bbf34eb4b7f4aec6b4f3f2` | 155 / 33 | +| #32 | `f0bf02a8a600924d254adfa7b4796aa2ef868165` | 158 / 34 | +| #33 | `18932783aeb336a6d58e8a19f6d7dd6ecfb9ab3a` | 163 / 36 | +| #34 | `853517c1c43fdaeea0cef57b2f0e63a34b0fe2db` | 168 / 37 | +| #36 | `87f02a91a9b5ea83ae842ef6b7eb83141aebfd66` | 198 / 38 | +| #37 | `692cb588b26a9cc878fbaa2b47aa30fd83ea47de` | 213 / 39 | +| #38 | `61bb211f798e9b91e921e65bc12d988e4b080dee` | 240 / 41 | + +The original #13 head's unchanged coverage gate passed 143/143 functions, 1,357/1,357 source-normalized regions and 244/244 normalized branch outcomes. Its raw LLVM totals were 1,508/1,530 lines, 2,126/2,161 regions and 206/244 branches. Final #38 passed 240 tests in 41 suites, including three doctests, plus the release build and unchanged coverage gate: 391/391 functions, 4,394/4,394 source-normalized regions and 728/728 normalized branch outcomes. Raw LLVM is 4,747/4,854 lines, 6,939/7,134 regions and 659/728 branches, not 100%. Intermediate-head coverage is not inferred from these endpoints. No gate or exclusion changed. The first root test invocation selected ambient Rust 1.97.1 and failed before tests; explicit pinned Rust 1.98.0 commands corrected the invocation, without lowering the production baseline or installing dependencies. + +The September 5 source audits added Keyverse's closed identity/client/claim boundary and inkspan's document/revision/selector-evidence contracts: 25 → 27 of the 76-repository census, leaving 49 unaudited at that depth. The September 6 paginated organization refresh again returned 76 repositories, one archived and 11 forks. Seven selected owners have GitHub releases with resolved source commits, but inkspan's sole immutable v0.3.1 lacks its relevant current contracts. Source declarations, test definitions, historical releases, packaged artifacts and verified adoption remain distinct; see the [capability inventory](doctoring/cwl_ontology_capability_inventory.md). No new Utility Repository is justified. + +On September 6 at 06:51–06:53 UTC, the normal all-PR GraphQL query succeeded after the earlier API reset: 32 open, 31 Draft, 36 unresolved threads and no current-head approval in the returned review evidence. All PR/thread/check pages were complete; reviews were bounded to the latest 30 per PR. #13–#37 matched the verified repair heads. Source Observation #6 independently advanced to `30d253f8c0c35a99d8eb4b2741cc660675bfc30c` in its existing owner task and was not modified here. #38's API base coordinate initially lagged its actual named base ref; the fresh fetch confirmed `codex/zotero-fulltext-review-context@692cb588b26a9cc878fbaa2b47aa30fd83ea47de`, which this integration adopts without retargeting. Re-read the final PR head/base after the documentation push; the pre-push audit cannot establish final-head hosted GREEN. + +Protected ConceptWeave main remains bootstrap `f4f440dd58c77d7cd90dff8a1eb2eeb9a9940425`. Effective ruleset 18156473 still requires one approval, stale-review dismissal, resolved threads, seven central workflows and deletion/non-fast-forward protection. Central main advanced to `fb2ae81dbeaacb0c630e51e9d772c6919fa220cf`; its master-context and product-goal blobs remain identical to the completely read earlier versions. The earlier dispatcher-allowlist source repair at `6f8c51d` is distinct from actual configuration and execution. Central matrix repair #1926 is now confirmed merged at `3f88e13af9dcde4b9da6958c02a78ce3b5c85800`, but old CodeQL handles 33958339895, 33958340068 and 33961083940 are now confirmed terminal failures, not queued and not retroactively GREEN. The canonical owner task has separate CodeQL admission, Strix/CO failover and Noema false-finding work; no duplicate rerun or consumer-side fallback was introduced. #35 retains its CHANGES_REQUESTED review and lacks independent acceptance. These facts do not authorize a protected merge. + +No real private campaign artifact, Zotero endpoint or model was read/called during this repair continuation. The last real campaign measurement remains 3,715 blank capture-bound slots and a 25-row pending view with 21 nonempty-text and four missing-text rows. Authentic decisions and independently approved labels remain separately 0/3,715; no real write or rollback occurred. Lifecycle capability metric 27 does not increase for regression hardening or repository discovery. + +Next: preserve current-head review/check evidence, obtain prerequisite protected integration, continue the 49 remaining source audits and the authentic decision/independent-approval campaign, and implement full-text-aware write admission under the required contract in ADR 0007. A complete reviewed set, explicit destinations and requested mode need separate authority verification, and the sealed binding must survive execution and every recovery outcome. That contract is documented, not implemented. Released CO consumption, upstream full-text version semantics and approved live write/rollback proof remain independent gaps. + +## Historical private-artifact owner repair checkpoint + Integrated runtime `b9c250632d04794773d4436665299610f6f9892d` preserves the original private-artifact fixes at their earliest owner and normally merges them forward through PR #38. The original reader/error is `ffa1150` in #29, not the later `5299219` helper extraction. Separate diagnostic RED/GREEN `4c0c8f0` → `25d4a78` and writer RED/GREEN `cdf8e12` → `0837c6f` repair source disclosure and the inclusive 16 MiB metadata save/read contract. PR #28's unchanged head also reproduced an inherited strict-Clippy fixture warning; `5c95bb7` restores the original clone-only intent before parent-to-child integration. [Proposed ADR 0006](adr/0006-zotero-research-intake.md) records the failure scenarios, rejected alternatives and propagation boundary. Each listed head passed locked Rust 1.98.0 workspace tests, strict all-target Clippy, formatting, warnings-denied rustdoc, CI contract and diff checks before normal push. Counts include doctests and exclude duplicate nested filtered subprocess totals. Every normal merge retains both the old child head and verified parent as ancestors; no predecessor delta was discarded or later full-text feature reverse-merged. @@ -111,7 +156,7 @@ All four new artifacts remain private mode `0600`, outside the repository: The repaired worksheet has 0/3,715 decisions and the first batch has 0/25; externally approved full-review coverage remains 0/3,715. No authorization prompt, approval, Zotero write, record merge/deletion or rollback was performed. The three historical Zotero 9 artifact hashes below were rechecked unchanged. The pre-repair schema-44 artifacts are also preserved. Stronger source binding is not classification correctness, business approval or loopback peer authentication. -The [CWL ontology capability inventory](doctoring/cwl_ontology_capability_inventory.md) now separates the 76-repository metadata census from 25 exact-default-source audits, up from 20. The latest five distinguish Psychometrics Commons' instrument/temporal domain contracts, DiagramWeave's revision-bound edits, NewsDOM's parser observations, and README-only supply-chain-control-plane/learning-record-store. Six selected candidates have GitHub releases with resolved source commits: RankWeave, mhtml-etl-gateway, fast-mlsirm, proprietary naruon, four-pillars and newsdom-api. Current four-pillars tests are not attributed to older v0.8.0; NewsDOM's v0.2.0 source lacks current-default authentication/upload bounds and has diverged history. Enumerated assets were not authenticated or installed. Missing licenses, absent releases, reference-only approval checks and source/default divergence remain owner cultivation gaps. NIST IR 8477 and OSCAL mapping references guide future relation-semantics tests without granting assurance or labels. Source-level discovery remains incomplete for 51 repositories; exact-consumer adoption remains unproved, and no additional utility owner is justified. +The [CWL ontology capability inventory](doctoring/cwl_ontology_capability_inventory.md) separates the 76-repository metadata census from 27 exact-default-source audits. The latest two add Keyverse's identity boundary and inkspan's revision-bound editor evidence to the earlier domain, measurement and document-contract audits. Seven selected candidates have GitHub releases with resolved source commits: RankWeave, mhtml-etl-gateway, fast-mlsirm, proprietary naruon, four-pillars, newsdom-api and inkspan. Current four-pillars tests are not attributed to older v0.8.0; NewsDOM's v0.2.0 source lacks current-default authentication/upload bounds and has diverged history; inkspan's v0.3.1 lacks its relevant current contracts. Enumerated assets were not authenticated or installed. Missing licenses, absent releases, reference-only approval checks and source/default divergence remain owner cultivation gaps. Standards guide future conformance tests without granting assurance or paper labels. Source-level discovery remains incomplete for 49 repositories; exact-consumer adoption remains unproved, and no additional utility owner is justified. Next: apply only authentic snapshot-bound steward decisions to the repaired batch, cover all 3,715 items and independently verify full-review approval; continue owner contract discovery and protected Foundation work while external checks/reviews are pending. The current body notes for PRs #11–#33 distinguish inherited source repair from historical head/check claims. No PR was closed, approved, merged or retargeted by this repair. @@ -129,7 +174,7 @@ Replayable retained-text coverage has progressed from 0 to 3,203/3,715 parents. Committed regressions repaired inherited environment proxies, exact-byte-limit rejection and replay checks occurring after digest allocation; clock fault injection verifies late/invalid-clock failures without changing the deadline. Final source verification at `733425df01511d894277fb8682e070f3dde03689` passed 173 tests across 37 suites including documentation tests, strict Clippy, formatting, rustdoc with warnings denied, the CI contract and the existing coverage gate. Coverage is 347/347 functions, 3,710/3,710 source-normalized regions and 674/674 source-normalized branch outcomes. Raw LLVM totals remain 4,159/4,255 lines, 6,129/6,274 regions and 603/674 branch outcomes; those are not 100%. The only source delta after the live run is a writer type alias resolving strict Clippy's complexity finding without changing runtime behavior. Hosted checks and independent protected approval remain separate gates. -At this earlier capture checkpoint, the next gap was the context-bound review chain implemented below, with 56 repository sources then unaudited. The latest checkpoint above reduces that remaining inventory to 51 while retaining authentic decisions, independent approval, full-text-aware write admission and the upstream version-contract repair as gaps. Unchanged predictions do not require a new proposal run merely to display retained evidence. A released contextual-orchestrator integration artifact remains unverified at the audited protected owner head, so source documentation alone does not authorize model-provider bypass. No new utility repository is justified by this one intake seam. +At this earlier capture checkpoint, the next gap was the context-bound review chain implemented below, with 56 repository sources then unaudited. The latest checkpoint above reduces that remaining inventory to 49 while retaining authentic decisions, independent approval, full-text-aware write admission and the upstream version-contract repair as gaps. Unchanged predictions do not require a new proposal run merely to display retained evidence. A released contextual-orchestrator integration artifact remains unverified at the audited protected owner head, so source documentation alone does not authorize model-provider bypass. No new utility repository is justified by this one intake seam. ### Canonical transport repair and released-owner audit @@ -187,7 +232,7 @@ At `2fadbdab`, the workspace test run completed successfully and its exact test The [real private-command receipt](doctoring/zotero_bound_review_commands_evidence.json) records a new 1,590,742-byte single-link `0600` pending view, generated offline in 2.22 seconds with maximum resident memory 288,505,856 bytes. The separate audit at 12:21:47.264 UTC checked unchanged input hashes, capture identity, 25 blank pending rows, 21 parents with nonempty text and four without, and byte equality with the earlier read-only view. All 3,715 worksheet slots remain blank. No Zotero/model request, new prediction, authentic decision, external approval or write occurred. Byte equality is reuse evidence, not increased full-text coverage. -At this private-command checkpoint the lifecycle capability metric advanced from 26 to 27 for callable review through finalization. This is local verified functionality, not protected-main shipment. The latest checkpoint above completes the then-pending minimal predecessor repair and advances repository audits from 20 to 25 of 76. Authentic full-denominator decisions and governance, full-text-aware write admission, upstream full-text version semantics and released CO integration remain gaps. Current protected checks/approval and a real immutable release remain independently necessary; no Utility Repository is justified by the current single-consumer seam. +At this private-command checkpoint the lifecycle capability metric advanced from 26 to 27 for callable review through finalization. This is local verified functionality, not protected-main shipment. The latest checkpoint above completes the then-pending minimal predecessor repairs and advances repository audits to 27 of 76. Authentic full-denominator decisions and governance, full-text-aware write admission, upstream full-text version semantics and released CO integration remain gaps. Current protected checks/approval and a real immutable release remain independently necessary; no Utility Repository is justified by the current single-consumer seam. ### Historical pre-repair Zotero 10 transition From 4820fef5d67486b282e9827b067f1554ddb5b4fd Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 7 Sep 2026 07:56:01 +0900 Subject: [PATCH 32/39] test: reject pending source completion in full text review --- .../src/full_text_capture_tests.rs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/crates/conceptweave-zotero/src/full_text_capture_tests.rs b/crates/conceptweave-zotero/src/full_text_capture_tests.rs index 282ff80b..f7df99cc 100644 --- a/crates/conceptweave-zotero/src/full_text_capture_tests.rs +++ b/crates/conceptweave-zotero/src/full_text_capture_tests.rs @@ -93,6 +93,11 @@ fn full_text_review_rejects_modified_display_and_never_overwrites_work() { ("/capture_digest", serde_json::json!("changed")), ("/metadata_report_digest", serde_json::json!("changed")), ("/proposal_digest", serde_json::json!("changed")), + ( + "/review_batch/proposal_digest", + serde_json::json!("changed"), + ), + ("/review_batch/pending_source_count", serde_json::json!(1)), ("/view_kind", serde_json::json!("changed")), ("/bibliographic_item_count", serde_json::json!(1)), ("/review_batch/remaining_count", serde_json::json!(1)), @@ -697,6 +702,27 @@ fn review_view_separates_two_text_parents_and_excludes_standalone_attachments() assert!(!second.contains("fixture text")); assert!(!second.contains("standalone evidence")); assert_eq!(report.pending_source_item_keys, ["FGHI789A"]); + + let bound = build_full_text_review_worksheet(&report, &capture).unwrap(); + let completed = completed_full_text_view(&report, &bound, &capture, 2); + let decided = apply_full_text_review_view(&report, &bound, &capture, &completed).unwrap(); + let golden = finalize_full_text_review( + &report, + &decided, + &capture, + full_text_approval_fixture(&report, &capture), + ) + .unwrap(); + let mut approval_calls = 0; + assert!( + evaluate_full_text_review(&report, &capture, &golden, |_| { + approval_calls += 1; + true + }) + .is_err() + ); + assert_eq!(approval_calls, 0); + assert_eq!(report.pending_source_item_keys, ["FGHI789A"]); } #[test] From 66abdda339c803df7b53d114ff21c4e97e95e54a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 7 Sep 2026 07:56:48 +0900 Subject: [PATCH 33/39] docs: checkpoint full text decision scope integration --- docs/product-technical-gap-baseline.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index e415611e..58422e48 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -4,6 +4,14 @@ This file records code-current product and technical gaps. Exact PR/check/run coordinates are evidence snapshots, not mutable-head dependencies. Live protected-branch, PR, issue and workflow state wins whenever it advances after this snapshot. Refresh the changed PR's metadata after each documentation commit; evidence from another PR or an earlier head does not transfer. +## September 7 PR38 source-scope integration in progress + +Original `e2c3a9fbbe36f44525833d4a94e164c6891a0f94` passed 243 tests/41 unfiltered suites. Normal merge `9439417` preserves that full-text decision/approval/private-command delta and verified PR37 `01395a0506ff80bc68ff0345e7c717d73af31c17`. Conflict resolution retains canonical output-pair admission, bounded metadata output, nonblocking private input and failure-safe output; it also preserves both dated Gap histories. PRD/UML retain full-text-aware write requirements and local checks before independent authority while replacing obsolete metadata-based recovery inference with directly verified outcomes. + +Independent bounded review finds the existing full-text consumer already delegates to shared batch, patch, finalization and complete-evaluation admission. No duplicate validator or hash is needed. Added tests mutate nested proposal and pending count and complete a mixed-library paper worksheet with a standalone source. Local finalization is preparation only: complete evaluation must reject before the external callback. These are synthetic unit decisions, not reviewed papers. Integration and full verification are running in `/tmp/conceptweave-pr38-integrated.log` and `/tmp/conceptweave-pr38-verified.log`; no current-head GREEN or coverage result is claimed yet. + +Actual decisions and independently approved labels remain separately 0/3,715 plus four unresolved sources. Native Zotero screenshot inspection succeeded at the PR37 checkpoint; older Mac-locked observations below are historical. No real Zotero write, hosted GREEN, protected merge or release is claimed. PR38 remains Draft behind prerequisites; root and later consumers still need source-scope adoption. + ## Historical September 6 local repair checkpoint Runtime `61bb211f798e9b91e921e65bc12d988e4b080dee` integrates the earliest-owner approval-order repair through the full dependent stack. Original planner `53b1d4dd046727d345fa2032d9426b2ba697b9df` in #13 could invoke an external approval verifier before rejecting locally invalid item, metadata or execute input. A one-use approval could therefore be consumed without a plan. Committed RED `505e111c993d8269e5b7b9e17a25a5ce20f8606e` demonstrated four failing negative test groups and a passing valid control. Minimal repair `8a684882005085d8b3cb47812e185975084e0475` moves the unchanged verifier block after all existing local checks. [Proposed ADR 0007](adr/0007-reviewed-zotero-write-plan.md) records 22 invalid-input scenarios with zero verifier calls, four valid controls with exactly one call, the intentional local-error precedence, alternatives and remaining authority limits. From 362b48b9af9544e4786a8277962761c0938b5e2c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 7 Sep 2026 07:59:10 +0900 Subject: [PATCH 34/39] docs: record proposed full text decision continuity --- docs/adr/0006-zotero-research-intake.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/adr/0006-zotero-research-intake.md b/docs/adr/0006-zotero-research-intake.md index 34e94390..72e54457 100644 --- a/docs/adr/0006-zotero-research-intake.md +++ b/docs/adr/0006-zotero-research-intake.md @@ -212,6 +212,16 @@ PR30 exposes incremental local progress and extracts shared worksheet comparison We reject snapshot-only identity because restored proposal and retained metadata can change under that coordinate. We reject treating nonbibliographic sources as paper decisions because their disposition is not inferred truth. We report both scopes, require zero pending sources for local completion, and retain separate approval and write gates. No new digest, source-text export or authority mechanism is added. Existing early finalization checks preserve error precedence. The stricter completion meaning corrects consumers that equated filled slots with complete source scope; later consumers must inherit it before claiming preparation completion. Protected review and release remain pending. +### September 7 full-text decision source continuity (Proposed) + +PR38 prepares and applies capture-bound decisions, then finalizes an unverified review and asks external governance to verify the whole envelope. The problem is preserving complete retained-source identity across that existing consumer after the prerequisite repairs, without treating filled paper slots as a completed library campaign. Normal merge `9439417` retains original `e2c3a9f` and parent `01395a0`, including the original private commands, duplicate-key parser, edit headroom and independent full-envelope verification. + +We retain the existing owner calls. Initialization builds a shared worksheet and verifies the capture/report relationship. Application validates the entire nested batch before comparing the remaining outer view and applying its patch atomically. Finalization delegates to bound worksheet conversion; complete evaluation delegates to the full-library gate before invoking the external verifier with the complete full-text envelope. A second source validator or hash is rejected because these boundaries already own admission and content identity. A metadata-only approval bridge is rejected because it would lose capture and label scope. + +Tests `4820fef` add nested proposal/pending-count tampering to the existing unchanged-input matrix and reuse the mixed-library fixture to fill every paper decision. Local finalization remains permissible preparation, but completion evaluation must fail with zero verifier calls while its standalone source remains pending. Rejecting all pending-source preparation would prevent stewards from making legitimate partial progress; accepting it as complete would silently omit sources. These tests create no real paper decisions or approval. Current verification results are recorded separately in the Gap baseline, never inferred from compilation or source review. + +Conflict resolution preserves canonical paired-output validation, nonblocking private reads and failure-safe buffered output. A failed write may retain a private partial file; it is not deleted by pathname and is not retried during buffer destruction. Both dated evidence histories are retained. Subsequent root/write consumers must inherit the same boundaries before claiming full-text completion or separately authorized mutation. Protected review, release, actual classification and independently verified approval remain open. + ### September 7 pending full-text view source scope (Proposed) PR37's read-only evidence view must preserve the source scope inherited from PR36 without turning viewing into a decision or approval. Normal merge `c4d40f7` preserves the child view and private-reader refactor together with complete retained-source validation, nonblocking opens and failure-safe buffered output. The PRD conflict is resolved by retaining both view requirements and duplicate-governance requirements. From 5aacfe1525b2f1eba3ad92845da2e59d85bd96ff Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 7 Sep 2026 08:02:15 +0900 Subject: [PATCH 35/39] docs: refresh remote review and orchestration release evidence --- docs/product-technical-gap-baseline.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 58422e48..6868840d 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -12,6 +12,8 @@ Independent bounded review finds the existing full-text consumer already delegat Actual decisions and independently approved labels remain separately 0/3,715 plus four unresolved sources. Native Zotero screenshot inspection succeeded at the PR37 checkpoint; older Mac-locked observations below are historical. No real Zotero write, hosted GREEN, protected merge or release is claimed. PR38 remains Draft behind prerequisites; root and later consumers still need source-scope adoption. +The September 7 remote refresh confirms PR38 still OPEN Draft at original `e2c3a9f`, with no review decision and only the CodeRabbit status context in the returned rollup. That status is not independent approval or complete required checks, and it does not validate the unpushed source. Next PR39 remains OPEN Draft at `6779fc40c71eccb03b0784cee6c3b5c14fb6e25a`, based on PR38's named branch. `gh release list --repo ContextualWisdomLab/contextual-orchestrator --limit 5 --json tagName,publishedAt,isDraft,isPrerelease` returns an empty list: no GitHub release was observed. This query alone does not establish package-registry or deployed-service state, and supplies no immutable consumption proof. No direct-provider fallback is authorized by that gap. + ## Historical September 6 local repair checkpoint Runtime `61bb211f798e9b91e921e65bc12d988e4b080dee` integrates the earliest-owner approval-order repair through the full dependent stack. Original planner `53b1d4dd046727d345fa2032d9426b2ba697b9df` in #13 could invoke an external approval verifier before rejecting locally invalid item, metadata or execute input. A one-use approval could therefore be consumed without a plan. Committed RED `505e111c993d8269e5b7b9e17a25a5ce20f8606e` demonstrated four failing negative test groups and a passing valid control. Minimal repair `8a684882005085d8b3cb47812e185975084e0475` moves the unchanged verifier block after all existing local checks. [Proposed ADR 0007](adr/0007-reviewed-zotero-write-plan.md) records 22 invalid-input scenarios with zero verifier calls, four valid controls with exactly one call, the intentional local-error precedence, alternatives and remaining authority limits. From dd86e6b7d609a52c4de8aa196ea6e1de72482b43 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 7 Sep 2026 08:12:09 +0900 Subject: [PATCH 36/39] docs: link owner reported research integration prerequisites --- docs/product-technical-gap-baseline.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 6868840d..f40f26f2 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -14,6 +14,8 @@ Actual decisions and independently approved labels remain separately 0/3,715 plu The September 7 remote refresh confirms PR38 still OPEN Draft at original `e2c3a9f`, with no review decision and only the CodeRabbit status context in the returned rollup. That status is not independent approval or complete required checks, and it does not validate the unpushed source. Next PR39 remains OPEN Draft at `6779fc40c71eccb03b0784cee6c3b5c14fb6e25a`, based on PR38's named branch. `gh release list --repo ContextualWisdomLab/contextual-orchestrator --limit 5 --json tagName,publishedAt,isDraft,isPrerelease` returns an empty list: no GitHub release was observed. This query alone does not establish package-registry or deployed-service state, and supplies no immutable consumption proof. No direct-provider fallback is authorized by that gap. +The existing CO owner task separately reports no verified immutable release/client/schema coordinates for ConceptWeave model-backed research classification. Its reported work is ContextualWisdomLab/contextual-orchestrator#1053 at `43165156d3799cee6bb23c0afdad22da07744831`, #1067 at `9ccfc7b9f22d77d172dcd68b56a78a069ccc11f8` and #1074 at `15e14d6c48a795fa0d60df95492e968fa287655f`; reported prerequisites include central ContextualWisdomLab/.github#1978, an EgressWeave portable-hash repair and protected release of full response-lifetime behavior. These are owner-reported dependency coordinates, not independently refreshed check or merge claims. The owner explicitly distinguishes hosted test/supply-chain success from a deployed research-classification contract. Keep the released consumer boundary closed until immutable contract and runtime evidence are supplied; do not copy owner source or route directly to a provider. + ## Historical September 6 local repair checkpoint Runtime `61bb211f798e9b91e921e65bc12d988e4b080dee` integrates the earliest-owner approval-order repair through the full dependent stack. Original planner `53b1d4dd046727d345fa2032d9426b2ba697b9df` in #13 could invoke an external approval verifier before rejecting locally invalid item, metadata or execute input. A one-use approval could therefore be consumed without a plan. Committed RED `505e111c993d8269e5b7b9e17a25a5ce20f8606e` demonstrated four failing negative test groups and a passing valid control. Minimal repair `8a684882005085d8b3cb47812e185975084e0475` moves the unchanged verifier block after all existing local checks. [Proposed ADR 0007](adr/0007-reviewed-zotero-write-plan.md) records 22 invalid-input scenarios with zero verifier calls, four valid controls with exactly one call, the intentional local-error precedence, alternatives and remaining authority limits. From 30aa8487dad56a8d9094370c0cd4c284f788cefc Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 7 Sep 2026 08:14:32 +0900 Subject: [PATCH 37/39] docs: corroborate canonical release evidence gap --- docs/product-technical-gap-baseline.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index f40f26f2..6ada5eec 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -16,6 +16,8 @@ The September 7 remote refresh confirms PR38 still OPEN Draft at original `e2c3a The existing CO owner task separately reports no verified immutable release/client/schema coordinates for ConceptWeave model-backed research classification. Its reported work is ContextualWisdomLab/contextual-orchestrator#1053 at `43165156d3799cee6bb23c0afdad22da07744831`, #1067 at `9ccfc7b9f22d77d172dcd68b56a78a069ccc11f8` and #1074 at `15e14d6c48a795fa0d60df95492e968fa287655f`; reported prerequisites include central ContextualWisdomLab/.github#1978, an EgressWeave portable-hash repair and protected release of full response-lifetime behavior. These are owner-reported dependency coordinates, not independently refreshed check or merge claims. The owner explicitly distinguishes hosted test/supply-chain success from a deployed research-classification contract. Keep the released consumer boundary closed until immutable contract and runtime evidence are supplied; do not copy owner source or route directly to a provider. +The central coordinator corroborates that it has no verified CO immutable released client/schema/runtime coordinates. It reports ContextualWisdomLab/.github#1978 (`5dad3fe`), ContextualWisdomLab/.github#1668 (`82b19c`), EgressWeave's dependency-graph repair and the response-lifetime owner as prerequisites. These abbreviated coordinates are communication references, not independently resolved or approved revisions. Its reported local CO test pass count is explicitly not deployed evidence; required checks remain unfinished in that report. ConceptWeave therefore retains its port/ACL/test-double boundary rather than adopting those open branches as production contracts. + ## Historical September 6 local repair checkpoint Runtime `61bb211f798e9b91e921e65bc12d988e4b080dee` integrates the earliest-owner approval-order repair through the full dependent stack. Original planner `53b1d4dd046727d345fa2032d9426b2ba697b9df` in #13 could invoke an external approval verifier before rejecting locally invalid item, metadata or execute input. A one-use approval could therefore be consumed without a plan. Committed RED `505e111c993d8269e5b7b9e17a25a5ce20f8606e` demonstrated four failing negative test groups and a passing valid control. Minimal repair `8a684882005085d8b3cb47812e185975084e0475` moves the unchanged verifier block after all existing local checks. [Proposed ADR 0007](adr/0007-reviewed-zotero-write-plan.md) records 22 invalid-input scenarios with zero verifier calls, four valid controls with exactly one call, the intentional local-error precedence, alternatives and remaining authority limits. From c0a3694fb17c912dbdc7817bc1df1cc143c670e3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 7 Sep 2026 08:20:54 +0900 Subject: [PATCH 38/39] docs: record next owner preparation and recovery gaps --- docs/product-technical-gap-baseline.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 6ada5eec..d4529773 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -18,6 +18,12 @@ The existing CO owner task separately reports no verified immutable release/clie The central coordinator corroborates that it has no verified CO immutable released client/schema/runtime coordinates. It reports ContextualWisdomLab/.github#1978 (`5dad3fe`), ContextualWisdomLab/.github#1668 (`82b19c`), EgressWeave's dependency-graph repair and the response-lifetime owner as prerequisites. These abbreviated coordinates are communication references, not independently resolved or approved revisions. Its reported local CO test pass count is explicitly not deployed evidence; required checks remain unfinished in that report. ConceptWeave therefore retains its port/ACL/test-double boundary rather than adopting those open branches as production contracts. +### Next PR39 integration findings observed September 7 + +Read-only CodeGraph exploration of root `6779fc40c71eccb03b0784cee6c3b5c14fb6e25a` identifies an extracted `prepare_full_text_review` that checks label cardinality and calls the sampled preparation helper, instead of retaining the complete evaluator's pending-source gate. Parent adoption must preserve that check in the shared preparation path before either meaning or write verifier; changing only the public evaluation wrapper would leave full-text write admission exposed. The PR38 mixed-source test is a regression witness for the forthcoming integration. The independently reviewed extracted write-plan preparation likewise needs the inherited shared-report and proposal checks at its actual caller boundary, to be verified during that integration. + +The same current-root inspection shows `reconcile_full_text_rollback` retains a binding, new observation result and untouched operations but not the complete preceding rollback receipt. That drops earlier confirmed outcomes and the exact failed request/earlier observation from the next envelope. Reuse the existing retained-receipt observation pattern and add a regression retaining every prior coordinate; do not reconstruct authority from an operation slice. The parent executor's exact indeterminate request should be reused rather than captured independently a second time. These are next-owner repair findings, not implemented fixes, test results or permission to execute real writes. Root files remain unchanged during this preview. + ## Historical September 6 local repair checkpoint Runtime `61bb211f798e9b91e921e65bc12d988e4b080dee` integrates the earliest-owner approval-order repair through the full dependent stack. Original planner `53b1d4dd046727d345fa2032d9426b2ba697b9df` in #13 could invoke an external approval verifier before rejecting locally invalid item, metadata or execute input. A one-use approval could therefore be consumed without a plan. Committed RED `505e111c993d8269e5b7b9e17a25a5ce20f8606e` demonstrated four failing negative test groups and a passing valid control. Minimal repair `8a684882005085d8b3cb47812e185975084e0475` moves the unchanged verifier block after all existing local checks. [Proposed ADR 0007](adr/0007-reviewed-zotero-write-plan.md) records 22 invalid-input scenarios with zero verifier calls, four valid controls with exactly one call, the intentional local-error precedence, alternatives and remaining authority limits. From 76d3df5733c37761b6778e24d89f22c01f367bb3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 7 Sep 2026 08:21:44 +0900 Subject: [PATCH 39/39] docs: record verified full text decision scope --- docs/product-technical-gap-baseline.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index d4529773..7aabf3f1 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -4,11 +4,13 @@ This file records code-current product and technical gaps. Exact PR/check/run coordinates are evidence snapshots, not mutable-head dependencies. Live protected-branch, PR, issue and workflow state wins whenever it advances after this snapshot. Refresh the changed PR's metadata after each documentation commit; evidence from another PR or an earlier head does not transfer. -## September 7 PR38 source-scope integration in progress +## September 7 PR38 source-scope integration verification Original `e2c3a9fbbe36f44525833d4a94e164c6891a0f94` passed 243 tests/41 unfiltered suites. Normal merge `9439417` preserves that full-text decision/approval/private-command delta and verified PR37 `01395a0506ff80bc68ff0345e7c717d73af31c17`. Conflict resolution retains canonical output-pair admission, bounded metadata output, nonblocking private input and failure-safe output; it also preserves both dated Gap histories. PRD/UML retain full-text-aware write requirements and local checks before independent authority while replacing obsolete metadata-based recovery inference with directly verified outcomes. -Independent bounded review finds the existing full-text consumer already delegates to shared batch, patch, finalization and complete-evaluation admission. No duplicate validator or hash is needed. Added tests mutate nested proposal and pending count and complete a mixed-library paper worksheet with a standalone source. Local finalization is preparation only: complete evaluation must reject before the external callback. These are synthetic unit decisions, not reviewed papers. Integration and full verification are running in `/tmp/conceptweave-pr38-integrated.log` and `/tmp/conceptweave-pr38-verified.log`; no current-head GREEN or coverage result is claimed yet. +Independent bounded review finds the existing full-text consumer already delegates to shared batch, patch, finalization and complete-evaluation admission. No duplicate validator or hash is needed. Tests `4820fef` mutate nested proposal and pending count and complete a mixed-library paper worksheet with a standalone source. Local finalization is preparation only: complete evaluation rejects before the external callback. These are synthetic unit decisions, not reviewed papers. Independent follow-up confirms the intended failure boundary and retained private-file protections. Proposed ADR0006 `362b48b` records alternatives and consequences. + +Integration and verified source `4820fef` each pass 295 tests/41 unfiltered suites including three doctests. Strict Clippy, warnings-denied rustdoc, formatting, CI contract and diff checks pass. The unchanged pinned coverage gate passes 414/414 functions, 4,370/4,370 normalized regions and 708/708 normalized branches. Raw LLVM remains 5,157/5,241 lines, 7,748/7,920 regions and 646/708 branches, not 100%. Logs `/tmp/conceptweave-pr38-{baseline,integrated,verified,clippy,rustdoc,coverage}.log` are terminal success. Later commits are documentation only. Local success is not hosted verification or protected approval. Actual decisions and independently approved labels remain separately 0/3,715 plus four unresolved sources. Native Zotero screenshot inspection succeeded at the PR37 checkpoint; older Mac-locked observations below are historical. No real Zotero write, hosted GREEN, protected merge or release is claimed. PR38 remains Draft behind prerequisites; root and later consumers still need source-scope adoption.