mirror of
https://github.com/ruvnet/RuView.git
synced 2026-10-21 22:22:33 -05:00
Explore RuField and WorldGraph with bounded CSI context experiments
Retain the stronger ACF model after an 18-configuration temporal ablation regresses on development data. Add a pinned actual-library integrity probe, strict bounded replay inspection, measured synthetic overhead, and CI tests. Preserve source hashes and separate software integrity from sensing accuracy.
This commit is contained in:
1 parent
6e5c72fc93
commit
56e5ae1a6d
14 files changed
+1965
No files matched your search
@@ -4,6 +4,7 @@ on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'scripts/csi_training/**'
|
||||
- 'scripts/field-context-probe/**'
|
||||
- 'scripts/train-csi-encoder.py'
|
||||
- 'scripts/train-espfi.py'
|
||||
- 'scripts/improve-espfi.py'
|
||||
@@ -39,10 +40,31 @@ jobs:
|
||||
- name: Validate real training code using temporary synthetic data
|
||||
env:
|
||||
PYTHONPATH: scripts
|
||||
OPENBLAS_NUM_THREADS: '1'
|
||||
OMP_NUM_THREADS: '1'
|
||||
run: python -m unittest discover -s scripts/tests -p 'test_csi_training*.py' -v
|
||||
- name: Validate public amplitude intake and optimization with synthetic fixtures
|
||||
env:
|
||||
PYTHONPATH: scripts
|
||||
OPENBLAS_NUM_THREADS: '1'
|
||||
OMP_NUM_THREADS: '1'
|
||||
run: python -m unittest discover -s scripts/tests -p 'test_espfi*.py' -v
|
||||
- name: Enforce capture data policy
|
||||
run: bash scripts/csi-data-policy-check.sh --tracked
|
||||
|
||||
field-context-contract:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
defaults:
|
||||
run:
|
||||
working-directory: scripts/field-context-probe
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Install fixed Rust toolchain
|
||||
run: rustup toolchain install 1.90.0 --profile minimal
|
||||
- name: Verify actual field and graph library contracts
|
||||
run: cargo +1.90.0 test --locked
|
||||
- name: Exercise synthetic evidence pipeline
|
||||
run: cargo +1.90.0 run --locked --quiet --bin ruview-field-context-probe
|
||||
@@ -76,3 +76,25 @@ original frozen bundle and label this development evidence, not a new test.
|
||||
Only a fresh independent confirmation dataset can authorize a stronger
|
||||
generalization claim. Feature extraction, model latency and artifact integrity
|
||||
must be measured together for the new artifact family.
|
||||
|
||||
## Field context research boundary
|
||||
|
||||
The RuField and WorldGraph exploration uses two explicitly separate paths.
|
||||
The Python amplitude experiment may add temporal descriptors within a trial;
|
||||
it cannot invent continuous chronology, verified empty room calibration,
|
||||
positions or room topology. The standalone Rust integrity probe calls pinned
|
||||
actual RuField and WorldGraph crates using authored synthetic events. Public
|
||||
unlabeled replay is inspected for format only and never promoted to labeled
|
||||
activity evidence or graph beliefs.
|
||||
|
||||
Root sensor evidence and derived graph beliefs remain distinct. The probe
|
||||
bridge rejects derived input as new independent roots, duplicate event hashes,
|
||||
stale observations and expired or untraceable beliefs. The bridge is not a
|
||||
production graph cycle detector or an enrollment authority. Native WorldGraph
|
||||
permits cycles, and native RuField simulation trust does not enforce wall clock
|
||||
freshness. Neither behavior proves a production defect when used as documented.
|
||||
|
||||
The frozen temporal extension improved internal training folds but regressed
|
||||
development accuracy and macro F1. Reject it and preserve the retained motion
|
||||
ACF model. Evidence, executable commands and future field acceptance are in
|
||||
[`espfi-field-context-research.md`](../espfi-field-context-research.md).
|
||||
@@ -0,0 +1,186 @@
|
||||
# RuField and WorldGraph sample data experiment
|
||||
|
||||
Date: 2026-10-12. This is a bounded research result, not model promotion.
|
||||
The existing motion ACF candidate remains the retained model.
|
||||
|
||||
## Decision and evidence boundary
|
||||
|
||||
RuField supplies typed sensor events, trust verification and temporal fusion.
|
||||
WorldGraph supplies a typed graph of environmental beliefs and their evidence.
|
||||
They do not supply missing labels, independent room calibration or physical
|
||||
track positions for an amplitude activity dataset. There is no measured
|
||||
classification improvement from attaching their names or APIs to a pipeline.
|
||||
|
||||
We therefore separate two executable questions:
|
||||
|
||||
1. Can ordered summaries inside each existing recording improve the frozen
|
||||
activity model? Measure on the authorized public ESP-Fi bundle.
|
||||
2. Can actual RuField and WorldGraph libraries maintain source ancestry and
|
||||
reject duplicate, stale or circular evidence? Test deterministic synthetic
|
||||
events and characterize the real replay sample boundary separately.
|
||||
|
||||
The first question is a temporal representation ablation inspired by the
|
||||
context discussion. It does not use graph features or call the Rust libraries.
|
||||
The second question calls actual pinned libraries, but cannot establish field
|
||||
accuracy. These outputs must never be combined into an accuracy claim.
|
||||
|
||||
## Sample audit
|
||||
|
||||
ESP-Fi contains labeled individual trials. The prepared input is 52 amplitude
|
||||
channels over 190 pooled samples. It has no validated wall clock sampling rate,
|
||||
continuous sequence across trials, independently annotated empty room interval,
|
||||
sensor position or room geometry. Numeric activity names remain unresolved.
|
||||
The existing 837 training and 70 development recordings are reused; final test
|
||||
arrays are never opened. Sorting trial filenames is not evidence of chronology.
|
||||
|
||||
At [RuField revision 99556728](https://github.com/ruvnet/rufield/tree/995567283606b86594439d6594387ff551a21ede),
|
||||
the public replay fixtures have the following structural properties, verified
|
||||
without exporting signal values:
|
||||
|
||||
| Fixture | Complete JSON frames | Time span | Structural status |
|
||||
| --- | ---: | ---: | --- |
|
||||
| `real_csi_small.jsonl` | 3 | approximately 0.201 s | increasing timestamps |
|
||||
| `real_csi_medium.jsonl` | 199 | approximately 13.415 s | increasing complete frames, truncated JSON at line 200 |
|
||||
|
||||
Git blob hashes are `fb2b2aa1ccd65306e4eccf5b30309c071b6da3ea` and
|
||||
`81b8097a54940319ff4b6df290c34628d4418772`, respectively. The source path is
|
||||
`crates/rufield-adapters/tests/fixtures/`. The [adapter](https://github.com/ruvnet/rufield/blob/995567283606b86594439d6594387ff551a21ede/crates/rufield-adapters/src/csi_replay.rs)
|
||||
and [replay tests](https://github.com/ruvnet/rufield/blob/995567283606b86594439d6594387ff551a21ede/crates/rufield-adapters/tests/csi_replay_fusion.rs)
|
||||
describe unlabeled replay. Its first frames are a statistical reference, not
|
||||
independently verified emptiness. The code license is MIT; no separate dataset
|
||||
permission or consent record was found in the inspected sources. These samples
|
||||
are not imported into model training or committed here.
|
||||
|
||||
The [RuView BatVu fixture test](https://github.com/ruvnet/RuView/blob/5cfbb8d98d561bddb7c8918ae431407267517016/v2/crates/wifi-densepose-rufield/tests/ultrasonic_gates.rs)
|
||||
identifies its 72 pings as simulator output. It cannot provide an independent
|
||||
physical sensing confirmation set. The inspected WorldGraph sources likewise
|
||||
did not supply independently annotated continuous sensing data for this task.
|
||||
|
||||
## Frozen model experiment
|
||||
|
||||
The current ACF representation is invariant under reversing a recording. This
|
||||
is an exact feature property, not proof that direction loss explains a class
|
||||
failure. Test three families: unchanged ACF with 468 features; ACF plus three
|
||||
ordered per-carrier means with 624; and those features plus a signed linear
|
||||
trend with 676. The three temporal bins have 63, 64 and 63 samples. Reversal
|
||||
swaps the first and last bin and negates the trend. All log amplitude centering
|
||||
and global RMS arithmetic remain float64.
|
||||
|
||||
Each family uses gamma multipliers 0.1 and 1, ridge penalties 0.01, 0.1 and 1,
|
||||
128 RFF dimensions and seed 42. The 18 candidates were fixed before the run.
|
||||
Selection maximizes the minimum of the two original training fold macro F1
|
||||
scores, then mean score, then smaller state, with declaration order for ties.
|
||||
Scalers and models fit only their respective training rows. No room label is an
|
||||
input feature. The complete selection is saved before development extraction.
|
||||
|
||||
The unchanged ACF control must reproduce every learned tensor exactly. The
|
||||
retention gate requires development macro F1 at least 0.03 above the retained
|
||||
ACF candidate, no accuracy regression, no newly zero class F1, and measured p95
|
||||
under 1 ms. Repeatedly viewed development data remains adaptive research data.
|
||||
|
||||
MEASURED: `ordered_trend`, gamma 0.1, alpha 0.1 wins the training selection.
|
||||
|
||||
| Metric | Retained ACF | Ordered candidate |
|
||||
| --- | ---: | ---: |
|
||||
| Training fold 1 macro F1 | 0.332115 | 0.347404 |
|
||||
| Training fold 2 macro F1 | 0.335944 | 0.364028 |
|
||||
| Development correct | 39/70 | 34/70 |
|
||||
| Development accuracy | 55.71% | 48.57% |
|
||||
| Development macro F1 | 0.499871 | 0.437458 |
|
||||
| Explicit float64 artifact bytes | 496488 | 712816 |
|
||||
|
||||
The candidate is rejected. Activity 3 still has zero F1; its confusion row is
|
||||
unchanged. Activity 1 improves only from one to two correct predictions, while
|
||||
other classes regress. Better internal fold scores did not transfer to this
|
||||
development room and participant. No second accuracy search follows this result.
|
||||
|
||||
Exact baseline reconstruction and exact exported reload pass. The paired
|
||||
1000-call run observed candidate median/p95 0.782435/4.814087 ms versus
|
||||
0.512245/0.861634 ms for the ACF control. An independent Rust compilation was
|
||||
running on the shared machine, so this timing is recorded as contended execution,
|
||||
not a stable runtime regression estimate. The accuracy failures alone reject
|
||||
the candidate. Acquisition, pooling and I/O are excluded from these timings.
|
||||
|
||||
The [aggregate receipt](validation/espfi-context-20261012.json) binds source,
|
||||
model, baseline and manifest hashes. Full experiment records remain with the
|
||||
executor; no individual input recordings or model weights enter the repository.
|
||||
|
||||
```sh
|
||||
OPENBLAS_NUM_THREADS=1 OMP_NUM_THREADS=1 PYTHONPATH=scripts \
|
||||
python -m csi_training.espfi_context \
|
||||
--manifest /private/espfi/prepared/manifest.json \
|
||||
--baseline /private/espfi/motion-002-worst \
|
||||
--output /private/espfi/context-001
|
||||
```
|
||||
|
||||
## Actual library integration
|
||||
|
||||
[`scripts/field-context-probe`](../scripts/field-context-probe/README.md) is a
|
||||
standalone Rust executable with exact Git dependency revisions and a lockfile.
|
||||
It creates signed synthetic RuField events, runs actual RuField trust and
|
||||
fusion, creates actual WorldGraph semantic beliefs with `DerivedFrom` edges,
|
||||
and serializes and restores the graph.
|
||||
|
||||
The additional bridge admits only fresh unique root evidence. It refuses derived
|
||||
events as new roots and refuses missing source handles, expired beliefs and
|
||||
duplicate belief publication. These are deliberately conservative application
|
||||
rules; they are not native WorldGraph cycle detection. WorldGraph permits a
|
||||
self edge, which is demonstrated in a separate boundary test. RuField simulation
|
||||
trust intentionally skips wall clock freshness, whereas its production policy
|
||||
rejects synthetic evidence. No synthetic key is enrolled for production use.
|
||||
|
||||
The bridge never turns a model score into a calibrated probability. No person
|
||||
track, physical position or room adjacency is fabricated. In this closed
|
||||
synthetic probe, every declared derived input is rejected as independent root
|
||||
evidence. The caller supplies that ancestry declaration; it is not a complete
|
||||
cryptographic proof of independence. A production bridge would need verified
|
||||
lineage, including correlation between observations from the same original
|
||||
signal, before claiming to prevent all circular confidence amplification.
|
||||
|
||||
The optional replay inspector uses a bounded regular UTF-8 file, a 1 MiB input
|
||||
limit and at most 10,000 nonempty lines. It validates every required field,
|
||||
numeric conversion, increasing timestamp and consistent channel width before
|
||||
calling the native adapter. The parsed frame count must match the admitted line
|
||||
count. It does not silently recover truncated or schema-invalid tails. This is
|
||||
a stricter research intake policy than the native replay parser's documented
|
||||
tail recovery behavior. No input capture is calibrated or inserted into a graph.
|
||||
|
||||
The official small sample is accepted as three frames. The medium sample is
|
||||
rejected at malformed line 200 with exit status 2; the native adapter alone
|
||||
accepts its complete prefix. Synthetic regressions also cover a valid JSON but
|
||||
invalid final schema and numeric overflow. These checks establish completeness
|
||||
and bounded parsing, not labels or physical truth.
|
||||
|
||||
The release benchmark compares actual RuField alone against the bridge with
|
||||
actual WorldGraph in 1,000 alternating pairs after 100 warmups. Both paths create
|
||||
a fresh isolated engine, parse the rule, verify a signature, ingest and infer.
|
||||
The bridge adds its guards, graph creation and ancestry publication. The exact
|
||||
timings and source hashes are in the probe's `example-result.json` and
|
||||
`verification-receipt.json`. Final added p95 is 0.038340 ms on synthetic
|
||||
fixtures. They exclude signing, acquisition, calibration,
|
||||
classification and persistent streaming costs, and do not establish target
|
||||
device performance. Benchmark improvement is not model improvement.
|
||||
|
||||
All 44 public pipeline tests and 28 original encoder tests pass on ruOS. The
|
||||
first isolated original-suite attempt lacked an unchanged collector dependency;
|
||||
copying that exact existing script restored all 28 passes. Python source review
|
||||
found no blocking issue. Rust review exposed tail completeness and numeric
|
||||
conversion gaps in the new inspector; explicit validation and regression tests
|
||||
address those gaps. All six Rust tests and the release build pass; an independent
|
||||
review found no remaining blocker. The primary agent reran the six tests and
|
||||
verified the tested source hashes against the committed receipt. CI includes both
|
||||
Python suites and the standalone locked Rust probe.
|
||||
|
||||
## Next acceptance
|
||||
|
||||
Keep the current ACF model. A useful deployment experiment needs independently
|
||||
annotated continuous captures with timestamps, permitted data use, verified
|
||||
empty intervals, sensor placement, room connectivity and activity boundaries.
|
||||
Compare the same streams with classifier alone, RuField validation, and RuField
|
||||
plus WorldGraph context. Proposed acceptance is 20% fewer false alerts at the
|
||||
same recall, plus less than 0.1 ms incremental p95 processing overhead on the
|
||||
target device. Measure event detection delay separately from compute time.
|
||||
|
||||
Those are future acceptance targets, not results of the isolated clips or the
|
||||
synthetic integrity probe. No production model, published Hugging Face weights,
|
||||
live sensor configuration or canonical learned memory is promoted by this work.
|
||||
@@ -464,3 +464,18 @@ explicit float64 artifact is larger. One class remains at zero F1 and two
|
||||
classes regress. The optimized compact incumbent remains available. The audit
|
||||
contains source revisions, all retention checks, full limitations and commands
|
||||
to reproduce both policies without opening final test arrays.
|
||||
|
||||
## RuField and WorldGraph context experiment
|
||||
|
||||
The [field context research report](espfi-field-context-research.md) separates
|
||||
an actual Rust library integrity probe from a Python temporal model ablation.
|
||||
The frozen 18-candidate experiment adds ordered means and signed trends within
|
||||
individual recordings. Its selected candidate improves both training folds but
|
||||
regresses development accuracy to 34/70 and macro F1 to 0.437458. It is rejected;
|
||||
the retained motion ACF model stays at 39/70 and 0.499871 on these reused data.
|
||||
|
||||
Actual RuField and WorldGraph tests verify source ancestry and conservative
|
||||
rejection policies. Public unlabeled replay is inspected for format only, with
|
||||
no assumed empty room, activity labels or fabricated positions. Software
|
||||
integrity and synthetic timing are not predictive accuracy evidence. The report
|
||||
and aggregate receipts preserve that distinction and the negative model result.
|
||||
@@ -0,0 +1,221 @@
|
||||
{
|
||||
"status": "complete",
|
||||
"promotionAuthorized": false,
|
||||
"task": "public_context_ablation",
|
||||
"evidence": "MEASURED",
|
||||
"development": {
|
||||
"accuracy": 0.4857142857142857,
|
||||
"macro_f1": 0.4374577417173766,
|
||||
"per_class_f1": [
|
||||
0.3333333333333333,
|
||||
0.6896551724137931,
|
||||
0,
|
||||
0.6666666666666666,
|
||||
0.7058823529411765,
|
||||
0.5128205128205128,
|
||||
0.15384615384615385
|
||||
],
|
||||
"confusion_matrix": [
|
||||
[
|
||||
2,
|
||||
8,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
[
|
||||
0,
|
||||
10,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
[
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
6,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
0,
|
||||
2,
|
||||
5,
|
||||
0,
|
||||
3,
|
||||
0
|
||||
],
|
||||
[
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
6,
|
||||
4,
|
||||
0
|
||||
],
|
||||
[
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
10,
|
||||
0
|
||||
],
|
||||
[
|
||||
0,
|
||||
0,
|
||||
3,
|
||||
0,
|
||||
0,
|
||||
6,
|
||||
1
|
||||
]
|
||||
],
|
||||
"samples": 70
|
||||
},
|
||||
"incumbent_development": {
|
||||
"accuracy": 0.5571428571428572,
|
||||
"macro_f1": 0.4998706443528175,
|
||||
"per_class_f1": [
|
||||
0.16666666666666666,
|
||||
0.6451612903225806,
|
||||
0,
|
||||
0.8421052631578947,
|
||||
0.5333333333333333,
|
||||
0.6451612903225806,
|
||||
0.6666666666666666
|
||||
],
|
||||
"confusion_matrix": [
|
||||
[
|
||||
1,
|
||||
9,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
[
|
||||
0,
|
||||
10,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
[
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
6,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
8,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
[
|
||||
0,
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
4,
|
||||
4,
|
||||
0
|
||||
],
|
||||
[
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
10,
|
||||
0
|
||||
],
|
||||
[
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
6
|
||||
]
|
||||
],
|
||||
"samples": 70
|
||||
},
|
||||
"baseline_state_exact": true,
|
||||
"reload_max_abs_logit_error": 0,
|
||||
"retention_checks": {
|
||||
"macro_f1_gain": false,
|
||||
"accuracy_nonregression": false,
|
||||
"protect_each_previously_nonzero_class": true,
|
||||
"p95_under_1ms": false
|
||||
},
|
||||
"retained_as_research_candidate": false,
|
||||
"latency": {
|
||||
"candidate": {
|
||||
"p50_ms": 0.782435,
|
||||
"p95_ms": 4.814087499999998
|
||||
},
|
||||
"incumbent": {
|
||||
"p50_ms": 0.5122450000000001,
|
||||
"p95_ms": 0.8616344999999994
|
||||
}
|
||||
},
|
||||
"model_bytes": 712816,
|
||||
"model_sha256": "589a04de997594a7e83dbb487e40509f038b4df21c76c81151c974fc5af8d317",
|
||||
"baseline_model_sha256": "8d3c8b144663808159159f7af9d14b09f54ee78dc1f852c643fbd001a23d2fc5",
|
||||
"baseline_receipt_sha256": "f8a565cb031d3ce9b488432dfb97408a58cee8f511c0ddc89aa74edb90388b88",
|
||||
"source_sha256": {
|
||||
"espfi_context.py": "736f3e68ef23298cbcd14f41343557f96698221596eeeda3909bf5ac870a3f5c",
|
||||
"espfi_motion.py": "c6a8529ccf9d9f834383a2bdf5b14626764ec7ca2ade7ad5d4c1c14797d76054",
|
||||
"espfi_generalize.py": "956b3a5e918f7c263a2b9183b4a6a70c8f2157604262a4ec6cee2c324a69224f",
|
||||
"espfi_kernel.py": "ae837cb2e3fac7f052b4bd371cd496e9636e5d5dbaf3a341526a4fc5aef23478"
|
||||
},
|
||||
"elapsed_seconds": 6.865275681000185,
|
||||
"scope": "18 configurations selected by original training folds; adaptive research on reused development data. 1000 paired batch1 calls include feature extraction/checks/classifier, exclude acquisition/pooling/IO. No cross-recording chronology, empty-room calibration, actual graph feature, final test or field accuracy.",
|
||||
"selected": {
|
||||
"candidate": {
|
||||
"family": "ordered_trend",
|
||||
"kind": "rff",
|
||||
"dimensions": 128,
|
||||
"gamma_multiplier": 0.1,
|
||||
"alpha": 0.1
|
||||
},
|
||||
"minimum_cv_macro_f1": 0.3474042415498739,
|
||||
"mean_cv_macro_f1": 0.35571616304408127,
|
||||
"scalar_count": 89039
|
||||
},
|
||||
"fold_f1": [
|
||||
0.3474042415498739,
|
||||
0.3640280845382886
|
||||
],
|
||||
"manifest_sha256": "74e0fe039acbed1b287bd11ee6b779becbb8f9b66c3c249d260ed66f5583f02a",
|
||||
"full_receipt_sha256": "d84edfd5273edd22d23aead1d2f0500015c1e1e9b744d6923c81d216001cf50b",
|
||||
"validation": {
|
||||
"public_tests": 44,
|
||||
"original_tests": 28,
|
||||
"independent_review": "NO BLOCK",
|
||||
"original_suite_setup": "First isolated run lacked unchanged collector script; exact dependency restored and 28 tests rerun successfully",
|
||||
"final_test_opened": false
|
||||
},
|
||||
"latency_caveat": "Shared CPU timing overlaps independent Rust dependency compilation; not a stable runtime regression estimate. Accuracy and macro F1 alone reject this candidate."
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
"""Bounded within-recording order ablation; not inferred room context.
|
||||
|
||||
Only source training folds select the candidate. Each recording is independent:
|
||||
no artificial timeline, empty-room baseline, graph belief or target label enters
|
||||
the features. The separate Rust probe exercises actual field/graph integration.
|
||||
"""
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import time
|
||||
|
||||
import numpy as np
|
||||
from safetensors.numpy import load, save_file
|
||||
|
||||
from .espfi_generalize import amplitudes
|
||||
from .espfi_kernel import fit, predict_features
|
||||
from .espfi_motion import MotionBundle, _read, _validate, motion_features
|
||||
from .espfi_train import digest, scores, write_json
|
||||
|
||||
WIDTHS = {'motion_acf': 468, 'ordered_mean': 624, 'ordered_trend': 676}
|
||||
GRID = [dict(family=family, kind='rff', dimensions=128, gamma_multiplier=gamma, alpha=alpha)
|
||||
for family in WIDTHS for gamma in (.1, 1.) for alpha in (.01, .1, 1.)]
|
||||
BASELINE = dict(family='motion_acf', kind='rff', dimensions=128, gamma_multiplier=.1, alpha=.1)
|
||||
CONTRACT = {
|
||||
'seed': 42, 'grid': GRID, 'input': 'nonnegative_amplitude_N52T190',
|
||||
'selection': 'minimum training-fold macro F1, then mean, then fewer scalars, then declaration order',
|
||||
'ordered_bins': [[0, 63], [63, 127], [127, 190]],
|
||||
'trend': 'OLS slope on linspace(-1,1,190) of per-carrier centered log amplitude divided by global RMS',
|
||||
'log_floor': 1e-6, 'rms_floor': 1e-6,
|
||||
'context': 'within one recording only; no chronology, physical direction or room calibration inferred',
|
||||
'retention_gate': {'development_macro_f1_absolute_gain': .03,
|
||||
'accuracy_nonregression': True,
|
||||
'protect_each_previously_nonzero_class': True,
|
||||
'paired_p95_limit_ms': 1.0},
|
||||
'promotionAuthorized': False,
|
||||
}
|
||||
|
||||
|
||||
def context_features(values, family):
|
||||
if family not in WIDTHS:
|
||||
raise ValueError('Unknown context feature family')
|
||||
x = amplitudes(values)
|
||||
original = motion_features(x, 'motion_acf')
|
||||
if family == 'motion_acf':
|
||||
return original
|
||||
logged = np.log(np.maximum(x, CONTRACT['log_floor']))
|
||||
relative = logged - logged.mean(-1, keepdims=True)
|
||||
relative /= np.maximum(np.sqrt(np.mean(relative**2, axis=(1, 2), keepdims=True)),
|
||||
CONTRACT['rms_floor'])
|
||||
ordered = np.stack([relative[..., a:b].mean(-1) for a, b in CONTRACT['ordered_bins']], -1)
|
||||
chunks = [original, ordered.reshape(len(x), -1)]
|
||||
if family == 'ordered_trend':
|
||||
time_axis = np.linspace(-1., 1., 190)
|
||||
chunks.append((relative * time_axis).sum(-1) / np.dot(time_axis, time_axis))
|
||||
result = np.ascontiguousarray(np.concatenate(chunks, axis=1))
|
||||
if result.shape != (len(x), WIDTHS[family]) or not np.isfinite(result).all():
|
||||
raise ValueError('Invalid context feature values')
|
||||
return result
|
||||
|
||||
|
||||
class ContextBundle:
|
||||
def __init__(self, directory):
|
||||
root = Path(directory)
|
||||
status = json.loads(_read(root / 'status.json', 65536))
|
||||
report = json.loads(_read(root / 'metrics.json', 16 * 1024**2))
|
||||
if (not isinstance(status, dict) or not isinstance(report, dict)
|
||||
or status.get('status') != 'complete' or report.get('status') != 'complete'
|
||||
or status.get('promotionAuthorized') is not False
|
||||
or report.get('promotionAuthorized') is not False
|
||||
or report.get('contract') != CONTRACT
|
||||
or report.get('task') != 'public_context_ablation'):
|
||||
raise ValueError('Invalid completed context artifact')
|
||||
self.candidate = report['selected']['candidate']
|
||||
if self.candidate not in GRID:
|
||||
raise ValueError('Unknown context candidate')
|
||||
data = _read(root / 'model.safetensors', 1024**2)
|
||||
if hashlib.sha256(data).hexdigest() != report.get('model_sha256'):
|
||||
raise ValueError('Model hash mismatch')
|
||||
self.state = load(data)
|
||||
width = WIDTHS[self.candidate['family']]
|
||||
shapes = {'feature_mean': (width,), 'feature_scale': (width,),
|
||||
'projection': (width, 128), 'phase': (128,), 'output_mean': (128,),
|
||||
'weights': (128, 7), 'bias': (7,)}
|
||||
if (set(self.state) != set(shapes) or any(self.state[k].shape != shape
|
||||
or self.state[k].dtype != np.float64 or not np.isfinite(self.state[k]).all()
|
||||
for k, shape in shapes.items()) or not (self.state['feature_scale'] > 0).all()):
|
||||
raise ValueError('Invalid context model state')
|
||||
|
||||
def predict(self, values):
|
||||
return predict_features(self.state, context_features(values, self.candidate['family']))
|
||||
|
||||
|
||||
def search(parts, provenance, baseline_directory, output, max_seconds=600):
|
||||
if not 1 <= max_seconds <= 3600:
|
||||
raise ValueError('Invalid time budget')
|
||||
fold_ids = _validate(parts)
|
||||
baseline = MotionBundle(baseline_directory)
|
||||
prior_bytes = _read(Path(baseline_directory) / 'metrics.json', 16 * 1024**2)
|
||||
prior = json.loads(prior_bytes)
|
||||
if (baseline.candidate != BASELINE or not provenance.get('manifest_sha256')
|
||||
or prior.get('provenance', {}).get('manifest_sha256') != provenance['manifest_sha256']):
|
||||
raise ValueError('Baseline model or data provenance mismatch')
|
||||
root = Path(output)
|
||||
root.mkdir(exist_ok=False, parents=True)
|
||||
write_json(root / 'status.json', {'status': 'running', 'promotionAuthorized': False})
|
||||
write_json(root / 'search_contract.json', CONTRACT)
|
||||
start = time.monotonic()
|
||||
try:
|
||||
train, dev = parts['train'], parts['validation']
|
||||
cache = {family: context_features(train['x'], family) for family in WIDTHS}
|
||||
results, selected, best = [], None, None
|
||||
for candidate in GRID:
|
||||
evaluations = []
|
||||
for fit_ids, eval_ids in fold_ids:
|
||||
if time.monotonic() - start > max_seconds:
|
||||
raise TimeoutError('Context search budget exceeded')
|
||||
state = fit(cache[candidate['family']][fit_ids], train['y'][fit_ids], candidate)
|
||||
result = predict_features(state, cache[candidate['family']][eval_ids])
|
||||
evaluations.append(scores(train['y'][eval_ids], result.argmax(1), 7))
|
||||
row = {'candidate': candidate, 'folds': evaluations,
|
||||
'minimum_cv_macro_f1': min(v['macro_f1'] for v in evaluations),
|
||||
'mean_cv_macro_f1': float(np.mean([v['macro_f1'] for v in evaluations])),
|
||||
'scalar_count': sum(v.size for v in state.values())}
|
||||
results.append(row)
|
||||
rank = (row['minimum_cv_macro_f1'], row['mean_cv_macro_f1'], -row['scalar_count'])
|
||||
if best is None or rank > best:
|
||||
best, selected = rank, row
|
||||
# Persist the complete train-only choice before any development feature extraction.
|
||||
write_json(root / 'selection.json', {'selected': selected, 'candidates': results, 'contract': CONTRACT})
|
||||
chosen = selected['candidate']
|
||||
state = fit(cache[chosen['family']], train['y'], chosen)
|
||||
# Require the unchanged control to reproduce the exact existing learned state.
|
||||
control = fit(cache['motion_acf'], train['y'], BASELINE)
|
||||
if any(not np.array_equal(control[k], baseline.state[k]) for k in control):
|
||||
raise ValueError('Unchanged baseline state did not reproduce')
|
||||
save_file(state, str(root / 'model.safetensors'))
|
||||
actual = predict_features(state, context_features(dev['x'], chosen['family']))
|
||||
incumbent = scores(dev['y'], baseline.predict(dev['x']).argmax(1), 7)
|
||||
measured = scores(dev['y'], actual.argmax(1), 7)
|
||||
replay = load(_read(root / 'model.safetensors', 1024**2))
|
||||
if not np.array_equal(actual, predict_features(replay, context_features(dev['x'], chosen['family']))):
|
||||
raise ValueError('Reload changes logits')
|
||||
def candidate_predict(x):
|
||||
return predict_features(state, context_features(x, chosen['family']))
|
||||
for i in range(20):
|
||||
x = dev['x'][i % len(dev['x']):i % len(dev['x']) + 1]
|
||||
candidate_predict(x)
|
||||
baseline.predict(x)
|
||||
timings = {'candidate': [], 'incumbent': []}
|
||||
for i in range(1000):
|
||||
x = dev['x'][i % len(dev['x']):i % len(dev['x']) + 1]
|
||||
calls = [('candidate', candidate_predict), ('incumbent', baseline.predict)]
|
||||
if i % 2:
|
||||
calls.reverse()
|
||||
for name, fn in calls:
|
||||
before = time.perf_counter_ns()
|
||||
fn(x)
|
||||
timings[name].append((time.perf_counter_ns() - before) / 1e6)
|
||||
timing = {k: {'p50_ms': float(np.percentile(v, 50)), 'p95_ms': float(np.percentile(v, 95))}
|
||||
for k, v in timings.items()}
|
||||
gate = CONTRACT['retention_gate']
|
||||
checks = {
|
||||
'macro_f1_gain': measured['macro_f1'] >= incumbent['macro_f1'] + gate['development_macro_f1_absolute_gain'],
|
||||
'accuracy_nonregression': measured['accuracy'] >= incumbent['accuracy'],
|
||||
'protect_each_previously_nonzero_class': all(b == 0 or a > 0 for a, b in
|
||||
zip(measured['per_class_f1'], incumbent['per_class_f1'])),
|
||||
'p95_under_1ms': timing['candidate']['p95_ms'] < gate['paired_p95_limit_ms'],
|
||||
}
|
||||
report = {
|
||||
'status': 'complete', 'promotionAuthorized': False, 'task': 'public_context_ablation',
|
||||
'contract': CONTRACT, 'selected': selected, 'candidates': results,
|
||||
'baseline_control': next(r for r in results if r['candidate'] == BASELINE),
|
||||
'baseline_state_exact': True, 'reload_max_abs_logit_error': 0.0,
|
||||
'evidence': 'SYNTHETIC' if provenance.get('evidence') == 'SYNTHETIC' else 'MEASURED',
|
||||
'provenance': provenance, 'development': measured, 'incumbent_development': incumbent,
|
||||
'retention_checks': checks, 'retained_as_research_candidate': all(checks.values()),
|
||||
'latency': timing, 'model_bytes': (root / 'model.safetensors').stat().st_size,
|
||||
'model_sha256': digest(root / 'model.safetensors'),
|
||||
'baseline_model_sha256': prior['model_sha256'],
|
||||
'baseline_receipt_sha256': hashlib.sha256(prior_bytes).hexdigest(),
|
||||
'source_sha256': {name: digest(Path(__file__).with_name(name)) for name in
|
||||
('espfi_context.py', 'espfi_motion.py', 'espfi_generalize.py', 'espfi_kernel.py')},
|
||||
'elapsed_seconds': time.monotonic() - start,
|
||||
'scope': '18 configurations selected by original training folds; adaptive research on reused development data. '
|
||||
'1000 paired batch1 calls include feature extraction/checks/classifier, exclude acquisition/pooling/IO. '
|
||||
'No cross-recording chronology, empty-room calibration, actual graph feature, final test or field accuracy.',
|
||||
}
|
||||
write_json(root / 'metrics.json', report)
|
||||
write_json(root / 'status.json', {'status': 'complete', 'promotionAuthorized': False})
|
||||
return report
|
||||
except Exception:
|
||||
write_json(root / 'status.json', {'status': 'failed', 'promotionAuthorized': False})
|
||||
raise
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
for name in ('manifest', 'baseline', 'output'):
|
||||
parser.add_argument('--' + name, required=True)
|
||||
parser.add_argument('--max-seconds', type=int, default=600)
|
||||
args = parser.parse_args()
|
||||
from .espfi import load_development
|
||||
parts, provenance = load_development(args.manifest)
|
||||
result = search(parts, provenance, args.baseline, args.output, args.max_seconds)
|
||||
print(json.dumps({k: result[k] for k in ('selected', 'development', 'incumbent_development',
|
||||
'retention_checks', 'retained_as_research_candidate', 'latency', 'model_bytes')}, indent=2))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1 @@
|
||||
/target/
|
||||
Generated
+547
@@ -0,0 +1,547 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "base64ct"
|
||||
version = "1.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
|
||||
|
||||
[[package]]
|
||||
name = "const-oid"
|
||||
version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "curve25519-dalek"
|
||||
version = "4.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"curve25519-dalek-derive",
|
||||
"digest",
|
||||
"fiat-crypto",
|
||||
"rustc_version",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "curve25519-dalek-derive"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "der"
|
||||
version = "0.7.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
|
||||
dependencies = [
|
||||
"const-oid",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ed25519"
|
||||
version = "2.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
|
||||
dependencies = [
|
||||
"pkcs8",
|
||||
"signature",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ed25519-dalek"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9"
|
||||
dependencies = [
|
||||
"curve25519-dalek",
|
||||
"ed25519",
|
||||
"serde",
|
||||
"sha2",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "fiat-crypto"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
|
||||
|
||||
[[package]]
|
||||
name = "fixedbitset"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.14.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.190"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
||||
|
||||
[[package]]
|
||||
name = "petgraph"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db"
|
||||
dependencies = [
|
||||
"fixedbitset",
|
||||
"indexmap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkcs8"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
|
||||
dependencies = [
|
||||
"der",
|
||||
"spki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rufield-adapters"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/ruvnet/rufield?rev=b74bc035e4a3d4023665aa5479b41e938facd113#b74bc035e4a3d4023665aa5479b41e938facd113"
|
||||
dependencies = [
|
||||
"rufield-core",
|
||||
"rufield-provenance",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rufield-core"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/ruvnet/rufield?rev=b74bc035e4a3d4023665aa5479b41e938facd113#b74bc035e4a3d4023665aa5479b41e938facd113"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rufield-fusion"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/ruvnet/rufield?rev=b74bc035e4a3d4023665aa5479b41e938facd113#b74bc035e4a3d4023665aa5479b41e938facd113"
|
||||
dependencies = [
|
||||
"rufield-core",
|
||||
"rufield-provenance",
|
||||
"serde",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rufield-provenance"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/ruvnet/rufield?rev=b74bc035e4a3d4023665aa5479b41e938facd113#b74bc035e4a3d4023665aa5479b41e938facd113"
|
||||
dependencies = [
|
||||
"ed25519-dalek",
|
||||
"rufield-core",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
|
||||
dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruview-field-context-probe"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"rufield-adapters",
|
||||
"rufield-core",
|
||||
"rufield-fusion",
|
||||
"rufield-provenance",
|
||||
"serde_json",
|
||||
"wifi-densepose-worldgraph",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.154"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"serde",
|
||||
"serde_core",
|
||||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signature"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
|
||||
dependencies = [
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spki"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"der",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.119"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "3.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_write",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_write"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.20.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.1+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
name = "wifi-densepose-geo"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/ruvnet/worldgraph?rev=9b1c79c836cdacfb7b44f058c593157bac4c1dab#9b1c79c836cdacfb7b44f058c593157bac4c1dab"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wifi-densepose-worldgraph"
|
||||
version = "0.3.2"
|
||||
source = "git+https://github.com/ruvnet/worldgraph?rev=9b1c79c836cdacfb7b44f058c593157bac4c1dab#9b1c79c836cdacfb7b44f058c593157bac4c1dab"
|
||||
dependencies = [
|
||||
"petgraph",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"wifi-densepose-geo",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e13084392c5e4bc371903e2935a5eaeed24905a7511356b883835e18a78f6879"
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
||||
@@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "ruview-field-context-probe"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.90"
|
||||
publish = false
|
||||
|
||||
[workspace]
|
||||
|
||||
[dependencies]
|
||||
rufield-core = { git = "https://github.com/ruvnet/rufield", rev = "b74bc035e4a3d4023665aa5479b41e938facd113" }
|
||||
rufield-provenance = { git = "https://github.com/ruvnet/rufield", rev = "b74bc035e4a3d4023665aa5479b41e938facd113" }
|
||||
rufield-fusion = { git = "https://github.com/ruvnet/rufield", rev = "b74bc035e4a3d4023665aa5479b41e938facd113" }
|
||||
wifi-densepose-worldgraph = { git = "https://github.com/ruvnet/worldgraph", rev = "9b1c79c836cdacfb7b44f058c593157bac4c1dab" }
|
||||
serde_json = "1.0"
|
||||
rufield-adapters = { git = "https://github.com/ruvnet/rufield", rev = "b74bc035e4a3d4023665aa5479b41e938facd113" }
|
||||
@@ -0,0 +1,94 @@
|
||||
# RuField to WorldGraph integrity probe
|
||||
|
||||
This standalone Rust executable calls the actual pinned `rufield-core`,
|
||||
`rufield-provenance`, `rufield-fusion`, and `wifi-densepose-worldgraph` crates.
|
||||
It demonstrates evidence handling with deterministic **SYNTHETIC** fixtures.
|
||||
It does not train an activity classifier or establish sensing accuracy.
|
||||
|
||||
## Run
|
||||
|
||||
Requires Rust 1.90.0 or newer. Dependencies are pinned to full Git revisions in
|
||||
`Cargo.toml`; `Cargo.lock` locks their resolved transitive dependencies.
|
||||
|
||||
```sh
|
||||
cd scripts/field-context-probe
|
||||
cargo test --locked
|
||||
cargo run --locked --quiet
|
||||
cargo run --release --locked --quiet -- --benchmark
|
||||
# Optional bounded format-only inspection, no calibration or fusion:
|
||||
cargo run --release --locked --quiet -- --inspect-jsonl /path/to/capture.csi.jsonl
|
||||
```
|
||||
|
||||
No network, file writes, hardware access, person tracking, or external release
|
||||
occurs when the built executable runs. Initial Cargo dependency resolution needs
|
||||
network access. The public fixed signing seed is exclusively a test fixture.
|
||||
|
||||
## Actual pipeline
|
||||
|
||||
1. Construct a signed RuField `FieldEvent` with `synthetic: true`. Its clock,
|
||||
amplitude vector and presence feature are explicitly authored test values.
|
||||
2. Verify its signature with `rufield_provenance::verify_event`, then ingest it
|
||||
through the actual `RuFieldFusion` simulation trust policy and a tiny explicit
|
||||
presence rule. The output score is a heuristic, not a calibrated probability.
|
||||
3. A small bridge checks fresh, unique root evidence and inference expiry before
|
||||
calling `WorldGraph::add_semantic_state`. Original event IDs become mandatory
|
||||
provenance handles and `DerivedFrom` edges to concrete event nodes.
|
||||
4. Serialize and restore the actual WorldGraph. Assert preserved node counts,
|
||||
edge counts and ancestry.
|
||||
|
||||
The bridge has a fixed capacity of 32 root events. The synthetic bridge accepts no external input. The separate optional replay inspector reads a regular UTF-8 file of at most 1 MiB and 10,000 nonempty lines. It calls actual `CsiReplayAdapter` parsing, but never calibration, event generation or graph insertion. Every malformed JSON line is rejected, including a truncated tail which the native adapter deliberately tolerates. Parsed frame count must also match nonempty line count, preventing schema-invalid final objects from being silently discarded. Rejection exits with status 2; accepted format exits with status 0. Format acceptance establishes no labels, empty-room baseline or sensing accuracy.
|
||||
The executable prints a JSON receipt; all rejected fixtures leave the WorldGraph
|
||||
unchanged. Nothing is automatically promoted.
|
||||
|
||||
## Native guarantees versus bridge policy
|
||||
|
||||
| Check | Responsible component |
|
||||
| --- | --- |
|
||||
| Exact event replay and nonmonotonic timestamp rejection | Actual RuField trust verifier, including simulation mode |
|
||||
| Rejection of synthetic input under production policy | Actual RuField production verifier |
|
||||
| Signature corruption | Actual RuField signature verification invoked explicitly by bridge |
|
||||
| Wall clock freshness for this simulation | Bridge; native simulation deliberately skips wall clock freshness |
|
||||
| Repeated content hash under a different event ID | Bridge |
|
||||
| Derived evidence feeding back as independent root evidence | Bridge conservatively rejects every nonempty ancestry declaration |
|
||||
| Expired inference, missing ancestry, duplicate belief | Bridge |
|
||||
| Typed belief and source ancestry persistence | Actual WorldGraph |
|
||||
| General cycle prevention | Not supplied by native WorldGraph; a separate boundary test demonstrates that native self edges are accepted |
|
||||
|
||||
This is deliberately conservative. It is not a complete general purpose graph
|
||||
cycle detector or a production authorization service. Production use requires
|
||||
independently enrolled sensor keys, appropriate privacy policy, fresh calibrated
|
||||
measurements and application specific lineage rules.
|
||||
|
||||
## Why no ESP-Fi samples enter this probe
|
||||
|
||||
The available activity recordings do not establish calibrated empty room
|
||||
baselines, synchronized wall clock times, persistent physical tracks, or complete
|
||||
world topology. Fabricating those fields would create false evidence. The
|
||||
synthetic probe therefore validates software integrity only. The independent
|
||||
activity training harness continues to evaluate its own actual dataset.
|
||||
|
||||
## Pinned source
|
||||
|
||||
* RuField: `b74bc035e4a3d4023665aa5479b41e938facd113`
|
||||
* WorldGraph: `9b1c79c836cdacfb7b44f058c593157bac4c1dab`
|
||||
|
||||
Acceptance: all six tests pass; the receipt reports synthetic evidence,
|
||||
uncalibrated confidence, complete root ancestry, rejected duplicate/stale/derived
|
||||
fixtures, successful graph roundtrip and `promotionAuthorized: false`.
|
||||
|
||||
## Measured overhead
|
||||
|
||||
The release benchmark uses 100 warmups and 1,000 alternating pairs on the same
|
||||
signed synthetic fixture. Both paths construct a fresh engine, parse the rule,
|
||||
verify the signature, clone the fixture, ingest and infer. The bridge additionally
|
||||
checks its guards, constructs WorldGraph and publishes ancestry. Signing, sensing,
|
||||
training, calibration and persistent-stream behavior are outside the measurement.
|
||||
The JSON receipt reports both paths and the distribution of paired differences;
|
||||
its target is added p95 below 0.1 ms. This is a software integrity cost measurement,
|
||||
not predictive improvement or a production throughput guarantee.
|
||||
|
||||
The optional recording inspector also applies stricter policy than the native
|
||||
adapter: required numeric timestamp and amplitude array, finite nonnegative
|
||||
values, nanoseconds below u64 maximum, amplitudes within f32 range, 1–2,048
|
||||
channels, fixed channel width and strictly increasing integer nanosecond times.
|
||||
These format/representation guards do not certify capture provenance or labels.
|
||||
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"benchmark": {
|
||||
"bridge_ms": {
|
||||
"p50": 0.08661999999999999,
|
||||
"p95": 0.12714
|
||||
},
|
||||
"evidence": "MEASURED software timing on SYNTHETIC fixtures",
|
||||
"extra_p95_target_met": true,
|
||||
"extra_p95_target_ms": 0.1,
|
||||
"iterations": 1000,
|
||||
"native_ms": {
|
||||
"p50": 0.08199000000000001,
|
||||
"p95": 0.122819
|
||||
},
|
||||
"paired_extra_ms": {
|
||||
"p50": 0.00452000000000001,
|
||||
"p95": 0.03833999999999997
|
||||
},
|
||||
"profile": "release required for reported comparison",
|
||||
"scope": "Fresh isolated engine per call: signature verify, fixture clone, rule parsing, ingestion and inference; bridge adds guards, WorldGraph creation and ancestry publication. Excludes signing, sensing, training and persistent-stream costs.",
|
||||
"warmup": 100
|
||||
},
|
||||
"bridge": {
|
||||
"ancestryEdges": 1,
|
||||
"derivedCircularRejected": true,
|
||||
"duplicateBeliefRejected": true,
|
||||
"duplicateRejected": true,
|
||||
"edges": 1,
|
||||
"expiredBeliefRejected": true,
|
||||
"missingAncestryRejected": true,
|
||||
"nodes": 2,
|
||||
"rejectionsDoNotMutateWorldGraph": true,
|
||||
"staleRejected": true,
|
||||
"worldGraphRoundTrip": true
|
||||
},
|
||||
"confidenceIsCalibrated": false,
|
||||
"evidence": "SYNTHETIC",
|
||||
"heuristicScore": 0.8999999761581421,
|
||||
"limitations": [
|
||||
"No real hardware or ESP-Fi timestamps/labels used",
|
||||
"Bridge freshness/ancestry guards are additional policy, not native WorldGraph guarantees",
|
||||
"Simulation trust is not production sensor enrollment",
|
||||
"No model training or predictive accuracy uplift established"
|
||||
],
|
||||
"native": {
|
||||
"duplicateRejected": true,
|
||||
"productionRejectsSynthetic": true,
|
||||
"simulationSkipsWallClockFreshness": true,
|
||||
"worldGraphAllowsCycles": true
|
||||
},
|
||||
"promotionAuthorized": false,
|
||||
"purpose": "integrity and reliability, not activity accuracy",
|
||||
"rawDataIncluded": false,
|
||||
"ruFieldRevision": "b74bc035e4a3d4023665aa5479b41e938facd113",
|
||||
"worldGraphRevision": "9b1c79c836cdacfb7b44f058c593157bac4c1dab"
|
||||
}
|
||||
@@ -0,0 +1,378 @@
|
||||
//! Synthetic reliability probe using actual pinned RuField and WorldGraph.
|
||||
//! No activity model, physical capture, confidence calibration or person track.
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use rufield_core::{FieldEvent, FieldTensor, FieldAxis, Modality, Observation, PrivacyClass,
|
||||
ProvenanceRef, SensorDescriptor, FusionEngine, InferenceQuery, FieldInference};
|
||||
use rufield_fusion::{RuFieldFusion, RuleSet};
|
||||
use rufield_provenance::{sha256_hex, Signer, verify_event, TrustVerifier, TrustPolicy, TrustedKeyRegistry};
|
||||
use wifi_densepose_worldgraph::{WorldGraph, WorldNode, WorldId, WorldEdge, SemanticProvenance};
|
||||
use serde_json::{json, Value};
|
||||
|
||||
const NOW: u64 = 1_800_000_000_000_000_000; // Authored simulation clock, not capture metadata.
|
||||
const MAX_AGE: u64 = 2_000_000_000;
|
||||
const MAX_EVENTS: usize = 32;
|
||||
const RULES: &str = r#"
|
||||
[rule.synthetic_presence]
|
||||
inputs = ["wifi_csi"]
|
||||
method = "weighted_bayes"
|
||||
feature = "presence"
|
||||
threshold = 0.5
|
||||
privacy_max = "P2"
|
||||
"#;
|
||||
|
||||
fn fixture(id: &str, timestamp: u64) -> FieldEvent {
|
||||
let tensor = FieldTensor::new(timestamp, Modality::WifiCsi,
|
||||
vec![FieldAxis::Frequency], vec![3], vec![1., 2., 3.],
|
||||
0.9, 0.01, Some("synthetic_room_cal_v1".into()), PrivacyClass::P2).unwrap();
|
||||
let mut observation = Observation::occupancy(0.9, PrivacyClass::P2);
|
||||
observation.features.insert("presence".into(), 0.9);
|
||||
let mut event = FieldEvent::new(id, timestamp, SensorDescriptor {
|
||||
modality: "wifi_csi".into(), vendor: "SYNTHETIC_FIXTURE".into(),
|
||||
device_id: "synthetic_sensor".into(), placement: "authored_fixture".into(),
|
||||
coordinate_frame: None, position_m: None, orientation_xyzw: None,
|
||||
clock_domain: "synthetic_clock".into(),
|
||||
}, tensor, observation, ProvenanceRef {
|
||||
raw_hash: sha256_hex(format!("SYNTHETIC fixture {id} {timestamp}").as_bytes()),
|
||||
firmware_hash: sha256_hex(b"SYNTHETIC fixture generator"),
|
||||
model_id: "authored_feature_not_trained_model".into(),
|
||||
calibration_id: "synthetic_room_cal_v1".into(), synthetic: true,
|
||||
signature_hex: None, signer_pubkey_hex: None,
|
||||
});
|
||||
// Public deterministic test key; never a production credential.
|
||||
Signer::from_seed(&[17; 32]).sign_event(&mut event).unwrap();
|
||||
event
|
||||
}
|
||||
|
||||
/// Thin conservative bridge policy; these guards are NOT native WorldGraph features.
|
||||
/// Only fresh signed synthetic root events enter this demonstration. Derived
|
||||
/// observations cannot feed back into the root evidence stream.
|
||||
struct Bridge {
|
||||
fusion: RuFieldFusion,
|
||||
world: WorldGraph,
|
||||
roots: BTreeMap<String, WorldId>,
|
||||
hashes: BTreeSet<String>,
|
||||
published: BTreeSet<String>,
|
||||
}
|
||||
impl Bridge {
|
||||
fn new() -> Self {
|
||||
Self { fusion: RuFieldFusion::with_rules(RuleSet::from_toml(RULES).unwrap()),
|
||||
world: WorldGraph::new(Default::default()), roots: BTreeMap::new(),
|
||||
hashes: BTreeSet::new(), published: BTreeSet::new() }
|
||||
}
|
||||
fn ingest(&mut self, event: FieldEvent, ancestry: &[String], now: u64) -> Result<(), String> {
|
||||
if !event.provenance.synthetic { return Err("synthetic_probe_only".into()); }
|
||||
if !ancestry.is_empty() { return Err("derived_or_circular_evidence_excluded".into()); }
|
||||
if event.timestamp_ns > now || now.saturating_sub(event.timestamp_ns) > MAX_AGE {
|
||||
return Err("stale_or_future_bridge_gate".into());
|
||||
}
|
||||
if self.roots.len() >= MAX_EVENTS { return Err("bounded_capacity".into()); }
|
||||
if self.roots.contains_key(&event.event_id) || self.hashes.contains(&event.provenance.raw_hash) {
|
||||
return Err("duplicate_evidence_bridge_gate".into());
|
||||
}
|
||||
verify_event(&event).map_err(|e| format!("signature: {e}"))?;
|
||||
let id = event.event_id.clone();
|
||||
let hash = event.provenance.raw_hash.clone();
|
||||
let at = i64::try_from(event.timestamp_ns / 1_000_000).map_err(|_| "timestamp_overflow")?;
|
||||
self.fusion.ingest_at(event, now).map_err(|e| e.to_string())?;
|
||||
let node = self.world.upsert_node(WorldNode::Event { id: WorldId::UNASSIGNED,
|
||||
event_type: format!("SYNTHETIC root {id}"), at_unix_ms: at, located_in: None });
|
||||
self.roots.insert(id, node);
|
||||
self.hashes.insert(hash);
|
||||
Ok(())
|
||||
}
|
||||
fn inference(&self) -> FieldInference {
|
||||
self.fusion.infer(&InferenceQuery::all()).unwrap().into_iter().next().unwrap()
|
||||
}
|
||||
fn publish(&mut self, inference: &FieldInference, now: u64) -> Result<WorldId, String> {
|
||||
if inference.expires_ns <= now || inference.produced_ns > now {
|
||||
return Err("expired_inference_bridge_gate".into());
|
||||
}
|
||||
if !inference.confidence.is_finite() || !(0.0..=1.0).contains(&inference.confidence) {
|
||||
return Err("invalid_heuristic_score".into());
|
||||
}
|
||||
let sources: BTreeSet<_> = inference.supporting_events.iter().cloned().collect();
|
||||
if sources.is_empty() || sources.len() != inference.supporting_events.len() ||
|
||||
sources.iter().any(|id| !self.roots.contains_key(id)) {
|
||||
return Err("missing_or_repeated_root_ancestry".into());
|
||||
}
|
||||
let fingerprint = format!("{}:{:?}:{:?}", inference.model_id, sources, inference.produced_ns);
|
||||
if self.published.contains(&fingerprint) { return Err("duplicate_belief_bridge_gate".into()); }
|
||||
let nodes: Vec<_> = sources.iter().map(|id| self.roots[id]).collect();
|
||||
let handles: Vec<_> = sources.into_iter().collect();
|
||||
let state = self.world.add_semantic_state(
|
||||
"SYNTHETIC fixture presence; heuristic score, not calibrated probability".into(),
|
||||
inference.confidence, (inference.produced_ns / 1_000_000) as i64,
|
||||
SemanticProvenance { evidence: handles, model_version: inference.model_id.clone(),
|
||||
calibration_version: inference.calibration_id.clone().unwrap_or_default(),
|
||||
privacy_decision: "SYNTHETIC P2 local probe; no external release".into() }, &nodes);
|
||||
self.published.insert(fingerprint);
|
||||
Ok(state)
|
||||
}
|
||||
}
|
||||
|
||||
fn probe() -> Value {
|
||||
let event = fixture("root-1", NOW);
|
||||
let mut native = TrustVerifier::simulation();
|
||||
native.verify_and_record_at(&event, NOW).unwrap();
|
||||
let native_duplicate = native.verify_and_record_at(&event, NOW).is_err();
|
||||
let old_event = fixture("old-root", NOW - MAX_AGE - 1);
|
||||
let mut simulation = TrustVerifier::simulation();
|
||||
let native_simulation_accepts_old = simulation.verify_and_record_at(&old_event, NOW).is_ok();
|
||||
let mut production = TrustVerifier::new(TrustPolicy::production(), TrustedKeyRegistry::new());
|
||||
let native_production_rejects_synthetic = production.verify_and_record_at(&event, NOW).is_err();
|
||||
|
||||
let mut bridge = Bridge::new();
|
||||
bridge.ingest(event.clone(), &[], NOW).unwrap();
|
||||
let before = bridge.world.node_count();
|
||||
let duplicate_blocked = bridge.ingest(event, &[], NOW).is_err();
|
||||
let stale_blocked = bridge.ingest(old_event, &[], NOW).is_err();
|
||||
let cycle_blocked = bridge.ingest(fixture("derived-1", NOW + 1), &["derived-1".into()], NOW + 1).is_err();
|
||||
assert_eq!(before, bridge.world.node_count());
|
||||
let inference = bridge.inference();
|
||||
let belief = bridge.publish(&inference, NOW).unwrap();
|
||||
let before_rejections = bridge.world.to_json().unwrap();
|
||||
let repeated_belief = bridge.publish(&inference, NOW).is_err();
|
||||
let expired = bridge.publish(&inference, inference.expires_ns).is_err();
|
||||
let mut missing = inference.clone();
|
||||
missing.supporting_events = vec!["unknown-root".into()];
|
||||
let missing_blocked = bridge.publish(&missing, NOW).is_err();
|
||||
assert_eq!(before_rejections, bridge.world.to_json().unwrap());
|
||||
let ancestry = bridge.world.neighbors(belief);
|
||||
assert_eq!(ancestry.len(), 1);
|
||||
assert_eq!(ancestry[0].0, bridge.roots["root-1"]);
|
||||
assert!(matches!(&ancestry[0].1, WorldEdge::DerivedFrom { evidence } if evidence == "root-1"));
|
||||
let wire = bridge.world.to_json().unwrap();
|
||||
let restored = WorldGraph::from_json(&wire).unwrap();
|
||||
assert_eq!(restored.node_count(), bridge.world.node_count());
|
||||
assert_eq!(restored.edge_count(), bridge.world.edge_count());
|
||||
let restored_ancestry = restored.neighbors(belief);
|
||||
assert_eq!(restored_ancestry[0].0, bridge.roots["root-1"]);
|
||||
assert!(matches!(&restored_ancestry[0].1, WorldEdge::DerivedFrom { evidence } if evidence == "root-1"));
|
||||
|
||||
// Characterize native WorldGraph's boundary honestly in an isolated graph.
|
||||
let mut unrestricted = WorldGraph::new(Default::default());
|
||||
let a = unrestricted.upsert_node(WorldNode::Event { id: WorldId::UNASSIGNED,
|
||||
event_type: "SYNTHETIC cycle boundary fixture".into(), at_unix_ms: 0, located_in: None });
|
||||
let native_cycle_allowed = unrestricted.add_edge(a, a, WorldEdge::DerivedFrom { evidence: "fixture".into() }).is_ok();
|
||||
json!({"evidence":"SYNTHETIC", "purpose":"integrity and reliability, not activity accuracy",
|
||||
"ruFieldRevision":"b74bc035e4a3d4023665aa5479b41e938facd113",
|
||||
"worldGraphRevision":"9b1c79c836cdacfb7b44f058c593157bac4c1dab",
|
||||
"native": {"duplicateRejected":native_duplicate,
|
||||
"simulationSkipsWallClockFreshness":native_simulation_accepts_old,
|
||||
"productionRejectsSynthetic":native_production_rejects_synthetic,
|
||||
"worldGraphAllowsCycles":native_cycle_allowed},
|
||||
"bridge": {"duplicateRejected":duplicate_blocked,"staleRejected":stale_blocked,
|
||||
"derivedCircularRejected":cycle_blocked,"duplicateBeliefRejected":repeated_belief,
|
||||
"expiredBeliefRejected":expired,"missingAncestryRejected":missing_blocked,
|
||||
"rejectionsDoNotMutateWorldGraph":true,"ancestryEdges":ancestry.len(),
|
||||
"worldGraphRoundTrip":true,"nodes":restored.node_count(),"edges":restored.edge_count()},
|
||||
"heuristicScore":inference.confidence,"confidenceIsCalibrated":false,
|
||||
"rawDataIncluded":false,"promotionAuthorized":false,
|
||||
"limitations":["No real hardware or ESP-Fi timestamps/labels used",
|
||||
"Bridge freshness/ancestry guards are additional policy, not native WorldGraph guarantees",
|
||||
"Simulation trust is not production sensor enrollment",
|
||||
"No model training or predictive accuracy uplift established"]})
|
||||
}
|
||||
|
||||
fn native_once(event: &FieldEvent) {
|
||||
verify_event(event).unwrap();
|
||||
let mut fusion = RuFieldFusion::with_rules(RuleSet::from_toml(RULES).unwrap());
|
||||
fusion.ingest_at(event.clone(), NOW).unwrap();
|
||||
std::hint::black_box(fusion.infer(&InferenceQuery::all()).unwrap());
|
||||
}
|
||||
fn bridge_once(event: &FieldEvent) {
|
||||
let mut bridge = Bridge::new();
|
||||
bridge.ingest(event.clone(), &[], NOW).unwrap();
|
||||
let inference = bridge.inference();
|
||||
std::hint::black_box(bridge.publish(&inference, NOW).unwrap());
|
||||
}
|
||||
fn quantile(values: &[f64], q: f64) -> f64 {
|
||||
let mut ordered = values.to_vec();
|
||||
ordered.sort_by(f64::total_cmp);
|
||||
ordered[((ordered.len() - 1) as f64 * q).round() as usize]
|
||||
}
|
||||
fn benchmark() -> Value {
|
||||
let event = fixture("benchmark-root", NOW);
|
||||
for _ in 0..100 { native_once(&event); bridge_once(&event); }
|
||||
let mut native = Vec::with_capacity(1000);
|
||||
let mut bridge = Vec::with_capacity(1000);
|
||||
for i in 0..1000 {
|
||||
let mut measure = |with_bridge: bool| {
|
||||
let start = std::time::Instant::now();
|
||||
if with_bridge { bridge_once(&event); } else { native_once(&event); }
|
||||
let elapsed = start.elapsed().as_secs_f64() * 1000.;
|
||||
if with_bridge { bridge.push(elapsed); } else { native.push(elapsed); }
|
||||
};
|
||||
if i % 2 == 0 { measure(false); measure(true); } else { measure(true); measure(false); }
|
||||
}
|
||||
let differences: Vec<_> = bridge.iter().zip(&native).map(|(b,n)| b-n).collect();
|
||||
json!({"evidence":"MEASURED software timing on SYNTHETIC fixtures",
|
||||
"iterations":1000,"warmup":100,"profile":"release required for reported comparison",
|
||||
"scope":"Fresh isolated engine per call: signature verify, fixture clone, rule parsing, ingestion and inference; bridge adds guards, WorldGraph creation and ancestry publication. Excludes signing, sensing, training and persistent-stream costs.",
|
||||
"native_ms":{"p50":quantile(&native,0.5),"p95":quantile(&native,0.95)},
|
||||
"bridge_ms":{"p50":quantile(&bridge,0.5),"p95":quantile(&bridge,0.95)},
|
||||
"paired_extra_ms":{"p50":quantile(&differences,0.5),"p95":quantile(&differences,0.95)},
|
||||
"extra_p95_target_ms":0.1,"extra_p95_target_met":quantile(&differences,0.95)<0.1})
|
||||
}
|
||||
// Strict wrapper deliberately differs from the adapter's truncated-tail recovery.
|
||||
fn inspect_text(text: &str) -> Value {
|
||||
let hash = sha256_hex(text.as_bytes());
|
||||
let lines: Vec<_> = text.lines().filter(|line| !line.trim().is_empty()).collect();
|
||||
if lines.len() > 10_000 { return json!({"accepted":false,"reason":"line_limit","sha256":hash}); }
|
||||
let mut previous_timestamp = None;
|
||||
let mut channel_width = None;
|
||||
for (index, line) in lines.iter().enumerate() {
|
||||
if serde_json::from_str::<Value>(line).is_err() {
|
||||
return json!({"accepted":false,"reason":"strict_json_parse","nonempty_line":index+1,"sha256":hash,
|
||||
"native_adapter_accepts":rufield_adapters::CsiReplayAdapter::from_jsonl(text).is_ok(),
|
||||
"calibration_performed":false,"graph_mutated":false});
|
||||
}
|
||||
}
|
||||
for line in &lines {
|
||||
let value: Value = serde_json::from_str(line).unwrap();
|
||||
let Some(timestamp) = value.get("timestamp").and_then(Value::as_f64) else {
|
||||
return json!({"accepted":false,"reason":"strict_schema","sha256":hash});
|
||||
};
|
||||
let Some(amplitudes) = value.get("subcarriers").and_then(Value::as_array) else {
|
||||
return json!({"accepted":false,"reason":"strict_schema","sha256":hash});
|
||||
};
|
||||
let nanos = timestamp * 1e9;
|
||||
if !timestamp.is_finite() || timestamp < 0. || !nanos.is_finite() || nanos >= u64::MAX as f64
|
||||
|| amplitudes.is_empty() || amplitudes.len() > 2048
|
||||
|| amplitudes.iter().any(|v| v.as_f64().map_or(true, |x| !x.is_finite() || x < 0. || x > f32::MAX as f64)) {
|
||||
return json!({"accepted":false,"reason":"strict_numeric_range","sha256":hash});
|
||||
}
|
||||
let timestamp_ns = nanos as u64;
|
||||
if previous_timestamp.is_some_and(|previous| timestamp_ns <= previous)
|
||||
|| channel_width.is_some_and(|width| width != amplitudes.len()) {
|
||||
return json!({"accepted":false,"reason":"strict_recording_order_or_width","sha256":hash});
|
||||
}
|
||||
previous_timestamp = Some(timestamp_ns);
|
||||
channel_width = Some(amplitudes.len());
|
||||
}
|
||||
match rufield_adapters::CsiReplayAdapter::from_jsonl(text) {
|
||||
Ok(adapter) => {
|
||||
let frames = adapter.frames();
|
||||
if frames.len() != lines.len() {
|
||||
return json!({"accepted":false,"reason":"native_adapter_dropped_line","sha256":hash,
|
||||
"nonempty_lines":lines.len(),"parsed_frames":frames.len(),
|
||||
"calibration_performed":false,"graph_mutated":false});
|
||||
}
|
||||
let span = frames.last().unwrap().timestamp_ns.saturating_sub(frames[0].timestamp_ns);
|
||||
json!({"accepted":true,"sha256":hash,"frames":frames.len(),"timestamp_span_seconds":span as f64 / 1e9,
|
||||
"calibration_performed":false,"graph_mutated":false,"labels_inferred":false,
|
||||
"evidence":"Format-only replay parsing using actual pinned CsiReplayAdapter"})
|
||||
},
|
||||
Err(_) => json!({"accepted":false,"reason":"native_adapter_validation","sha256":hash,
|
||||
"calibration_performed":false,"graph_mutated":false})
|
||||
}
|
||||
}
|
||||
fn inspect_path(path: &str) -> Result<Value, Box<dyn std::error::Error>> {
|
||||
use std::io::Read;
|
||||
const MAX_BYTES: u64 = 1_048_576;
|
||||
let metadata = std::fs::symlink_metadata(path)?;
|
||||
if !metadata.file_type().is_file() || metadata.len() > MAX_BYTES { return Err("regular file <= 1 MiB required".into()); }
|
||||
let mut text = String::new();
|
||||
std::fs::File::open(path)?.take(MAX_BYTES+1).read_to_string(&mut text)?;
|
||||
if text.len() as u64 > MAX_BYTES { return Err("file exceeds limit".into()); }
|
||||
Ok(inspect_text(&text))
|
||||
}
|
||||
fn main() {
|
||||
let args: Vec<_> = std::env::args().skip(1).collect();
|
||||
if args.len() == 2 && args[0] == "--inspect-jsonl" {
|
||||
match inspect_path(&args[1]) {
|
||||
Ok(report) => { println!("{}", serde_json::to_string_pretty(&report).unwrap());
|
||||
if report["accepted"] != true { std::process::exit(2); } },
|
||||
Err(_) => { eprintln!("Replay rejected: invalid, unreadable, oversized or non-UTF8 regular file"); std::process::exit(2); }
|
||||
}
|
||||
return;
|
||||
}
|
||||
let mut report = probe();
|
||||
if args == ["--benchmark"] { report["benchmark"] = benchmark(); }
|
||||
else if !args.is_empty() { eprintln!("Usage: ruview-field-context-probe [--benchmark | --inspect-jsonl PATH]"); std::process::exit(2); }
|
||||
println!("{}", serde_json::to_string_pretty(&report).unwrap());
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn strict_replay_rejects_truncated_tail_without_calibrating() {
|
||||
let valid = r#"{"timestamp":1.0,"subcarriers":[1.0,2.0]}
|
||||
{"timestamp":1.1,"subcarriers":[2.0,3.0]}"#;
|
||||
let report = inspect_text(valid);
|
||||
assert_eq!(report["accepted"], true);
|
||||
assert_eq!(report["frames"], 2);
|
||||
assert_eq!(report["calibration_performed"], false);
|
||||
let truncated = format!("{valid}\n{{");
|
||||
let report = inspect_text(&truncated);
|
||||
assert_eq!(report["accepted"], false);
|
||||
assert_eq!(report["native_adapter_accepts"], true);
|
||||
assert_eq!(report["graph_mutated"], false);
|
||||
for tail in ["{}", r#"{"timestamp":"wrong-type","subcarriers":[1.0]}"#] {
|
||||
let report = inspect_text(&format!("{valid}\n{tail}"));
|
||||
assert_eq!(report["accepted"], false);
|
||||
assert_eq!(report["reason"], "strict_schema");
|
||||
}
|
||||
for bad in [
|
||||
r#"{"timestamp":1e300,"subcarriers":[1.0]}"#,
|
||||
r#"{"timestamp":1.0,"subcarriers":[1e300]}"#,
|
||||
r#"{"timestamp":1.0,"subcarriers":[-1.0]}"#,
|
||||
r#"{"timestamp":1.0,"subcarriers":[]}"#,
|
||||
] { assert_eq!(inspect_text(bad)["accepted"], false); }
|
||||
for tail in [r#"{"timestamp":1.0,"subcarriers":[1.0,2.0]}"#,
|
||||
r#"{"timestamp":1.2,"subcarriers":[1.0]}"#] {
|
||||
assert_eq!(inspect_text(&format!("{valid}\n{tail}"))["reason"], "strict_recording_order_or_width");
|
||||
}
|
||||
assert_eq!(inspect_text("")["accepted"], false);
|
||||
}
|
||||
#[test]
|
||||
fn actual_libraries_and_bridge_contract() {
|
||||
let result = probe();
|
||||
for key in ["duplicateRejected", "staleRejected", "derivedCircularRejected",
|
||||
"duplicateBeliefRejected", "expiredBeliefRejected", "missingAncestryRejected",
|
||||
"rejectionsDoNotMutateWorldGraph", "worldGraphRoundTrip"] {
|
||||
assert_eq!(result["bridge"][key], true, "{key}");
|
||||
}
|
||||
assert_eq!(result["native"]["duplicateRejected"], true);
|
||||
assert_eq!(result["native"]["simulationSkipsWallClockFreshness"], true);
|
||||
assert_eq!(result["native"]["productionRejectsSynthetic"], true);
|
||||
assert_eq!(result["native"]["worldGraphAllowsCycles"], true);
|
||||
}
|
||||
#[test]
|
||||
fn synthetic_replay_is_deterministic() { assert_eq!(probe(), probe()); }
|
||||
#[test]
|
||||
fn tampering_and_future_evidence_cannot_change_graph() {
|
||||
let mut bridge = Bridge::new();
|
||||
let mut event = fixture("tamper", NOW);
|
||||
event.tensor.values[0] = 999.;
|
||||
assert!(bridge.ingest(event, &[], NOW).is_err());
|
||||
assert!(bridge.ingest(fixture("future", NOW + 1), &[], NOW).is_err());
|
||||
assert_eq!(bridge.world.node_count(), 0);
|
||||
}
|
||||
#[test]
|
||||
fn capacity_and_invalid_score_fail_without_graph_mutation() {
|
||||
let mut bridge = Bridge::new();
|
||||
for i in 0..MAX_EVENTS {
|
||||
bridge.ingest(fixture(&format!("capacity-{i}"), NOW+i as u64), &[], NOW+i as u64).unwrap();
|
||||
}
|
||||
let before = bridge.world.to_json().unwrap();
|
||||
assert!(bridge.ingest(fixture("overflow", NOW+100), &[], NOW+100).is_err());
|
||||
let mut inference = bridge.inference();
|
||||
inference.confidence = f32::NAN;
|
||||
assert!(bridge.publish(&inference, NOW+100).is_err());
|
||||
assert_eq!(before, bridge.world.to_json().unwrap());
|
||||
}
|
||||
#[test]
|
||||
fn repeated_raw_hash_under_new_id_is_not_independent_evidence() {
|
||||
let mut bridge = Bridge::new();
|
||||
let event = fixture("a", NOW);
|
||||
let hash = event.provenance.raw_hash.clone();
|
||||
bridge.ingest(event, &[], NOW).unwrap();
|
||||
let mut correlated = fixture("b", NOW + 1);
|
||||
correlated.provenance.raw_hash = hash;
|
||||
Signer::from_seed(&[17; 32]).sign_event(&mut correlated).unwrap();
|
||||
assert!(bridge.ingest(correlated, &[], NOW + 1).is_err());
|
||||
assert_eq!(bridge.world.node_count(), 1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"date_utc": "2026-10-12",
|
||||
"rust": "1.90.0",
|
||||
"evidence": "Measured software integrity and format parsing; no predictive accuracy claim",
|
||||
"test_result": {
|
||||
"passed": 6,
|
||||
"failed": 0
|
||||
},
|
||||
"commands": [
|
||||
"cargo test --locked",
|
||||
"cargo build --release --locked",
|
||||
"target/release/ruview-field-context-probe --benchmark",
|
||||
"target/release/ruview-field-context-probe --inspect-jsonl EXCLUDED_FIXTURE_PATH"
|
||||
],
|
||||
"completionVerified": true,
|
||||
"test_build_benchmark_exit_code": 0,
|
||||
"independent_review": "NO BLOCK on final source",
|
||||
"primary_agent_test_rerun": {
|
||||
"completionVerified": true,
|
||||
"exitCode": 0,
|
||||
"passed": 6,
|
||||
"source_hashes_match": true
|
||||
},
|
||||
"files_sha256": {
|
||||
"Cargo.toml": "6cbae892247fc9fb3c5e9c02c1d8fc3c5cdea9efc15f8794a5885db797ca1c75",
|
||||
"Cargo.lock": "c614e88fbf703f79f7f15a273f03b185a580efd04cc8452f302c23013d4c3267",
|
||||
"src/main.rs": "c85aa8bcdfbc49a13ae0ebc807fcb09e4d0d619a4125d33dbe3c105d7f4eaa2a",
|
||||
"example-result.json": "dbfbeac396588fefb3c0067897f74b80c76cdadb88cdfce60fd0e3566fe6a6fa"
|
||||
},
|
||||
"public_fixture_source_revision": "995567283606b86594439d6594387ff551a21ede",
|
||||
"public_replay": [
|
||||
{
|
||||
"name": "real_csi_small.jsonl",
|
||||
"sha256": "af90245da89f91b2ac9408aa933e654b43b57033f1d7c77db4f0ab27b801eeab",
|
||||
"accepted": true,
|
||||
"exit_code": 0,
|
||||
"frames": 3,
|
||||
"timestamp_span_seconds": 0.200999936
|
||||
},
|
||||
{
|
||||
"name": "real_csi_medium.jsonl",
|
||||
"sha256": "fb0c7cd81a968f99020ce16b4c35fecd5638062278aecf0f0f1ed883afba1f67",
|
||||
"accepted": false,
|
||||
"exit_code": 2,
|
||||
"reason": "strict_json_parse",
|
||||
"nonempty_line": 200,
|
||||
"native_adapter_accepts": true
|
||||
}
|
||||
],
|
||||
"raw_capture_committed": false,
|
||||
"calibration_performed": false,
|
||||
"labels_inferred": false,
|
||||
"production_promotion": false,
|
||||
"strict_schema_tail_regressions": [
|
||||
"empty object rejected",
|
||||
"wrong timestamp type rejected"
|
||||
],
|
||||
"strict_numeric_recording_regressions": [
|
||||
"timestamp1e300",
|
||||
"amplitude1e300",
|
||||
"negative amplitude",
|
||||
"empty channels",
|
||||
"nonincreasing timestamp",
|
||||
"channel width change"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
"""Synthetic feature and experiment contracts, not real sensing accuracy."""
|
||||
import copy
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
import numpy as np
|
||||
from safetensors.numpy import save_file
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||
from csi_training.espfi_context import BASELINE, CONTRACT, GRID, WIDTHS, ContextBundle, context_features, search
|
||||
from csi_training.espfi_kernel import fit
|
||||
from csi_training.espfi_motion import ROBUST_CONTRACT, motion_features
|
||||
from csi_training.espfi_train import digest, write_json
|
||||
from test_espfi_generalize import fixture
|
||||
|
||||
|
||||
def make_baseline(parts, provenance, root):
|
||||
root.mkdir()
|
||||
state = fit(motion_features(parts['train']['x'], 'motion_acf'), parts['train']['y'], BASELINE)
|
||||
save_file(state, str(root / 'model.safetensors'))
|
||||
write_json(root / 'status.json', {'status': 'complete', 'promotionAuthorized': False})
|
||||
write_json(root / 'metrics.json', {
|
||||
'status': 'complete', 'promotionAuthorized': False, 'task': 'public_motion_ablation',
|
||||
'contract': ROBUST_CONTRACT, 'selected': {'candidate': BASELINE},
|
||||
'model_sha256': digest(root / 'model.safetensors'), 'provenance': provenance,
|
||||
})
|
||||
|
||||
|
||||
class ContextTests(unittest.TestCase):
|
||||
def test_order_reversal_gain_and_exact_control(self):
|
||||
rng = np.random.default_rng(43)
|
||||
x = np.exp(rng.normal(size=(2, 52, 190)))
|
||||
actual = context_features(x, 'ordered_trend')
|
||||
reverse = context_features(x[..., ::-1], 'ordered_trend')
|
||||
np.testing.assert_array_equal(actual[:, :468], motion_features(x, 'motion_acf'))
|
||||
np.testing.assert_allclose(actual[:, :468], reverse[:, :468], atol=1e-12)
|
||||
np.testing.assert_allclose(actual[:, 468:624].reshape(2, 52, 3),
|
||||
reverse[:, 468:624].reshape(2, 52, 3)[..., ::-1], atol=1e-12)
|
||||
np.testing.assert_allclose(actual[:, 624:], -reverse[:, 624:], atol=1e-12)
|
||||
self.assertGreater(np.max(np.abs(actual[:, 468:] - reverse[:, 468:])), .01)
|
||||
gain = np.exp(rng.normal(size=(2, 52, 1)))
|
||||
np.testing.assert_allclose(actual, context_features(x * gain, 'ordered_trend'), atol=1e-12)
|
||||
np.testing.assert_array_equal(context_features(x, 'ordered_mean'), actual[:, :624])
|
||||
# Independent mathematical reference for the added descriptors.
|
||||
z = np.log(x)
|
||||
z -= z.mean(-1, keepdims=True)
|
||||
z /= np.sqrt(np.mean(z**2, axis=(1, 2), keepdims=True))
|
||||
expected = np.stack((z[..., :63].mean(-1), z[..., 63:127].mean(-1), z[..., 127:].mean(-1)), -1)
|
||||
np.testing.assert_allclose(actual[:, 468:624], expected.reshape(2, -1), atol=1e-12)
|
||||
t = np.linspace(-1., 1., 190)
|
||||
np.testing.assert_allclose(actual[:, 624:], np.sum(z * t, -1) / np.sum(t*t), atol=1e-12)
|
||||
|
||||
def test_numerical_edges_and_input_bounds(self):
|
||||
for family, width in WIDTHS.items():
|
||||
for level in (0., 1e-20, 1., 1e100):
|
||||
result = context_features(np.full((1, 52, 190), level), family)
|
||||
self.assertEqual(result.shape, (1, width))
|
||||
self.assertTrue(np.isfinite(result).all())
|
||||
x = np.ones((1, 52, 380))[:, :, ::2]
|
||||
x[..., 100] += 1e-12
|
||||
self.assertTrue(np.isfinite(context_features(x, family)).all())
|
||||
for x in (np.ones((0, 52, 190)), np.ones((1, 52, 189)),
|
||||
-np.ones((1, 52, 190)), np.full((1, 52, 190), np.nan)):
|
||||
with self.assertRaises(ValueError):
|
||||
context_features(x, 'ordered_mean')
|
||||
with self.assertRaises(ValueError):
|
||||
context_features(np.ones((1, 52, 190)), 'room_history')
|
||||
|
||||
def test_selection_isolation_control_reload_and_tamper(self):
|
||||
parts, provenance = fixture()
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
root = Path(tmp)
|
||||
make_baseline(parts, provenance, root / 'baseline')
|
||||
report = search(parts, provenance, root / 'baseline', root / 'a')
|
||||
changed = copy.deepcopy(parts)
|
||||
changed['validation']['x'] = changed['validation']['x'][..., ::-1] * 1.2
|
||||
changed['validation']['y'] = (changed['validation']['y'] + 1) % 7
|
||||
changed['validation']['identity'][:, 2] = changed['validation']['y'] + 1
|
||||
other = search(changed, provenance, root / 'baseline', root / 'b')
|
||||
self.assertEqual(len(GRID), 18)
|
||||
self.assertEqual(report['selected'], other['selected'])
|
||||
self.assertEqual(report['model_sha256'], other['model_sha256'])
|
||||
self.assertTrue(report['baseline_state_exact'])
|
||||
self.assertEqual(report['baseline_control']['candidate'], BASELINE)
|
||||
winner = max(report['candidates'], key=lambda r: (r['minimum_cv_macro_f1'],
|
||||
r['mean_cv_macro_f1'], -r['scalar_count']))
|
||||
self.assertEqual(report['selected'], winner)
|
||||
self.assertEqual(report['contract'], CONTRACT)
|
||||
self.assertEqual(report['evidence'], 'SYNTHETIC')
|
||||
self.assertFalse(report['promotionAuthorized'])
|
||||
self.assertEqual(report['reload_max_abs_logit_error'], 0)
|
||||
self.assertEqual(ContextBundle(root / 'a').predict(parts['validation']['x']).shape, (7, 7))
|
||||
# Changing the scored development labels cannot alter the persisted choice.
|
||||
selection = json.loads((root / 'a' / 'selection.json').read_text())
|
||||
self.assertEqual(selection['selected'], report['selected'])
|
||||
self.assertNotIn('development', selection)
|
||||
original = (root / 'a' / 'model.safetensors').read_bytes()
|
||||
(root / 'a' / 'model.safetensors').write_bytes(original + b'corruption')
|
||||
with self.assertRaisesRegex(ValueError, 'hash mismatch'):
|
||||
ContextBundle(root / 'a')
|
||||
(root / 'a' / 'model.safetensors').write_bytes(original)
|
||||
write_json(root / 'a' / 'status.json', {'status': 'failed', 'promotionAuthorized': False})
|
||||
with self.assertRaises(ValueError):
|
||||
ContextBundle(root / 'a')
|
||||
|
||||
def test_partition_provenance_and_failed_control(self):
|
||||
parts, provenance = fixture()
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
root = Path(tmp)
|
||||
make_baseline(parts, provenance, root / 'baseline')
|
||||
invalid = dict(parts, test=parts['validation'])
|
||||
with self.assertRaises(ValueError):
|
||||
search(invalid, provenance, root / 'baseline', root / 'bad_split')
|
||||
with self.assertRaisesRegex(ValueError, 'provenance mismatch'):
|
||||
search(parts, dict(provenance, manifest_sha256='different'), root / 'baseline', root / 'bad_hash')
|
||||
# Wrong learned control cannot silently support a comparison.
|
||||
changed = copy.deepcopy(parts)
|
||||
changed['train']['x'][:, :, 10] *= 2.
|
||||
with self.assertRaisesRegex(ValueError, 'did not reproduce'):
|
||||
search(changed, provenance, root / 'baseline', root / 'bad_control')
|
||||
self.assertEqual(json.loads((root / 'bad_control' / 'status.json').read_text())['status'], 'failed')
|
||||
with self.assertRaises(FileExistsError):
|
||||
search(parts, provenance, root / 'baseline', root / 'bad_control')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user