Skip to content

CVN-N001-EI-S07 — Gate 2 (drift-rate) — measurement design

Status: design (measurement protocol) — 2026-05-27. Phase-0 (2a+2b cheap-first) executed 2026-05-27 → no bug-dominated early-exit, §15 provisionally unblocked (§1ter). Extends the Gate-2 table in the Lever #1 dossier §265 (§9bis). A measurement experiment, not a runtime component: it yields a number + a policy recommendation, then is discarded. Gates: the runtime policy --check-drift ∈ {opt-in, auto-above-pin-age, default-on}, the max pin age, and the §9 retention TTL. Blocking: the §0bis/2a bug check hard-blocks implementation if drift is bug-dominated; otherwise the policy constant (config, ADR-59) is gated, not the code — see §1bis (provisional pass). Touches audit relevance / currency (not correctness-vs-pin — §0) → a material operator-behaviour change still reopens D11 committee re-confirmation (§5). Review: - r1 — Approve with minor required changes (2026-05-27) — folded: min/recommended panel (§2c), p_hash_drift/p_consequential_drift split (§1/§4), explicit ε_f1 (§1), panel-validity rule (§2c), trigger-fragility mitigation (§6), CI-straddle (§5), 2a early-exit (§2a), machine-readable result (§8). - r2 — 3 fundamental holes + 3 hardenings (2026-05-27) — folded: (1) the two rates aren't same-sample → same-panel conditional P(consequential | hash-drift) via bounded replay on §2c drift events (§4) + plain p_consequential_drift demoted to biased lower-bound (§1); (2) §2ter reconciliation — drift = relevance/currency loss, NOT consistency loss → default leans auto-above-pin-age (§0/§5); (3) §0bis AAVE hash-vs-f1 confirmation before enshrining the anchor (§2c); (4) "primarily hash" cut → hash=integrity / consequential=aggressiveness (§1); (5) §2b "reconstruct backward" corrected — backward=consequential only, forward=hash (§2b); (6) §1bis provisional pass — calendar-W gates the config constant, not the implementation.


0. Why this gate exists (one paragraph)

Lever #1 Model B pins the first successful capture and reuses it by cell_ref. Per §2bis, a closed historical window can still drift when re-derived (the WF engine re-slices the live feature store; the ETL re-pulls Binance on a Redis TTL — no pinned upstream snapshot).

What drift actually threatens (reconciled with §2ter). §2ter certifies inter-re-audit consistency via the frozen pin — and a frozen pin keeps re-audits mutually consistent by construction, regardless of pin age (they all read the same blob). So drift does NOT break §2ter consistency, and the re-audit stays correct-with-respect-to-the-pin. What drift erodes is the pin's relevance / currency — how far the pinned D_capture1 has diverged from the live data the operator implicitly wants to learn about — exactly the fidelity §2ter declares out of scope. An audit on a long-drifted pin answers a question about stale data: still true, just less useful.

