nmarshall commited on
Commit
da6b345
·
1 Parent(s): 17714da

gameplay: port filter metadata update policy

Browse files
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": 189,
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