feat(F257): #2 L系列段观测粒度 — native-L0 L1-L7 trace from the actual L0 compiler manifest - #49
Merged
Merged
Conversation
…e path ## What Make L1-L7 (governance-core session identity) observable per-segment for native-L0 cats (Claude/Codex/OpenCode), so the Console segment lifeline is no longer blank for them. ## Why (root cause — verified by reading, not the plan's guess) The slice-2 plan located the fix at trace-collector.ts:116-129 (the `session-init-pack-only` aggregate branch). Investigation of the actual call graph proved that branch is DEAD CODE for native-L0: - native-L0 builds session identity via buildStaticIdentityPackOnly() which does NOT run the hook pipeline → drainCapturedTraces().session is null. - route → buildFromPipeline(null, turnResult, …): trace-bridge.ts:50 returns non-null (turnResult/D-hooks present) with an EMPTY session-segment array → the collectTrace fallback (incl. pack-only branch) is never reached. - Net: native-L0 traces carry only D-series per-turn segments; L1-L7 never become ObservedSegments → segment-lifeline queries return nothing. ## Fix (bridge path, single-source) collectNativeL0SessionTrace() runs the session-init pipeline in trace-only mode (prompt discarded — the native --system-prompt-file is authoritative) and returns the L-scoped PipelineResult. The routes feed it as buildFromPipeline's sessionResult for native-L0 only (non-native path unchanged). eventsToSegments already maps L1-L7 → ObservedSegment (id/version/fired/hash/tokens); zero bridge or downstream-consumer changes. Respects the override snapshot already refreshed by the routes (refreshOverrideSnapshot precedes the trace block). ## Tradeoff (disclosed for review) Adds one session-init pipeline run per native-L0 turn, but only inside the fire-and-forget trace block (after prompt assembly; never on the model-call critical path) and only when a trace store is present. L-series is deterministic per cat, so a per-cat cache (invalidate on OverrideChangeEvent) is the follow-up optimization (KD-15 "L0 编译链 + cache invalidation" runway). Fail-open: returns null on error → degrades to prior D-only behavior, never breaks invocation. ## Non-goal Pack-block observation (buildStaticIdentityPackOnly output has no hookIds) stays out of scope; #2 is L-series. Delivery channel left 'pack-only' (existing contract, trace-bridge.test.js:155) — lifeline/judgment read segments, not channel. ## Evidence - red→green: pre-fix native-L0 = 0 observable L segments; post-fix = 7 (L1-L7). - new test f257-lseries-trace.test.js 4/4 (unit + bridge map + §16e store→lifeline reachability + registry metadata). - tsc clean; zero new biome warnings (count parity vs develop_base baseline). - affected suites green: trace-bridge/injection-trace-store/segment-lifeline/ l0-pipeline-equivalence/hook-pipeline/segment-judgment-engine(+harness-eval)/ segment-lifeline-chain/dual-path/transport-boundary/hook-pipeline-integration/ hook-registry/system-prompt-builder. - agent-router 89/11 and prompt-segments-eval-domain 11/4 failures proven pre-existing via git-stash baseline parity (identical names before/after). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…nifest Reworks the rejected parallel producer per sol 2b R1 (all four findings). ## P1-2 (root) — source the trace from the ACTUAL compiled artifact, not a reconstruction Deleted collectNativeL0SessionTrace (reran the API hook pipeline — a separate, override-respecting code path that could report L content the override-blind native compiler never delivered). Instead: - scripts/compile-system-prompt-l0.mjs: compileL0WithManifest() captures each L1-L7 section at the one loop where per-segment content still exists (before flattening + label-stripping) + new --manifest-out CLI flag (orthogonal to --out; stdout still carries the prompt for Codex/OpenCode). - l0-compiler.ts: l0ManifestCache populated in LOCKSTEP with l0Cache inside doCompileL0 (same subprocess, same generation guard, cleared together); getL0ManifestViaSubprocess() is cache-first and, on a cold cache, shares the provider's own compile via in-flight dedup — no redundant full-stage run. Manifest read is best-effort (never fails the fail-closed string compile). - l0-manifest-trace.ts: adapts the manifest → session PipelineResult (hash/token via the existing conventions; version from the hook registry) → bridged through buildFromPipeline. Verified end-to-end: real mjs emits all 7 L segments with byte-accurate content (L4 = "1. **Runtime data safety**…"). ## P1-1 — native-l0 delivery channel (not pack-only) trace-bridge buildDelivery emits channel 'native-l0' when the session came from the compiler manifest (new meta.sessionFromNativeCompiler); 'pack-only' stays correct for actual pack blocks (no global relabel — trace-bridge.test.js:155 still green). Test asserts it. ## P2-1 — not a pre-invoke tax + visible failure native-l0-trace.ts persistNativeL0SessionTrace() is called fire-and-forget (`void …`) from both routes, so it never blocks the model critical path; the redundant full-stage run is gone (cache-first manifest). An empty manifest emits a visible producer warning instead of silently persisting D-only — tested (warn fired, zero fabricated L segments). ## P2-2 — one producer seam (de-duplicated), unit-tested Wiring centralized in persistNativeL0SessionTrace (was duplicated in two large route functions); both routes call the single shared helper. Seam unit-tested (success + failure). NOTE: a full routeSerial/routeParallel driver integration test is not yet added — flagged. ## Evidence - f257-lseries-trace 6/0; f257-l0-manifest 5/0; l0-compiler 17/0 (updated for --manifest-out). - Regression green: trace-bridge / segment-lifeline / injection-trace-store / hook-pipeline / l0-pipeline-equivalence / system-prompt-builder / segment-judgment-engine / transport-boundary-l0-equivalence / codex+claude-bg+opencode L0. - Real mjs manifest smoke: 7 segments byte-accurate. tsc clean; biome changed files clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…-seam tests
Addresses sol's 2b R2 REQUEST CHANGES (all three; R1's four remain CLOSED).
## P1-1 (blocking) — partial/foreign/blank manifest was recorded as healthy fired data
The row-filter (`readL0Manifest`) + "non-empty ⇒ success" adapter marked every row `fired`,
so `[{L1:'only-one'}]` persisted 1 native-l0 segment with NO warning (L2-L7 silently gone),
and `[{L4:''}]` counted an empty iron-law segment as injected — the original incident in
partial form. Now `validateL0Manifest()` validates the manifest as ONE atomic artifact:
exactly L1-L7 in canonical order, non-blank content, no foreign/duplicate/reordered rows.
ANY violation → `l0ManifestToSessionResult` returns null → the visible producer-failure path
(warning with the specific reason), never a partial success. red→green: missing / foreign /
duplicate / blank-content / reordered all reject.
## P2-1 — no automated real-compiler regression
Added `f257-l0-manifest-cli.test.js` (no fake spawn): invokes the real `compileL0WithManifest`
+ the actual CLI, proving exactly L1-L7 and each manifest content byte-for-byte in the
compiled prompt, and `--manifest-out` orthogonal to `--out` (file + stdout modes). If the
producer stops writing or diverges, this goes red instead of the fake-spawn tests staying green.
## P2-2 — route-seam evidence (R1 gap now filled)
Added `f257-route-seam.test.js`: drives the REAL route-serial + route-parallel with a
bootstrapped fake trace store + prewarmed manifest cache (sol's recipe, no full-suite driver).
Native-L0 service → persisted L1-L7 with channel `native-l0` (both routes); non-native control
→ no compiler L segments, not native-l0 (existing path unchanged).
## Evidence
- f257-lseries-trace 14/0 (incl. 6 atomic-reject cases + empty/partial seam-failure),
f257-l0-manifest 5/0, f257-l0-manifest-cli 2/0, f257-route-seam 4/0.
- Regression green: trace-bridge, l0-compiler, l0-pipeline-equivalence, segment-lifeline,
injection-trace-store, hook-pipeline, segment-judgment-engine. tsc + biome clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…file isolation
Addresses sol's 2b R3 REQUEST CHANGES (both test-proof/isolation gaps; production code
CLOSED, no correctness finding).
## P2-1 — non-native route control passed vacuously on empty results
The non-native cases only asserted "no summary uses native-l0" + "no summary has L
segments" — both true when summaries=[] (e.g. if the non-native persistence path were
deleted/broken), so it did not prove "existing non-native path unchanged". Now REQUIRE a
persisted summary first, then assert the expected non-native session channel
(message-prepend) + an existing pipeline S/D segment with a real observed/absent status.
Kept for both serial and parallel.
## P2-2 — real-CLI test read potential real profile data + left temp files
compileL0WithManifest({catId}) and the CLI ran without --profile-dir, so the compiler
resolved options.profileDir > CAT_CAFE_PROFILE_DIR > <repo>/private/profile and could read
real user capsule/primer, then left mkdtemp compiled prompts behind. Now both the direct
call and the CLI are pointed at a dedicated EMPTY temp profile dir (explicit profileDir /
--profile-dir), and every temp dir (profile + compile outputs) is tracked and removed in
after(). Test-data isolation boundary, not cosmetic.
## Evidence
- f257-route-seam 4/0 (non-native now asserts message-prepend + S/D segment), f257-l0-manifest-cli
2/0 (isolated empty profile + cleanup). biome clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.
What
F257 修复清单 #2 — L 系列段观测粒度. Native-L0 cats (Claude/Codex/OpenCode) had a permanently blank segment lifeline: their L1-L7 governance identity is delivered via the compiled native prompt, so the session hook pipeline never ran and L1-L7 were never observed. This sources the injection trace for L1-L7 from the actual L0 compiler artifact and persists it per-segment.
Design (sourced from the real compile boundary)
compile-system-prompt-l0.mjs:compileL0WithManifest()captures each L1-L7 section at the one loop where per-segment content still exists (before flatten + label-strip) + a new--manifest-outCLI flag (orthogonal to--out).l0-compiler.ts:l0ManifestCachepopulated in LOCKSTEP withl0CacheinsidedoCompileL0(same subprocess / generation guard / cleared together);getL0ManifestViaSubprocess()is cache-first and, cold, shares the provider's own compile via in-flight dedup — no redundant work.l0-manifest-trace.ts: atomic validation (exactly canonical L1-L7, non-blank, no foreign/duplicate/reordered) → adapt to a sessionPipelineResult→ existingbuildFromPipelinebridge with delivery channelnative-l0(pack-only stays for actual pack blocks). Any violation → visible producer-failure path, never a partial "healthy" trace.native-l0-trace.ts: single shared, fire-and-forget producer seam called by both routes (never a pre-invoke tax).Cross-family review (缅因猫 sol)
R1 (parallel-producer rejected → compiler-manifest) → R2 (atomic validation + real-CLI test + route-seam) → R3 (test rigor) → R4 APPROVE @
724885886(0 P1/P2/P3).Evidence
pnpm --dir packages/api test): 19148 pass / 69 fail — all 69 pre-existing develop_base baseline (identical count to feat(#476): unify callback auth — headers replace body/query #3's gate; failures in signal-fetcher/capabilities/audit-cc/F188/CLAUDE.md/Redis-timeout suites, categorically unrelated). All F257 feat(#476): unify callback auth — headers replace body/query #2 suites pass in-gate. Zero new failures.🤖 Generated with Claude Code