This reframes the policy bar: --check-drift does not prevent an incorrect audit (it isn't incorrect) — it alerts on relevance decay. That's a lower bar than correctness, and it leans the default toward auto-above-pin-age + notify, not blanket default-on (§5). Gate 2 measures how fast that relevance decays (i.e. how often the pin diverges from live) so the --check-drift default + max pin age + TTL are set on evidence, not assumption. (The D11 governance still applies: a material change to operator behaviour goes back to committee whatever we name the risk — §5.)

We have a seed observation — but read it precisely (validation runs 2026-05-27): LDOUSDC/3 reproduced bit-exactly (Δf1 = 0.0000) while AAVEUSDC/3 diverged on output (observed f1 0.3591 vs 0.3520, |Δ| = 0.0070 > ε_f1). This is a consequential-output delta, NOT a confirmed INPUT_DATA_SHA_V1 change. A Δf1 of 0.0070 can come from input drift or from training-pass non-determinism (seed / threading / LightGBM) — which §3c classifies as a bug (non-deterministic derivation), not as input drift. So we know AAVEUSDC's f1 moved; we do not yet know its hash moved. Gate 2 turns this into a rate only after §0bis confirms the anchor's class.

0bis. Pre-panel calibration — confirm the anchor is actually hash-drift (point (b))

Before freezing the panel, confirm INPUT_DATA_SHA_V1 truly changes under a forced regeneration for AAVEUSDC/3 (and stays stable for LDOUSDC/3) — a tiny load + hash ×2, no replay probe.

⚠ Must force regeneration (the cache confounder). The feature-store cache is keyed on (crypto, tf, history_months)not the date range — with a 7-day TTL (CACHE_TTL_DAYS). A plain ×2 read hits the same cached blob → identical hash → a false no-drift result (it measures cache-hit determinism, not re-derivation drift). So each read must set FORCE_FEATURE_STORE=1 to regenerate the slice.

  1. h1 = INPUT_DATA_SHA_V1(get_feature_store(AAVEUSDC, [train_start…test_end])) with FORCE_FEATURE_STORE=1; repeat → h2 also forced (so each call regenerates, not re-reads the cache).
  2. If h1 ≠ h2 → a forced regen on the (legacy, sliding) path does move the hash → AAVEUSDC is a genuine hash-drift anchor (knob class), and P(regen changes blob) ≈ 1 is confirmed → enshrine it in §2c. If h1 == h2 despite the f1 divergence → either the path is cutoff-bounded (frozen window) or the f1 delta is replay non-determinism (§3c bug class), not input drift → AAVEUSDC is mis-labelled; investigate, and recruit a confirmed hash-drift cell instead.
  3. Same forced ×2 check on LDOUSDC/3.

This is cheap (two forced regenerations + two hashes per anchor, no replay), operator-triggered, and it must pass before §2c is frozen. Together with the two config-reads (active path + cache TTL), it likely settles P(stale at age A) directly (a step at the 7-day cache TTL) — making the calendar panel §2c unnecessary unless this result is ambiguous (see the Phase-0 report).

1. The exact quantity to measure

For a closed window [train_start … test_end] (all timestamps in the past):

  • p_drift(cell, Δt) — probability that the canonical INPUT_DATA_SHA_V1 of the re-derived live slice differs between two observations separated by wall-time Δt.
  • Onset — distribution of days-stable-before-first-change per cell (drives max pin age / TTL).
  • Drift location & magnitude — on each change, which columns/rows moved and by how much (drives the root-cause classification, §3c).

INPUT_DATA_SHA_V1 is the §4a contract (sha256(canonical_table(input_slice)), dossier line 112). Gate 2 is its first real exercise — it MUST be frozen for the whole measurement window (any change to the contract mid-measurement invalidates all comparisons).

Two rates, not one — and ε is explicit. The Step-0 replay catches drift only when it moves f1 past the tolerance ε_f1 = 0.005 (the Step-0 replay epsilon, observed live as event=s18_step0_verdict … epsilon=0.0050). Gate 2 reads ε_f1 from the Step-0 config at run time — it never hard-codes a value that could diverge from the gate the harness actually applies. The canonical hash, by contrast, flips on any byte change. So Gate 2 reports two rates:

Metric Meaning
p_hash_drift the canonical input slice (INPUT_DATA_SHA_V1) changed
p_consequential_drift the change moved the Step-0 f1 proxy / downstream verdict beyond ε_f1

p_hash_drift ≥ p_consequential_drift always. They play two distinct roles (don't say "primarily hash"): - p_hash_drift = integrity signalis the pin still the live data-truth? It flips on any canonical-byte change, so by the §2a edge-recompute hypothesis it may fire routinely for edge-window cells. Keying the policy aggressiveness on it alone would over-trigger default-on for edge cells with no verdict benefit (f1 stable). - p_consequential_drift = policy aggressivenessdoes the drift change the answer? This is what justifies a more aggressive --check-drift / shorter TTL.

So: hash-drift decides whether the pin is stale data; consequential-drift decides how hard the policy should fight it. A high-hash / ≈zero-consequential cell → auto-above-pin-age + notify (relevance decay, §0), not default-on; high consequential-drift → default-on / short TTL / committee reopen.

Commensurability caveat (the two rates are NOT from the same sample). p_hash_drift is measured cleanly forward (§2c, load+hash, no replay). p_consequential_drift needs a Step-0 replay (the ~10 CPU-h the project avoids) → the §2c panel cannot produce it, and the §2b retrospective figure is a biased opportunity sample (only audits that happened). Treating them as a clean pair ("high hash / low consequential") therefore requires the same-panel conditional P(consequential | hash-drift) — measured by a bounded replay fired only on §2c hash-drift events (§4), not the two marginal rates compared across different populations. Until that conditional exists, p_consequential_drift is reported as a biased retrospective lower-bound, not a co-equal rate.

1bis. What this gate blocks — and what it does NOT (the provisional pass)

The header says blocking before Lever #1 implementation; §2c says the forward panel needs W = 14–30 irreducible calendar days. Taken literally that freezes implementation for ~a month — which is wrong, because what Gate 2 sets is a runtime policy constant, not the entity code:

  • The --check-drift default, max_pin_age, and TTL all live in ftf_config (PG, Console — ADR-59). They are config, tunable post-implementation without code rework.
  • Lever #1 §15 (the DIAGNOSTIC_CAPTURE entity, get_diagnostic_capture, PG advisory-lock, canonical hashes, S3 lifecycle) does not depend on the value of that constant — only on the mechanism existing.

So the sequencing is a provisional pass → confirm/revise: 1. 2a (config-read) + §0bis (anchor calibration) + 2b (history) — all cheap, now. They either early-exit (bug-dominated → stop, fix upstream) or yield a provisional, conservative default (lean auto-above-pin-age with a deliberately short A, §5) that unblocks §15 implementation immediately. 2. 2c (forward panel) then confirms or revises the constant before rollout (retiring skip_phase_a / enabling the warm path in anger) — not before writing the code.

Net: the calendar-W measurement gates the rollout-time policy constant, not the implementation. The only hard pre-implementation blocker is the §0bis/2a bug check — if drift is bug-dominated, neither the code nor the policy should proceed until the upstream bug is fixed.

Constraint making the unblock real (multi-mode impl). The provisional pass holds only if the implementation is built policy-agnostic — all three --check-drift modes (opt-in / auto-above-pin-age / default-on) switchable by config (ADR-59), with no code change. If the code hard-assumes the leaned mode and the gate later lands elsewhere, we re-pay a code change and the "unblock" was illusory. So the §15 implementation MUST expose the mode + max_pin_age + TTL as ftf_config values, defaulting to the conservative auto-above-pin-age lean, not bake one mode in.

1ter. Phase-0 results — 2a + 2b cheap-first (2026-05-27)

Run read-only before any build. No bug-dominated early-exit; §15 implementation provisionally unblocked.

2b — retrospective s18_step0_verdict mining (7 d, 11 lines, 5 defi_top5 fold-3 cells):

Cell abs_delta (|observed − expected|) status (ε=0.005) obs delta reproducible?
LDOUSDC/3 0.0000 PASS ×3 yes (bit-exact)
UNIUSDC/3 0.0047 PASS ×2 yes (sub-ε but non-zero)
OPUSDC/3 0.0063 FAIL ×2 yes
AAVEUSDC/3 0.0070 FAIL ×3 yes
ARBUSDC/3 0.0156 FAIL ×1 n/a
  • 4/5 cells drift (Δ≠0), 3/5 cross ε — drift is common, not rare.
  • ⚠ Critical reframing — 2b measures the WRONG drift for the pin policy. observed = re-derived now; expected = the original FTF baseline. So 2b captures the FTF→now gap (a one-time offset; the FTF data is unrecoverable, feature_hash="unknown" — the §2ter limitation), not the now→now drift that actually stales a pin. The pin freezes "now" and only goes stale if now→now drifts. So "3/5 FAIL" does NOT prove the pin stales — it proves nowFTF. 2b is therefore an even more biased proxy than first stated; only §0bis/2c measure the pin-relevant quantity.
  • Per-cell deltas are reproducible (AAVE = 0.0070 on both 05-25 and 05-27; OP = 0.0063 ×2). → current re-derivation is deterministic/stable over ~2 daysnot training-pass non-determinism (which would vary) → the AAVE anchor leans knob class, and now→now looks stable (pin would stay valid → opt-in/long-TTL). But n=2 days is thin — §0bis (hash ×2) + 2c (panel over W) must confirm.

2a — config-read: - ETL re-pulls fresh from Binance (src/ETL/cvntrade_etl_pipeline.py:412/420): cutoff path = bounded window; legacy path = "{history_months} months ago UTC"window slides daily. - Binance closed candles are immutableno raw-OHLCV rewrite → the "ETL bug" early-exit is NOT triggered. - A feature_store cache keyed on (crypto, timeframe, history_months) (src/commun/cache/cvntrade_cache_interface.py:191) — not on the date range — means re-derivation hits the cache (stable) unless FORCE_FEATURE_STORE / cache-miss forces regeneration (drift possible). This cache key explains 2b's reproducibility (same key → same blob). - Predicted mechanism (knob class, not bug): on regeneration, the legacy "months ago" window shifts the left-edge warm-up → edge rolling-feature recompute near train_start. Bounded, expected.

Provisional verdict: no hard blocker → §15 impl unblocked on a conservative auto-above-pin-age lean (§5). The open question — is now→now stable (→ opt-in/long-TTL) or does it drift over W (→ auto-age/default-on)? — is left to §0bis + 2c. Early indicators (cached key, reproducible deltas, 2 stable days) lean opt-in/long-TTL, unconfirmed.

2. Three readouts, cheap-first

Sequenced so a cheap result can pre-empt the expensive one (mirrors Gate 1 = pure read).

2a. Config-read prior — free, now (no workload)

Read the mechanism that causes drift and bound its cadence before measuring: - Redis/ETL TTL + re-pull cadence: how often the OHLCV cache expires and Binance is re-pulled (src/ETL/cvntrade_etl_pipeline.py:412, Redis TTL config). - Does the ETL rewrite closed candles, or only append new ones? If closed candles are immutable, raw-OHLCV drift for a closed window should be ≈ 0, and any INPUT_DATA_SHA change must come from feature recomputation (rolling lookback at the window edge) or non-determinism — which narrows the hypothesis before a single hash is computed. - WF re-slice determinism: does get_feature_store([train_start…test_end]) return identical rows on two calls with no upstream change? (cvntrade_wf_backtest_engine.py:220).

Output: a predicted drift mechanism + an a-priori plausibility for how fast it recurs. Cheap; may already settle the policy direction.

Early exit: if 2a identifies a raw-OHLCV-rewrite path for closed candles, Gate 2 is immediately bug-dominated (§5) — stop before the panel, file the ETL bug, and do not proceed to policy tuning. Conversely, if closed candles are provably immutable and feature derivation is deterministic, the panel can be narrowed to edge-window cells (where the only remaining drift source lives).

2b. Retrospective mining — cheap, now (read history)

Mine existing signals for drift events at zero marginal compute: - Backward (consequential only): Step-0 replay JSON artifacts + event=s18_step0_verdict lines in Loki carry observed/expected f1 per re-audit per cell — each |Δ| > ε_f1 is a consequential drift event already on record (AAVEUSDC = one such). This is the only thing mineable backward — and it's a biased opportunity sample (§1 caveat). ⚠ It measures FTF→now, not now→now (observed=now, expected=original FTF): a one-time gap, not the pin-staleness quantity (§1ter). Useful as a floor on output sensitivity, not as the pin-drift rate. - Forward (hash): add a one-line emit event=capture_input_sha cell_ref=… input_data_sha=… observed_at=… to the existing cold-capture path so every future cold audit records the hash for free (piggyback — like Gate 3's size piggyback). This accrues a hash series going forward; it cannot reconstruct hashes for past audits that never computed one (you can't mine a number that was never recorded).

Output: a backward consequential lower-bound (biased f1-proxy) plus a forward-accruing hash series — two different things, not a single reconstructed panel.

2c. Forward panel — the primary clean measurement

For the K cells (spec below) across defi_top5 (the control group), recompute INPUT_DATA_SHA_V1 over the same closed window once/day for a window W (spec below). Light: load + hash only, no replay. ⚠ Same cache confounder as §0bis: a daily read on the (crypto,tf,history_months)-keyed cache hits the same blob within the 7-day TTL → an identical, meaningless series. So either force regeneration (FORCE_FEATURE_STORE=1) each day, or rely on natural eviction (only observations across a cache-TTL boundary are informative) — and W must then span several TTL cycles. Forced regen is cleaner. - Stratify by window age: recent-edge (test_end near today) vs deep-historical (test_end months old) — the edge-lookback hypothesis (§2a) predicts edge windows drift more. - Anchors: include LDOUSDC/3 (known hash-stable) and AAVEUSDC/3 (known f1-divergent; hash-drift status confirmed-or-refuted by §0bis — enshrined as the drift anchor only if §0bis shows its hash actually moves; otherwise replaced by a confirmed hash-drift cell). - Calendar time cannot be compressed — drift is driven by real ETL/TTL cycles, so W must be real days. 2a/2b give an immediate estimate while 2c accrues.

Minimum viable panel (defends against "K small, CI wide" challenge): - K = 10 cells; W = 14 calendar days; - ≥ 2 recent-edge windows and ≥ 2 deep-historical windows; - includes the anchors AAVEUSDC/3 and LDOUSDC/3.

Recommended panel (if the implementation timeline allows): - K ≈ 20 cells; W = 21–30 calendar days.

Panel validity

The forward panel is valid only if: - ≥ 80 % of scheduled observations were collected; - ≥ 8 cells remain valid after skipped/failed observations; - both anchors (AAVEUSDC/3, LDOUSDC/3) have ≥ 10 observations each; - INPUT_DATA_HASH_VERSION is unchanged for the full window.

If these conditions are not met → extend W or mark Gate 2 inconclusive (never report a rate off an invalid panel — no false confidence).

3. Per-observation procedure

For each (cell, day): 1. slice = get_feature_store(crypto, [train_start … test_end]) (metered per §8 — bound the load). 2. h = INPUT_DATA_SHA_V1(slice); record (cell, observed_at, h, n_rows, n_cols). 3. If h ≠ h_prev for that cell → drift event: run the root-cause sub-probe (§3c) and record the fingerprint. 4. Emit event=gate2_drift_observation cell_ref=… input_data_sha=… changed=<bool> … (structured, Loki) — no print, no raise (ADR-31/25; a failed load on one cell is logged severity=warn and that cell is skipped, never aborts the panel — independent-probe isolation).

3c. Root-cause sub-probe (on every drift event) — guards against treating a bug as a knob

Diff the two canonical tables → localize the change and classify the source: | Class | Signature | Implication | |---|---|---| | Edge feature recompute | change confined to rows near test_end, derived (rolling) columns only | expected, bounded → a policy knob (--check-drift/TTL) is the right answer | | Raw-OHLCV rewrite | raw open/high/low/close/volume of closed candles changed | ETL bug (closed candles must be immutable) → upstream fix, not a policy knob | | Non-deterministic derivation | identical raw input → different feature columns across two reads | bug (feature pipeline non-determinism) → upstream fix; pin would be unstable regardless | | Interior/global shift | broad change across the whole window | investigate (re-indexing, tz, dtype) before any policy call |

If drift is dominated by a bug class → Gate 2 is inconclusive on policy until the upstream bug is fixed; we do not paper over a correctness bug with a --check-drift default (no-silent-fail).

4. Statistics & outputs

  • Per-cell and pooled (with bootstrap CI — K is small, report intervals, no false precision):
  • daily p_hash_drift and p_consequential_drift (§1); fraction of cells that ever drift within W; median days-to-first-drift.
  • Stratified by window age (edge vs deep-historical).
  • Drift-source breakdown (§3c table) — the share that is expected vs bug.
  • Same-panel conditional P(consequential | hash-drift) — the figure the §5 decision tree actually needs, and the only commensurable bridge between the two rates (§1 caveat). Measured by a bounded Step-0 replay fired ONLY on the hash-drift events detected in §2c (not every observation): cost = #hash-drift events × one replay, not K × W × replay. Because hash-drift events are the rare sub-events, this stays affordable while putting p_consequential_drift on the same controlled panel as p_hash_drift. The plain p_consequential_drift from §2b remains reported separately as a biased retrospective lower-bound — never silently merged with the conditional.
  • Headline deliverable: P(pin stale at age A) (hash) and P(consequential | stale) as functions of pin age A → drive the max-pin-age / TTL choice and the --check-drift default.

5. Decision tree (drift-rate → policy)

Proposed knees (the gate's job is to produce the number that lands one branch; knees are judgment, confirmable):

Regime Observed Policy TTL / max pin age D11
Rare < ~5 % of cells drift within the candidate TTL and median days-to-drift ≫ TTL --check-drift = opt-in long TTL ≈ Gate-1 re-audit gap stays waived (no material change vs ca64c4d8)
Moderate drift occurs but onset > a usable pin age A --check-drift = auto-above-pin-age A (re-derive + compare only when pin older than A) TTL ≈ A borderline — operator call whether it's "material"
Frequent a material fraction drift within the TTL / short onset --check-drift = default-on short TTL REOPEN — material change to the operator behaviour validated under ca64c4d8 → back to committee (D11 reopen condition)
Bug-dominated §3c finds raw-rewrite / non-determinism dominates none yet n/a file upstream bug; Gate 2 inconclusive on policy until fixed

Default lean = auto-above-pin-age, not default-on. Because drift is a relevance risk, not a correctness one (§0 — the audit stays correct-with-respect-to-the-pin), the bar for the most aggressive policy is high. default-on is reserved for the Frequent regime evidenced by a high same-panel P(consequential | hash-drift) (§4) — not by a high marginal p_hash_drift alone (edge-recompute can inflate that with no verdict impact, §1). When in doubt between Moderate and Frequent, prefer auto-above-pin-age + notify: it surfaces relevance decay without paying the every-re-audit --check-drift cost.

Per D11 (dossier line 354): the operator's waiver covers only the absence of a re-confirmation now — it does not pre-approve the outcome. A default-on / short-TTL result reopens the committee delta.

Decision under uncertainty: a regime is selected only if the bootstrap CI does not materially straddle an adjacent regime. If it does, do not over-classify — either choose the safer adjacent policy (more checking / shorter TTL — failing toward audit correctness, not toward a silent-stale pin) or mark the result inconclusive and extend W. The two-rate split (§1) is part of this: if p_hash_drift's CI is wide but p_consequential_drift is tightly ≈0, the safer call is auto-above-pin-age, not default-on.

6. Execution discipline

  • No autonomous launch, no python scripts/… — the forward panel runs as an operator-triggered light probe (a thin Airflow probe DAG, schedule=None per ADR-18, manually triggered once/day over W; max_active_runs=1), or purely via the piggyback emit (§2b) on cold audits. 2a/2b need no workload at all.
  • Reuses INPUT_DATA_SHA_V1 (§4a) + get_feature_store (metered, §8). No new infra.
  • Frozen contract: INPUT_DATA_HASH_VERSION must not change during W.
  • Trigger-fragility mitigation: a 14-day daily manual trigger will drop observations. Keep schedule=None (ADR-18), but back it with either (a) an operator daily checklist + reminder, or (b) the probe shipped as an Airflow automation disabled-by-default, explicitly enabled by the operator for window W and disabled at its end — an operator-gated, time-boxed schedule, not a standing one (no autonomous recurrence). The panel-validity ≥ 80 % rule (§2c) backstops residual gaps.

7. Pitfalls

  • Calendar time is irreducible for the forward panel — front-load 2a (config) + 2b (history) for an immediate estimate; let 2c accrue.
  • Selection bias — edge windows drift more; always report per stratum, never a single pooled rate that hides the edge effect.
  • Small K → wide CI — report intervals; resist over-precise knees.
  • Hash sensitivityINPUT_DATA_SHA_V1 flips on any canonical-byte change; a benign dtype/ordering wobble must be excluded by the canonicalization itself (§4a) — if the hash is flipping on canonicalization noise rather than data, that's a §4a contract bug, surfaced by §3c "interior/global shift".

8. Definition of done

A Gate-2 results dossier carrying: the per-stratum + pooled drift-rate (with CI), the drift-source breakdown, and one policy recommendation — --check-drift default + max pin age + TTL — that lands a single row of §5. If the result is default-on/short-TTL or bug-dominated, the explicit D11 reopen / upstream-bug escalation is filed. The number feeds §4a (--check-drift default), §9 (TTL, max pin age), and unblocks (or re-gates) Lever #1 §15 implementation.

The dossier embeds a machine-readable result for injection into the design / OP / committee:

gate: CVN-N001-EI-S07-G2
panel_valid: true              # per §2c validity rule; if false → inconclusive
k_cells: 10
w_days: 14
p_hash_drift: ...              # with CI
p_consequential_drift: ...     # with CI; epsilon_f1 = 0.005 (read from Step-0 config)
median_days_to_first_drift: ...
drift_source_dominant: edge_recompute | raw_ohlcv_rewrite | nondeterministic | interior_shift
recommended_policy: opt_in | auto_above_pin_age | default_on | inconclusive
max_pin_age_days: ...
ttl_days: ...
d11_reopen: false