Skip to content

fix(periodic-report): name declared source adapters that failed activation - #6426

Merged
huangruiteng merged 3 commits into
loopx-project:mainfrom
songoow:codex/hook-adapter-failure-surfacing-20261014
Oct 14, 2026
Merged

huangruiteng merged 3 commits into
loopx-project:mainfrom
songoow:codex/hook-adapter-failure-surfacing-20261014

Conversation

@songoow

@songoow songoow commented Oct 14, 2026

Copy link
Copy Markdown
Collaborator

Goal / source

docs/reference/extensions.md says hook-adapter discovery "admits a factory only when the extension is enabled, doctor-ready, and authorized for every declared adapter permission" and that failures stay isolated. The periodic-report README says "a temporarily unavailable owner leaves the request pending; LoopX never falls through to another provider." Both hold. What was missing is that the failure was invisible.

Current gap

loopx/extensions/hook_adapters.py already records each failed adapter as a content-free ExtensionHookAdapterFailure, but discover_periodic_report_request_ports (request_action.py) built PeriodicReportRequestPorts(adapters=complete) and dropped discovery.failures. So:

  • a declared Lark adapter whose factory import failed, or whose extension was disabled / doctor-stale / API-incompatible, produced periodic-report request source adapter is unavailable with no adapter id, no code and no next step — the same text as "no provider installed";
  • an unreadable extensions/state.json returned an empty discovery, also indistinguishable from "nothing installed".

This is the silent-fallback shape AGENTS.md asks to avoid: control flow stayed fail-closed, but the actionable context was discarded.

Observable result

situation before after
--source-adapter-id probe-source, factory import fails …unavailable: probe-source …unavailable: probe-source is declared but failed activation (extension_hook_adapter_unavailable); inspect loopx extension doctor --execute``
no selector, only failed adapters …unavailable …unavailable: declared adapters failed activation: probe-source (extension_hook_adapter_unavailable); inspect …
adapter declared twice / missing a port silently absent ambiguous_adapter_ports / incomplete_adapter_ports named
extensions/state.json unreadable empty catalog discovery state_error_code = extension_state_unavailable; selection says extension state could not be read

Receipts, journal entries and schemas are unchanged; only refusal text gains identity and next action. ExtensionHookAdapterDiscovery.state_error_code and PeriodicReportRequestPorts.failed_adapters/state_error_code are defaulted, so existing constructors keep working.

Real-path readback (this head)

Installed an extension through loopx extension install --execute whose [[hook_adapters]].factory = "missing_extension.hooks:build_adapter", then called discover_periodic_report_request_ports on the real state file (no monkeypatch): adapters=(), failed={'probe-source': 'extension_hook_adapter_unavailable'}; both selection errors shown above reproduced verbatim.

Validation

  • New test_request_ports_name_declared_adapters_that_failed_activation and test_request_ports_name_unreadable_extension_state; existing real-path discovery test extended with the unreadable-state case. All fail on base.
  • test_lark_periodic_report_request + test_extension_runtime + test_capability_code_maps: 79 passed. Periodic-report suites (pending_intent, native_successor, turn_writeback, cadence_journal): 72 passed.
  • ruff clean; semantic advisory: no new vocabulary carriers. README states the rule.

Rollback

Revert the single commit.

@loopx-agent loopx-agent left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewer: model_agent; gpt-6.1-sol; OpenAI; runtime_reported; reasoning_effort=xhigh.

Exact head: 51f6671. REQUEST_CHANGES — 两项 P2:显式 selector 隐藏 state-read loss;真实 disabled/doctor-stale 声明在 producer 前被跳过。

动机

从 Goal Channel 请求报告的用户和维护该来源适配器的操作者,需要区分未安装、声明无法激活和状态读取失败。以前导入失败的已声明 adapter 与空 catalog 都只报不可用;本改动让部分失败带上名称、无内容的错误码和修复提示,但显式选择下的坏状态文件、disabled/doctor-stale 声明仍退回旧通用提示。实际 CLI 已验证 factory 失败和未指定 adapter 时的状态错误更明确;完整承诺仍有两处已复现的诊断缺口,不能认定所有声明失败都已可观察。本 PR 不新增报告能力、开启扩展、运行 provider 修复、授予权限或外部写入;不认证真实 Lark、已安装前端或模型采用。

