A hermetic smoke writes a real-shape OpenClaw session file (#555 item 1) - #558
A hermetic smoke writes a real-shape OpenClaw session file (#555 item 1)#558philcunliffe wants to merge 3 commits into
Conversation
#543 shipped green because tier 2 never touched an OpenClaw session file in any shape. Codex and Claude each have a hermetic flow that stages a real-shape transcript and drives `hyp backfill` against it; OpenClaw had no analog, so the only gate that could catch the two-level nesting bug was the manual acceptance smoke. Adds `backfill_openclaw_fixture`, mirroring `backfill_codex_fixture.js` and `backfill_claude_fixture.js`: boots `@hypaware/ai-gateway` + `@hypaware/openclaw` against a tmp HYP_HOME, stages a v3 session file under the fake HOME's `.openclaw/agents/<agentId>/sessions/<id>.jsonl`, drives `hyp backfill openclaw`, and asserts non-zero `ai_gateway_messages` rows with native record-line ids, envelope content, `provider=anthropic`, `conversation_source=openclaw`, the `backfill.provider_finish` / `backfill.write` spans and the `backfill.finish` log under the run's `dev_run_id`, and a zero-new-rows idempotent rerun. The fixture writer lives in `smoke/lib/openclaw_session_fixture.js` so no flow can invent a flatter, friendlier record shape, and takes a caller-chosen `mtimeMs` so the scheduled-sweep quiesce work (LLP 0173 T12) can extend it rather than replace it. Co-Authored-By: Claude <noreply@anthropic.com>
openclawMessageLine's JSDoc claimed writing `timestamp` at both levels "makes the envelope-first read of it observable," but the implementation copied the same destructured binding to both levels, so they were byte-identical and no assertion could tell envelope-first from line-first precedence. Add an optional `messageTimestamp` field that overrides the nested `message.timestamp` only, keeping the no-arg default (same value at both levels) unchanged since a real session file does write it that way. backfill_openclaw_fixture.js now stages the assistant record with a distinct envelope timestamp and asserts the projected row's `message_created_at` carries the envelope value, genuinely exercising the LLP 0158 / #555 envelope-first precedence. Also strengthen the run-2 idempotency assertion, which checked only `status === 'ok' && rows_written === 0` under a comment claiming "all part_ids already present" but never pinned that the session was actually re-read. A whole-file skip (e.g. the mtime-based quiesce-window skip LLP 0173 T12 will add, using the `mtimeMs` hook this PR introduced) would report the same zero rows_written and pass identically. Require `items_seen >= 1 && rows_skipped >= 1` too, so the predicate pins the dedupe mechanism the comment describes instead of being satisfied vacuously by a skip. Co-Authored-By: Claude <noreply@anthropic.com>
Review round 1 (head
|
Round-2 review nits on the tier-2 gate added for #543. Both are in the `session_projected` telemetry assertion, and this file exists purely as a regression gate, so assertion precision is its whole job. The filter matched on the log body alone, the only one of the four telemetry assertions not constrained to the run. Two records exist: the idempotency rerun dispatches with a fresh `DEV_RUN_ID`, and the plugin logger stamps it, so run 2 emits its own `session_projected` under `<dev_run_id>-rerun`. Reading `projected[0]` was correct only because emission order happens to put run 1 first, which is ordering rather than identity. Constrain it to `harness.devRunId` and this session's native `session_id`, matching what the three sibling filters already do. The `identity_source === 'native'` conjunct could never be false: the plugin hardcodes the string at `session_projected`'s single emission site, so the conjunct held whenever the log existed at all, while the comment read as though it separated a native-identity path from a fallback one. Drop it, and state what the assertion does pin: that the projection was reached, for this session, with both messages. The identity claim now sits where it can genuinely vary - `session_id` on the log, alongside the record lines' `message_id`s already asserted on the rows. No behavior change. Both round-2 mutations still fail the smoke: a flat `openclawMessageEnvelope` reproduces #543's `status: ok` / `rows_written: 0` on the first behavioral assertion, and swapping `messageField`'s envelope/line order for `timestamp` fails the envelope-precedence assertion with the record-line 10:00:02 value. Co-Authored-By: Claude <noreply@anthropic.com>
Review round 2 (head
|
|
The review fix-loop reached its 2-round cap, so this landed on the triage rung (LLP 0017). I independently re-verified all four findings raised across both review rounds against the
I also re-ran Zero unresolved findings remain. Per the task's guidance, an issue enumerating nothing would be Judgement: this PR can ship safely. |
Follow-up to PR #552. Actions item 1 only of issue #555; item 2 is deliberately not actioned (see below).
What landed
hypaware-core/smoke/flows/backfill_openclaw_fixture.js, the missing sibling ofbackfill_codex_fixture.js/backfill_claude_fixture.js. It:@hypaware/ai-gateway+@hypaware/openclawagainst a tmpHYP_HOME.openclaw/agents/<agentId>/sessions/<sessionId>.jsonlwith a
type: "session"v3 header and twotype: "message"records in the verified two-levelshape: the record line states only
id/message/parentId/timestamp/type, androle,content,model,provider,api,stopReason,usageare nested undermessagecannot quietly flatten the fixture back into something the pre-OpenClaw backfill reads message fields one level too high, projecting 0 rows #552 reader would also pass
hyp backfill openclawthroughdispatch/createCommandRegistry, mirroring the Codex flowai_gateway_messages(the literal OpenClaw backfill projects 0 rows from real session files: message fields are read at the top level but OpenClaw nests them under 'message' #543regression, which reported
status: okwithrows_written: 0), carrying the record lines'native
message_ids, the envelope'scontent_textandmodel, andprovider = anthropic/conversation_source = openclaw/client_name = openclawbackfill.provider_finishspanand a
backfill.finishlog under the run'sdev_run_idwith matching row counts, abackfill.writespan forai_gateway_messages, plus the provider's ownopenclaw.backfill.session_projectedlog withidentity_source = nativestill returns exactly two
hypaware-core/smoke/lib/openclaw_session_fixture.jsholds the fixture writer(
openclawMessageLine/writeOpenclawSessionFixture), mirroringtest/plugins/openclaw-backfill.test.js'smessageLine()andtest/plugins/openclaw-settlement.test.js'ssessionFileLine()rather than inventing a shape.Flows are resolved by filename by
hypaware-core/smoke/lib/harness.js, so registering the flow isthe file itself; there is no separate index list to edit. No LLP changed: this is a test-coverage
gap, not a design change (no LLP in the 0157-0162 set states a smoke inventory).
Mutation-test evidence
The fix here is the test, so it has to be shown to have teeth. The two-level handling in
hypaware-core/plugins-workspace/openclaw/src/session_file.jswas temporarily reverted to thepre-#552 behavior (read every message field off the record line):
function openclawMessageEnvelope(row) { - return isPlainObject(row.message) ? row.message : row + return row }Under mutation, the new smoke FAILS with #543's exact signature (
status: ok,rows_written: 0):Restored (
git checkout -- hypaware-core/plugins-workspace/openclaw/src/session_file.js;git statusclean of the mutation), and it passes again:So the smoke would have caught #543.
Other checks in the worktree:
npm test3281 pass / 0 fail / 1 skipped;npm run typecheckclean;npm run build:typesclean.Item 2 was deliberately NOT actioned
Issue #555 item 2 (the
timestamp/idprecedence asymmetry insession_file.js, wheretimestampresolves envelope-first viamessageFieldwhileidresolves line-first) is leftexactly as merged. Quoting the issue's own reasoning:
The issue asks to revisit it only "if a future change ever gives
backfill.jsandsettle.js(or athird consumer) independent access to the raw
recordfortimestamporidrather than goingthrough the shared reader." This PR adds no such consumer. The issue's "Non-issues (do not action)"
section was likewise left untouched.
Base branch note
This PR is stacked on
fix/issue-543(PR #552), not onmaster. PR #552 is still open andunmerged, so on
masterthe OpenClaw reader still takes message fields off the record line and thissmoke cannot pass there by construction. LLP 0173 records the same finding for its own T12: the
fixture "genuinely cannot be built correctly against the current tree." GitHub will retarget this
PR's base to
masterautomatically when #552 merges. Merge #552 first.Coordination with
integration/openclaw-two-lane-capture(LLP 0173, task T12)T12 on that branch also plans a
backfill_openclaw_fixture, extended with a quiesce-windowdimension (mtime-based skipping of recently-touched session files) and a two-lane dedupe
assertion. T12 is currently blocked and explicitly held until #552 merges, which is the same
blocker this PR has.
This flow is written so T12 extends rather than replaces it:
smoke/lib/openclaw_session_fixture.js, importable by asecond flow rather than private to this one
writeOpenclawSessionFixturetakes an optional caller-chosenmtimeMs(applied viafs.utimes),which is precisely the "controllable-mtime
agents/<id>/sessions/tree" T12 calls for. Nothing inthis flow uses it; it is there so T12 does not have to fork the helper
header,records,agentId, andsessionIdare all parameterizable, so a quiesce case canstage a second session file under the same agents root
sweep_openclaw_quiesce) rather than claiming thegeneric name for this one case
Backlink for whoever merges: LLP 0173 task T12 ("the hermetic smoke gap", Section 9),
llp/0173-openclaw-two-lane-capture.plan.mdonintegration/openclaw-two-lane-capture.Fixes #555