docs(adr): ADR 0200 — reconcile channel-weight measurement across the two active lines - #574
docs(adr): ADR 0200 — reconcile channel-weight measurement across the two active lines#574seonghobae wants to merge 6 commits into
Conversation
…ross lines The two active lines carry ADR 0145 with opposite decisions (estimation active + constants deleted vs. rejection + constants retained), on divergent lineage_channel_weight schemas. ADR 0200 keeps the operator directive (no hand-picked weight anywhere), upgrades the estimator to expected-information weighting over the fitted latent distribution (answering the theta-conditionality critique), adds an anchor-honesty label plus a TEPP criterion-validity gate (answering the criterion-validity critique, amending ADR 0003 explicitly), merges the two schemas as a union, and moves llm pair scoring to the durable Valkey queue idiom per the operator's no-bulk-synchronous-LLM directive and issue #289. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015HgzNGsCxqiTaT4YuJEb5J
|
Warning Review limit reachedNext included review available in 29 minutes. View limit detailsLimit details: You’ve used the included review currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Review configuration: ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (9)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Implementation note from cross-session coordination: a parallel session is adding a Valkey-backed batch-job registry to contextual-orchestrator ( |
…t 2, no activation) Replaces main's unconditional-refusal estimation stub with the full estimator ADR 0200 specifies: MLS2PLM over dichotomized channel scores with multilevel cluster intercepts, weights = normalized EXPECTED item information over the fitted latent distribution, computed on the fitted person parameters with the package's own predict_proba -- answering the theta-conditionality critique in main's ADR 0145 rejection instead of ignoring it. Non-converged fits are rejected outright (convergence_status must be 'converged'). Method code: mls2plm_expected_information. NOTHING ACTIVATES: the product loader's authorized anchor set remains empty, so persisted-weight activation still fails closed exactly as main's ADR 0145 requires -- this stage lands the estimator and its parameter-recovery evidence only. The demo generative design's follow probabilities are re-declared (0.80/0.72/0.66) so the fixture estimate preserves the designed A-100 demo fork under expected-information weighting; the estimate over that design converges and recovers the declared ordering. Supersedes main's test_unanchored_channel_scores_never_run_a_fit by design: running the fit is now permitted, activation is not. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015HgzNGsCxqiTaT4YuJEb5J
…nt 4, no activation) Migration 0200 unions the two lines' lineage_channel_weight schemas: primary key (channel_set_code, channel_code) -- one persisted set per active-channel combination -- carrying main's full per-run provenance contract and integrity constraints. Validated live against BOTH predecessor shapes (main's 0135 provenance table and the customer-master line's 0135+0136 set table), replay-idempotent per the ADR 0166 window, with a rollback that restores main's single-set shape. Pre-provenance rows are deleted, not backfilled with invented provenance -- the loader refuses them either way and re-estimation is the operator's next action. The loader becomes set-aware (exact active-channel match of exactly one set) while keeping every provenance-integrity gate; a pre-0200 schema is probed via the catalog (never a failing statement that would abort the caller's transaction) and read as the single implicit deterministic set. ACTIVATION IS UNCHANGED: the authorized anchor set stays empty, so every loaded vector is still refused. The operator script becomes the full estimator front-end: fetch on one short-lived connection, no connection held while fitting, persist with full provenance (fresh run uuid, installed estimator version, honest anchor_method_code=unanchored_internal_structure, reproducible source-snapshot digest over the ordered sampled rows, knowledge cutoff = max sampled created_at) on a fresh connection. Its report names activation as blocked until an anchor is authorized. The llm channel is deliberately absent -- bulk synchronous provider calls are banned; llm scoring arrives with the queued worker (ADR 0200 point 5). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015HgzNGsCxqiTaT4YuJEb5J
feat(estimation): ADR 0200 points 2+4 — expected-information estimator, schema union, provenance persistence (no activation)
| # produced), using the package's own item response function | ||
| # (predict_proba) rather than a re-derived one (van der Linden, | ||
| # 2005, on expected/target information as the design quantity). | ||
| probabilities = numpy.asarray(predict_proba(result.params, factor_id), dtype=float) |
There was a problem hiding this comment.
🔍 predict_proba receives the item-to-factor map, not person parameters
predict_proba(result.params, factor_id) (lineageweave/channel_weight_estimation.py:207) passes factor_id -- the all-zeros item-to-latent-dimension map of length len(channels) built for fit(). The docstring says expected information is averaged over the fitted person parameters, which would need the fitted theta, not factor_id. If the second argument is treated as person parameters, probabilities are evaluated at three placeholder points rather than the fitted latent distribution, diverging from the stated design. Runs only when fast_mlsirm is importable; confirm against its actual signature.
Was this helpful? React with 👍 or 👎 to provide feedback.
| if probabilities.shape[1] != len(channels): | ||
| return None |
There was a problem hiding this comment.
🔍 1D probabilities would raise instead of failing closed
The guard reads probabilities.shape[1]. A 1D return from predict_proba makes this raise IndexError and escape, unlike every other path here which returns None. Reachable only with fast_mlsirm installed and depends on its return shape.
Was this helpful? React with 👍 or 👎 to provide feedback.
| all_rows = await conn.fetch( | ||
| f"select {set_column_sql} as channel_set_code, " | ||
| "channel_code, weight_value, estimation_run_id, " | ||
| "estimation_method_code, estimator_version, anchor_method_code, " | ||
| "source_snapshot_sha256, sample_pair_count, knowledge_cutoff " | ||
| "from lineage_channel_weight" | ||
| ) | ||
| sets: dict[str, list] = {} | ||
| for row in all_rows: | ||
| sets.setdefault(row["channel_set_code"], []).append(row) | ||
| rows = next( | ||
| ( | ||
| candidate | ||
| for candidate in sets.values() | ||
| if {row["channel_code"] for row in candidate} == active_channels | ||
| ), | ||
| [], | ||
| ) | ||
| persisted = {row["channel_code"]: float(row["weight_value"]) for row in rows} | ||
| if not persisted or set(persisted) != active_channels: | ||
| return None |
There was a problem hiding this comment.
📝 Info: Set-matching and migration idempotency check out
load_estimated_channel_weights groups rows by channel_set_code and activates only a set whose channel codes exactly equal active_channels, else None, so no partial vector mixes runs. The catalog probe avoids a failing statement on pre-0200 schemas, and its literal default matches migration 0200's. Across both predecessors the delete guard empties rows before NOT NULL provenance columns are added, and the PK swap and constraint re-assertion are idempotent.
Was this helpful? React with 👍 or 👎 to provide feedback.
| estimate_fixture_channel_weights, | ||
| simulate_fixture_pair_scores, | ||
| ) | ||
| from lineageweave.models import Record |
…verges fast-mlsirm's FitConfig default (max_iter=1000) is tuned against its GPU/f32 kernel; the f64 CPU fallback -- the only path available on any CI runner, since none expose a GPU -- needs materially more EM iterations to reach the same optimum at full precision (observed up to ~1850 on this module's own recovery fixture). Confirmed locally by forcing rust_device="cpu": both channel-weight recovery tests reliably hit max_iter_reached at the old default and reliably converge at 3000.
| all_rows = await conn.fetch( | ||
| f"select {set_column_sql} as channel_set_code, " | ||
| "channel_code, weight_value, estimation_run_id, " | ||
| "estimation_method_code, estimator_version, anchor_method_code, " | ||
| "source_snapshot_sha256, sample_pair_count, knowledge_cutoff " | ||
| "from lineage_channel_weight" | ||
| ) |
There was a problem hiding this comment.
📝 Info: Loader probes channel_set_code but not provenance columns
load_estimated_channel_weights probes table and channel_set_code existence to avoid a transaction-aborting statement, but still selects estimation_run_id and the other provenance columns unconditionally (backend/app/lineage_ingestion.py:118-124). On main these always exist since 0135, so it is safe there. Against a pre-0200 customer-master schema (no provenance columns) the query would fail and abort the caller's transaction.
Was this helpful? React with 👍 or 👎 to provide feedback.
Merge conflict: adjudication_client.py's judge() combined this branch's extracted parse_confidence() helper with main's chat_completion_content() response-body accessor -- use both together instead of the old direct body["choices"][0]["message"]["content"] indexing. Also carries the max_iter=3000 fix from #574 for the same shared channel_weight_estimation.py module: fast-mlsirm's default max_iter=1000 is tuned against its GPU/f32 path, and the f64 CPU fallback every CI runner uses needs materially more EM iterations to converge (observed up to ~1850 locally under a forced rust_device="cpu" repro).
Summary
mainanddocs/customer-master-scope-adrcarry ADR 0145 with opposite decisions: the scope line estimates fusion weights with fast-mlsirm and deleted the hand-picked constants (fail-closed product paths), while main records the proposal as Rejected, authorizes zero anchor methods, stubs the estimation command — and retains the hand-picked constants "for compatibility". Same ADR number, contradictory decisions, divergentlineage_channel_weightschemas: an exact-head contradiction.ADR 0200 proposes the reconciliation, engaging main's methodological critique on the merits instead of overriding it:
w_j ∝ E_θ[a_j² P_j(θ)Q_j(θ)]over the fitted multilevel latent distribution replace discrimination-proportional weights — integrating Birnbaum information's θ-conditionality rather than ignoring it (van der Linden, 2005); non-converged fits rejected via fast-mlsirm's official diagnostics.anchor_method_code='unanchored_internal_structure'and full provenance until a TEPP-anchored criterion-validity gate exists; a failed gate retires the set. Amends ADR 0003's scope boundary explicitly (ADR-first, as main's 0145 required).(channel_set_code, channel_code)PK + main's per-run provenance columns, one migration with rollbacks for both predecessors.post_content_queue/worker idiom — resumable, rate-governed, no bulk synchronous provider calls (per the operator's 2026-08-24 directive after the 400-pair run saturated the shared gateway), satisfying issue Activate the optional lineage LLM channel through a bounded asynchronous rebuild #289's bounded-durable-worker requirement.Docs only — no code changes in this PR.
🤖 Generated with Claude Code
https://claude.ai/code/session_015HgzNGsCxqiTaT4YuJEb5J