改动思路

保留 extension lifecycle/permission 与报告 journal 的原 owner;只把无内容的失败事实投影到现有拒绝信息,不新建启用策略、Python 授权源或恢复框架。当前边界是既有报告来源选择的诊断修复;在名称/原因传输完整后可独立交付,不需要把扩展升级、API 兼容或安装态报告旅程全部并入本 PR。 最强反例是测试手工供应 failure record,真实 discovery 却提前丢掉声明;formatter 正确并不证明操作者能得到其原因。当前 factory failure 的改善确有价值,不因 line count 或拒绝配额否定;完整宣称须与真实 producer/caller 一致。

源码流程为 CLI request → installed catalog/activation → complete port选择或失败信息 → 原 journal/settlement。此路径说明源自代码,非额外运行证据。使用下面实测对照表而非未经渲染核验的图。

实际来源/选择 固定 base 当前 head
已启用、doctor通过、factory import失败;显式/省略id 通用 unavailable 名称、content-free code和doctor指针
状态坏JSON;省略id 通用 unavailable 明确 extension state could not be read
同一坏状态;显式id 通用 unavailable: id 仍相同,state loss被遮住
真disabled或真实runtime identity失效;两种id选择 通用 unavailable 仍相同,failure集合空
真空catalog;三个selector 原拒绝 全部完整输出相同,无效果

doctor提示不执行doctor或enable,不授扩展权限。原绑定/结算仍只认journal中的adapter,未增加fallback。声明可见与factory准入应复用既有owner分别处理;把“不能激活”表现成“未声明”会让修复路径失真。

具体改动

改动前规范为 docs/reference/extensions.md 的完整版本 70bc981,Manifest Contract L770–828,以及同revision loopx/capabilities/periodic_report/README.md 的 Request from a Goal Channel L59–96;实现前已读。完整PR为5文件+186/-8,两处Python owner、两处测试、一段README。Manifest Contract 与 Request from a Goal Channel implemented:原权限/完整port选择/固定journal owner保留。current-state-diagnostic 和 current-declared-failure-diagnostic 是本PR明确提出的当前交付要求(不是发明的roadmap编号),分别not_met:下面的真实反例仍失败。S8/S10只用于归属/诊断框架,不要求完成整个路线图,也不拿新README反向定义旧规则。

关键代码讲解

  • ExtensionHookAdapterDiscovery L41 增加defaulted transient state_error_code;catalog读失败返回既有extension_state_unavailable词汇,不带异常内容。
  • discover_extension_hook_adapters L78 仍由原activation验证后才导入factory。但L107的ready预过滤使disabled/stale连匹配声明与_failure都未进入,影响新诊断宣称。
  • _unavailable_detail L85 集中命名failure,但L92 selected-id通用返回早于L94 state error;当前全局读损失因此只在省略selector时出现。
  • discover_periodic_report_request_ports L185 保持complete bind/settle grouping,转交已有failure和ambiguous/incomplete标签;不改变合法selection和settlement_adapter。
  • 新测试覆盖手填broken/partial/duplicate与省略id的state error,旧真实factory fixture扩展坏state;README承诺disabled/stale等都命名。手填记录不能证明真实生命周期failure producer已采用。

新state字段是派生诊断hint,不是新持久化权威;两类port标签局限于该formatter,不能驱动权限。既有extension_state_unavailable复用原resolver词汇,没有新capability、provider包或配置开关。Python在当前专门manifest/factory IO及展示边界,TS授权规则未复制。

对主干的风险

P2,显式selector遮住状态读取失败。 真实CLI安装合成extension后,把独立state改为坏JSON;省略id正确报state read loss,传--source-adapter-id probe-source或never-declared却只报unavailable: id。没有raw content、factory或journal效果;问题是可行动上下文丢失。将全局 state_error_code 在显式 selector 的通用 unknown-id 返回之前处理;保留真实 failed-id 的明确诊断、合法选择、现有错误 schema 与无效果拒绝。真实 CLI 覆盖破损 state+已知/未知显式 id,不能只测 selector=None。 这不是要求改变selector授权或为错误状态选择另一provider。

