When a community corpus accepts a feature, it implicitly vouches that the feature means the same thing across sessions, devices, and protocols. We audited every feature in the SDK — the 187-dimension framework and the 85-dimension physical-primitive vector — against three gates: invariance census, protocol confounds, and compute-robustness on real windows. 98 of 272 features (36%) pass all three. This essay is that whitelist and the reasoning behind it.

The Three Gates

  1. Invariance census — what the feature cancels (gain, scale) versus what it carries (device constants, calibration need). From the framework's labels: 93 DEV_BOUND, 46 INV_GAIN, 6 INV_SCALE, 36 NONE, 6 CALIBRATED, inside a 187-dim vector.
  2. Protocol confound — does the feature's meaning depend on flow rate, dead volume, purge protocol, or baseline→exposure→recovery discipline? Everything kinetic does.
  3. Compute robustness — is the feature actually defined on real windows (nan_rate = 0)? We checked on 24 live SmellNet windows.

The Counts

VectorTransferableDevice-boundNeeds calibrationProtocol-confoundedTotal
Framework (187-dim)34394272187
Primitives (85-dim)67601285
Total101454284272

Intersect with "fully-defined on real windows" and the deployable whitelist is 98 features:

Survivor categoryFrameworkPrimitivesWhy it survives
Dose & amplitude (_da_ ratio family)2212Ratios cancel gain; only sensitivity constant survives
Direction & selectivity (incl. log-ratio, covariance)2155Fingerprint-like; robust to absolute offsets
Saturation / nonlinearity60Shape feature, gauge-cancelling
Survivors total4967—

What Must Never Be Shared Raw

  • The 84 protocol-confounded features — every rise/decay/recovery/kinetic feature (54 framework + 12 primitives) plus dynamics features. Their value depends on the exact flow profile of the rig that recorded them. Sharing them raw is sharing a flow-rate, not a smell.
  • The 45 device-bound features — health/hardware probes that mean "this specific unit," not the environment.
  • The 42 calibration-bound features — absolute resistance, voltage, circuit/thermal/ADC probes, calibrated ppm. Meaningful only with per-device calibration metadata alongside.

What This Means for the Data Commons

The commons schema should do two things automatically:

  1. Allow the 98 — the _da_ amplitude/dose family, selectivity ratios, saturation index, and the phys_ normalized/direction/covariance primitives. These are the honest, comparable subspace of the SDK.
  2. Reject or require-tags on the rest — kinetic features may be uploaded, but only with protocol metadata (flow, dead volume, purge) attached, and never into the interoperable comparison pool.

This is the difference between a data dump and a data commons: not more columns, but honest columns. A corpus that accepts all 272 features raw would be training models on device identity and flow rates while believing it is learning chemistry. The 98-feature core fixes that at the schema boundary, where it is cheapest.

Sources & Further Reading

  • benchmarks/bench_features_audit.py + reports/bench_features_audit.json — the full audit, category rollups, and compute-robustness census.
  • features/existing_mapping.py — the invariance labels (INV_GAIN / INV_SCALE / DEV_BOUND / NONE / CALIBRATED).
  • features/physical_features.py — the 85-dim phys_ primitive vector.
  • The 187-dimensions essay — what every framework dimension is and where it lives.
  • The anomaly-first essay — why deploying this core now is the strategy, with interoperability as the compounding step.