Repository navigation
[PR] feat(obs): M1 Native Identity Observation Layer — Convert the #5571 collection table from static forensics to live measu | feat(obs): M1 原生身份观测层 — 把 #5571 采集表从静态取证变成实测数据(默认关) - #5573
Conversation
…oint, five client profiles, digest-only session ext, default off
ranxianglei
left a comment
There was a problem hiding this comment.
自审(46fabdd)——结论:可合并,CI 全 lane 绿 + owner 在 WSL 盒开开关即可开始采集。逐点复核记录:
- 单采集点核实:
observeNativeIdentity全仓仅 handle.ts 一处调用(rg 验证),位于 sessionId 绑定后、任何改写前;passthrough/bypass 早退分支在其之前 return,天然不采(正确:#1117/#920 的匿名直转流量不该被指纹化)。 - fail-open 不是注释而是结构:函数体整体 try/catch、内部零 await、store 不可用(replicasAvailable=false)与空 sessionId 双早退;profiles 抽取层自己也带独立 try/catch——双层兜底,坏行最差后果 = 无观测。
- 隐私断言双向钉住:测试同时断言原始值 不在 ext blob 里 且 不在日志行里(digest-only);unknown 客户端走
profile.id === "unknown"早退,duck-store 断言零写入。 - 前缀匹配器只锚文本开头(
startsWith,非 includes):中段引用压缩文案的对抗样本有独立用例,不命中。 - 非阻塞观察(留 M2 处理):① codex 头族(
x-codex-window-id等)目前是"存在即采集、拼写待实测",这正是本层要关掉的待抓包项,不做预判过滤是有意为之;②msgN/msgLastH只记最后值+计数,wb 消息级 id 每轮必变,M2 绑定表会把它变成真信号——M1 阶段这是正确的最小形态。 - 文档链完整:rollout.yaml 条目 → CONFIGURATION.md/zh-CN 重生成产物同 PR(gen-config-docs i18n 校验 246 keys 通过),不会复现 #5374 式 docs-sync 长红。
📦 Built Package ArtifactBranch: Latest build — install this exact versionnpm install -g billion-context@0.0.9046-pr.5573.ac9f267This exact version is immutable and reproducible. The rolling Recent builds (commit → version)
Download artifact
tar xzf billion-context-pr5573.tgz
npm install -g packageInstall instructions are refreshed on each push. |
(ai: vllm-qwen/qwen3.8-27b) 查重:不重复。 检索 open+closed issue/PR(关键词:native identity observation / nidv / #5571 采集表 / M2 绑定表 / re-baseline):无其他相关条目;本 PR 即 #5571 设计稿的实现,无并行在途工作。 标签: pr(已有)+ scope/wiring(relay 单点采集,属请求管线接线)+ p3(默认关的纯观测功能,存量用户零行为变化)+ dsh / claude-code / opencode / codex / gemini / workbuddy(模式表覆盖的五家客户端家族,dsh 为主流量车道)。理由:功能面精确对应这五家的 wire 身份信号。 审核进行中:全 diff 已通读完毕,正在跑定向测试与 CI 状态核对;有发现会直接在 PR 分支修掉,结论按八问体例回复。 |
…raw ids (#5571 M1) Review of the M1 PR: the dual-channel mismatch event carried up to 64 chars of the raw client session id (cc metadata.user_id.session_id) into the persisted ext blob, breaking the stated privacy redline that only sha1-8 digests ever reach storage or logs. Evidence is now hdr=<h8> body=<h8>, and the stability-matrix test pins that neither raw channel value appears in the serialized state.
…gnments (#5571 M1) CI static gate check-unused-exports flagged seven exports of the new modules: six module-local types (NidvProfile, NidvCompactionSignal, NidvEvent, NidvState, NidvStoreView, ObserveNativeIdentityArgs) lose their leading export per the gate's own remedy, and NidvHeaders was dead code entirely — deleted, its structural-typing rationale folded into detectNativeProfile's doc. Also removes two self-assigning no-op statements from the fail-open catch in extractNativeIdentity.
…ine restart reds, #4599 protocol)
🔶 需要决策 背景:存在两个并行的 M1 实现 PR——本线程的 #5573(按 02:04 设计稿原文实现:单采集点、纯头抽取)与 #5576(按你 02:10 的更正实现:双采集点 + 有界文本扫描 + 派生记录键 n: + BILI_NATIVE_IDENTITY_PROFILES_JSON 扩展 env)。两者注册同一个 rollout 开关(nativeIdentityObservation)、写同一个 session ext 键(nidv:obs)、新增同名不同内容的文件——互斥,只能保留一个。 待决问题:保留哪个 M1 PR? 选项(各带代价):
推荐:A。 另需确认一件小事:WB profile 表的压缩头拼写是 复审结论(结构化八问)
CI 状态:旧头的 unused-exports 红已由 870265d 修复;新头 870265d 有 6 个 test 分片红——拉取两个分片日志核实,失败均为已知环境敏感族「storage engine: full-mode restart is invisible × 4 wires」(#4599 有档),与本 PR diff 足迹零交集,本地定向套件 143/143 绿 ⇒ 判定继承性/环境性。已按协议 merge origin/ci-fixes(d03c578,验证全绿)进分支并推送,新头 e1a3ef3,CI 重跑中,转绿后补报。 判定:代码侧 ready(本地门禁全绿、两处发现已修、隐私红线恢复);合并次序等你拍板保留哪个 M1。 |
(ai: vllm-qwen/qwen3.8-27b) 查重补充:本线程此前「查重不重复」的结论产生于 GitHub PR #5576(镜像线程 #5578)出现之前,现已过时——#5576 是同一 M1 交付物的第二个实现:配置面、 |
NidvProfile.extraHeaders was declared and populated in the codex row but no code path reads it: detection keys x-codex-installation-id directly, extraction has no storage slot for extra headers, and no test asserts them. Remove the field and the row entry; the pending live-capture spellings stay in issue #5571's collection table until M2 decides what to actually capture.
ranxianglei
left a comment
There was a problem hiding this comment.
(ai: vllm-qwen/qwen3.8-27b)
APPROVE — M1 observation layer is correct, safe-by-construction, and well-pinned. Reviewed end-to-end (full diff, integration points, tests, docs); no kernel/ touch, no version/lockfile churn.
What I verified
- Red lines hold: single call site in
src/server/handle.tsright afterenterSessionContext(beforeprocessTurn), read-only, whole body in one try/catch (fail-open), default OFF (rollout.features.nativeIdentityObservation, percent 0 ⇒ off for every install; env fold toBILI_ROLLOUT_NATIVE_IDENTITY_OBSERVATIONchecked againstrolloutEnvName). Off-path cost is one cached config lookup + early return; off ⇒ zero ext writes + zero logs is pinned by test 8. - Privacy: persistence stores sha1-8 digests only (
nidvH8), raw id values live in request memory for microseconds, log lines print digests/truncations, unknown clients write NOTHING — all asserted (tests 4/9). - Store integration:
setSessionExt/getSessionExtare sync and thereplicasAvailable()gate makes legacy-only instances inert (verified againstsrc/persist.ts); new ext keynidv:obsdoes not collide withinc:*/pf:*/threads:*namespaces. - Tests are real, both-directional: prefix-match fires at text START only (mid-text quoted copy must NOT fire), compact-endpoint suffix match, cc dual-channel match/mismatch/object-dialect/malformed, profile-flip & restart continuity over JSON round-trip, FIFO ≤32 events + 4KB blob cap, digest-only mismatch evidence regex-pinned. Local targeted run: 9/9 pass; typecheck clean.
- Docs: CONFIGURATION.md/.zh-CN +
website/config-reference/rollout.yamlseed rows are consistent with the implementation (default off, env name, digest-only semantics).
Small finding fixed on the spot (commit f6968ee9f): NidvProfile.extraHeaders was declared and populated in the codex row but consumed nowhere — detection keys x-codex-installation-id directly, extraction has no storage slot for extra headers, and no test asserts them. Removed the field + row entry; the pending live-capture spellings stay in issue #5571's collection table until M2 decides what to actually capture.
Non-blocking observations (no code change requested)
shapeAssertAbsentmatches markers over the WHOLE body text (bodyText.includes) — user-authored content that legitimately containsses_/msg_/prt_(e.g. pasted opencode docs) can produce spuriousshape_assert_failevents. Noise is bounded (consecutive dedupe + 32-event FIFO + 4KB cap); strictness is a defensible data-quality choice for M2. Revisit if the event turns out to be noisy in practice.- The codex fallback rule (responses wire + generic
x-client-request-idpresence) may label other Responses-API clients as codex. Observation-only impact (family mislabel); the unknown⇒no-write redline still holds. - WB compaction header spelling (
x-compacted/x-context-compactedhere vsX-Compactionas written in #5571) is still unconfirmed pending live capture — already escalated on-thread. IfX-Compactionis right, the WB explicit-compaction class simply never fires until the row is corrected; per the module's own convention that fix belongs in this profile row (+ tests), so it does not block M1.
Note for the owner: parallel PR #5576 implements the same M1 surface with dual collection points (it also covers incremental-lane traffic this single choke point structurally misses). Which of the two survives is an open on-thread decision — this review judges #5573 on its own merits.
(posted as a comment review — GitHub does not allow the author account to file a formal APPROVE on its own PR; the verdict stands as stated.)
(ai: vllm-qwen/qwen3.8-27b) 审查完成,GitHub 已发评论式 review(APPROVE 结论——本账号即 PR 作者,平台不允许正式自批,故以 COMMENT 形式发布,判定不变):#5573 (review) 已顺手修:删除死字段 结构化八问:
非阻塞发现(已记入 GitHub review,未动代码):① shapeAssertAbsent 对整段 body 文本做子串匹配——用户内容恰好含 ses_/msg_/prt_(如粘贴 opencode 文档)会误报 shape_assert_fail 事件,噪音有界(dedup+FIFO≤32+4KB),严格度留给 M2 数据质量取舍;② codex 回落规则(responses + 通用 x-client-request-id)可能把其他 Responses 客户端标成 codex(仅观测层面误标);③ WB 压缩头拼写(x-compacted/x-context-compacted vs #5571 所写 X-Compaction)待 live capture 确认——floor 5 已升级,按模块自身惯例修法属于 profile 行本身,不阻塞 M1。 状态:PR head 现为 f6968ee(CI 重跑中;旧头 e1a3ef3 经镜像缓存确认绿:12 success / 18 skipped-条件车道 / 0 failed)。#5573 vs #5576 的留一关一裁决仍在 owner 手里(floor 5/7 已有记录,本账号不关闭任何条目)。等 owner 批准合并。 |
…026-11-03 Owner directive (issue #5571 thread): drop byte-stability from the hit gate; verify on sessionid + msgid only, count stays diagnostic (already so since v4). - Head element loop: under a shape-valid host msgid list, id/byte inequality demotes to a diagnostic warn stream (feeds M3 re-seeding signals); pure-count clients keep the pre-v5 byte gate unchanged (their ids carry no proof). - Session continuity gate: new state field cs (h8 digest of the client-native session header: wb x-conversation-id / cc x-claude-code-session-id / opencode x-session-affinity); an identity swap riding one bili session is a sess-change miss; recorded identity follows the traffic, swaps always cost a legacy pass. readSaved whitelist extended for cs. - System text has no msgid (flagged deviation in the ruling): head-drift stays gating in both modes.
#5403/#5418 v6 canonical-ledger unification. The knob governs what the watermark hit decision does with a client structural rewrite whose shape survived the width + origin gates: ON (default) takes one conservative legacy pass and rides the increment passthrough-as-new-message from the next turn (fresh ids append to the cumulative host-id ledger, no suppression logic — contract R4/O3); OFF restores pre-v6 ride-with- diagnostic for those turns. Nested under pluginFullResendWatermark so it is dead weight outside the watermark cohort; count-only turns stay byte-identical either way. Docs follow the sibling-knob convention: CONFIGURATION(.zh-CN) feature + env tables, website/config-reference/rollout.yaml entry (en+zh), and a v6 paragraph in the Full-resend watermark fastpath TECHNICAL-NOTES section (en+zh).
The watermark identity channel already carried the per-turn tail-id list; this commit adds the CUMULATIVE ledger it needs — the set-diff baseline for the canonical-ledger hit decision (contract R1/R2, owner rulings 2026-11-03): - PFSavedState.lg: ordered real host ids ever observed on this session's channel, FIFO-capped (PF_LEDGER_CAP 2048 entries / PF_LEDGER_MAX_BYTES 48KB serialized, oldest dropped first). '?' slots never recorded. MODEL-INDEPENDENT by design (R6): projections partition on (model × wire format), the logical ledger does not. - Sole growth site is finalizeWatermarkTurn: grow only when the turn delivered a shape-valid list AND the new watermarkCompactionPassthrough knob is on AND the conversation did not swap (ledgerHold, contract R7 — a cs-gated turn's ids describe a DIFFERENT conversation and must not fold into this session's baseline). Non-grow turns carry the stored ledger forward unchanged. Bootstrap/migration falls back to l. - Append-only: no server-authored text ever enters (R2); entries are the client's own screened id surface (parseTailIds all-or-nothing). - Fixes noteCreditedMiss arm-path dropping l on breaker trip — the breaker firing would have wiped exactly the ledger state rewrite-heavy clients rely on (l is now preserved alongside lg). - FastpathOutcome gains optional ledgerHold (additive, consumed by finalize; produced in the next commit).
…cs (R1/R4/R5)
The watermark HIT DECISION now derives from the canonical ledger instead
of the fastpath's own parallel byte gates. Consuming snapshot-render's
full walk on the hit path would pay the O(N) parse this lane exists to
avoid, so the shim tail-ids SET-DIFF against pf:state.lg fronts the same
semantic contract as the primary cheap gate — host msgids are unique
forever, so a pure-append list is equivalent to the backward id-chain
anchored on the transcript succeeding (snapshot-render's id fast-path).
- New layer after the cs gate (R7 placement), id mode only, knob-gated:
a REAL id arriving mid-list before a still-known id proves in-range
structural mutation (client compaction/insertion); verdict = ONE
conservative CREDITED legacy pass ('rewrite-passthrough'), then the
increment rides passthrough-as-new-message from the next turn — fresh
ids append naturally via finalize set-diff, NO suppression logic, never
speculative rebuilds (R4/O3). Knob OFF skips the layer entirely: pre-v6
ride-with-diagnostic semantics.
- Ids OUTSIDE [K-1..T-1] legitimately never reappear in traffic, so
'vanishing from the list' is not a signal — only mid-list unknowns are.
Index 0 (window origin) stays with the origin pin, which explicitly
neutralizes an unknown no-claim origin (#5418 v3/#5571 v5).
- R5: body grew (T > K) but the increment carries no host id at all ⇒
hit proceeds on the stashed snapshot (every verifiable gate agreed) +
structured diagnostic, ledger append stays a natural no-op; model-
switch/fork id-space questions deferred to research holds O1/O2.
- tail-ids-prev miss now carries the shape-valid list so its observed ids
still enter the ledger (R2 completeness; count-only turns unaffected).
- cs-gate miss sets ledgerHold=true (finalize then holds lg — a swapped-in
conversation's ids never fold into this session's baseline, R7).
- Header doctrine + PFSeed.model comment updated: m demoted from
world-partition to projection-cache key over ONE logical ledger (R6);
kept gates documented where alignment cannot cover them (width =
shim-protocol shape/origin K; origin pin = gap-tolerant alignment would
ride a reminted origin; head system-text = prompt has no msgid).
The cheap set-diff gate is the id fast-path of the canonical alignment — this adds the confirmation layer where it costs nothing: on a watermark MISS turn the full O(N) parse is already paid, so prepare-openai runs a light backward id-chain over store.unifiedMessageIdRows (identity rows ONLY — no content bytes loaded) and logs whether the client's live tail anchors into the unified transcript. One log per session at most (metadata latch); dormant unless the snapshotRender rollout cohort is active with ≥1 active block — outside that cohort it is a blocks check. probeTranscriptAlignment is deliberately standalone rather than extracted from buildView: this module is render authority (#3812/#4241), and an observability probe must not couple doctrine-critical walk code to a diagnostics path. Strict-contiguous walk + MIN_SEAM_RUN floor mirror the alignment's gap discipline; it reports reasons (no-active-blocks / store-unavailable / transcript-error / transcript-empty / chain-not-started / seam-too-short) for M3 ledger-vs-transcript cross-checks.
…gh (R4/O3) The structural-rewrite layer fires only on "unknown real id BEFORE a still-known slot". Spell out WHY that is the sole id-provable mutation shape: - lists span elements [K-1..T-1], so old ledger ids legitimately never reappear; "vanishing from traffic" is not a signal; - an unknown at index 0 (the ORIGIN slot) can never reach this layer — the origin pin above has already verified or missed it (#5418 v3 / #5571 v5); - a pure trailing run of fresh ids — incl. a full-history compaction summary minting fresh ids — is indistinguishable from ordinary appends, so it rides by DEFAULT per R4/O3 (passthrough-as-new-message; fresh ids enter the ledger via finalize; NO suppression logic). Docs-only: the loop already implements cursor > i. Closes the review question "which rewrites does v6 catch, which ride?" in the code where the answer lives. Ref contract R4/R5 + O3 hold; #5403/#5418 v6 canonical-ledger unification.
…, R5 no-append, R6 projections, FIFO cap Five new cases in tests/fastpath.test.ts (suite now 27), encoding the v6 contract face in host-id space (#5403/#5418 v6; contract R4/R5/R6 + O1–O3 holds): 1. rewrite-passthrough (R4/O3): seed a world [s,a,b,c] with a hit whose list [h-b,h-c] grows the ledger, then send T=6 with slots ["h-c","X1","h-b"] — unknown real id BEFORE a still-known one. Width law len===T-K+1 still holds and slot K-1 keeps its id, so every cheap gate passes; the mid-list proof fires: ONE conservative legacy pass, CREDITED (parity with the pre-v6 credited catches so pathological cycles stay breaker-bounded), ids still enter the ledger. The next turn rides clean on the grown ledger → hit(no-count). Convergence in one pass, no speculative rebuild. 2. Same turn with BILI_ROLLOUT_WATERMARK_COMPACTION_PASSTHROUGH=0: the v6 layer is skipped entirely, the byte-compare covers only unchanged prefix positions ⇒ no warns, plain "hit" — exact pre-v6 behavior for that turn. 3. unidentifiable increment (R5): body grows but the increment slot is "?" (host minted no id) ⇒ hit WITHOUT ledger append + structured diagnostic "[pf-v6] unidentifiable-increment ... O1/O2"; the anonymous content still rides the replayed snapshot. 4. per-model projections share ONE logical ledger (R6): m-one hits, flip to m-two ⇒ ONE "seed-stale" legacy pass builds m-two's projection, then pure incremental; flipping back is symmetric. The ledger itself is model-independent. 5. FIFO cap: inflate the persisted lg to PF_LEDGER_CAP=2048 via ext, append one honest id ⇒ exactly the single oldest entry drops (oldest-first trim, newest kept) — bounded like the obs ≤4KB discipline. Geometry notes (cost hours to derive, kept here for the next maintainer): - shim lists span elements [K-1..T-1]; shape gate demands length T-K+1 where K = seed.stampT; slot[0] is the origin anchor (origin-pin vs last id of the previous turn's list, '?' neutral either side); - parseTailIds REJECTS duplicate real ids within one list ('?' may repeat), so a mid-insertion case needs TWO distinct known ids in play; - known-id re-emergence must sit at index ≥ 1 — index 0 can never be an unknown at this layer (the pin settles it first). Ref contract R4/R5/R6, owner rulings 2026-11-03 (m02699/m02725/m02730 line); #3756/#3658 alignment doctrine consumed as the id-fast-path domain per implementation guidance item 2.
) Union-of-intent resolutions: - src/rollout.ts: keep all four feature entries (PR: nativeIdentityObservation, watermarkCompactionPassthrough; master: identityChannelHarvest) - src/server/prepare-openai.ts: msgid pin block BEFORE the v6 transcript-alignment probe (anthropic-twin order), so the probe sees the post-pin view - website/config-reference/rollout.yaml: master's desc_en/desc_zh format kept; both PR entries carried over with doc refs updated to post-migration technical-notes(.zh-CN).md names - TECHNICAL-NOTES.md/.zh-CN.md, CONFIGURATION.md/.zh-CN.md: root files take master's redirect stubs (#5920 layout migration); PR content transplanted — watermark-section superset (incl. v6 paragraph + M1 section) spliced into docs/technical-notes.md/.zh-CN.md; docs/configuration.md/.zh-CN.md regenerated via tools/gen-config-docs.mjs (i18n ok, coverage ok) - tests/rollout.test.ts: master's generic decisions test adapted for nested (base:) features — source 'off' when the base cohort excludes the install (watermarkCompactionPassthrough inside the 5% pluginFullResendWatermark grey) Verified: typecheck clean; targeted suites 100/100 (issue5571-native-identity-obs, fastpath, issue5403-watermark-fastpath, msgid-pin, openai-msgid-pin, snapshot-render + chain, rollout + identity-unified, msgid-rule-parity).
…erge - reference/architecture.md: list src/server/native-identity-obs.ts and native-identity-profiles.ts in the src/server/ tree (guard requires full src/ coverage; the map predates these modules from the #5571 M1 PR) - src/server/snapshot-render.ts: TranscriptProbeResult is used only inside snapshot-render.ts (probeTranscriptAlignment return annotation) — drop the leading export per the unused-exports gate (INTERNAL_ONLY)
(ai: vllm-qwen/qwen3.8-27b) CI 车道本轮处置完毕:合并冲突 + 两道静态门禁 + 一个测试 shard 失败,全部解决,head 1. 是什么问题 2. 什么原因导致的 3. 影响范围与严重程度 4. 怎么修复的
5. 是否有回归问题 6. 当前实时状态(本轮实测) 7. 收工判定 |
(ai: vllm-qwen/qwen3.8-27b) 本轮处理了一次新的冲突唤醒:master 在我上次合并之后又前进了(#6132 移除 compressAtFoldPoint 特性 + #6160 e2e 测试修正),GitHub 因此把 PR 重新标成 dirty。已合并解决并推送,head 现为 1. 是什么问题 2. 什么原因导致的 3. 影响范围与严重程度 4. 怎么修复的 5. 是否有回归问题 6. 当前实时状态(本轮实测) 7. 收工判定 |
[bot] (ai: vllm-qwen/qwen3.8-27b) Progress report — re-review of new head Since my last review (head
Currently doing: reading all new hunks end-to-end, verifying M1 files are byte-identical to |
(ai: vllm-qwen/qwen3.8-27b) CI lane — stale trigger confirmed, nothing to fix this run. This wake reported Live verification performed in this run (no memory/earlier-comment assertions):
No code changes and no push were needed this run. The CI lane has nothing outstanding: the PR is green and awaits human review/merge (I do not merge). |
M1 原生身份观测层(#5571)
这是什么
每条经过 bili 的模型请求都自带客户端的原生身份(WB/codex/CC/opencode/Gemini 各自的 session id、消息级宿主 id、压缩信号——全是 wire 上的普通头/正文字段)。这个 PR 在 relay 单点咽喉加一个纯采集器,把这些信号按客户端家族识别出来、以 sha1-8 摘要形式记进 session ext(
nidv:obs),让后续 M2 绑定表 / M3 re-baseline 建立在实测数据上,而不是二进制字符串推测上。设计稿与红线见 issue #5571 评论(thread: m1-design)。改了什么(1 commit,+~1000 行含测试/文档)
src/server/native-identity-profiles.ts(新):五家客户端模式表 + 纯函数抽取。压缩文案做成一张带测试钉住的常量表——将来文案漂移改表即可,禁止别处硬编码。src/server/native-identity-obs.ts(新):observeNativeIdentity()+ 状态机(纯函数 mergeNidvState,可单测)+ ext 读写。整体 try/catch fail-open,常数成本查找,正文扫描有界(USER_TEXT_SCAN_CAP=3)。src/server/handle.ts:一行调用,插在 sessionId 绑定后(enterSessionContext之后)。设计稿原写 L396,body 解码/会话绑定实际更晚,按真实管线落位——这是与设计稿唯一的位置性修正。src/rollout.ts+website/config-reference/rollout.yaml:开关rollout.features.nativeIdentityObservation,默认关(percent 0),envBILI_ROLLOUT_NATIVE_IDENTITY_OBSERVATION=1|0。CONFIGURATION.md/zh-CN 重生成产物在本 PR 内(避免 [PR] docs: regenerate CONFIGURATION tables (guardrails.repetition drift breaks docs-sync) #5374 式 docs-sync 长红)。红线保持(设计稿 §6 逐条)
零转发字节改动(模块只读)· 零决策(下游不得 gate 本数据,直到 M2/M3)· 零 agent lane 改动 · 未知客户端不写任何值(隐私)· 关闭时零 ext 写入且管线字节一致(显式 off 用例钉住)· 原始 id 不落盘不打日志(只用摘要,两个测试断言原始值不出现在 blob/日志)。
验证
tests/issue5571-native-identity-obs.test.ts9/9:profile 命中顺序钉死、五家 fixture 抽取、前缀锚定开头(中段嵌入不得命中)、双通道互验+畸形吞错、形状断言、稳定性矩阵(含重启续接)、事件 FIFO/4KB 预算、off/inert 全路径。tsc --noEmit0 错(注:本机过期 node_modules 会假报 ~40 个存量错误,npm install 后清零——与本 PR 无关)。合并后动作(owner)
你这台 WSL 盒开
BILI_ROLLOUT_NATIVE_IDENTITY_OBSERVATION=1开始采集(dsh/CC/opencode/codex 混流),约一周后对照出口判据回填 #5571 表 → 解锁 M2。