P2,新声明失败投影没有真实disabled/stale producer。 README L78 明确列出disabled、doctor-stale且称never silently dropped;真实install后调用原disable,或修改doctor已验证的可执行文件使identity失效,catalog的ready=false,discovery在L107跳过。实际ports=()、failed_adapters={}、state_error_code=None;显式和省略id都是原通用unavailable。gate仍正确拒绝,不是权限绕过或新增执行回归,但当前完整改善未交付。在既有 discovery owner 中先识别匹配声明,再把生命周期未准入记录为现有 content-free failure;disabled/stale 分支禁止加载 factory 或执行 doctor。保留每个 hook 的 scope filter和完整端口规则。若当前只交付已有 factory failures,应明确缩小 README/PR/测试资格,而不能宣称 disabled/doctor-stale 已命名。

语义与 CI 对齐

独立真实CLI共5状态×3selector的base/head对照,完整JSON/exit/stderr比较;9个空catalog/disabled/stale输出逐字相同,所有15拒绝前后runtime文件hash不变。factory显式/省略两个独立语义oracle在base失败、head通过;显式坏state及disabled/stale三项head oracle仍失败。原生install/doctor用合成无外部效果provider;source请求始终拒绝,无真实Goal、Lark消息或provider内容。全流程在各自隔离root,未借activeGoal。

当前七个完整module 151 passed/0 skipped,63.82s;固定base三个extension/codemap module 77 passed/0 skipped,25.42s。72个报告pending/native-successor/writeback/cadence用例验证原journal/retry/ACK边界。测试正向有效,未覆盖上面的实际failure组合。canary 5 direct/15 selected、0失败/0hold;Ruff、diff、5路径公开scan、advisory后全semantic通过。advisory只分析2生产路径/0 supported carriers,不证明动态/局部string语义;全semantic producer/cross-runtime未验限制保留。预算workload/limit/root未改。

检查了相关#6425 API revalidation、#6418 disabled upgrade、#6420参考manifest修复的当前frame;它们是各自owner的提案,不继承其验证,不要求先merge它们。这两处实际诊断缺口在本head独立成立。默认未安装/禁用仍无factory/admission/side effect;有意诊断文字改变已披露,generic owner无Lark私有payload/词汇或substring判定。新字段不叫权限或actor lifecycle;doctor是guidance,原权限/settlement是machine obligation。

未查询、轮询、等待或触发GitHub CI。CLI错误路径实际验证;真实Lark/安装前端/模型理解未执行,也不宣称新产品能力已完整发布。首次reviewer调用用了不存在的codemap路径、install位置参数,均保存并按仓库原CLI修正;不是PR失败,不以删断言变绿。

我的整体评价

REQUEST_CHANGES,只绑定 51f6671。归属与规模合理、部分实测改善成立;两处关键failure composition仍未达到当前明确宣称。保留 extension lifecycle/permission 与报告 journal 的原 owner;只把无内容的失败事实投影到现有拒绝信息,不新建启用策略、Python 授权源或恢复框架。当前边界是既有报告来源选择的诊断修复;在名称/原因传输完整后可独立交付,不需要把扩展升级、API 兼容或安装态报告旅程全部并入本 PR。 bounded future-facing pass已通过共同formatter收敛;下一步应在同owner让声明诊断与activation分离并修复优先级,不增加parallel readiness/state框架。最小重验是实际installed生命周期和坏state的已知/未知/省略selector对照,保留无效果/原owner/no fallback,再跑现有完整测试。合并/启用/对外写权限另行判断。

