Skip to content

P3-M0 Spike · strip + cache + budget prototypes(3 plugins · fail-closed) - #1

Merged
bri12afdsarker96-lgtm merged 19 commits into
mainfrom
spike/p3-m0
Aug 10, 2026
Merged

bri12afdsarker96-lgtm merged 19 commits into
mainfrom
spike/p3-m0

Conversation

@bri12afdsarker96-lgtm

@bri12afdsarker96-lgtm bri12afdsarker96-lgtm commented Aug 9, 2026 •

Copy link
Copy Markdown
Owner

P3-M0 最终合入报告

  • 状态:merged
  • merge commit:a5ec1785b902e560c3cb2d3964dbdc795c2478ea
  • 验证 HEAD:0170c9bc70c9309e670495e1466eae3a1e4b7029
  • base:main@326bdfb
  • merge method:merge commit
  • core files modified:0
  • 共享 fixture:LF canonical SHA-256 47155c24dfd58019d1c7abdf3870e30099da29f56fe9535d4054d16b50168666

验证

后续

P3-M1 基线分支:feature/p3-m1-baseline@a5ec1785。

claude and others added 19 commits August 9, 2026 18:23
P3-M0 首轮 fork 侧 spike · Gate 0 CONDITIONAL PASS + Codex 批准。
本 commit 与 Hermes_AI 侧 claude/p3-m0-contracts 契约互链。

