gameplay: port filter metadata update policy
Browse files- crates/urt-gameplay/src/bin/urt-filter-policy.rs +4 -0
- crates/urt-gameplay/src/filter_lifecycle.rs +78 -0
- docs/FINDINGS.md +2 -0
- docs/PROVISIONAL_TODO.md +2 -0
- docs/RESEARCH_LEDGER.md +4 -0
- docs/REVERSE_ENGINEERING_TODO.md +2 -0
- docs/research/README.md +2 -0
- docs/research/RUST_FILTER_METADATA.md +5 -0
- docs/research/rust-filter-metadata.json +0 -0
- docs/research/rust-health-impulse.json +1 -1
- scripts/analyze_rust_filter_metadata.py +18 -0
- scripts/test_rust_filter_metadata.py +9 -0
- xtask/checks.tsv +14 -0
crates/urt-gameplay/src/bin/urt-filter-policy.rs
CHANGED
|
@@ -80,6 +80,10 @@ fn main() {
|
|
| 80 |
assert_eq!(w.len(), 5 + l + 3 * n);
|
| 81 |
let mut records = rows(&w[5 + l..]);
|
| 82 |
match kind.as_str() {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
"remove" => {
|
| 84 |
let p = remove_filter(&mut records, &text).unwrap();
|
| 85 |
println!("{}", mutation_json(p, &records, vec![]));
|
|
|
|
| 80 |
assert_eq!(w.len(), 5 + l + 3 * n);
|
| 81 |
let mut records = rows(&w[5 + l..]);
|
| 82 |
match kind.as_str() {
|
| 83 |
+
"metadata" => {
|
| 84 |
+
let p = update_filter_metadata(&mut records, &text, w[1], w[2] as i32).unwrap();
|
| 85 |
+
println!("{}", mutation_json(p, &records, vec![]));
|
| 86 |
+
}
|
| 87 |
"remove" => {
|
| 88 |
let p = remove_filter(&mut records, &text).unwrap();
|
| 89 |
println!("{}", mutation_json(p, &records, vec![]));
|
crates/urt-gameplay/src/filter_lifecycle.rs
CHANGED
|
@@ -140,3 +140,81 @@ mod removal_tests {
|
|
| 140 |
assert_eq!(rows, before);
|
| 141 |
}
|
| 142 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 140 |
assert_eq!(rows, before);
|
| 141 |
}
|
| 142 |
}
|
| 143 |
+
|
| 144 |
+
/// Original252127 replaces only metadata on the first exact mask/comparison
|
| 145 |
+
/// match; a nonzero save argument requests export after that successful write.
|
| 146 |
+
pub fn update_filter_metadata(
|
| 147 |
+
records: &mut [FilterRecord],
|
| 148 |
+
text: &[u8],
|
| 149 |
+
metadata: u32,
|
| 150 |
+
save: i32,
|
| 151 |
+
) -> Result<FilterMutation, Error> {
|
| 152 |
+
if records.len() > 4096 {
|
| 153 |
+
return Err(Error::InvalidCount);
|
| 154 |
+
}
|
| 155 |
+
let parsed = crate::native_policy::parse_filter(text)?;
|
| 156 |
+
let mut notices = vec![];
|
| 157 |
+
let mut found = false;
|
| 158 |
+
match parsed {
|
| 159 |
+
Err(remaining) => notices.push(FilterNotice::BadAddress(remaining)),
|
| 160 |
+
Ok((mask, comparison)) => {
|
| 161 |
+
if let Some(row) = records
|
| 162 |
+
.iter_mut()
|
| 163 |
+
.find(|r| r.mask == mask && r.comparison == comparison)
|
| 164 |
+
{
|
| 165 |
+
row.metadata = metadata;
|
| 166 |
+
found = true;
|
| 167 |
+
} else {
|
| 168 |
+
notices.push(FilterNotice::NotFound(
|
| 169 |
+
text.split(|b| *b == 0).next().unwrap_or_default().to_vec(),
|
| 170 |
+
));
|
| 171 |
+
}
|
| 172 |
+
}
|
| 173 |
+
}
|
| 174 |
+
Ok(FilterMutation {
|
| 175 |
+
result: found,
|
| 176 |
+
notices,
|
| 177 |
+
export: found && save != 0,
|
| 178 |
+
})
|
| 179 |
+
}
|
| 180 |
+
#[cfg(test)]
|
| 181 |
+
mod metadata_tests {
|
| 182 |
+
use super::*;
|
| 183 |
+
#[test]
|
| 184 |
+
fn only_first_metadata_changes_and_negative_save_exports() {
|
| 185 |
+
let row = FilterRecord {
|
| 186 |
+
mask: u32::MAX,
|
| 187 |
+
comparison: 0x04030201,
|
| 188 |
+
metadata: 9,
|
| 189 |
+
};
|
| 190 |
+
let mut rows = [row, row];
|
| 191 |
+
let p = update_filter_metadata(&mut rows, b"1.2.3.4", 0x80000000, -1).unwrap();
|
| 192 |
+
assert!(p.result && p.export);
|
| 193 |
+
assert!(p.notices.is_empty());
|
| 194 |
+
assert_eq!(rows[0].metadata, 0x80000000);
|
| 195 |
+
assert_eq!(rows[0].comparison, row.comparison);
|
| 196 |
+
assert_eq!(rows[0].mask, row.mask);
|
| 197 |
+
assert_eq!(rows[1], row);
|
| 198 |
+
}
|
| 199 |
+
#[test]
|
| 200 |
+
fn no_save_and_parse_errors_do_not_request_export() {
|
| 201 |
+
let mut rows = [FilterRecord {
|
| 202 |
+
mask: u32::MAX,
|
| 203 |
+
comparison: u32::MAX,
|
| 204 |
+
metadata: 9,
|
| 205 |
+
}];
|
| 206 |
+
let p = update_filter_metadata(&mut rows, b"255.255.255.255", 42, 0).unwrap();
|
| 207 |
+
assert!(p.result);
|
| 208 |
+
assert!(!p.export);
|
| 209 |
+
assert_eq!(rows[0].comparison, u32::MAX);
|
| 210 |
+
let before = rows;
|
| 211 |
+
assert!(update_filter_metadata(&mut rows, &[b'1'; 128], 7, 1).is_err());
|
| 212 |
+
assert_eq!(rows, before);
|
| 213 |
+
assert!(
|
| 214 |
+
!update_filter_metadata(&mut rows, b"bad", 7, 1)
|
| 215 |
+
.unwrap()
|
| 216 |
+
.export
|
| 217 |
+
);
|
| 218 |
+
assert_eq!(rows, before);
|
| 219 |
+
}
|
| 220 |
+
}
|
docs/FINDINGS.md
CHANGED
|
@@ -1809,3 +1809,5 @@ RE-936: Thirty actual253296 cases execute parser252304/37159, scan exact mask/co
|
|
| 1809 |
RE-937: Rust matches all30 original removal cases, retaining first-match ordering, unchanged mask/metadata/count, repeated sentinel matches, parser diagnostics and conditional export requests. Unsupported token domains fail atomically. Unit tests cover duplicate preservation and unsupported input without mutation; native export remains captured at this stage. [Evidence](research/RUST_FILTER_REMOVE.md).
|
| 1810 |
|
| 1811 |
RE-938: Across180 actual252127 fixtures, parser252304/37159 precedes an exact mask/comparison scan; only the first match receives the supplied raw metadata word. Comparison/mask/count remain unchanged. Export is requested only after a successful match with a nonzero save argument. Invalid parse and missing match return0 with diagnostics. A comparison-1 sentinel can match and receive metadata without becoming active; timestamp interpretation and native callers remain open. [Evidence](research/QVM_FILTER_METADATA.md).
|
|
|
|
|
|
|
|
|
| 1809 |
RE-937: Rust matches all30 original removal cases, retaining first-match ordering, unchanged mask/metadata/count, repeated sentinel matches, parser diagnostics and conditional export requests. Unsupported token domains fail atomically. Unit tests cover duplicate preservation and unsupported input without mutation; native export remains captured at this stage. [Evidence](research/RUST_FILTER_REMOVE.md).
|
| 1810 |
|
| 1811 |
RE-938: Across180 actual252127 fixtures, parser252304/37159 precedes an exact mask/comparison scan; only the first match receives the supplied raw metadata word. Comparison/mask/count remain unchanged. Export is requested only after a successful match with a nonzero save argument. Invalid parse and missing match return0 with diagnostics. A comparison-1 sentinel can match and receive metadata without becoming active; timestamp interpretation and native callers remain open. [Evidence](research/QVM_FILTER_METADATA.md).
|
| 1812 |
+
|
| 1813 |
+
RE-939: Rust matches180 original metadata-update cases, retaining the first exact pair, every unaffected row, raw metadata bits, parser/missing-match diagnostics and save gating. Unit tests verify negative save requests, duplicate preservation, sentinel matching and atomic rejection of unsupported tokens. Metadata is deliberately a raw word; native time/expiry interpretation remains open. [Evidence](research/RUST_FILTER_METADATA.md).
|
docs/PROVISIONAL_TODO.md
CHANGED
|
@@ -1855,3 +1855,5 @@ RE-924–933 completed bounded bot settings, announcement, connect-child composi
|
|
| 1855 |
- [x] RE-937 Rust exact-match filter removal and diagnostics ([evidence](research/RUST_FILTER_REMOVE.md)).
|
| 1856 |
|
| 1857 |
- [x] RE-938 Original filter metadata update and save gating ([evidence](research/QVM_FILTER_METADATA.md)).
|
|
|
|
|
|
|
|
|
| 1855 |
- [x] RE-937 Rust exact-match filter removal and diagnostics ([evidence](research/RUST_FILTER_REMOVE.md)).
|
| 1856 |
|
| 1857 |
- [x] RE-938 Original filter metadata update and save gating ([evidence](research/QVM_FILTER_METADATA.md)).
|
| 1858 |
+
|
| 1859 |
+
- [x] RE-939 Rust first-match filter metadata policy ([evidence](research/RUST_FILTER_METADATA.md)).
|
docs/RESEARCH_LEDGER.md
CHANGED
|
@@ -2417,3 +2417,7 @@ Rust matches all30 original removal cases, retaining first-match ordering, uncha
|
|
| 2417 |
## 2026-10-28 — RE-938 Original filter metadata update and save gating
|
| 2418 |
|
| 2419 |
Across180 actual252127 fixtures, parser252304/37159 precedes an exact mask/comparison scan; only the first match receives the supplied raw metadata word. Comparison/mask/count remain unchanged. Export is requested only after a successful match with a nonzero save argument. Invalid parse and missing match return0 with diagnostics. A comparison-1 sentinel can match and receive metadata without becoming active; timestamp interpretation and native callers remain open. Focused `cargo xtask test-group qvm-filter-metadata` passes with deterministic report regeneration, relevant regressions and storage/documentation/whitespace checks. Full suite deferred to the tenth-commit boundary. Original assets unchanged; native IO effects remain captured.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2417 |
## 2026-10-28 — RE-938 Original filter metadata update and save gating
|
| 2418 |
|
| 2419 |
Across180 actual252127 fixtures, parser252304/37159 precedes an exact mask/comparison scan; only the first match receives the supplied raw metadata word. Comparison/mask/count remain unchanged. Export is requested only after a successful match with a nonzero save argument. Invalid parse and missing match return0 with diagnostics. A comparison-1 sentinel can match and receive metadata without becoming active; timestamp interpretation and native callers remain open. Focused `cargo xtask test-group qvm-filter-metadata` passes with deterministic report regeneration, relevant regressions and storage/documentation/whitespace checks. Full suite deferred to the tenth-commit boundary. Original assets unchanged; native IO effects remain captured.
|
| 2420 |
+
|
| 2421 |
+
## 2026-10-28 — RE-939 Rust first-match filter metadata policy
|
| 2422 |
+
|
| 2423 |
+
Rust matches180 original metadata-update cases, retaining the first exact pair, every unaffected row, raw metadata bits, parser/missing-match diagnostics and save gating. Unit tests verify negative save requests, duplicate preservation, sentinel matching and atomic rejection of unsupported tokens. Metadata is deliberately a raw word; native time/expiry interpretation remains open. Focused `cargo xtask test-group rust-filter-metadata` passes with deterministic report regeneration, relevant regressions and storage/documentation/whitespace checks. Full suite deferred to the tenth-commit boundary. Original assets unchanged; native IO effects remain captured.
|
docs/REVERSE_ENGINEERING_TODO.md
CHANGED
|
@@ -1892,3 +1892,5 @@ Purpose: turn the supplied installation into an evidence-backed specification of
|
|
| 1892 |
- [x] RE-937 Rust exact-match filter removal and diagnostics ([evidence](research/RUST_FILTER_REMOVE.md)).
|
| 1893 |
|
| 1894 |
- [x] RE-938 Original filter metadata update and save gating ([evidence](research/QVM_FILTER_METADATA.md)).
|
|
|
|
|
|
|
|
|
| 1892 |
- [x] RE-937 Rust exact-match filter removal and diagnostics ([evidence](research/RUST_FILTER_REMOVE.md)).
|
| 1893 |
|
| 1894 |
- [x] RE-938 Original filter metadata update and save gating ([evidence](research/QVM_FILTER_METADATA.md)).
|
| 1895 |
+
|
| 1896 |
+
- [x] RE-939 Rust first-match filter metadata policy ([evidence](research/RUST_FILTER_METADATA.md)).
|
docs/research/README.md
CHANGED
|
@@ -1427,3 +1427,5 @@ The extension collector adds five focused tests for native tail calls, command-r
|
|
| 1427 |
- [RE-937: Rust exact-match filter removal and diagnostics](RUST_FILTER_REMOVE.md).
|
| 1428 |
|
| 1429 |
- [RE-938: Original filter metadata update and save gating](QVM_FILTER_METADATA.md).
|
|
|
|
|
|
|
|
|
| 1427 |
- [RE-937: Rust exact-match filter removal and diagnostics](RUST_FILTER_REMOVE.md).
|
| 1428 |
|
| 1429 |
- [RE-938: Original filter metadata update and save gating](QVM_FILTER_METADATA.md).
|
| 1430 |
+
|
| 1431 |
+
- [RE-939: Rust first-match filter metadata policy](RUST_FILTER_METADATA.md).
|
docs/research/RUST_FILTER_METADATA.md
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Rust first-match filter metadata policy
|
| 2 |
+
|
| 3 |
+
RE-939. Focused validation: `cargo xtask test-group rust-filter-metadata`. [Machine report](rust-filter-metadata.json).
|
| 4 |
+
|
| 5 |
+
Rust matches180 original metadata-update cases, retaining the first exact pair, every unaffected row, raw metadata bits, parser/missing-match diagnostics and save gating. Unit tests verify negative save requests, duplicate preservation, sentinel matching and atomic rejection of unsupported tokens. Metadata is deliberately a raw word; native time/expiry interpretation remains open.
|
docs/research/rust-filter-metadata.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
docs/research/rust-health-impulse.json
CHANGED
|
@@ -2985,7 +2985,7 @@
|
|
| 2985 |
"exit_code": 2
|
| 2986 |
}
|
| 2987 |
],
|
| 2988 |
-
"rust_unit_tests":
|
| 2989 |
"source_report": "docs/research/qvm-health-impulse.json",
|
| 2990 |
"source_report_sha256": "625fd8064004ea6dbdf398d51d0af844d9c635ff04c0a0790886dddb6ab81e71"
|
| 2991 |
}
|
|
|
|
| 2985 |
"exit_code": 2
|
| 2986 |
}
|
| 2987 |
],
|
| 2988 |
+
"rust_unit_tests": 191,
|
| 2989 |
"source_report": "docs/research/qvm-health-impulse.json",
|
| 2990 |
"source_report_sha256": "625fd8064004ea6dbdf398d51d0af844d9c635ff04c0a0790886dddb6ab81e71"
|
| 2991 |
}
|
scripts/analyze_rust_filter_metadata.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Compare the Rust filter policy with the original QVM report."""
|
| 2 |
+
import hashlib,json,os,subprocess
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
from analyze_qvm import write_json
|
| 5 |
+
from rust_lifecycle_support import result
|
| 6 |
+
|
| 7 |
+
def main():
|
| 8 |
+
subprocess.run(['cargo','build','--offline','--package','urt-gameplay','--bin','urt-filter-policy'],check=True,capture_output=True)
|
| 9 |
+
binary=Path(os.environ.get('CARGO_TARGET_DIR','target'))/'debug'/'urt-filter-policy'
|
| 10 |
+
path=Path('docs/research/qvm-filter-metadata.json');data=path.read_bytes();probes=[]
|
| 11 |
+
for p in json.loads(data)['probes']:
|
| 12 |
+
a=p['input'];words=encode(a)
|
| 13 |
+
probes.append(dict(source={k:p[k] for k in ['result', 'diagnostics', 'exports', 'exported', 'rows']},rust=result(binary,'metadata',words)))
|
| 14 |
+
write_json(Path('docs/research/rust-filter-metadata.json'),dict(source_report=str(path),source_report_sha256=hashlib.sha256(data).hexdigest(),probes=probes,limits='Finite recovered policy parity; native filesystem/network effects are not executed.'))
|
| 15 |
+
|
| 16 |
+
def encode(a):
|
| 17 |
+
text=bytes.fromhex(a['text']);return [len(a['rows']),a['metadata'],a['save'],a['handle'],len(text),*text,*[v for r in a['rows'] for v in r]]
|
| 18 |
+
if __name__=='__main__':main()
|
scripts/test_rust_filter_metadata.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import hashlib,json,unittest
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
class PortTests(unittest.TestCase):
|
| 4 |
+
def test_original_parity(self):
|
| 5 |
+
r=json.loads(Path('docs/research/rust-filter-metadata.json').read_text());self.assertGreaterEqual(len(r['probes']),18)
|
| 6 |
+
for p in r['probes']:self.assertEqual(p['source'],p['rust'])
|
| 7 |
+
def test_identity(self):
|
| 8 |
+
r=json.loads(Path('docs/research/rust-filter-metadata.json').read_text());self.assertEqual(hashlib.sha256(Path(r['source_report']).read_bytes()).hexdigest(),r['source_report_sha256'])
|
| 9 |
+
if __name__=='__main__':unittest.main()
|
xtask/checks.tsv
CHANGED
|
@@ -3146,3 +3146,17 @@ report scripts/analyze_qvm_filter_metadata.py docs/research/qvm-filter-metadata.
|
|
| 3146 |
edge docs/research/qvm-filter-metadata.json scripts/test_qvm_filter_metadata.py
|
| 3147 |
group qvm-filter-metadata scripts/analyze_qvm_filter_metadata.py
|
| 3148 |
edge scripts/analyze_qvm_filter_metadata.py scripts/analyze_qvm_resumer_audit.py
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3146 |
edge docs/research/qvm-filter-metadata.json scripts/test_qvm_filter_metadata.py
|
| 3147 |
group qvm-filter-metadata scripts/analyze_qvm_filter_metadata.py
|
| 3148 |
edge scripts/analyze_qvm_filter_metadata.py scripts/analyze_qvm_resumer_audit.py
|
| 3149 |
+
report scripts/analyze_rust_filter_metadata.py docs/research/rust-filter-metadata.json
|
| 3150 |
+
edge docs/research/rust-filter-metadata.json scripts/test_rust_filter_metadata.py
|
| 3151 |
+
group rust-filter-metadata scripts/analyze_rust_filter_metadata.py
|
| 3152 |
+
edge scripts/analyze_rust_filter_metadata.py scripts/analyze_qvm_resumer_audit.py
|
| 3153 |
+
edge Cargo.toml scripts/analyze_rust_filter_metadata.py
|
| 3154 |
+
edge Cargo.lock scripts/analyze_rust_filter_metadata.py
|
| 3155 |
+
edge crates/urt-gameplay/Cargo.toml scripts/analyze_rust_filter_metadata.py
|
| 3156 |
+
edge crates/urt-gameplay/src/lib.rs scripts/analyze_rust_filter_metadata.py
|
| 3157 |
+
edge crates/urt-gameplay/src/filter_lifecycle.rs scripts/analyze_rust_filter_metadata.py
|
| 3158 |
+
edge crates/urt-gameplay/src/native_policy.rs scripts/analyze_rust_filter_metadata.py
|
| 3159 |
+
edge crates/urt-gameplay/src/bin/urt-filter-policy.rs scripts/analyze_rust_filter_metadata.py
|
| 3160 |
+
edge docs/research/qvm-filter-metadata.json scripts/analyze_rust_filter_metadata.py
|
| 3161 |
+
group rust-filter-metadata scripts/analyze_rust_health_impulse.py
|
| 3162 |
+
group gameplay scripts/analyze_rust_filter_metadata.py
|