English verdict: REQUEST_CHANGES — 51f6671. Real install/discovery/CLI comparisons confirm the factory-failure and omitted-selector state diagnostics improve, while explicit selectors still hide unreadable extension state and disabled/stale declarations are dropped before failure production. Preserve admission/no-effect/no-fallback, fix these projection branches or accurately narrow the promised lifecycle scope, and exercise real installed cases. All151 current tests and risk checks pass; mocked failure records do not cover the two reproduced gaps. No new permission or production effect regression is alleged.

…ation

`discover_extension_hook_adapters` already isolates every lifecycle,
permission, factory-import and port-mismatch failure as a content-free
record, but `discover_periodic_report_request_ports` read only `ports` and
dropped `failures`. An operator whose Lark adapter was declared yet did not
activate saw "source adapter is unavailable" with no adapter id, no code
and no next step, indistinguishable from having no provider installed.
An unreadable extension state produced the same empty result.

Carry the failures through: `PeriodicReportRequestPorts` keeps a
content-free `failed_adapters` map (declared-but-failed, ambiguous ports,
incomplete ports) and the discovery-level `state_error_code`; selection
names the failed adapter and points at `loopx extension doctor`. Discovery
reports an unreadable state file as `extension_state_unavailable` instead
of an empty catalog. Receipts and journal entries are unchanged; only the
refusal text gains the missing identity and next action.

Signed-off-by: song <liusongstep@gmail.com>
…ions

Hook-adapter discovery skipped every catalog entry that was not ready
before matching its declarations, so a disabled or doctor-stale extension
left no failure record and the periodic-report request fell back to the
generic "source adapter is unavailable" refusal.

Match each declaration against the requested phase, capability and hook
first, then record lifecycle non-admission as a content-free failure that
reuses the extension readiness statuses `extension_disabled` and
`extension_doctor_not_ready`, now named once in the runtime owner and
shared with optional capability resolution. These branches never resolve
activation, import the factory or run the doctor; ready extensions keep
the existing activation path and `extension_hook_adapter_unavailable`.

Signed-off-by: song <liusongstep@gmail.com>
…electors

With `--source-adapter-id`, an unreadable extension state was reported
only as "unavailable: <id>", hiding the state loss that the omitted
selector already named. Report `extension_state_unavailable` before the
generic unknown-id detail. A named failed id keeps its own diagnostic;
legal selection, journal ownership, settlement and the no-fallback
refusal are unchanged.

Word non-activated declarations as "not activated", since a disabled one
never attempted activation, and narrow the README to the codes that are
delivered (the API-incompatible claim had no producer). Cover the real
installed lifecycle through the CLI: disable, doctor-identity
invalidation and corrupt state across omitted, known and unknown
selectors, asserting no factory import and unchanged runtime files, plus
the plain refusal when no extension is installed.

Signed-off-by: song <liusongstep@gmail.com>
@songoow
songoow force-pushed the codex/hook-adapter-failure-surfacing-20261014 branch from 51f6671 to 361ddf1 Compare October 14, 2026 16:58
@songoow

songoow commented Oct 14, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed both P2s. The branch is rebased on f713e0f; the new head is 361ddf1, with two commits on top of the original one.

P2-1, explicit selector hid state loss (request_action.py, _unavailable_detail):

  • The global state_error_code is now checked before the generic unavailable: <id> return.
  • Bad state with an omitted, known (probe-source) or unknown (never-declared) selector now always reports extension state could not be read (extension_state_unavailable).
  • A named failed id still gets its own diagnostic, and selection, the journal, settlement and no-fallback are unchanged.

P2-2, disabled/doctor-stale declarations dropped (hook_adapters.py):

  • The ready pre-filter is gone. Each installed extension's declarations are first matched by phase, capability and hook.
  • Non-ready ones become content-free failures with the existing readiness codes extension_disabled / extension_doctor_not_ready. Those codes are now constants in extensions/runtime.py, shared with optional-binding resolution.
  • That branch never resolves activation, imports the factory or runs the doctor.
  • Declared-only, not-installed manifests are still skipped, so they cannot be reported as "disabled".