双仓 baseline SHA(冻结):
- fork(本方): spike/p3-m0 @ base main @ 326bdfb
- Hermes_AI:  claude/p3-m0-contracts @ base aa5c3c4(PR #27 merge commit)
- fixture SHA-256(跨仓一致): 0cd6ac14035292103ecf574aa6434cf3fcc4d0bdcbe9f21f32a0ba7e9ae26212

3 minimal prototypes(遵 AGENTS.md · 不改 core files · 只加 plugin dirs):

plugins/spike_p3_m0_strip/
- hook: transform_llm_output(agent/turn_finalizer.py:561)
- 覆盖出口 ① 普通最终响应 · 其他 4 出口 fixture 冻结不实装
- fail-open on sanitizer exception · 保留 raw canonical transcript
- 使用现有 24 lifecycle hook · 无 core hook 变更

plugins/spike_p3_m0_cache/
- seam: run_llm_execution_middleware(hermes_cli/middleware.py:187 · byte-identical fresh)
- fail-closed 无 tenant · fail-closed 非严格集合 request/response
- 只缓存 non-streaming + tool-free + finish=stop + tenant/permission_scope 完整
- key schema v1(sha256 · 12 字段 · 含 profile_version + collection_version)
- usage 拆分 origin/billable=0/saved · origin 不进 session totals
- 内存 LRU cap=256 · 非 persistent · 非 cross-session
- module-level API 供 fork core middleware 接线(接线由独立后续 PR)

plugins/spike_p3_m0_budget/
- hooks: pre_api_request reserve + post_api_request settle(T2 · profile.yaml)
- 明令不用 HERMES_DAILY_TOKEN_BUDGET 冒充 per-tenant budget
- fail-open 无 tenant · fail-close BudgetExceeded 超预算
- cache hit(response.cache_meta.hit=True)· settle 不 charge
- retry / fallback · reservation 每 api_request_id 独立

plugins/spike_p3_m0_fixtures/
- citation_cases.py 副本 · SHA-256 与 Hermes_AI 侧完全一致
- 17 case: 三态 + Unicode + 字节切分 + tool_result + history replay
- SHA-256 校验测试锁死跨仓一致性

Test results(在 fresh-ci venv + `HERMES_HOME=temp` + `SPIKE_*_ENABLED=1`):
- scripts/run_tests.sh plugins/spike_p3_m0_{strip,cache,budget}
- 3 files, 49 tests passed, 0 failed, 0.6s

明确不做(M0 边界):
- 生产接线 fork core middleware(需独立 PR · spike 只暴露 module API)
- streaming/interim/interrupt/history-replay/tool-result 各出口实装
- CAS · 跨进程 budget 同步 · persistent cache
- fork core files 改动
- M1 rebrand · Provider 正式接线 · MCP · Flutter
- 转 Ready / merge

后续:双 Draft PR 互链后停 · 交 Codex 复核。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FiKVo54oX4Uv2qksAHjYsZ
…are · fail-closed

Second-round Codex REQUEST CHANGES on fork PR #1 · all 9 blockers fixed:

1. Budget enforcement moved from pre_api_request hook (production swallows
   hook exceptions) into llm_execution middleware seam. cache plugin now
   registers as orchestrator: cache lookup → hit (no reserve, top-level
   usage=0) / miss → budget.reserve → next_call → success settle actual /
   exception release + re-raise.

2. Removed illegitimate config sources · no SPIKE_* env · no fork profile.yaml
   read · single source of truth = hermes_cli.config.load_config() reading
   plugins.spike_p3_m0_cache.{enabled,tenant}.

3. Strict tenant fail-closed · all 4 fields required (tenant_id, principal_id,
   permission_scope_version, profile_version) · non-empty · str type ·
   whitelist chars · len ≤ 128 · rejects default fills like "system"/"0".

4. Cache response usage split · top-level usage = billable_usage = 0 ·
   origin_usage lives only in cache_meta.

5. UTC daily bucket keyed by (tenant_id, YYYY-MM-DD) · threading.RLock ·
   verified by 40-thread barrier concurrent reserve/settle test.

6. Strip regex backward-compat with Hermes_AI ops/audit.py::strip_sources
   baseline · only strips explicit [source|来源:...] / independent-line
   source:/来源: · rejects unconditional bare [N] stripping · fail-CLOSED
   on sanitizer exception returns FAIL_CLOSED_PLACEHOLDER (NOT original text).

7. Fixture SHA-256 cross-repo gate · new SHA
   47155c24dfd58019d1c7abdf3870e30099da29f56fe9535d4054d16b50168666 · 8
   marker_types · 24 cases · byte-split enumeration for streaming S1
   verification.

8. Real PluginManager discovery tests · new shared helper
   spike_p3_m0_fixtures/pluginmgr_helper.py · fresh PluginManager() + tmp
   HERMES_HOME + monkeypatched global · discover_and_load(force=True) · 3
   spike test suites all invoke via run_llm_execution_middleware
   (cache/budget) or invoke_hook("transform_llm_output", ...) (strip) ·
   validates has_middleware/has_hook · does not pollute global manager ·
   disabled-plugin negative-case coverage.

9. Documentation fixes across docs/fork/ · orchestration order added ·
   observer-only contract for budget hook · 4-state budget clarified · M0
   final-only scope for strip (2/3/4/5 fixture-frozen · skeleton).

Test results:
- plugins/spike_p3_m0_cache/tests: 19 passed
- plugins/spike_p3_m0_budget/tests: 23 passed
- plugins/spike_p3_m0_strip/tests: 63 passed
- total: 105 passed in 10.72s

Zero non-plugin file changes · opt-in via plugins.enabled · discovery
manifest scan side-effect free.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FiKVo54oX4Uv2qksAHjYsZ
…et · fail-CLOSED · Windows CRLF

Second Codex re-review on fork PR #1 · all 7 blockers addressed:

§I Windows CRLF fixture SHA — .gitattributes now pins the shared strip
   fixture to LF; SHA gate reads canonical LF bytes so Windows autocrlf
   checkouts pass. Adds test_fixture_sha256_crlf_input_normalizes to
   assert the CRLF → LF normalization keeps the frozen SHA
   (47155c24…) intact.

§II cache + budget control-flow decoupled — cache eligibility no longer
   short-circuits budget enforcement. New order in
   cache_and_budget_middleware:
     disabled                  → next_call
     budget module missing     → fail-CLOSED (never call provider)
     unsupported api_mode      → fail-CLOSED (M0 narrows to chat_completions)
     non-dict request          → fail-CLOSED
     tenant context invalid    → fail-CLOSED (next_call == 0)
     budget config invalid     → fail-CLOSED (no silent None)
     missing api_request_id    → fail-CLOSED
     cacheable hit             → return cached, no reserve
     cacheable miss OR non-cacheable → reserve → next_call →
                                       success settle / exception release
   All fail-CLOSED paths return a non-empty assistant message with
   finish_reason=content_filter (Codex §V: safe, displayable, no retry).

§III Strict config typing + plugin dep gate — _is_enabled is now
   `v is True` (rejects "false", 1, [], {}, YAML-string coercion).
   BudgetConfigError never becomes None; orchestrator fail-CLOSES.
   Removed the loose sys.modules fallback in _budget_module so cache
   cannot silently satisfy the dependency when the budget plugin is
   disabled — new test_cache_alone_without_budget_fail_closed proves it.

§IV Budget ledger strict idempotency — new BudgetSettleError raised on
   settle without a live reservation (unknown / double / post-release);
   the old "settle unknown accumulates used" contract was a laundering
   hole and is removed. release() returns bool for idempotency.
   New tests:
     - test_concurrent_reserve_precise_success_and_exceeded_counts:
       40 threads at barrier, budget = 15 × per-req → exactly 15 succeed,
       25 raise BudgetExceeded, no overspend
     - test_utc_day_rollover_isolates_bucket: monkeypatch _today_utc to
       simulate Day2 checkout; yesterday's used cannot poison today.

§V Production adjacency — _estimate_needed_tokens no longer silently
   returns 0. It prefers ctx.approx_input_tokens (>0), else estimates
   from messages content chars / 4 (documented M0 approximation, with
   an M1+ pending contract for a real tokenizer). New tests prove:
     streaming + over-budget          → next_call == 0
     tools    + over-budget          → next_call == 0
     non-cacheable req                → still reserves+settles
     provider exception               → releases reservation, re-raises
     char-count fallback              → catches unbudgeted large prompts

§VI Strip differential + hook order risk + sys.modules snapshot —
   New plugins/spike_p3_m0_fixtures/hermes_ai_baseline.py vendors
   Hermes_AI hermes_devices/ops/audit.py::strip_sources verbatim
   with a byte-check test locking the pattern strings.
   New tests/test_strip_differential_vs_hermes_ai.py (27 tests)
   locks the two intentional UX divergences (\s? leading, .rstrip()
   vs .strip()) and proves coverage inclusion + negative parity.
   pluginmgr_helper adds snapshot_plugin_modules / addfinalizer
   cleanup; new leak test compares names + object ids after teardown.
   sanitize.py docstring pins the UX diffs explicitly.

§VII delivered via scripts/run_tests.sh; full CI-faithful regression
   run reported in PR body.

Test results (scripts/run_tests.sh, 8 workers, deterministic env):
  - test_strip_differential_vs_hermes_ai.py — 27 passed, 0.5s
  - test_strip_final.py                     — 65 passed, 5.3s
  - test_budget_reserve.py                  — 28 passed, 5.3s
  - test_cache_middleware.py                — 35 passed, 6.4s
  Total: 155 passed / 0 failed in 6.4s

Non-plugin footprint: only .gitattributes gained one line for the LF
rule on the shared fixture. All other changes stay inside opt-in plugin
directories.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FiKVo54oX4Uv2qksAHjYsZ
…t]?` newline swallow

Codex third-round §7 identified a real bug in the fork's UX-enhanced strip
regex: the leading `\s?` matches `\n` (Python `\s` includes newlines), so
`"Line A\n[source: k]Next word"` collapsed into `"Line ANext word"` — the
newline separating two lines was silently eaten by the marker match.

Fix:
- sanitize.py `_INLINE_SOURCE_RE`:  `\s?` → `[ \t]?`  (horizontal whitespace only)
- Docstring updated to reflect the tighter constraint and add the newline
  safety proof to the diff table.
- Differential test pattern-lock:
  `test_fork_inline_pattern_is_baseline_plus_leading_horizontal_space`
  now asserts `[ \t]?` rather than the previous `\s?`.

New negative-proof tests (8) locking Codex §7 requirements:
  1. test_leading_space_does_not_eat_newline_before_marker
     — `"Line A\n[source: k]Next"` → `"Line A\nNext"` (no line concatenation)
  2. test_leading_space_does_not_eat_double_newline_before_marker
     — paragraph separator preserved
  3. test_leading_space_only_absorbs_single_space_or_tab
     — parametrized: `" "` and `"\t"` absorbed; `"\n"` and `"\r"` preserved
  4. test_whitespace_diff_does_not_widen_source_deletion_range
     — non-source `[normal]` bracket preserved next to `[source: k]`
  5. test_trailing_whitespace_rstrip_scope_matches_baseline_content
     — trailing spaces / newlines both stripped, content byte-identical
  6. test_leading_whitespace_only_diff_between_baseline_and_fork
     — leading indent preserved by fork, stripped by baseline; content matches
  7. test_middle_line_source_marker_preserves_surrounding_newlines
     — `"Line 1\nLine 2 [source: k]\nLine 3"` → both `\n` preserved
  8. test_marker_at_start_of_line_preserves_prev_line_terminator
     — with and without leading horizontal whitespace variants

Also switched two docstrings to raw-string form (r"...") to silence
Python 3.13 DeprecationWarning on `\s` / `\[` escapes.

Test results (scripts/run_tests.sh, 8 workers):
  test_strip_differential_vs_hermes_ai.py — 35 passed (was 27, +8 §7 tests)
  test_strip_final.py                     — 65 passed
  test_budget_reserve.py                  — 28 passed
  test_cache_middleware.py                — 35 passed
  Total: 163 passed / 0 failed in 3.6s

The `\s?` → `[ \t]?` change is UX-neutral for existing fixture cases
(no fixture case had `\n` immediately before a source marker) but removes
the latent line-concatenation bug against real streaming/multiline
provider output.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FiKVo54oX4Uv2qksAHjYsZ
@bri12afdsarker96-lgtm
bri12afdsarker96-lgtm marked this pull request as ready for review August 10, 2026 14:16
@bri12afdsarker96-lgtm
bri12afdsarker96-lgtm merged commit a5ec178 into main Aug 10, 2026
42 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.

2 participants