feat(core,wasm): filter_by_collections single-source + org_manifest_decrypt_filtered

Add OrgManifest::filter_by_collections(&[String]) as the single source of
grant-filter logic. Refactor filter_for_member to delegate to it (no
duplicated loop). Add org_manifest_decrypt_filtered WASM binding that
decrypts + filters in core so ungranted entries never cross to JS (phase-1
SOFT/UX filter; per-collection crypto isolation is phase-2). Declare the
binding in extension/src/wasm.d.ts. Vec<String> compiled cleanly for
wasm32-unknown-unknown.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014s527M917W47LDrfQ4t47g
This commit is contained in:
adlee-was-taken
2026-06-26 22:19:55 -04:00
parent 0d56b3333b
commit 288956c089
3 changed files with 80 additions and 5 deletions

View File

@@ -672,6 +672,26 @@ pub fn org_manifest_decrypt(handle: &SessionHandle, encrypted: &[u8]) -> Result<
js_value_for(&out)
}
/// Decrypt an org manifest blob AND grant-filter it to `granted` collection slugs,
/// for the READ path. Filtering happens in core (`filter_by_collections`), so
/// ungranted entries never cross to JS. (SOFT phase-1 UX filter — see core
/// doc-comment; the member holds the org key, this is not crypto isolation.)
/// Dev-C writes use the UNFILTERED `org_manifest_decrypt` instead (re-encrypting a
/// filtered subset would wipe ungranted entries).
#[wasm_bindgen]
pub fn org_manifest_decrypt_filtered(
handle: &SessionHandle,
encrypted: &[u8],
granted: Vec<String>,
) -> Result<JsValue, JsError> {
need_key(handle)?;
let manifest = session::with(handle.0, |k| relicario_core::decrypt_org_manifest(encrypted, k))
.unwrap()
.map_err(|e| JsError::new(&e.to_string()))?;
let filtered = manifest.filter_by_collections(&granted);
js_value_for(&filtered)
}
/// Encrypt an OrgManifest (JSON) with an org session handle, for the write track
/// (Dev-C never touches WASM — this is exposed here so they don't have to).
#[wasm_bindgen]