{"criteria_id":"memory-scope-hygiene/v1","criteria_revision":"1.0.0","family":"memory-scope-hygiene/v1","family_note":"Criteria id memory-scope-hygiene/v1 is the coordinator's proposal in the M-45 brief, recorded as an interpretation in REGISTRY.md.","ops":["audit"],"ops_not_offered":[],"corpus":{"id":"memory-scope-hygiene-corpus/v1","sha256":"4e072f54c5dcd83e66342a31c4c328c311b63fd76b344906607a44ada4fedf34"},"corpus_pin_definition":"The corpus pin is the SHA-256 of the corpus's canonical serialization (the corpus object excluding its sha256 field), computed and verifiable by the published harness (harness.mjs). Expected outputs inside corpus cases are derived from the criteria text by an independent implementation in gen-corpus.mjs (never the engine's code path), and each case labels its provenance (all corpus exports are synthetic fixtures authored for this build; no real memory-store data is used or read, ever).","price":{"usdc_units_per_call":50000,"label":"$0.05","status":"RULED","basis":"per scope-hygiene audit (one submitted memory-store export)"},"price_status":"RULED","price_construction":"The $0.05 figure is proposed in the source (Eval #2, via BUILD-QUEUE Q-23) at the top of the verdict band: it is labelled RULED (price ruled 2026-11-03) on every surface of this service.","free_tier":{"per_day":100,"min_interval_ms":5000,"on_exhaustion":"402"},"input_contract":{"body":"One JSON request body carrying the export (family, form, variant?, manifest?, records) plus caller? on the free pre-check only.","caps":{"body_bytes":262144,"records_per_audit":1000},"over_cap":"An over-cap submission is a free pre-gate refusal naming the cap.","validation":"The export is validated FREE, before any payment: family, form, variant, manifest shape, per-record id and content-field rules, and the published caps. A malformed export fails free and never consumes a payment. The audit runs only after payment verification (or on a verified-marker resume with no second charge)."},"input_contract_text":"Input contract: the request body is one JSON object { family, form, variant?, manifest?, records } (plus caller on the free pre-check only). family is one of mem0, zep_graphiti, letta, generic. form is full_text (every record carries content_text, a string exactly as submitted) or hash_only (every record carries content_hash, a non-empty string taken as-is). variant is optional with published defaults: Mem0 platform or oss (default platform); Zep/Graphiti graphiti or zep_cloud (default graphiti); Letta mixed, blocks or passages (default mixed); generic claim-jsonl (only value). records is an array of 1 to 1,000 records; each record carries id (a string, unique in the submission) and the form's content field, plus any published optional fields: the family's scope keys, scope, attached_to, source, attributed_scope, storage_scope, history_scope, group_id, episode_group_ids, endpoint_group_ids, shared, sharing, metadata, origin_claim_id, origin_scope, supersedes, replaced_by, superseded_by, is_deleted, invalid_at, expired_at, lifecycle_state, state, is_template, template_id, base_template_id, file_id, source_passage, record_class. manifest is an optional object carrying declared_scope_keys, scope_filtered, multi_scope, global_scopes, global_content_keys, global_claim_ids, private_scopes. Caps: request body up to 262,144 bytes; up to 1,000 records per audit — an over-cap submission is a free refusal naming the cap, before any payment.","malformed_rule":"Malformed submissions (named FREE refusals, before any payment): body not a plain object (export_required); family missing (family_required) or not one of the four published families (family_unknown); form missing or not full_text or hash_only (form_invalid); records missing or not an array (records_required) or empty (records_empty); more than 1,000 records (record_count_cap); a record not a plain object (record_not_object); a record id missing or not a string (record_id_required) or duplicated in the submission (record_id_duplicate); a full-text record whose content_text is missing or not a string (content_text_required); a hash-only record whose content_hash is missing, not a string, or empty (content_hash_required); manifest present but not a plain object (manifest_invalid); declared_scope_keys present but not an array of strings (declared_scope_keys_invalid); a variant not published for the declared family (variant_unknown). A malformed submission fails free and never consumes a payment. Error surfaces carry static strings plus numeric facts only — never any part of an export.","factor_rules":"The five factors, as computed. (1) scope_presence: eligible records carrying at least one declared scope key with a non-empty value (Letta: a non-empty attached_to scope set), divided by eligible records, as a percent to one decimal with both counts printed; eligible excludes the Letta template and source-passage record classes, which are counted in coverage. (2) unscoped_rate: eligible records with no scope, divided by eligible records, gated per the assessability matrix (a Mem0 scope-filtered export grades NOT ASSESSABLE; the Zep/Graphiti multi-scope demonstration gate applies; a Zep Cloud submission with a single distinct scope and no manifest multi_scope demonstration grades NOT ASSESSABLE-by-construction). (3) cross_scope_clusters: per the marker convention — candidate, marked-shared, undeclared and ambiguous counts, the marked share of live records, and per-cluster member record ids, scopes and classification; no content and no grader-computed content hash is ever printed. (4) scope_conflicts: the published field-equality sub-checks — attributed_scope versus the effective storage scope (storage_scope if present, else the record's scope value); history_scope versus the effective storage scope; any episode_group_ids value not equal to the record's group_id; any endpoint_group_ids value not equal to the record's group_id — assessable exactly when at least one record carries at least one checked field and the family is not Letta or generic (whose factor is NOT ASSESSABLE per the matrix regardless of submitted fields); the report prints the conflict count by sub-check and the conflicting record ids. (5) duplicates: per scope partition, live records grouped by content key; excess is the sum over partitions of max(0, n_s - 1); duplicate clusters are partition groups with at least two live records, printed with member ids, scope and count; the live-duplicate rate is excess divided by live records, as a percent to one decimal; the generic scopeless form uses the single unnamed partition. Liveness: a record is not live when it is the target of another record's supersedes, carries replaced_by or superseded_by, has is_deleted true, has invalid_at or expired_at non-null (exactly one of the two set is a split-state anomaly counted in coverage, and the record is not live), or has lifecycle_state or state in {superseded, deleted, expired}; a generic head with state pending is contested, reported in coverage and excluded from the live-duplicate numerator's records. Factors are printed individually; there is no composite score, grade, or band anywhere in this service.","scope_text":"The report grades the export's scope hygiene as submitted (in hash-only form: as hashed by the submitter). It is never a whole-store certification and never proof that a leak did or did not occur at runtime. An undeclared cluster is reported exactly as: \"undeclared cross-scope identical content — contamination indicator, not proof of a runtime leak\". A cluster classified marked shared is reported as declared shared by the operator — never as verified legitimate and never as no leak.","marker_convention":"Sharing-marker convention (adopted from the S1 deepening pass, with two amendments recorded in DESIGN.md: M3-b keyed as submitted, and Rule set C implemented by partition construction). P0: scope must be representable before it can be marked; without a scope the convention does not engage. M1: shared with the exact boolean value true, or sharing with the exact value \"global\", at record level or inside the record's metadata object. A non-boolean shared is malformed (N4); shared: false is an explicit non-sharing declaration. M2: the record's scope value is exactly the reserved string \"global\" — the record lives in the global partition; global copies are noted in a cluster's composition and are never themselves classified; \"shared\", \"all\", \"*\", \"GLOBAL\" and \" global\" are NOT reserved values. M3: the submission manifest declares global_scopes (scope values treated as M1-marked, this submission only), global_content_keys (content keys as submitted: the content_hash in hash-only form, the exact content text in full-text form), or global_claim_ids (record ids declared canonical global originals; pairs with M4); declared-but-absent entries are counted and never held against the operator. M4: origin_claim_id on a copy, optionally with origin_scope; it classifies only when it resolves (the origin exists in the submission or is attested by M3-c; origin content key equals the copy's; origin_scope, if present, equals the origin record's actual scope; chains are followed to their root with max depth equal to the cluster size, and a cycle with no covered root does not resolve); a failed pointer is ambiguous (N2), not unmarked. Non-markers (exhaustive for v1): identical source values; source naming a document, product or artefact; identical timestamps; identical evidence hashes; a supersedes relation; the content looking generic; the cluster being large. Clusters: a candidate is a content key whose LIVE records sit under at least two distinct non-global scopes (Letta: at least two records with disjoint scope sets); superseded records never form or join a cluster. Decision precedence: N4 malformed marker, then N3 contradictory declaration, then N2 broken pointer, then N5 partition ambiguity (copies carrying different sets of declared scope keys), then B5 attribution veto, then N1 partial coverage, then B1 marked shared, then B2 undeclared. An ambiguous cluster is its own NOT ASSESSABLE line, counted in neither B1 nor B2; ambiguity is never resolved by majority vote. B1 requires every live non-global copy covered by at least one marker, no contradiction, and no attribution conflict. B5: source agreement is never a marker — agreement plus coverage leaves B1 standing; agreement plus no coverage leaves B2 standing; disagreement plus coverage classifies NOT ASSESSABLE (attribution conflict; the records are also referred to factor scope_conflicts where computable); disagreement plus no coverage leaves B2 standing with the distinct source count reported; where source is omitted, B5 does not run and the coverage block says so. No double-counting with factor duplicates: factor duplicates counts only intra-scope excess (per scope, the sum of max(0, n_s - 1)), so a marked-shared copy that is the sole live copy of its content in its scope contributes zero to duplicates; an unmarked cross-scope spread is classified once under cross_scope_clusters and only its intra-scope excess counts under duplicates; a marker never excuses intra-scope duplication; a marker on a non-live record is inert. Exactness: clustering is by exact content identity (the text exactly as submitted, or the supplied hash as-is) — no normalisation of any kind is applied before identity is tested, and near-duplicates are not clustered and are counted in neither cross_scope_clusters nor duplicates. A marker is a declaration, not evidence: the coverage block reports the marked share of live records. Minimum viable form: M1 — shared: true on every record whose content is legitimately global.","extension_rule":"Extension rule for declared scope keys: factors scope_presence, unscoped_rate and cross_scope_clusters are computed over exactly the declared scope keys. The criteria declare the family defaults (Mem0: user_id, agent_id, app_id, run_id, with session_id normalised to run_id when run_id is absent; Zep/Graphiti: group_id, graph_id); manifest.declared_scope_keys replaces the default set for that submission. Undeclared keys are NEVER guessed: no scope is parsed out of a source string, a record id, or content, and a key outside the declared set is ignored for scope. Under this published rule, a generic-family export with no declared scope keys grades scope_presence, unscoped_rate and cross_scope_clusters NOT ASSESSABLE (its schema defines no scope field), scope_conflicts NOT ASSESSABLE in all cases, and duplicates graded. Letta scope is the record's attached_to set and needs no key declaration. A record's scope value is its declared keys' values joined in declared order; the coverage block names the keys relied on.","assessability_matrix":{"mem0":{"full_text":{"scope_presence":{"status":"ASSESSABLE","note":"graded for any in-contract Mem0 export"},"unscoped_rate":{"status":"ASSESSABLE","note":"except a scope-filtered export (manifest.scope_filtered true), which grades NOT ASSESSABLE"},"cross_scope_clusters":{"status":"CONDITIONAL","note":"clustering is exact; the marker condition: no native marker exists, M1 lives in free-form metadata, under the adopted convention"},"scope_conflicts":{"status":"CONDITIONAL","note":"narrow field-equality sub-checks only; on an export with no attribution fields it grades NOT ASSESSABLE, never zero conflicts"},"duplicates":{"status":"ASSESSABLE","note":"exact-content only; semantic duplicates are invisible to an identical-content factor by design"}},"hash_only":{"scope_presence":{"status":"ASSESSABLE","note":"survives intact"},"unscoped_rate":{"status":"ASSESSABLE","note":"survives intact; same scope-filtered exception"},"cross_scope_clusters":{"status":"CONDITIONAL","note":"survives with one degradation: the marker half is conditional on metadata being submitted; withheld metadata means every cluster reports no marker by construction"},"scope_conflicts":{"status":"CONDITIONAL","note":"degrades to structural sub-checks where those fields are submitted; otherwise NOT ASSESSABLE"},"duplicates":{"status":"ASSESSABLE","note":"survives intact; matches the store's own exact-hash dedup semantics"}}},"zep_graphiti":{"full_text":{"scope_presence":{"status":"ASSESSABLE","note":"Graphiti: computable but vacuous-by-construction (group_id is model-required) unless read with the distinct-scope count, which travels with the rate; Zep Cloud: collapses to graph kind"},"unscoped_rate":{"status":"CONDITIONAL","note":"multi-scope must be demonstrated from the submission (at least two distinct scopes, or manifest.multi_scope true); Zep Cloud user graphs are NOT ASSESSABLE-by-construction"},"cross_scope_clusters":{"status":"CONDITIONAL","note":"Graphiti: assessable as an indicator under the convention (per-group re-extraction is the family's design, not leakage). Zep Cloud: NOT ASSESSABLE intra-graph (one scope); graded across a multi-graph submission"},"scope_conflicts":{"status":"CONDITIONAL","note":"the exact structural sub-checks are this family's strongest factor; all semantic forms are NOT ASSESSABLE"},"duplicates":{"status":"ASSESSABLE","note":"after the liveness redefinition: an edge is live iff invalid_at and expired_at are both null"}},"hash_only":{"scope_presence":{"status":"ASSESSABLE","note":"survives intact, same vacuity note"},"unscoped_rate":{"status":"CONDITIONAL","note":"survives with a tighter gate: withheld metadata narrows the multi-scope demonstration routes; the arithmetic is untouched"},"cross_scope_clusters":{"status":"CONDITIONAL","note":"Graphiti survives intact as an exact-content factor; Zep Cloud under the same cross-graph condition"},"scope_conflicts":{"status":"CONDITIONAL","note":"degrades to its exact structural forms, which are pure field comparisons and survive intact"},"duplicates":{"status":"ASSESSABLE","note":"survives intact; every input is structural or hash-based"}}},"letta":{"full_text":{"scope_presence":{"status":"CONDITIONAL","note":"reframed: records carry no user/agent/run fields, so this measures submission assembly and orphanhood over the assembled bundle; template blocks and source passages are excluded from the denominator"},"unscoped_rate":{"status":"CONDITIONAL","note":"reframed as the orphan / unattached-archive rate, with the same exclusions"},"cross_scope_clusters":{"status":"CONDITIONAL","note":"computable only under Rules L-1 to L-4 (identity first; scope sets, not points; archive as the passage scope unit; designed-sharing exclusions); after them it measures unmanaged copying"},"scope_conflicts":{"status":"NOT ASSESSABLE","note":"no source/target scope field exists in this family; the two narrow structural sub-checks in the source record are not this factor and are not sold as it"},"duplicates":{"status":"CONDITIONAL","note":"passages assessable; blocks coarse (record-level duplication only; duplicate facts inside one block's blob are invisible)"}},"hash_only":{"scope_presence":{"status":"CONDITIONAL","note":"survives intact under the same reframe"},"unscoped_rate":{"status":"CONDITIONAL","note":"survives intact under the same reframe"},"cross_scope_clusters":{"status":"CONDITIONAL","note":"survives intact under Rules L-1 to L-4; identity collapse needs only ids and the hash is the native cluster key"},"scope_conflicts":{"status":"NOT ASSESSABLE","note":"loses nothing; it was not computable in full-text form either"},"duplicates":{"status":"CONDITIONAL","note":"survives intact; passages assessable, blocks coarse as in full-text form"}}},"generic":{"full_text":{"scope_presence":{"status":"CONDITIONAL","note":"the generic schema defines no scope field: graded only via scope keys declared in the manifest (the extension rule); absent them it grades NOT ASSESSABLE"},"unscoped_rate":{"status":"CONDITIONAL","note":"same extension condition; source diversity never substitutes for scope evidence"},"cross_scope_clusters":{"status":"CONDITIONAL","note":"same extension condition; this is the convention's home family (M1-M4 as published)"},"scope_conflicts":{"status":"NOT ASSESSABLE","note":"no storage-scope or target fields exist in the schema"},"duplicates":{"status":"ASSESSABLE","note":"computable unconditionally, fully specified in the criteria"}},"hash_only":{"scope_presence":{"status":"CONDITIONAL","note":"same extension condition; hashing is not the cause when it grades NOT ASSESSABLE"},"unscoped_rate":{"status":"CONDITIONAL","note":"same extension condition"},"cross_scope_clusters":{"status":"CONDITIONAL","note":"same extension condition; the factor hash-only costs least, except the B5 attribution sub-check, which runs only if source is submitted in clear"},"scope_conflicts":{"status":"NOT ASSESSABLE","note":"it was already not computable in full-text form"},"duplicates":{"status":"ASSESSABLE","note":"survives intact; every input is structural or hash-based by construction"}}}},"letta_rules":"Letta rules L-1 to L-4. L-1 identity first: one record, one row; its scope is a set (attached_to), so a block shared by several agents is one record in several scopes and never a cross-scope cluster by itself. L-2 scope sets, not points: a factor cross_scope_clusters candidate needs at least two records whose scope sets are disjoint. L-3 the archive is the passage scope unit, resolved into attached_to when the operator assembles the submission bundle (the .af agent file does not contain archival passages; the submission is the assembled bundle of agent files plus a passages dump plus the archive-to-agent resolution). L-4 designed-sharing exclusions: template blocks (is_template) are excluded from the scope_presence and unscoped_rate denominators and from cross_scope_clusters; copies sharing a template_id or base_template_id lineage are reported as a separate template-lineage count and never as undeclared clusters; source passages (source_passage true, or file_id present with no archive resolution) are a different record class and are excluded from the unscoped_rate denominator. For Letta, scope_presence and unscoped_rate measure submission assembly and orphanhood, not operator diligence.","undeclared_label":"undeclared cross-scope identical content — contamination indicator, not proof of a runtime leak","verdict_grammar":{"line":"MEMORY SCOPE-HYGIENE — records: <n> · live: <n> · unscoped: <rate|NOT ASSESSABLE> · undeclared cross-scope clusters: <k|NOT ASSESSABLE> · conflicts: <k|NOT ASSESSABLE> · live duplicates: <k> — <check-end UTC>","vocabulary":"The report's facts are the published closed set: the five factors, each ASSESSABLE with its counts/rates or NOT ASSESSABLE with its reason, plus the coverage block. There is no composite score, grade, or band anywhere in this service."},"free_precheck":"The free pre-check (POST /precheck) validates the export under the published contract and returns assessability ONLY: the common signed fields, the FULL assessability matrix row for the declared family and form, the resolved per-factor status for this submission (ASSESSABLE, or NOT ASSESSABLE with the reason), and the record and live-record counts — with NO factor computed and NO hygiene finding returned. Free allowance: 100 per day per caller wallet, minimum interval 5,000 ms, 402 on exhaustion.","custody":{"construction":"In-memory processing within a single request lifetime; the export persists nowhere, on any path including errors.","ledger_closed_fields":["payer wallet","timestamp/day","price (USDC units) with the CAD fair-market-value block (rate, rate date, basis, cad_pending, refund)","payment tx id + Solana fee (lamports)","criteria version","verdict line","op","undeclared-cluster count (null when the factor is NOT ASSESSABLE)","house flag"],"ledger_note":"The ledger list above is closed: adding a field is a criteria/handling version change. The ledger NEVER carries any export content, any record's content, any content hash supplied by the submitter, or any grader-computed content hash.","errors":"Error handling is closed-form: static strings plus numeric facts only (lengths against caps, rule ids). No error path interpolates any part of an export beyond the published refusal codes."},"forbidden":"Forbidden in every surface of this service (report, pre-check, errors, ledger, metrics): any composite score or grade; any export content; any grader-computed content hash; any statement that an undeclared cluster proves a runtime leak; any statement that a marked-shared cluster is verified legitimate; any NOT ASSESSABLE factor printed as a zero; any scope derived from an undeclared key.","honesty_line":"Every fact in the report is computed from the one submitted export under the published rules and nothing else: the service contacts no store, fetches nothing, and infers nothing beyond the five factors. A factor the export's family or form cannot support is stated NOT ASSESSABLE with its reason — never printed as a zero, never guessed, and never filled in from an undeclared key.","retention":"The export is processed in memory only and is never stored — not in KV, not in logs, not in the payments ledger, not in metrics, and not in error records. The report carries counts, rates, record ids, scopes, and cluster classifications only — never export content and never a grader-computed content hash of any content. The payments ledger records only the closed field list published in the criteria. Because nothing is kept, a paid retry re-submits the export.","payment_includes":"$0.05 per scope-hygiene audit — RULED price. The payment includes exactly one scope-hygiene audit of the one memory-store export you submitted (up to 1,000 records inside a request body of up to 262,144 bytes) and one signed report under memory-scope-hygiene/v1 stating the five factors individually: the scope-field presence rate; the unscoped-record rate; the cross-scope identical-content clusters with their classifications, undeclared clusters carrying the exact wording \"undeclared cross-scope identical content — contamination indicator, not proof of a runtime leak\"; the attribution/storage scope conflicts; and the duplicate clusters plus live-duplicate rate — with every factor the export's family or form cannot support stated as NOT ASSESSABLE with its reason, and the coverage block naming the scope keys relied on. No composite score is computed. A free pre-check discloses the assessability matrix for your declared family and form in full and validates the export before any payment; a malformed export is a free refusal. The export is processed in memory only and is never stored; the payments ledger records only the payer, the time, the price, the undeclared-cluster count, and the verdict line.","kill_line":"Kill line (M-45 / Q-23, under the operator's 2026-11-03 build-bank-hold doctrine): build, bank, and hold; judged at the fleet's periodic reviews. There is NO demand kill line for this item. Day-0 = first outside paid settle (accounting only; house proofs never count). Tripwires, all stop-and-review: (1) refunds >5% of paid calls in any 30-day window; (2) free use >10x paid use; (3) a wrong verdict from undisclosed stale or partial data (stop-serving under the affected criteria version; the error enters the corpus first as a regression case, corrected rules publish as a new criteria version, the corpus re-passes at 100% before serving resumes; issued reports are never amended); (4) an undeclared cluster reported as proof of a runtime leak, or a marked-shared cluster reported as verified legitimate; (5) any retained surface carrying export content; (6) any composite score on any surface; (7) a NOT ASSESSABLE factor reported as a zero on any surface.","never":"This service never contacts a memory store; never fetches anything about the export; never stores an export or any part of one; never prints export content; never computes a composite score; never guesses an undeclared scope key.","service":"scope-hygiene","version":"1.0.0","endpoints":{"criteria":"GET /criteria (free)","precheck":"POST /precheck (free, input validation + assessability only: the full matrix row for the declared family + form and the resolved per-factor status; no factor computed, no hygiene finding)","audit":"POST /audit (paid, $0.05 per audit — RULED)","metrics":"GET /metrics","not_offered":"Any other POST path answers NOT COVERED. No best-effort result is produced."}}