Tests (real installed lifecycle, no hand-built failure records):

  • Discovery: install, then a positive control showing a ready extension reaches the factory loader. Then real disable, re-enable, and changing the doctor-verified executable. Assertions: named adapter and code, zero factory loads, state bytes unchanged, and an adapter declared for a different hook is not named.
  • Real loopx periodic-report request --execute CLI over {disabled, doctor-stale, bad state} × {omitted, known, unknown selector}. Assertions: exit 1, the adapter or state-loss code, factory module not imported, file tree unchanged, no journal.
  • Reverting either fix fails its tests.

Results:

  • Extension and periodic-report modules: 382 passed.
  • Broader 44-module set: 1428 passed / 1 failed. The failure is test_content_digest_single_owner.py::test_only_the_owner_module_states_a_whole_value_digest_shape (codex_sessions.py), which fails identically on clean main.
  • canary premerge --from-git-diff: 5 direct / 18 selected / 0 failures / 0 holds, run with --timeout-seconds 600. The drift smoke takes about 285 s on both base and head, which exceeds the default 120 s limit.
  • Semantic advisory: 0 candidates.

Scope wording:

中文:显式 selector 下坏 state 现在优先报告;disabled/doctor-stale 声明改为先匹配再以既有就绪码记为无内容失败,且不导入 factory、不运行 doctor。测试走真实安装/禁用/doctor 失效与真实 CLI;README 只承诺当前分支实际产生的代码。

@loopx-agent loopx-agent left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewer: model_agent · gpt-6.1-sol · OpenAI · runtime_reported · xhigh

Exact head: 361ddf1. APPROVE.

动机

从已连接来源请求报告的用户,需要知道来源没安装、被禁用、doctor 资格失效,还是扩展状态无法读取。过去这些情况混成“来源不可用”,即使操作者显式指定来源,也可能看不到真正原因。本次把现有声明和读取失败传到原拒绝信息,真实 CLI 已显示对应名称、原因和修复指针;有效来源的选择、固定结算 owner 与无效果拒绝保持。这个切片完成既有来源选择的诊断修复,不扩大报告能力。真实 Lark、已安装前端、模型理解、全部安装升级故障和长期净收益未验证;不新增启用、发布或合并权限。

改动思路

保留原 extension lifecycle/permission 和报告 journal owner:discovery 区分“声明存在但未准入”与“状态根本读不到”,报告 ports 只把事实投影成可行动的错误。disabled/stale 分支先匹配 phase、capability、hook,再记录既有 lifecycle code,不导入 factory,不运行 doctor。ready 分支继续走原 activation、权限与完整端口校验;失败不能变成另一个 provider 的授权。

共享诊断事实并复用已有 readiness 状态,比再建启用策略、重试框架或 Python 授权源更小。当前 PR 边界是 discovery 到既有报告 request 的诊断传递及必要测试和文档,不新增 capability、设置或用户入口。runtime 中两处状态常量复用已完成有界重构;现有共享入口保留原调用与规则,没有进一步框架化的必要。

下面是实际 base/head CLI 对照;表格解释结果,不替代执行证据。

触发 common base 当前 head
disabled/stale;省略或已知 id 通用不可用 名称及 lifecycle code,未导入 factory
状态坏 JSON;省略/已知/未知 id 通用不可用 明确 state-read loss
ready 但 factory 缺失 通用不可用 命名失败和 doctor 指针
空 catalog、有效选择、未知结算 owner 原拒绝或原选择 原行为保持

具体改动

完整复核 common base f713e0f 上七文件 +565/-21,三个生产模块、两份文档和两个测试模块;三次提交均有 DCO。规范依据 docs/reference/extensions.md @ f713e0f 的 Manifest Contract:只有 enabled、doctor-ready、逐项授权才准入,失败隔离,implemented;同 revision 的 loopx/capabilities/periodic_report/README.md 的 Request from a Goal Channel:绑定 journal owner、不可临时换 provider、失败待恢复,implemented。当前修复也覆盖旧 review 指出的显式 selector/state-loss 与真实 lifecycle producer 缺口。

