feat(F171): migrate weixin-mp to plugin framework - #6
Closed
mindfn wants to merge 6 commits into
Closed
Conversation
[宪宪/Opus-4.6🐾] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
mindfn
force-pushed
the
feat/f197-weixin-mp-plugin
branch
from
May 12, 2026 01:25
6245bd6 to
d3d6b08
Compare
Phase 1: Declarative plugin framework with manifest-based discovery, three-layer env safety, loopback+identity guards on all write endpoints, limb startup rehydration, and skill UI gating. - PluginRegistry: scan plugins/, validate manifests, reject reserved IDs - PluginResourceActivator: per-resource lifecycle (skill/mcp/limb) - PluginLimbNode: generic ILimbNode adapter for plugin-declared limbs - limb-yaml-loader: YAML → LimbDeclaration parser - plugin-routes: CRUD + healthCheck with loopback/identity/audit guards - Startup rehydration of enabled limb plugins from capabilities.json - PerCatToggles disabled when parent plugin is disabled - 17 security tests for manifest parsing + env safety Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
P1: reject envName values that aren't valid shell variable names (blocks newline injection into .env.local via crafted manifest). P2: resolve limb nodeId from YAML declaration instead of using plugin id for healthCheck lookup; return 501 for unimplemented mcpProbe. 20/20 security tests pass. [宪宪/Opus-46🐾] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
healthCheck.limbCommand must exist in the plugin's limb capability commands whitelist — prevents using the test endpoint to probe arbitrary commands outside the plugin's declared scope. [宪宪/Opus-46🐾] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 2: WeChat MP article publishing moved from hardcoded to declarative plugin framework. - plugins/weixin-mp/plugin.yaml — manifest with config, limb, skill, healthCheck - plugins/weixin-mp/limbs/weixin-mp.yml — capability declaration - plugins/weixin-mp/skills/weixin-mp/SKILL.md — skill documentation - WeixinMpLimbNode + weixin-mp domain code (token, client, markdown converter) - limbAdapterFactory: weixin-mp → WeixinMpLimbNode (Redis), others → PluginLimbNode - Startup rehydration uses platform-aware factory weixin-mp not in BUILTIN_PLUGIN_IDS — env prefix WEIXIN_MP_* matches naturally. [宪宪/Opus-46🐾] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
mindfn
force-pushed
the
feat/f197-weixin-mp-plugin
branch
from
May 12, 2026 01:27
d3d6b08 to
f946af5
Compare
…tion P1: Add URL validation (protocol/private-IP/localhost/metadata blocking) and content-type/size checks to image fetch in weixin-mp-client. P1: Split limb capabilities — read-only ops (check_status, list_drafts, publish_status) as authLevel: free; write ops (publish_article, upload_image) as authLevel: gated. P2: Escape HTML entities in markdown converter, validate URL schemes (block javascript:/data:), prevent attribute injection via quote escaping. 18 new security tests for URL safety + HTML sanitization. [宪宪/Opus-46🐾] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Owner
Author
|
Superseded by upstream PR zts212653#688 (zts212653/clowder-ai) — clean Phase 2 based on new Phase 1 branch. |
mindfn
pushed a commit
that referenced
this pull request
May 16, 2026
…ed wiring (zts212653#695) * fix(embedding): batch splitting + reprobe + collection embed deps Bug #1: Extract embedIndexedItems from IndexBuilder into shared embed-utils.ts with 64-item batch splitting. IndexBuilder's private version sent all items at once — embed-api MAX_BATCH_SIZE=64 caused >64 items to fail with HTTP 400, silently swallowed by fail-open. Bug #2: Add reprobeIfNeeded() to EmbeddingService. load() probes /health once at startup; if embed-api starts later, isReady() stays false forever. reprobeIfNeeded() re-probes when not ready. Bug #6: Wire embedDeps into CollectionIndexBuilder and library.ts rebuild route. Collection rebuild now produces vector embeddings (was FTS-only). Also pass force param from request body (was ignored). Upstream: zts212653#693 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> [宪宪/Opus-46🐾] * fix(embedding): wire production call sites for reprobeIfNeeded and per-collection VectorStore P1 fixes from gpt55 review: - embed-utils.ts: call reprobeIfNeeded() before isReady() check so all callers (IndexBuilder + CollectionIndexBuilder) get late-start recovery - library.ts: accept embeddingService instead of static embedDeps; dynamically load sqlite-vec and create per-collection VectorStore at rebuild time (prevents cross-DB vector pollution) - index.ts: pass memoryServices.embeddingService to libraryRoutes Tests: add reprobe recovery test, update all embedding mocks with reprobeIfNeeded method. 68/68 embedding tests pass. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(embedding): wire setEmbedDeps on collection store for search-time hybrid P1 from gpt55 R2: rebuild writes vectors via CollectionIndexBuilder but the collection's SqliteEvidenceStore never gets setEmbedDeps, so search() stays lexical-only. Now the rebuild route calls store.setEmbedDeps() after constructing the per-collection VectorStore, using the system embedMode. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: tianyiliang <tianyiliang@tianyiliangdeMacBook-Pro.local> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Lysander Su <773678591@qq.com>
mindfn
pushed a commit
that referenced
this pull request
May 18, 2026
…ed wiring (zts212653#695) * fix(embedding): batch splitting + reprobe + collection embed deps Bug #1: Extract embedIndexedItems from IndexBuilder into shared embed-utils.ts with 64-item batch splitting. IndexBuilder's private version sent all items at once — embed-api MAX_BATCH_SIZE=64 caused >64 items to fail with HTTP 400, silently swallowed by fail-open. Bug #2: Add reprobeIfNeeded() to EmbeddingService. load() probes /health once at startup; if embed-api starts later, isReady() stays false forever. reprobeIfNeeded() re-probes when not ready. Bug #6: Wire embedDeps into CollectionIndexBuilder and library.ts rebuild route. Collection rebuild now produces vector embeddings (was FTS-only). Also pass force param from request body (was ignored). Upstream: zts212653#693 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> [宪宪/Opus-46🐾] * fix(embedding): wire production call sites for reprobeIfNeeded and per-collection VectorStore P1 fixes from gpt55 review: - embed-utils.ts: call reprobeIfNeeded() before isReady() check so all callers (IndexBuilder + CollectionIndexBuilder) get late-start recovery - library.ts: accept embeddingService instead of static embedDeps; dynamically load sqlite-vec and create per-collection VectorStore at rebuild time (prevents cross-DB vector pollution) - index.ts: pass memoryServices.embeddingService to libraryRoutes Tests: add reprobe recovery test, update all embedding mocks with reprobeIfNeeded method. 68/68 embedding tests pass. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(embedding): wire setEmbedDeps on collection store for search-time hybrid P1 from gpt55 R2: rebuild writes vectors via CollectionIndexBuilder but the collection's SqliteEvidenceStore never gets setEmbedDeps, so search() stays lexical-only. Now the rebuild route calls store.setEmbedDeps() after constructing the per-collection VectorStore, using the system embedMode. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: tianyiliang <tianyiliang@tianyiliangdeMacBook-Pro.local> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Lysander Su <773678591@qq.com>
mindfn
pushed a commit
that referenced
this pull request
May 18, 2026
…ed wiring (zts212653#695) * fix(embedding): batch splitting + reprobe + collection embed deps Bug #1: Extract embedIndexedItems from IndexBuilder into shared embed-utils.ts with 64-item batch splitting. IndexBuilder's private version sent all items at once — embed-api MAX_BATCH_SIZE=64 caused >64 items to fail with HTTP 400, silently swallowed by fail-open. Bug #2: Add reprobeIfNeeded() to EmbeddingService. load() probes /health once at startup; if embed-api starts later, isReady() stays false forever. reprobeIfNeeded() re-probes when not ready. Bug #6: Wire embedDeps into CollectionIndexBuilder and library.ts rebuild route. Collection rebuild now produces vector embeddings (was FTS-only). Also pass force param from request body (was ignored). Upstream: zts212653#693 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> [宪宪/Opus-46🐾] * fix(embedding): wire production call sites for reprobeIfNeeded and per-collection VectorStore P1 fixes from gpt55 review: - embed-utils.ts: call reprobeIfNeeded() before isReady() check so all callers (IndexBuilder + CollectionIndexBuilder) get late-start recovery - library.ts: accept embeddingService instead of static embedDeps; dynamically load sqlite-vec and create per-collection VectorStore at rebuild time (prevents cross-DB vector pollution) - index.ts: pass memoryServices.embeddingService to libraryRoutes Tests: add reprobe recovery test, update all embedding mocks with reprobeIfNeeded method. 68/68 embedding tests pass. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(embedding): wire setEmbedDeps on collection store for search-time hybrid P1 from gpt55 R2: rebuild writes vectors via CollectionIndexBuilder but the collection's SqliteEvidenceStore never gets setEmbedDeps, so search() stays lexical-only. Now the rebuild route calls store.setEmbedDeps() after constructing the per-collection VectorStore, using the system embedMode. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: tianyiliang <tianyiliang@tianyiliangdeMacBook-Pro.local> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Lysander Su <773678591@qq.com>
mindfn
pushed a commit
that referenced
this pull request
May 19, 2026
…ed wiring (zts212653#695) * fix(embedding): batch splitting + reprobe + collection embed deps Bug #1: Extract embedIndexedItems from IndexBuilder into shared embed-utils.ts with 64-item batch splitting. IndexBuilder's private version sent all items at once — embed-api MAX_BATCH_SIZE=64 caused >64 items to fail with HTTP 400, silently swallowed by fail-open. Bug #2: Add reprobeIfNeeded() to EmbeddingService. load() probes /health once at startup; if embed-api starts later, isReady() stays false forever. reprobeIfNeeded() re-probes when not ready. Bug #6: Wire embedDeps into CollectionIndexBuilder and library.ts rebuild route. Collection rebuild now produces vector embeddings (was FTS-only). Also pass force param from request body (was ignored). Upstream: zts212653#693 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> [宪宪/Opus-46🐾] * fix(embedding): wire production call sites for reprobeIfNeeded and per-collection VectorStore P1 fixes from gpt55 review: - embed-utils.ts: call reprobeIfNeeded() before isReady() check so all callers (IndexBuilder + CollectionIndexBuilder) get late-start recovery - library.ts: accept embeddingService instead of static embedDeps; dynamically load sqlite-vec and create per-collection VectorStore at rebuild time (prevents cross-DB vector pollution) - index.ts: pass memoryServices.embeddingService to libraryRoutes Tests: add reprobe recovery test, update all embedding mocks with reprobeIfNeeded method. 68/68 embedding tests pass. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(embedding): wire setEmbedDeps on collection store for search-time hybrid P1 from gpt55 R2: rebuild writes vectors via CollectionIndexBuilder but the collection's SqliteEvidenceStore never gets setEmbedDeps, so search() stays lexical-only. Now the rebuild route calls store.setEmbedDeps() after constructing the per-collection VectorStore, using the system embedMode. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: tianyiliang <tianyiliang@tianyiliangdeMacBook-Pro.local> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Lysander Su <773678591@qq.com>
mindfn
pushed a commit
that referenced
this pull request
May 23, 2026
…12653#753) Phase F (trace persistence via pointer association) actually shipped via zts212653#579 (merged 2026-04-28, commit 8cc6f9a) but the spec header was never updated. AC-F1..F7 are all [x] in the AC section already; only the section/header status was stale (still "设计中"/"spec"). Changes (docs-only): - Phase F section header: "(设计中)" → "✅" + status block with Provenance (zts212653#592), Implementation PR (zts212653#579), and Scope note clarifying AC-F8 is intentionally deferred to Phase H. - AC section header: "Phase F(...)" → "Phase F(...)✅" for parity with Phase B/C/D/E/G/I that already have ✅ markers. No code, no AC changes. Source of truth: implementation already in production on both zts/main and clowder-labs/main. [宪宪/Opus-47🐾] Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
mindfn
added a commit
that referenced
this pull request
Jul 13, 2026
…e, MemoryQuery, sunset fix Comprehensive revision based on Sol's CHANGES REQUESTED (5 P1s) and Fable's follow-up (instance boundary retraction + acceptance criteria): P1-1 recall three-mode coverage: - Added MemoryQuery discriminated union (relevance | recent) - related() stays as separate method (key-based graph traversal) - scope → content filter, namespace → library selector: two independent small tables replace the old 30-cell matrix P1-2 rebuild/maintain boundary: - rebuild = host replays full ingest (scanner → remember() × N) - maintain() = component-internal index health only - Phase 1 updated: IndexBuilder scanner stays OUTSIDE component - This split resolves 16 getDb escapes (not new work, fixing known leak) P1-3 sunset lifecycle contradiction fixed: - §5.1 signal table refined: "observed source absence → dormant" vs "explicit user delete → hard delete + cascade" (Sol precision) - Phase 2 rewritten: no more cascade delete as default; 188 orphans classified by deletion reason, not bulk-cleared - Cascade delete violates Iron Law #5 (Fable: user data default persistent) - Doc-type lifecycle downgraded from "基本完整" to "⚠️ no executor" - Denial #11 added: cascade delete as sunset fix P1-4 old remnants cleaned: - §2 problem statement: "can't replace conversation backend" → "no unified capability boundary, 16 getDb escapes" - Phase 2: ConversationProvider/两个Provider removed P1-5 Wire = transport adapter: - §4.6: Wire changed from "删除" to "降级为 transport adapter" - §8: MemoryComponent = logical contract; HTTP/socket = optional transport - Parent ADR sync obligation explicitly noted Instance boundary (Sol ruling, Fable agreed): - One Clowder runtime = one MemoryComponent instance - Federation (cross-namespace RRF) is component-internal capability - Architecture diagram: Federation Layer removed from external - MemoryKey.namespace = library identifier (project/global/collection-id) - Denial #10 added: per-namespace replacement = coordinator resurrection 13-capability mapping refined (Fable acceptance criteria): - #6 entity resolution: internal, not on contract - #7 materialization: host-side governance, component only ingests output - #11 provenance: two coverage points (MemoryInput + recall filter) - #13 federation: now component-internal (was external) File renamed: echomem-collaboration-design.md → memory-component-abstraction.md 465 → 505 lines. All three reviewers' inputs consolidated. [布偶猫/claude-opus-4-6🐾] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
mindfn
added a commit
that referenced
this pull request
Jul 21, 2026
…erve-only) (#47) * feat(F257): #4 签名 lint O2→O1 — 检测层(observe-only)+ 账本闭环显式 deferred Why: 消息末行 [昵称/模型🐾] 签名此前仅靠 prompt 文本约定(O2 观测层),零 结构覆盖 → dev-7a882ba0(Fable 漏签靠 operator 人工发现)。本切片交付 #4 的 **检测层**(O1 结构强制):agent 消息末行是否以被识别的猫签名结尾。 两阶段(owner vision-guardian 校准,AC 完成 ≠ feature 完成): - ① 检测层(本切片):复用 isCatSignatureLine(F167,不重造正则)→ post-seam 记 message.extra.signatureLint(message 级可观测、denominator-bearing、 observe-only 非阻断,绝不 reject 已持久化用户可见消息)。 - ② 账本闭环(deferred 到切片 2 / #3 后):检测到 miss 自动 emit deviation 归因 obj-identity-integrity 进 harness ledger,自动化 dev-7a882ba0 的手动 report_harness_signal。deferred 原因:ledger 读 DeviationEventLog/ GuardRejectionEventLog/eval verdict 不扫 message.extra;正确 deviation 需 registered objective(否则重蹈 #3 修的 free-string 考古)+ segment/condition 归因基建(condition_hit 需 condition registry+typed fact,均属 #2/#3 数据根)。 extra-only ≠ #4 完成——已在 spec #4 + 代码 SCOPE 注释显式在案。 设计自决:observe-only 非阻断(阻断逼近铁律5/硬排除,风险高);#4 检测 ⊥ #3 (不依赖 registry);signatureLintExtra helper 提取使 post-message 巨型 handler biome 复杂度对基线 0 delta。 证据:19 单测(纯函数+helper)+ 2 wiring e2e(extra.signatureLint signed true/false)+ 79 blast-radius 回归全绿;tsc/biome clean;新文件 65 行、无 any。 [宪宪/claude-opus-4-8🐾] Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(F257): #4 检测层完整性 — sol R3 REQUEST CHANGES 全部修复(P1×3 + P2×1) R3 review 四项全部核实成立(读码验证,非反射接受),本 commit 逐项修复 + red→green: P1-3 严格契约 matcher(correctness):lintCatSignature 之前复用 isCatSignatureLine (routing 的 permissive STRIP matcher,容忍 [Spark🐾] 无模型 / [砚砚/GPT-5.5] 无爪), 把契约违规签名误判为 compliant。改用严格 `[nickname/model🐾]` 正则(slash+model+paw 必需),断言 governance-l0「签名必须含模型型号」。strip=permissive(routing) 与 lint=strict(compliance) 分离。两个原正例 flip 成反例 + 保留严格正例。 P1-1 覆盖(coverage):签名 lint 之前只接 callback post-message 两支,漏掉 serial/ parallel 以 origin:'stream' 持久化的普通 agent 最终文本 → 从不 post_message 的猫不入 分母,签名率系统性偏差。route-serial.ts:2805 + route-parallel.ts:1248 各接 signatureLintExtra(storedContent)。加 serial+parallel 集成测试(signed/unsigned 均入分母)。 P1-2 持久化(durability):serializeExtra 写入但 safeParseExtra 白名单重建时丢弃 signatureLint → Redis 回读后字段消失(in-memory 测试测不到)。parser 加 signatureLint strict 支持(typeof signed === 'boolean')+ serialize→parse round-trip 测试(true/false/ malformed drop/其他字段不受影响)。 P2-1 可达(reachability):GET /api/messages 白名单、两处 callback socket broadcast、 web ChatMessage.extra、server AgentMessage.extra 均漏 signatureLint → Console 读不到。 逐一透传(broadcast 转发 storedMsg.extra.signatureLint,单一真相源)+ cold-hydration (GET)+ live-broadcast 契约测试。无 UI 渲染(#6 消费)。 证据:F257 slice-1 全套 82 tests + blast-radius 回归 60 tests 全绿;tsc/web-tsc clean; biome 新文件 clean,route-serial/parallel/callbacks 复杂度对基线 0 delta;safeParseExtra 48→50、messages GET 70→73(+2/+3,逐字段 established pattern,函数本已远超限,无新增 erroring function)。两阶段账本闭环仍 deferred(sol 已 accept)。 [宪宪/claude-opus-4-8🐾] Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(F257): #4 R4 — P1 provider-qualified model + P2 §16e 全链 reachability sol R4 REQUEST CHANGES 两项核实成立(读码 + runtime 真相源),逐项修 + §16e 全量 sweep: P1 严格 matcher 拒真实模型标识(correctness):STRICT_SIGNATURE_LINE_RE 假设单斜杠,但 opencode 运行时 defaultModel="codex-for-me/gpt-5.4"(provider-qualified,含斜杠)→ [金渐层/codex-for-me/gpt-5.4🐾] 被误判 unsigned、按 cohort 污染分母。改:首斜杠分隔 nickname/model,model 允许 provider-qualified 斜杠,两段 trim 后非空必需(拒 [ /m🐾] / [n/ 🐾])。flip multi-slash 为正例 + roster provider-qualified 正例 + 空段负例。 P2 §16e 全链 reachability(补锅匠 → failure-mode sweep):R3 只补 server 侧 + web ChatMessage 类型,漏 web 投影链全体 + 2 处 dup-recovery broadcast。做全量 sweep(api 20 点 + web 14 点)逐点核 fixed/N-A,修复 14 点: - API: A13/A14 dup-recovery broadcast forward(+ A11-A14 抽 pickSignatureLint helper, callbacks 复杂度对基线 0 delta) - Web 类型: W3 AgentMsg / W4 BackgroundAgentMessage / W5 history-response - Web runtime: W6 cold-hydration(guard+emit) / W7 mergeMessageExtra(sweep 发现的 uncited 点) / W8-W14 useAgentMessages callback side-patch×7(pickSignatureLint 统一 forward) - sweep 发现 sol 未列的 3 个 drop 点(W7/W13/W14)——证明逐点补 sol 列表会再 bounce Divergence: 6+ 异构 allowlist(不同 key 子集),不可统一 projector;pickSignatureLint 1-line forwarder per-package DRY(sweep 建议)。 证据:api F257+回归 105 全绿;web reachability vitest 8 + web tsc clean;biome clean、 callbacks 复杂度 0-delta;无 any。A18/A19(F246 dispatch origination,fresh extra 无 drop)+ A16/A17(full passthrough)文档化 N-A。 [宪宪/claude-opus-4-8🐾] Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
mindfn
added a commit
that referenced
this pull request
Jul 21, 2026
…d canonical tuple (sol R1) P1-1: verdict vocabulary was inferred from usage sites, not the authoritative SegmentVerdict type — corrected to the frozen 6-term set; removed keep_observe (Eval Hub verdict-handoff domain, not SegmentVerdict); added observability-debt + needs-denominator. P1-2: rewrote unmeasurable (无分母/injectionCount=0/anti-miskill) + dormant (有分母+连续2周期零触发零违规) to judgment-schema-v1 §2 frozen semantics. P2-1: moved canonical SEGMENT_VERDICTS tuple + SegmentVerdict to shared (SSOT); engine consumes it; UI map uses satisfies Record<SegmentVerdict,…> (compile-time fail-closed); test iterates shared tuple + asserts domain separation; tightened shared EvalStageSummary.verdict. Follow-up (out of scope): UI VersionEpoch/EvalStageSummary local 'verdict: string' mirrors across ChainView/Modal/EvalStagePanel — dedup to shared type is a separate cleanup. Gate: shared/api/web tsc 0, vitest 62 pass, biome clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
mindfn
added a commit
that referenced
this pull request
Jul 21, 2026
…contract (sol P2-2) Three explanations drifted from the actual SegmentJudgment producer: - alive: producer is injectionCount>0 → alive even with violationCount===0 (segment-judgment-engine.ts:299-308). Removed '违规仍有下降空间' (contradicts a displayed 违规次数 0); now states zero-violation is a valid alive measurement. - observability-debt: dropped the '连续2周期 observabilityDeadline' clock — that pressure rule binds to 2 consecutive unmeasurable periods, not this verdict's definition. - needs-denominator: denominator contract is fired-count | session-count | none; replaced 'typed fact' (objective-metrics plane) with the 注入/会话 denominator. Added 3 regression tests (alive+zero-violations anchor + 2 lock tests). Full 6-word sweep; dormant/unmeasurable/retire-candidate unchanged (sol OK). Gate: web tsc 0, vitest 65 pass, biome clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
mindfn
added a commit
that referenced
this pull request
Jul 21, 2026
… Console (判据③) (#52) * feat(F257): #6 slice 6a — verdict vocabulary explanations in lifeline Console (判据③) Why: operator screenshot showed eval(unmeasurable) rendered as amber with no meaning — LifelineChainView/EvalStagePanel only hard-coded alive/dormant/retire-candidate, so real verdicts (unmeasurable/keep_observe) fell through unexplained. Adds a single source of truth (verdict-explanations.ts: verdict→{label,explanation,tone} covering alive/keep_observe/unmeasurable/dormant/retire-candidate + safe fallback), wired into the eval badge (tone + hover tooltip) and the 判定 detail row (label + explanation). Gate: vitest 60 pass (8 new incl. wiring contract + 52 lifeline regression), biome clean, tsc 0. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(F257): #6 slice 6a R1 — correct SegmentVerdict vocabulary + shared canonical tuple (sol R1) P1-1: verdict vocabulary was inferred from usage sites, not the authoritative SegmentVerdict type — corrected to the frozen 6-term set; removed keep_observe (Eval Hub verdict-handoff domain, not SegmentVerdict); added observability-debt + needs-denominator. P1-2: rewrote unmeasurable (无分母/injectionCount=0/anti-miskill) + dormant (有分母+连续2周期零触发零违规) to judgment-schema-v1 §2 frozen semantics. P2-1: moved canonical SEGMENT_VERDICTS tuple + SegmentVerdict to shared (SSOT); engine consumes it; UI map uses satisfies Record<SegmentVerdict,…> (compile-time fail-closed); test iterates shared tuple + asserts domain separation; tightened shared EvalStageSummary.verdict. Follow-up (out of scope): UI VersionEpoch/EvalStageSummary local 'verdict: string' mirrors across ChainView/Modal/EvalStagePanel — dedup to shared type is a separate cleanup. Gate: shared/api/web tsc 0, vitest 62 pass, biome clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(F257): #6 slice 6a R2 — align 3 verdict explanations to producer contract (sol P2-2) Three explanations drifted from the actual SegmentJudgment producer: - alive: producer is injectionCount>0 → alive even with violationCount===0 (segment-judgment-engine.ts:299-308). Removed '违规仍有下降空间' (contradicts a displayed 违规次数 0); now states zero-violation is a valid alive measurement. - observability-debt: dropped the '连续2周期 observabilityDeadline' clock — that pressure rule binds to 2 consecutive unmeasurable periods, not this verdict's definition. - needs-denominator: denominator contract is fired-count | session-count | none; replaced 'typed fact' (objective-metrics plane) with the 注入/会话 denominator. Added 3 regression tests (alive+zero-violations anchor + 2 lock tests). Full 6-word sweep; dormant/unmeasurable/retire-candidate unchanged (sol OK). Gate: web tsc 0, vitest 65 pass, biome clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
mindfn
added a commit
that referenced
this pull request
Jul 22, 2026
…onable (判据①, sol R1) Why: sol R1 caught that 494f90f implemented actionability from the SYNTHESIZED governance.pending — the exact false signal of the original incident (operator saw 'pending' with no Candidate and asked '是要我审批吗'). This reworks per the 固化 boundary (main msg 0001784469935300-000115) and operator's option B (root-cause, staged): - Read model (API): deriveActiveStage() — loop model, not one-way pipeline. unmeasurable/observability-debt/needs-denominator/retire-candidate → tracing; only conclusive alive/dormant parks at governance. - actionable derived ONLY from real pending Candidate count via a new resolvePendingCandidateCount seam; projection unwired → response honestly reports source:'unavailable' + candidateCount:null (never guessed). - Shared types: ActiveStage + ActionableInfo on SegmentLifecycleResponse. - Web: loop marker ◈ on the active epoch's real stage; governance.pending rendered informationally (slate) — amber dot / N 待审 only with real candidate count; empty governance says 未进入治理环节 (not the misleading 等待治理决策); detail panel shows provenance-gap text when unwired. Evidence: API 81/81 (13 new contract tests incl. sol R1 regressions 1-4), web vitest targeted 78/78 (13 jsdom behavior tests, not source-regex), full web vitest 22 failures = identical to pre-change baseline (8 files, verified via stash A/B), tsc 0, biome clean. [小狸/k3🐾]
mindfn
added a commit
that referenced
this pull request
Jul 22, 2026
…rmational Why: same failure mode as sol R1 P1-2 in a sibling surface — VersionDetail's STATUS_MAP rendered the synthesized governance-pending as amber 待治理 (attention), the same class of false signal as the original incident. Now informational slate '评估已过·治理环节'; actionability remains gated on real Candidate count only. Behavior test added (no amber 待治理). [小狸/k3🐾]
mindfn
added a commit
that referenced
this pull request
Jul 22, 2026
Why (sol R2): - P1-4: UI still gated real Candidates behind synthesized governance.pending, hiding the decisive cross-state (retire-candidate: active=tracing + actionable=governance(N>0) + epoch.governance=null). Now Candidate projection is the sole actionability authority: chain label/tone/title and Governance detail show N 待审 + CTA independently of epoch.governance. - P2-2: dormant ≠ pass — wording now verdict-neutral '评估完成' everywhere (chain tooltip, GovPanel, epoch STATUS_MAP). - P2-3: resolveActionableInfo extracted, fail-closed: provider throw/negative/fractional/NaN → source:'unavailable' + server-side warn, never a guessed count. Route handler/GovPanel/fetchData refactored so this delta adds zero complexity warnings (only pre-existing chain 43 + 2 a11y). Evidence: API 87/87 (incl. R2 cross-state + 4 fail-safe cases); web jsdom 82/82 targeted (17 判据① behavior tests); full web vitest 22 fails identical to pre-change baseline (8 files, stash A/B); tsc 0; biome no new warnings. Isolated-chromium evidence (vite harness + playwright): /tmp/f257-6b-browser/state-a-unavailable.png + state-b-cross.png, 10/10 DOM assertions pass in real browser. [小狸/k3🐾]
mindfn
added a commit
that referenced
this pull request
Jul 22, 2026
* feat(F257): #6 slice 6b — activeStage/actionableStage 分离 (判据①) The lifeline chain conflated 'active' (which version is live) with 'needs an operator decision'. Adds an actionable-stage channel distinct from the active-version marker. Grounded in the producer: segment-lifeline-chain.ts sets governance=pending exactly when the winning verdict is alive/dormant (lines 304-306), so governance-pending is the sole operator-gated stage in v1. - exported isActionableStage(governance, stage) — governance-pending only - StageBadge: actionable prop → amber attention dot (separate visual channel from the blue active ● + selection ring) - governance badge wired; pending label → 待处理 - TDD: actionable-stage unit + wiring contract tests; loosened the 6a title contract regex to accept the actionable title override (intent preserved: eval title still flows through) Gate: web tsc 0, vitest 71 pass, biome clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(F257): #6 slice 6b rework — activeStage read-model + honest actionable (判据①, sol R1) Why: sol R1 caught that 494f90f implemented actionability from the SYNTHESIZED governance.pending — the exact false signal of the original incident (operator saw 'pending' with no Candidate and asked '是要我审批吗'). This reworks per the 固化 boundary (main msg 0001784469935300-000115) and operator's option B (root-cause, staged): - Read model (API): deriveActiveStage() — loop model, not one-way pipeline. unmeasurable/observability-debt/needs-denominator/retire-candidate → tracing; only conclusive alive/dormant parks at governance. - actionable derived ONLY from real pending Candidate count via a new resolvePendingCandidateCount seam; projection unwired → response honestly reports source:'unavailable' + candidateCount:null (never guessed). - Shared types: ActiveStage + ActionableInfo on SegmentLifecycleResponse. - Web: loop marker ◈ on the active epoch's real stage; governance.pending rendered informationally (slate) — amber dot / N 待审 only with real candidate count; empty governance says 未进入治理环节 (not the misleading 等待治理决策); detail panel shows provenance-gap text when unwired. Evidence: API 81/81 (13 new contract tests incl. sol R1 regressions 1-4), web vitest targeted 78/78 (13 jsdom behavior tests, not source-regex), full web vitest 22 failures = identical to pre-change baseline (8 files, verified via stash A/B), tsc 0, biome clean. [小狸/k3🐾] * fix(F257): #6 判据① §16e sweep — epoch status 'governance-pending' informational Why: same failure mode as sol R1 P1-2 in a sibling surface — VersionDetail's STATUS_MAP rendered the synthesized governance-pending as amber 待治理 (attention), the same class of false signal as the original incident. Now informational slate '评估已过·治理环节'; actionability remains gated on real Candidate count only. Behavior test added (no amber 待治理). [小狸/k3🐾] * fix(F257): #6 判据① R2 — candidate-first UI + fail-safe provider + wording Why (sol R2): - P1-4: UI still gated real Candidates behind synthesized governance.pending, hiding the decisive cross-state (retire-candidate: active=tracing + actionable=governance(N>0) + epoch.governance=null). Now Candidate projection is the sole actionability authority: chain label/tone/title and Governance detail show N 待审 + CTA independently of epoch.governance. - P2-2: dormant ≠ pass — wording now verdict-neutral '评估完成' everywhere (chain tooltip, GovPanel, epoch STATUS_MAP). - P2-3: resolveActionableInfo extracted, fail-closed: provider throw/negative/fractional/NaN → source:'unavailable' + server-side warn, never a guessed count. Route handler/GovPanel/fetchData refactored so this delta adds zero complexity warnings (only pre-existing chain 43 + 2 a11y). Evidence: API 87/87 (incl. R2 cross-state + 4 fail-safe cases); web jsdom 82/82 targeted (17 判据① behavior tests); full web vitest 22 fails identical to pre-change baseline (8 files, stash A/B); tsc 0; biome no new warnings. Isolated-chromium evidence (vite harness + playwright): /tmp/f257-6b-browser/state-a-unavailable.png + state-b-cross.png, 10/10 DOM assertions pass in real browser. [小狸/k3🐾] --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
mindfn
added a commit
that referenced
this pull request
Jul 23, 2026
Why: Console lifeline projected incomparable metrics into one context — tracing(18) came from the CURRENT query window while eval injectionCount=0 came from the judgment's OWN historical eval window. CachedJudgment dropped window + denominatorKind at persistence, so the query window silently substituted for the eval coordinate. - CachedJudgment: window + denominatorKind required on producer writes (SegmentJudgment always has both); legacy Redis JSON reads normalize to explicit null (fail-visible, never guessed from evaluatedAt) - read model: EvalStageSummary.evalWindow/denominatorKind; response.window documented as the QUERY window (distinct coordinate) - chain builder: toEvalStageSummary propagates both (extracted helper — keeps attachJudgments cognitive complexity at baseline) - Console: eval panel shows 评估窗口(采样区间) + 分母; tracing panel labels 当前查询窗口; legacy → 评估窗口未知/分母未知 fail-visible Red→green: cache round-trip/legacy (4) + chain/route attribution (6) + jsdom render 18-vs-0 (6). API full-suite A/B vs base 231f960: failing list byte-identical (90) — zero new failures. Web vitest A/B: same 22 baseline failures, +6 new green. tsc 0 errors; biome 0 new warnings. [小狸/k3🐾]
mindfn
added a commit
that referenced
this pull request
Jul 23, 2026
…nce fail-closed (sol P1-1/P2-1/P2-2) Why: sol R1 REQUEST CHANGES — (P1-1) query window and eval window were still in mutually exclusive panels, so tracing(18) vs eval(0) was NOT visible as two coordinates in one viewport (the stale test even encoded the bug by asserting the query window absent from the eval panel); (P2-1) the Redis read boundary trusted any PRESENT window/denominatorKind JSON, letting forged coordinates reach the UI as "Invalid Date ~ Invalid Date"; (P2-2) no real-browser evidence. - P1-1: EvalStagePanel gains queryWindow prop + compact 坐标对照 block — current observation count + CURRENT query window rendered next to the historical eval window/denominator in ONE viewport, explicitly labeled ≠ 评估窗口; LifelineStageDetail wires queryWindow through. The stale mutual-exclusion test is rewritten: query window may appear only as labeled contrast, never as the eval window. New composed render test (LifelineChainView + eval LifelineStageDetail) asserts one DOM holds tracing(18), 0, QUERY start/end, EVAL start/end, fired-count. - P2-1: normalizeCachedJudgment fails closed — window must be a non-array record with finite startMs/endMs in legal [start,end order; denominatorKind restricted to the closed union fired-count|session-count|none; non-record raw (e.g. JSON array) rejected outright. Malformed-present regressions added for get/getBatch/getHistory; legitimate legacy-missing still → null. - P2-2: replayable browser harness — showcase page /showcase/f257-eval-window (repo precedent: f052/f11-review) + packages/web/scripts/capture-f257-eval-window.mjs (production build + real chromium, fail-closed waits). Composed screenshot delivered in review handoff thread. Red→green: cache malformed 5 red → 25/25 green (file 20 + f257-eval-window 5); web composed 1 red → 7/7 green. API full-suite: 90 unique failures, zero touching judgment/lifeline/eval-window surface (baseline-count identical). Web vitest: same 22 baseline failures across same 8 unrelated files. tsc 0 errors; biome clean. [小狸/k3🐾] EOF )
mindfn
added a commit
that referenced
this pull request
Jul 23, 2026
…an evidence (sol P1-2/P2-3/P2-4) Why: sol R2 — the R1 test/showcase locked an IMPOSSIBLE judgment state (alive + injectionCount=0 + fired-count): segment-judgment-engine produceVerdict maps injectionCount=0 → unmeasurable + none, so the screenshot visibly contradicted the product contract (活跃:injectionCount>0 above 注入次数 0) and regressed the 6b loop model (unmeasurable returns the cycle to tracing, no governance). The committed capture script was machine-specific (user/OS/arch paths), and review-evidence/ dirtied the worktree. - P1-2: fixtures now reproduce the REAL incident — eval unmeasurable / injectionCount=0 / violationCount=0 / denominatorKind 'none', epoch status eval-pending, activeStage 'tracing', governance null (cycle back to tracing per 6b loop model). jsdom contract + showcase updated; composed test now asserts 无分母(不可计算比率)and the ABSENCE of fired-count for an unmeasurable judgment. Legacy case uses a producer-valid alive judgment with missing provenance fields. - P2-3: committed capture script REMOVED — the showcase route (/showcase/f257-eval-window) is the replayable artifact; evidence is captured with local browser tooling. - P2-4: review evidence moved outside the worktree (/tmp/f257-6c-eval-window); git status clean. Red→green: eval-window-provenance 7/7 green (producer-contract guard included); web tsc 0; biome clean; next build includes the showcase route. Fresh production-styled chromium capture attached in review thread. [小狸/k3🐾]
mindfn
added a commit
that referenced
this pull request
Jul 23, 2026
… P2-1)
Why: sol R4 REQUEST CHANGES — normalizeWindow only rejected startMs > endMs,
so a forged {startMs: t, endMs: t} survived the Redis read boundary and the
Console rendered `t ~ t` as a trusted eval coordinate. judgment-schema-v1
defines a [start,end sampling window (empty interval has no sampleable
instant), and every canonical selector/adapter already rejects
windowEndMs <= windowStartMs. The cache seam now enforces the same
invariant: startMs >= endMs → null.
Same-type audit (requested with the finding): scanned all provenance
validators on the slice surface for the interval invariant —
- publish-verdict/validation.ts ×3 selector validators: already reject
- harness-ledger-generator-adapter.ts: already reject
- segment-lifeline-chain.ts toEvalStageSummary: pure propagation, no
re-validation — cache read boundary is the sole seam (now closed)
- web EvalStagePanel: renders null as 评估窗口未知 (no own invariant needed)
normalizeWindow was the only gap; no sibling instances remain.
Integer constraint not added at this seam — producer window comes from
epoch ms timestamps (finite numbers); end > start is the blocking
requirement per the finding.
Red→green: zero-length window test red (1 fail) → cache 21/21 +
f257-eval-window 5/5 green; getHistory seam covered. Web jsdom 7/7;
web tsc 0; biome clean.
[小狸/k3🐾]
EOF
)
mindfn
added a commit
that referenced
this pull request
Jul 23, 2026
…ol P1/P2) Why: sol R5 REQUEST CHANGES (second-pass integration review) — (P1) the 6c contrast block rendered tracing observationCount (ALL observed rows, incl. observe-only, capped at MAX_OBSERVATIONS=100) as "当前观测 N 次注入" — reproducing the 18-vs-0 fake contradiction even with perfect window provenance, and presenting capped lower bounds as totals. (P2) legacy-missing and present-but-malformed provenance both normalized to null while the UI asserted "历史缓存缺字段" for both — a false cause for corrupted data. P1 — real metric + completeness: - TracingStageSummary gains firedCount + capped (shared contract) - collectObservations computes fired per row via segment-judgment-engine isFired (exported — single predicate source, producer semantics); capped=true ONLY when a matching row exists beyond MAX_OBSERVATIONS (exactly-100 is not "capped") - attachObservations counts both metrics + propagates capped - EvalStagePanel contrast block: 当前注入 = fired-count (≥N when capped, 下限 labeled); observe-only surplus shown as 观测行数 (≠ 注入次数); pending panel same split; 评估方式 keyed on firedCount - stale source-regex contract test asserting the old mislabel rewritten P2 — gap kind preserved end-to-end: - ProvenanceGapKind 'legacy-missing' | 'invalid-present' (shared) - cache read seam classifies both fields (absent=legacy, malformed=invalid), write path records gap null - toEvalStageSummary propagates; UI renders invalid-present as 评估窗口不可用(缓存数据损坏)/分母不可用 — never mislabeled legacy Red→green: API 5 red (observe-only / 101-cap / gap-kind chain+route+cache) → 99/99; web 3 red (contrast observe-only / capped / invalid-present render) → 93/93 across 4 affected files. Full API public suite: only the 4 known KD-17 stale assertions (17,421 pass); full web vitest: same 22 baseline failures in the same 8 files (5,169 pass, +50 new green). tsc 0 both packages; biome clean. [小狸/k3🐾]
mindfn
added a commit
that referenced
this pull request
Jul 23, 2026
…ap kind (sol P1/P2) Why: sol R6 REQUEST CHANGES (third-pass focused review @ edfa6d9) — (P1) the R5 global 100-row cap could project an active epoch with real observations as tracing:null (unsampled ≠ zero-data), and UI siblings still rendered bare capped counts; lower-bound provenance on counts was the wrong coordinate — completeness belongs to the DETAIL list only. (P2) raw == null cannot distinguish absent (legacy-missing) from present-null (invalid-present; the producer never writes null) — gap classification must be by own-property presence. P1 — aggregate counts exact, detail list capped: - collectObservations: full-window scan for counting; detail rows = 100 most recent + observationsCapped completeness flag (route response) - TracingStageSummary drops capped (counts are exact, never lower bounds) - EvalStagePanel/LifelineChainView/LifelineStageDetail: exact counts, no ≥/下限 markers; tracing panel shows 明细仅显示最近 100 条 note when observationsCapped P2 — presence-based gap classification: - SegmentJudgmentCache: Object.hasOwn presence → absent=legacy-missing, present-null/malformed=invalid-present (validatePresentWindow / validatePresentDenominatorKind) R6 failure-mode gate — two minimal matrices, every cell tested: - presence: absent / explicit-null / valid / invalid × {window, denominatorKind} × {get, getBatch, getHistory, route} - completeness: <100 / =100 / >100 × single/multi-epoch × fired/observe-only — incl. sol's multi-thread repro (101st row on active v2 → v2 tracing exact, never null) Evidence: API focused 126/126; web focused 94/94; web tsc 0; biome clean (1 pre-existing complexity + 2 pre-existing a11y warnings = baseline)
mindfn
added a commit
that referenced
this pull request
Jul 23, 2026
…54) * feat(F257): #6 slice 6c — eval window + denominator provenance (判据②) Why: Console lifeline projected incomparable metrics into one context — tracing(18) came from the CURRENT query window while eval injectionCount=0 came from the judgment's OWN historical eval window. CachedJudgment dropped window + denominatorKind at persistence, so the query window silently substituted for the eval coordinate. - CachedJudgment: window + denominatorKind required on producer writes (SegmentJudgment always has both); legacy Redis JSON reads normalize to explicit null (fail-visible, never guessed from evaluatedAt) - read model: EvalStageSummary.evalWindow/denominatorKind; response.window documented as the QUERY window (distinct coordinate) - chain builder: toEvalStageSummary propagates both (extracted helper — keeps attachJudgments cognitive complexity at baseline) - Console: eval panel shows 评估窗口(采样区间) + 分母; tracing panel labels 当前查询窗口; legacy → 评估窗口未知/分母未知 fail-visible Red→green: cache round-trip/legacy (4) + chain/route attribution (6) + jsdom render 18-vs-0 (6). API full-suite A/B vs base 231f960: failing list byte-identical (90) — zero new failures. Web vitest A/B: same 22 baseline failures, +6 new green. tsc 0 errors; biome 0 new warnings. [小狸/k3🐾] * fix(F257): #6 slice 6c R1 — composed 18-vs-0 viewport + Redis provenance fail-closed (sol P1-1/P2-1/P2-2) Why: sol R1 REQUEST CHANGES — (P1-1) query window and eval window were still in mutually exclusive panels, so tracing(18) vs eval(0) was NOT visible as two coordinates in one viewport (the stale test even encoded the bug by asserting the query window absent from the eval panel); (P2-1) the Redis read boundary trusted any PRESENT window/denominatorKind JSON, letting forged coordinates reach the UI as "Invalid Date ~ Invalid Date"; (P2-2) no real-browser evidence. - P1-1: EvalStagePanel gains queryWindow prop + compact 坐标对照 block — current observation count + CURRENT query window rendered next to the historical eval window/denominator in ONE viewport, explicitly labeled ≠ 评估窗口; LifelineStageDetail wires queryWindow through. The stale mutual-exclusion test is rewritten: query window may appear only as labeled contrast, never as the eval window. New composed render test (LifelineChainView + eval LifelineStageDetail) asserts one DOM holds tracing(18), 0, QUERY start/end, EVAL start/end, fired-count. - P2-1: normalizeCachedJudgment fails closed — window must be a non-array record with finite startMs/endMs in legal [start,end order; denominatorKind restricted to the closed union fired-count|session-count|none; non-record raw (e.g. JSON array) rejected outright. Malformed-present regressions added for get/getBatch/getHistory; legitimate legacy-missing still → null. - P2-2: replayable browser harness — showcase page /showcase/f257-eval-window (repo precedent: f052/f11-review) + packages/web/scripts/capture-f257-eval-window.mjs (production build + real chromium, fail-closed waits). Composed screenshot delivered in review handoff thread. Red→green: cache malformed 5 red → 25/25 green (file 20 + f257-eval-window 5); web composed 1 red → 7/7 green. API full-suite: 90 unique failures, zero touching judgment/lifeline/eval-window surface (baseline-count identical). Web vitest: same 22 baseline failures across same 8 unrelated files. tsc 0 errors; biome clean. [小狸/k3🐾] EOF ) * fix(F257): #6 slice 6c R2 — producer-reachable fixture + portable/clean evidence (sol P1-2/P2-3/P2-4) Why: sol R2 — the R1 test/showcase locked an IMPOSSIBLE judgment state (alive + injectionCount=0 + fired-count): segment-judgment-engine produceVerdict maps injectionCount=0 → unmeasurable + none, so the screenshot visibly contradicted the product contract (活跃:injectionCount>0 above 注入次数 0) and regressed the 6b loop model (unmeasurable returns the cycle to tracing, no governance). The committed capture script was machine-specific (user/OS/arch paths), and review-evidence/ dirtied the worktree. - P1-2: fixtures now reproduce the REAL incident — eval unmeasurable / injectionCount=0 / violationCount=0 / denominatorKind 'none', epoch status eval-pending, activeStage 'tracing', governance null (cycle back to tracing per 6b loop model). jsdom contract + showcase updated; composed test now asserts 无分母(不可计算比率)and the ABSENCE of fired-count for an unmeasurable judgment. Legacy case uses a producer-valid alive judgment with missing provenance fields. - P2-3: committed capture script REMOVED — the showcase route (/showcase/f257-eval-window) is the replayable artifact; evidence is captured with local browser tooling. - P2-4: review evidence moved outside the worktree (/tmp/f257-6c-eval-window); git status clean. Red→green: eval-window-provenance 7/7 green (producer-contract guard included); web tsc 0; biome clean; next build includes the showcase route. Fresh production-styled chromium capture attached in review thread. [小狸/k3🐾] * fix(F257): #6 slice 6c R4 — zero-length eval window fails closed (sol P2-1) Why: sol R4 REQUEST CHANGES — normalizeWindow only rejected startMs > endMs, so a forged {startMs: t, endMs: t} survived the Redis read boundary and the Console rendered `t ~ t` as a trusted eval coordinate. judgment-schema-v1 defines a [start,end sampling window (empty interval has no sampleable instant), and every canonical selector/adapter already rejects windowEndMs <= windowStartMs. The cache seam now enforces the same invariant: startMs >= endMs → null. Same-type audit (requested with the finding): scanned all provenance validators on the slice surface for the interval invariant — - publish-verdict/validation.ts ×3 selector validators: already reject - harness-ledger-generator-adapter.ts: already reject - segment-lifeline-chain.ts toEvalStageSummary: pure propagation, no re-validation — cache read boundary is the sole seam (now closed) - web EvalStagePanel: renders null as 评估窗口未知 (no own invariant needed) normalizeWindow was the only gap; no sibling instances remain. Integer constraint not added at this seam — producer window comes from epoch ms timestamps (finite numbers); end > start is the blocking requirement per the finding. Red→green: zero-length window test red (1 fail) → cache 21/21 + f257-eval-window 5/5 green; getHistory seam covered. Web jsdom 7/7; web tsc 0; biome clean. [小狸/k3🐾] EOF ) * fix(F257): #6 slice 6c R5 — current-side metric honesty + gap kind (sol P1/P2) Why: sol R5 REQUEST CHANGES (second-pass integration review) — (P1) the 6c contrast block rendered tracing observationCount (ALL observed rows, incl. observe-only, capped at MAX_OBSERVATIONS=100) as "当前观测 N 次注入" — reproducing the 18-vs-0 fake contradiction even with perfect window provenance, and presenting capped lower bounds as totals. (P2) legacy-missing and present-but-malformed provenance both normalized to null while the UI asserted "历史缓存缺字段" for both — a false cause for corrupted data. P1 — real metric + completeness: - TracingStageSummary gains firedCount + capped (shared contract) - collectObservations computes fired per row via segment-judgment-engine isFired (exported — single predicate source, producer semantics); capped=true ONLY when a matching row exists beyond MAX_OBSERVATIONS (exactly-100 is not "capped") - attachObservations counts both metrics + propagates capped - EvalStagePanel contrast block: 当前注入 = fired-count (≥N when capped, 下限 labeled); observe-only surplus shown as 观测行数 (≠ 注入次数); pending panel same split; 评估方式 keyed on firedCount - stale source-regex contract test asserting the old mislabel rewritten P2 — gap kind preserved end-to-end: - ProvenanceGapKind 'legacy-missing' | 'invalid-present' (shared) - cache read seam classifies both fields (absent=legacy, malformed=invalid), write path records gap null - toEvalStageSummary propagates; UI renders invalid-present as 评估窗口不可用(缓存数据损坏)/分母不可用 — never mislabeled legacy Red→green: API 5 red (observe-only / 101-cap / gap-kind chain+route+cache) → 99/99; web 3 red (contrast observe-only / capped / invalid-present render) → 93/93 across 4 affected files. Full API public suite: only the 4 known KD-17 stale assertions (17,421 pass); full web vitest: same 22 baseline failures in the same 8 files (5,169 pass, +50 new green). tsc 0 both packages; biome clean. [小狸/k3🐾] * fix(F257): #6 slice 6c R6 — exact aggregate counts + presence-based gap kind (sol P1/P2) Why: sol R6 REQUEST CHANGES (third-pass focused review @ edfa6d9) — (P1) the R5 global 100-row cap could project an active epoch with real observations as tracing:null (unsampled ≠ zero-data), and UI siblings still rendered bare capped counts; lower-bound provenance on counts was the wrong coordinate — completeness belongs to the DETAIL list only. (P2) raw == null cannot distinguish absent (legacy-missing) from present-null (invalid-present; the producer never writes null) — gap classification must be by own-property presence. P1 — aggregate counts exact, detail list capped: - collectObservations: full-window scan for counting; detail rows = 100 most recent + observationsCapped completeness flag (route response) - TracingStageSummary drops capped (counts are exact, never lower bounds) - EvalStagePanel/LifelineChainView/LifelineStageDetail: exact counts, no ≥/下限 markers; tracing panel shows 明细仅显示最近 100 条 note when observationsCapped P2 — presence-based gap classification: - SegmentJudgmentCache: Object.hasOwn presence → absent=legacy-missing, present-null/malformed=invalid-present (validatePresentWindow / validatePresentDenominatorKind) R6 failure-mode gate — two minimal matrices, every cell tested: - presence: absent / explicit-null / valid / invalid × {window, denominatorKind} × {get, getBatch, getHistory, route} - completeness: <100 / =100 / >100 × single/multi-epoch × fired/observe-only — incl. sol's multi-thread repro (101st row on active v2 → v2 tracing exact, never null) Evidence: API focused 126/126; web focused 94/94; web tsc 0; biome clean (1 pre-existing complexity + 2 pre-existing a11y warnings = baseline)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
index.tslimb registration to declarativeplugins/weixin-mp/plugin.yamllimbs/weixin-mp.yml→plugins/weixin-mp/limbs/weixin-mp.ymlplugins/weixin-mp/skills/weixin-mp/SKILL.mdlimbAdapterFactory: weixin-mp →WeixinMpLimbNode(with Redis), others →PluginLimbNodeBUILTIN_PLUGIN_IDS(env vars matchWEIXIN_MP_*prefix, builtin trust not needed)Depends on
Test plan
['test-skill-only', 'weixin-mp']🐾 [宪宪/Opus-46]
🤖 Generated with Claude Code