关键代码讲解

  • _lifecycle_non_admission / discovery 从 catalog 的 installed/enabled/ready 派生诊断;匹配作用域后命名 disabled/stale,不执行 factory 或 doctor。catalog 读取失败保留单独 state_error_code。
  • PeriodicReportRequestPorts._unavailable_detail 保留已知 failure 优先,再让全局 state loss 先于普通 unknown-id,省略、已知和未知 selector 都看得到读取失败。用“未激活”覆盖从未尝试激活的 disabled 状态,避免错误叙事。
  • discover_periodic_report_request_ports 继续完整 bind/settle 分组和重复端口拒绝,再转交失败集合;失败记录不能替代 complete adapter,settlement 仍按已持久 owner 查找。
  • runtime 状态常量 仅集中已有 extension_disabled / extension_doctor_not_ready 值。独立比较整个 runtime AST,代回两处常量后与 base 相同,没有复制新的 lifecycle 决策。

全仓调用扫描表明当前生产 discovery 消费者是该 request owner。失败集合与 state_error 是派生诊断,不新增持久状态或 actor lifecycle。Python 位于既有 manifest/factory IO 与显示边界;权限和内核权威未迁移。

对主干的风险

无当前阻塞发现。独立 Python 3.11.17 真实 CLI install/doctor、disable、可执行文件 identity 变异、坏状态、factory 缺失,对五种状态各跑省略/已知/未知 selector,共十五个拒绝。每次 runtime 全树 hash 不变,factory marker 不出现,doctor 计数不变,没有 Goal journal。实际 ready factory 的导入、单 provider 选择和结算 owner 查询有正向控制,防止“没导入”是空测试。base/head 有九处预期诊断变化、六处完整 JSON/exit/stderr 相等;未归一错误、授权或拒绝事实。

额外在真实旧提交 51f6671 运行同一 harness:disabled/stale 的省略/已知 id 及坏 state 的已知/未知 id 六个旧 P2 反例仍失败,当前全部修复。旧版已修的省略 state-loss 保留;factory 两处措辞由 failed activation 改为 not activated,不把额外格式变化混成新权限。

语义与 CI 对齐

156 项完整相关测试/0 skip,包括原 pending、successor、writeback、cadence journal 的重试与 ACK 边界;canary 5 direct / 18 selected 全通过,0 failure/hold;Ruff、diff、公开边界、advisory 后完整 semantic 通过。advisory 的零 supported carriers 不是无语义变化证明。现有 readiness code 复用;新增 transient port 诊断保持局部,没有私有组织文案或字符串型 Goal 分类。

feature-off 并未被省略:未安装保持原通用拒绝,disabled/stale 始终无 factory、doctor、provider 写入或 journal,新增的只是已披露诊断。doctor 指针是建议,原 admission/权限和 journal owner 是强制条件。没有 UI/设置变更,也没有真实 Lark、前端或模型采用资格;未查询、等待或触发 GitHub CI。最初 probe 误用无 main 的入口、再尝试同版本不同 manifest 重装,已按原 CLI 和独立 runtime 更正;这些测试准备错误没有计入产品失败或通过。

我的整体评价

APPROVE,仅绑定当前完整 head,不继承旧结论。长期维护改善在于失败能回到正确 owner,用户诊断改善有真实反例与读回;没有把测试数量当作整个报告旅程或长期收益。规模与已有 owner 合理,完整端口、scope filter、默认关闭和无 fallback 保持。旧正文仍有旧测试数、API-incompatible 及“revert single commit”描述,当前 README 和本评审明确最终范围;不宣称相关 API 重验或升级回执的其他 PR 已完成。合并 readiness 与维护者 authority 单独判断。

English verdict: APPROVE — 361ddf1. Full-scope re-review and real install/CLI probes verify both prior P2 fixes, preserved legal selection and fifteen effect-free refusals. Nine intended diagnostic deltas and six unchanged refusal outputs are recorded against the common base; six caller counterexamples fail on the prior head and pass here.156 tests and current risk checks pass. No live Lark, installed frontend, model adoption or merge authority is claimed.

@huangruiteng
huangruiteng merged commit faf1059 into loopx-project:main Oct 14, 2026
47 of 60 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants