mirror of
https://github.com/morluto/rea.git
synced 2026-11-05 06:45:22 +00:00
[GH-ISSUE #724] [Feature] Explain content, exit outcome, and temporal differences in process comparisons #322
Open
opened 2026-10-15 18:23:52 +00:00 by bsakaguchi
·
12 comments
No Branch/Tag specified
main
codex/procedure-reference-stacked
release-please--branches--main--components--rea-agents
release-please--branches--main--components--rea-agents--release-notes
codex/kuna-independent-retry
codex/byte-search-overlay-coverage
codex/open-failure-target-receipt
codex/kuna-xref-evidence
codex/ghidra-ambiguous-procedure-recovery
codex/function-source-bundle
codex/trace-function-integration-prototype
codex/snapshot-source-closure
codex/recorded-evidence-array-pages
codex/evidence-recovery-redesign
codex/trace-feature-address-scope
codex/call-discovery-scope
codex/ghidra-literal-candidate-evidence
codex/decompile-directory-independent-retry
codex/retry-provenance-base
codex/value-flow-capability-experiment
codex/ghidra-containing-instruction
cursor/readme-discord-button-ef9f
dependabot/github_actions/actions/setup-node-7.1.0
dependabot/github_actions/actions/upload-artifact-7.0.2
dependabot/npm_and_yarn/npm-minor-and-patch-d8d97ce6df
codex/kuna-1989
fix/8559-native-ui-fixture-ownership
fix/8555-go-verifier-mcp-ownership
codex/provider-capability-recovery
fix/8642-rizin-target-stdin
codex/ghidra-memory-effect-scope
codex/ghidra-annotation-entry-selector
codex/ghidra-readonly-recovery
codex/ghidra-regex-verifier-recovery
codex/evidence-discovery-view-guidance
fix/8545-bulk-append-overflow
fix/8428-navigation-refusal-alternatives
codex/address-context-recovery
codex/matching-member-limitations
codex/unknown-scope-contract
codex/evidence-view-guidance
feat/8373-native-trace-views
codex/ghidra-snapshot-verifier-binding
codex/elf-export-visibility
codex/evidence-view-recovery
codex/native-reference-cli-help
codex/provider-candidate-observation-scope
codex/kuna-session-verification
codex/ghidra-switch-lifetime-guidance
codex/pseudocode-paging-discovery
codex/clarify-evidence-view-support
codex/kuna-xrefs-selector-recovery
codex/context-capacity-recovery
codex/evidence-closure-large-links
codex/same-function-evidence-identity
codex/ghidra-verifier-inspected-jdk
codex/audit-guided-workflows
codex/crash-prompt-retained-dossiers
codex/analysis-view-error-recovery
codex/kuna-verifier-target-selection
codex/ghidra-selected-project-call-coverage
codex/docs-native-analysis-scope
codex/ghidra-java-socket-boundary
codex/ghidra-retained-snapshot-verifier
codex/ghidra-search-inventory-facts
fix/8284-zero-argument-parameter-bindings
fix/8198-ui-action-handler-offset
fix/8257-elf-entry-label-identity
fix/8304-deferred-output-schema-projection
fix/8277-call-path-computed-target-uncertainty
fix/pr-8089-conflicts
fix/ci-static-duplicate-export
dependabot/npm_and_yarn/lint-staged-17.6.0
dependabot/github_actions/actions/download-artifact-8.0.2
dependabot/npm_and_yarn/write-file-atomic-8.0.0
dependabot/npm_and_yarn/canonicalize-5.1.0
dependabot/github_actions/actions/cache/restore-6.1.0
dependabot/npm_and_yarn/toon-format/toon-4.4.0
dependabot/github_actions/actions/deploy-pages-5.0.1
codex/review-fixes-pr-2403
codex/review-fixes-pr-2400
codex/process-terminal-width
codex/test-brittleness-cleanup
verify/7167-browser-connect-macos
fix/7167-browser-connect
feat/native-matching-allowance-setting
feat/browser-scenario-dry-run
test/managed-caller-parameters-20261024
codex/parser-fortythird-boundaries
fix/6926-pinvoke-platform-decorations-20261023
fix/6889-har-body-encoding-20261023
fix/6888-sqlite-schema-budget-20261023
fix/dependency-security-gates-6708
cursor/evidence-overwrite-rebase-b4ac
feat/4057-verify-native-exports
fix/windows-acl-observer-overhead-20261022
cursor/erlang-beam-restack-4476
cursor/pe-certificate-review-bf32
fix/6116-javascript-application-fs-errors
fix/6061-unmapped-runtime-script-unknown
feat/browser-scenario-summary
fix/pe-certificates
codex/verify-3407
cursor/rebase-5699
cursor/rebase-5352
cursor/rebase-5600
cursor/fix-cli-schema-omission-10a8
codex/audit-hopper-shutdown-reasons-20261021
fix/demangle-swift-omitted-record
fix/nib-boolean-value-types
cursor/cancellation-partial-observation-a5ac
fix/ib-object-limit-reasons
fix/electron-fuse-limit-advice
fix/kuna-doctor-dangling-cache
fix/fixed-budget-refusal-advice
fix/js-ipc-listener-dedup
fix/js-return-member-provenance
fix/dmg-integrity-retry-advice
fix/ida-doctor-readiness
fix/kuna-version-probe-output-drain
fix/legacy-swift-runtime-name-matching
fix/dylib-lazy-load-commands
fix/browser-scenario-startup-cancel
fix/swift-nested-punycode-classification
fix/compare-unselected-scopes
fix/objc-method-budget-coverage
fix/dmg-mounted-root-observations
audit/ci-tests-20261019-round2
release/6.7.0
fix/wabt-cleanup-retry-resolution
fix/electron-fuse-elf-pe-architecture
fix/native-plist-descriptor-ownership
fix/kuna-caller-cleanup-ownership
experiment/atomic-codemode-20261016
review/first-contributor-3776
fix/call-resolution-full-read-cycles
fix/member-mutation-selection-repeat
experiment/atomic-re-tools-20261015
perf/member-mutation-lazy-origins
perf/static-network-mutation-index
fix/hopper-startup-lifecycle
audit/3050-windows-proof-20261015
feat/windows-window-capture-2183
audit/3049-windows-proof-20261015
audit/3735-macos-fixtures-20261015
fix/batch-decompile-null-pseudocode
fix/kuna-inventory-honors-jobs
experiment/browser-codemode-tool-design-20261015
fix/hopper-clean-runtime-argv
codex/review-2049-27f1b367
review-fixes/pr-2787-routing
fix/3360-scoped-this-emitters
fix/3367-replaced-global-effects
fix/3838-compact-any-value-items
test/hopper-download-probe-20261015
codex/windows-runtime-creation-red-green-20261015
codex/windows-readonly-root-red-green-20261015
codex/windows-root-share-red-green-20261015
codex/exp-1685-win-mcp-startup
codex/bug-sweep-20261015
investigate/2190-windows-package-phases
investigate/3543-macos-cli-deadline
perf/3281-reverse-tools-feedback-quality
fix/3406-hopper-doctor-fixture
fix/3341-wrapper-require-test-context
fix/3301-owned-uninstall-fixtures
fix/3164-compiler-input-ownership
fix/pcap-cleanup-stdout-newline-20261014
perf/interface-builder-summary
codex/review-fixes-pr-1977
fix/ghidra-effective-jvm-platform
fix/docs-turkish-rust-link-20261013
fix/main-maintenance-ci-20261010
fix/recover-binary-session-process-effects
codex/review-fixes-pr-2457
codex/ci-required-cancellation-gate
fix/main-format-baseline-20261013
fix/setup-preserve-entry-fields
cursor/restore-main-checks-1ffc
fix/ghidra-bridge-hashmap-import
codex/shared-ci-jadx-json-default-20261013
codex/shared-ci-ghidra-test-groups-20261013
codex/shared-ci-java-fixture-format-20261013
codex/fix-intrinsic-invocation-boundaries
test/release-preflight-reusable-jobs
test/js-parameter-provenance-expectation
fix/linux-hopper-verification-current
test/2419-ghidra-elf-probe-size
followup/pr-1544-native-ida
codex/fold-pr-2230-contribution
docs/readme-responsible-use
fix/issue-2064
fix/issue-2050
fix/issue-2053
fix/issue-1968
fix/issue-1945
experiment/codemode-bridge-20261011
fix/tool-effects-negative-wording-1904-20261012
fix/1842-electron-interop-provenance
feat/recover-javascript-multi-input
refactor/website-i18n-prep
fix/nativeaot-pe-virtual-extent
fix/browser-verifier-ci-hang
fix/pe-resources-input-selection
docs/beginner-friendly-guides
docs/beginner-setup-guide
fix/javascript-analysis-worker-1563
fix/1649-ghidra-duplicate-names
fix/javascript-export-keys-integration-1369
test/maintenance-boundary-contracts-20261011
review/javascript-export-keys-1369
review/process-live-progress-1483
test/javascript-1495-growth-ratio
fix/ci-escape-timing
docs/normalized-terminal-geometry-1450
release/6.3.0
archive/release-6.3.0-pr1381
release/6.2.0
fix/electron-preload-dynamic-override
fix/javascript-self-referential-bundler-edges
release/6.1.0
cursor/grok-client-cherry-pick-0b56
feat/signature-posture
feat/dyld-shared-cache
feat/artifact-integrity-policy
feat/pkg-inventory
release/6.0.0
chore/sync-release-5.0.0
release/5.0.0
cursor/cloud-agent-node-787e
fix/javascript-xhr-open-endpoints
review/pr-711-realpath-fallback-wording
cleanup/replay-confinement
codex/test-suite-performance
codex/issue-foundations
codex/address-open-issues-2026-07-20
agent/javascript-vite-rollup-esbuild-adapters
agent/process-cleanup
agent/triage-integration
agent/unknown-commands-registry
feat/mcp-identity-metadata
morlvps/issue_cleanup
codex/installer-redesign
morl/roadmap-investigation-core
morl/roadmap-investigation-docs
release-please--branches--main--components--rea
rea-agents-6.8.0
rea-agents-6.7.0
rea-agents-6.6.0
experiment/atomic-codemode-wait-runtime-20261016
rea-agents-6.5.0
rea-agents-6.4.0
rea-agents-6.3.0
rea-agents-6.2.0
rea-agents-6.1.0
rea-agents-6.0.0
rea-agents-5.0.0
rea-agents-4.1.0
rea-agents-4.0.1
rea-agents-4.0.0
rea-agents-3.2.1
rea-agents-3.2.0
rea-agents-3.1.0
rea-agents-3.0.0
rea-agents-2.7.0
rea-agents-2.6.0
rea-agents-2.5.1
rea-agents-2.5.0
rea-agents-2.4.0
rea-agents-2.3.0
rea-agents-2.2.0
rea-agents-2.1.0
rea-agents-2.0.0
rea-agents-1.7.0
rea-agents-1.6.0
rea-agents-1.5.0
rea-agents-1.4.0
rea-agents-1.3.0
rea-agents-1.2.0
rea-agents-1.1.0
rea-agents-1.0.0
rea-agents-0.5.0
rea-agents-0.4.0
rea-agents-0.3.0
rea-0.2.1
rea-0.2.0
Labels
Clear labels
area: bridge
area: bridge
area: build
area: build
area: build
area: cli
area: domain
area: evidence
area: mcp
area: native
area: providers
area: runtime
area: server
area: setup
area: tests
bug
design
documentation
enhancement
enhancement
enhancement
experiment
help wanted
i18n
invalid
P0: critical
P1: high
P2: medium
P2: medium
P2: medium
P3: low
performance
provider-request
pull-request
Mirrored from GitHub Pull Request
question
roadmap
triage
No labels
area: bridge
area: bridge
area: build
area: build
area: build
area: cli
area: domain
area: evidence
area: mcp
area: native
area: providers
area: runtime
area: server
area: setup
area: tests
bug
design
documentation
enhancement
enhancement
enhancement
experiment
help wanted
i18n
invalid
P0: critical
P1: high
P2: medium
P2: medium
P2: medium
P3: low
performance
provider-request
pull-request
question
roadmap
triage
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".
No due date set.
Dependencies
No dependencies set.
Reference
morluto/rea#322
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally created by @N0zoM1z0 on GitHub (Oct 6, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/724
Before submitting
Area
Process capture or runtime observation
Current limitation
Two captures of the exact same Node command had identical raw terminal text, final rendered lines, and exit objects
{code: 0, signal: 0, reason: "exited"}. In a fresh connection the comparison nevertheless reported terminal=changed and exit=changed. first_divergence showed the same normalized command/PIDs at different sample times; process remained unknown.A new identical pair with time_bucket_ms=1000 reported terminal=unchanged but exit=changed with identical exit objects. Settlement elapsed time differed (182 ms versus 86 ms), and first_divergence pointed to timestamps of empty filesystem checkpoints although filesystem observations were unknown.
Temporal capture differences are real. This request concerns explanation of comparison facets, not a claim that the entire traces are equivalent or that elapsed time may be discarded.
Use cases
A reconstruction analyst asks whether a command changed its printed result or exit outcome. The result should distinguish those facts from timing/settlement/sample variation and identify what remains unobserved.
Desired outcome and success criteria
Authority boundaries and non-goals
No additional execution, mutation, replay engine, automatic timing suppression, causal inference from wall-clock order, or equivalence claim for missing dimensions. Preserve complete observations and unknowns.
Alternatives considered
Raising the time bucket made terminal unchanged in one control, but did not explain the exit label. Inspecting entire captures manually recovers facts but leaves the comparison headline ambiguous.
Possible direction
Add precise outcome/content/timing facets or per-dimension explanations to the existing comparison contract; keep current evidence and provenance and evaluate compatibility before changing aggregate statuses.
References
Hands-on public CLI/MCP evaluation, findings F09. Baseline: REA 4.0.1, source revision c3bc38bf6c74d2175a4680530dcadd6cab0e7791; evaluation date 2026-10-06.
#61 previously considered repeatability and temporal variance; #558 addresses definite conclusions from partial coverage and has a different remaining scope. docs/process-capture.md
Related evaluation issues: #719.
@sunxiuguo commented on GitHub (Oct 6, 2026):
Source review at
9dd6cef(no runtime validation): terminal comparison includes the full timestamped frames, and exit comparison includes settlement, including elapsed_ms. This explains how the reported content and exit outcome can match while those dimensions change.A bounded first step could add optional explanations, preserving the existing aggregate statuses and acceptance of historical captures/results.
Regression cases should include identical content/exit with timing changes, identical stream with different chunking, different streams that render identically, true content/exit-code changes, and incomplete coverage, plus historical-payload parsing and CLI/MCP output checks.
Does this bounded additive approach fit the intended contract before any implementation changes?
@CassieFox23 commented on GitHub (Oct 9, 2026):
TL;DR: Still there on 6.2.0 on Linux. I captured the exact same one-line Node command twice. Most pairs read
status: changed. The only value that differs issettlement.elapsed_ms. That number is not bucketed, sotime_bucket_mscan't help. When the milliseconds happen to match, the verdict isunknown, neverunchanged.Runtime check of the source review above, on the published
rea-agents@6.2.0. Main atf6cfbfcc5still foldssettlementinto the exit dimension (src/domain/process/processComparison.tslines 139-140 and 380-381). It also foldsat_msinto each compared raw frame (processObservation.tslines 10-14).Scenario:
node -e "console.log('hello'); process.exit(0)", defaults excepttimeout_ms10000,idle_timeout_ms5000,settle_ms100. Captured 7 times attime_bucket_ms: 10and 7 times attime_bucket_ms: 1000, then compared five pairs from each set withrea compare-process-captures:time_bucket_mschangedunknownunchangedSo the facts the source review predicted hold at runtime on 6.2.0. The smallest split that would fix the headline here is this. Separate
settlement.elapsed_ms(and probablysettlement.state/cleanup_outcome) fromcode/signal/reasoninside the exit dimension.More detail
Every
changedpair looks the same:terminal: unchanged,interaction: unchanged,exit: changed,filesystem: unknown,process: unknown, andfirst_divergenceonexitwith identical{ code: 0, signal: 0, reason: "exited", settlement.state: "quiesced", cleanup_outcome: "not_required" }on both sides. The one differing value issettlement.elapsed_ms. It ranged from 99 to 105 across the 14 captures.settlement.elapsed_msis not bucketed: attime_bucket_ms: 1000the frame and process-sample times all read 0, butelapsed_msstill read 99, 100 or 101. So it comes down to whether two runs settle on the same millisecond. That decides whether an identical pair compares aschangedorunknown.The
unknownpairs are the ones where the milliseconds matched. They stop short ofunchanged, as documented. That is becausefilesystem(no paths selected) andprocess(sampled trees) carry residual unknowns.Terminal stayed
unchangedhere. The single output frame got the same bucketed time within each setting. That was 80 ms in all seven bucket-10 captures and 0 in all seven at 1000. So theterminal: changedfrom the report needs a frame landing near a bucket edge. A one-line program didn't hit that. Same mechanism (at_msis part of the compared frame), just not triggered.Two controls, same contract. Each was compared against one of the bucket-10 captures:
hello worldinstead ofhello):terminal: changedwith a correctfirst_divergence(data: "hello\r\n"vs"hello world\r\n", both at 80 ms). Butexit: changedas well, from 102 vs 103 ms.process.exit(3)):exit: changedwithfirst_divergenceshowingcode: 0vscode: 3. That is under the same label as the timing-only pairs. The headline can't tell the two apart without readingfirst_divergence.Test setup
🤖 Generated with Claude Code (Fable 5.1) 🦊
@sunxiuguo commented on GitHub (Oct 10, 2026):
Thanks @CassieFox23. I independently reproduced this using the CLI built from current main 7aa4d768 (source version 6.3.0, Linux x64, Node 24.19.0), with the same one-line Node command and timeout/settlement settings.
Across six adjacent pairs at each bucket size:
time_bucket_ms: 10: fivechanged, oneunknown.time_bucket_ms: 1000: fourchanged, twounknown; terminal unchanged in all six. One pair differed only within the compared exit observation atsettlement.elapsed_ms: 68 versus 65 ms.hello\r\nat 30 versus 40 ms, with the exit dimension unchanged.unknownbecause filesystem/process coverage was incomplete.There is already an explicit opt-in for the settlement-only case: a lifecycle trace declaring the expected exit and settlement events, with
ignore_fields: ["elapsed_ms"]on both declarations. For a 65/66 ms pair this returnedexit: unchanged, traceequivalent, and overallunknown; the code-3 control still returnedexit: changed, tracedifferent. The compiled stdio MCP gave the same comparison results. This does not suppress terminal timing or establish whole-capture equivalence. The first attempt with the ignore field on only one event was correctly rejected because declarations from one source must use the same ignored fields.Before changing default semantics, would you prefer the existing
exitdimension to remain an exact lifecycle comparison with additive outcome/timing explanations, or split outcome and settlement/timing into separate dimensions? Simply dropping elapsed time from the current comparison would lose a reported difference. I have not changed the runtime contract.AI-assisted investigation. The source build and six existing focused comparison/trace tests passed; no full-suite or non-Linux verification was performed.
Standalone synthetic reproduction and explicit lifecycle trace
Run from a checkout built at the commit above (
npm ci && npm run build:cached), on a supported Linux/macOS PTY host.nodemust resolve to a supported Node version. Use a new sibling directory namedrea-724-repro; all input and output files below are synthetic. The capture commands must run from the same working directory.Compare
normalized_resultinexact.jsonanddeclared.json. The elapsed values are measured, so two fresh captures may happen to match; repeat the captures if you want to observe a timing-only difference. The expected lifecycle events here are deliberately specific to this synthetic command. A different exit, settlement state, cleanup outcome, or undeclared lifecycle event must not be silently accepted. Both lifecycle declarations need the sameignore_fieldslist. Terminal timing/content and residual filesystem/process unknowns remain outside this lifecycle-only equivalence claim.@CassieFox23 commented on GitHub (Oct 10, 2026):
TL;DR: The workaround works. I tried it on three Node versions. Each time, the exit check stopped flagging timing. A real exit-code change still showed up.
I ran your exact files on the released 6.3.0 CLI. I took four captures per Node and compared three pairs.
changed,elapsed_msonlyunchanged, traceequivalentchanged, tracedifferentchanged, 2unknownunchanged, traceequivalentchanged, tracedifferentchanged, 1unknownunchanged, traceequivalentchanged, tracedifferentOverall stayed
unknownwith the opt-in, as you said. The plainunknownpairs were the ones whose milliseconds matched. Terminal stayedunchangedin all 21 compares. At a 1000 ms bucket every frame read 0. So this run did not test terminal timing. The opt-in does not claim to cover that.Thanks, sunxiuguo.
🤖 Generated with Claude Code (Opus 5.5) 🦊
@CassieFox23 commented on GitHub (Oct 10, 2026):
TL;DR: Terminal timing flips too, at a 1 ms bucket. The lifecycle file does not touch it. A trace file that also declares the terminal events does fix it, with two catches.
Follow-up on released 6.3.0. Six captures per bucket, five pairs each. Output was always
hello\r\n. The table shows terminal status.at_mshullochangedchangedchangedchangedAt 10 the frames sat mid-bucket here, so an edge case can still flip it. No flag or normalization setting covers frame timing. The trace file schema does accept
terminal_rawandterminal_renderedevents withignore_fields: ["sequence", "at_ms"]. Declaring both kinds gave terminalunchangedon all 30 pairs. Thehullocontrol still came backchanged. Catch one: the screen event and exit race, so it needs two trace variants. Catch two: a failed trace marks every declared dimensionchanged. Sohulloalso flipped exit, with equalelapsed_ms.Test setup
🤖 Generated with Claude Code (Opus 5.5) 🦊
@CassieFox23 commented on GitHub (Oct 10, 2026):
TL;DR: One piece of history that may help here. REA once had a runner that sorted timing noise from real change. It was removed as unused on 2026-09-29.
This may be obvious, since morluto wrote that code. I'm adding it so the thread has the link.
e1d755f7removed it:ProcessPairedExperiment.ts, its domain file and its tests.github.com/morluto/rea@e1d755f7cdunknownresults here. Partial evidence must stay unknown. It is never called unchanged. So that part looks like design, not a bug.So this request may be a smaller slice of that old idea. It would explain timing, not hide it.
🤖 Generated with Claude Code (Opus 5.5) 🦊
@CassieFox23 commented on GitHub (Oct 10, 2026):
TL;DR: I'd like to build the first slice of sunxiuguo's shape. It adds explanations and changes no verdicts. Before I start, does that scope fit?
What I'd propose for one PR:
Not in this PR:
For tests, I'd cover the cases from the issue and that review. Same output with different timing. Same stream with different chunks. A real output change. A real exit-code change. Incomplete coverage. Old payloads. CLI and MCP both. I already have these runs set up on Linux, on Node 22, 24 and 26.
If a different first slice fits better, I'm happy to follow that instead.
🤖 Generated with Claude Code (Opus 5.5) 🦊
@sunxiuguo commented on GitHub (Oct 10, 2026):
Thanks @CassieFox23 for checking the released package across Node versions and separating the lifecycle and terminal cases. Your proposed additive slice matches the shape I suggested. I’m another contributor, so the contract decision remains with @morluto.
A few details from that earlier scope seem worth keeping explicit:
differenttrace with differing outcomes can mark every declared dimension changed, whilenonconformingretains the exact comparisons. An explanation should therefore not call equal exit fields an exit-outcome change just because a terminal mismatch changed the combined trace verdict.Preserving the current statuses, historical payload parsing and unknown/truncated evidence, with CLI/MCP checks, keeps this bounded. The terminal trace results above are your additional runtime evidence; I have only checked the projection logic in source here.
AI-assisted review.
@CassieFox23 commented on GitHub (Oct 10, 2026):
TL;DR: Here is the full scope in one place. It adds explanations and changes no verdicts. It folds in every point from sunxiuguo's review. I'll wait for morluto's call before I start.
Edit: added a frame guard and test 10 from sunxiuguo's review.
Thanks, sunxiuguo. Your review filled in the gaps in my offer. Here is the whole list, so morluto can say yes or no to one thing.
What this PR would do:
Points sunxiuguo asked me to keep explicit, so they are part of the scope:
settlement.cleanup_outcome, not only the state.different. That marks every declared dimensionchanged. The note must still show the exit fields as equal. I checkedapplyTraceVerdictat commit7aa4d768and read the same thing. This is "catch two" from my terminal comment.Not in this PR:
Tests I'd add:
Six questions before I start
Each one has a default. A short answer like "1 yes, 3 always" is enough.
explanationswithexitandterminalkeys. Alternatives:exit_explanationandterminal_explanation, or a nesteddimensionsobject.outcome,settlement,settlement_timing;stream,final_screen,frame_timing. Summaries as in the plan below. The flipped-trace case istrace_only.changed,addedorremoved, and none when it isunknown. Or: always present, every facet unchanged. Or: an explicit all-unknown record when the dimension is unknown.differencesreuses the one-frame bound offirst_divergence;frame_timingkeeps the first 16 entries plus a count. Is a cap wanted, and where should it live?compare_process_capturesinsessionToolContracts.ts100-104 in this PR.streamobservable even when rendered frames are cut short.I have these runs set up on Linux, on Node 22, 24 and 26. Nothing is built yet. I'll wait for morluto's call on the contract/approval before I start.
Thanks, sunxiuguo, for the frame guard catch. It is in the plan below.
Draft plan for this PR (for review, nothing built yet)
Draft plan, read against main
6875d4a6. Every name in it is open to change. Nothing is built. Re-checked atde8fc671: the process files are unchanged. Changes from sunxiuguo's second review are marked (review 2).Contract shape
The result schema is zod:
processCaptureComparisonSchemainsrc/domain/process/processComparison.ts, lines 59-102. It is a non-strictz.object, so unknown keys are stripped, not rejected. There is no schema version id in the process domain. The optionaltracefield at line 86, spread at line 482, is the precedent to copy. The MCP output schema derives from the same object atsrc/contracts/toolOutputSchemaGroups.ts:499.Proposed field:
Exit facets:
outcome:exit.code,exit.signal,exit.reason.settlement:settlement.state,settlement.cleanup_outcome.settlement_timing:settlement.elapsed_ms. Basisraw. The producer writesDate.now() - startedwith no bucket (src/process/capture/ProcessCaptureLifecycle.ts627, 633, 641).These three cover
{ exit, settlement }exactly. That is the object lines 140 and 379-382 compare today.Terminal facets:
stream:frames[].datajoined insequenceorder.final_screen: the lastrendered_frames[]entry withoutsequenceandat_ms. If either side has no rendered frames, this facet isunknown, neverunchanged.rendered_payloads(review 2): everyrendered_frames[]entry, index-aligned, withoutsequenceandat_ms. A count mismatch or any differing earlier payload marks itchanged. No rendered frames on either side marks itunknown.frame_timing: theat_mslists for raw and rendered frames, plus the chunk layout (frame count and per-frame lengths). Basisbucketed.ProcessCaptureLifecycle.ts908-911 bucketsat_msthroughnormalizeProcessElapsedTime(ProcessNormalization.ts7-10) before the frame is stored. So no raw time exists in a capture. The note saysraw_timestamps: "unavailable"and never rebuilds one.With the fourth facet, the four cover the whole list from 104-119. Raw
dataisstream. Rendered fields other thanat_msandsequencearerendered_payloads, and the last one is alsofinal_screen. Times, sequence numbers and counts areframe_timing.Guards, as a second
superRefinenext to lines 89-99:summary: "timing_only"needs every other facetunchanged. For terminal that includesrendered_payloads(review 2). So every compared rendered payload must be equal onceat_msandsequenceare removed.frame_timingchanged beside a changed or unknown facet,summaryismixed. Thedifferenceslist then carries the timing entries and the first differing payload path. So the narrower timing difference is reported next to its remainder (review 2).unexplained. It is never all-unchanged.Old payloads: a stored result with no
explanationskey parses as before. Three consumers parse this result. They read onlystatus, the five dimension statuses andlimitations. Those arechangedBehavior.ts205-215 and 257-284,reconstructionVerification.ts218-227 and 262-270, andstaticRuntimeCorrelation.ts240-278. No version bump exists, and none is needed.One visible side effect:
evidence_idhashesnormalized_result(src/domain/evidence.ts219). A new comparison that carries a note gets a differentevidence_idthan before. Stored records still check out against their own content (parseEvidence, 233-240).Examples, trimmed to the keys that matter. Captures follow the
EMPTY_PROCESS_CAPTURE_EXAMPLEshape.Test 1, same output and exit, different timing (exit side):
Test 1, terminal side (same
hello\r\n,at_ms30 vs 40):Test 2, same stream, different chunks (
"hel"+"lo"vs"hello"). Facets:streamandfinal_screenunchanged,frame_timingchanged. Summarytiming_only. Differences:frames.length2 vs 1, plus the first frame's two values.Test 3, different streams, same final screen (
"bar"vs"foo\rbar", identicalrendered_frames):Test 4, real output change:
streamchanged,final_screenchanged when rendered frames differ.summaryis nevertiming_only.Test 5, real exit-code change (
code0 vs 3,elapsed_msalso differs):outcomechanged,settlement_timingchanged,summary: "mixed". Differences list both paths.Test 6, trace flipped by a terminal mismatch, exit fields equal:
The reported
changedcame fromapplyTraceVerdict, lines 315-316, not from a field.Test 7, coverage incomplete or truncated: the dimension is already
unknown(classify356-362, orhasUnknownat 377). The note is omitted, or (Q3) present with every facetunknownandsummary: "unknown". No facet readsunchangedeither way.Test 8, old payload: a result JSON with no
explanationskey parses throughprocessCaptureComparisonSchemaandparseEvidenceunchanged.Test 9, CLI and MCP:
normalized_resultfromcompareProcessEvidenceFilesequalsstructuredContent.normalized_resultfrom the tool, notes included, sameevidence_id.Test 10 (review 2), an earlier rendered payload that differs together with its time. Both sides have the same raw
dataand two rendered frames each. Frame 0 differs inlinesand inat_ms(10 vs 20). Frame 1, the final screen, is the same.Never
timing_only.Files to touch (lines at
6875d4a6)src/domain/process/processComparisonExplanations.ts: the field schema and a pure builder, roughlyexplainProcessDimensions(left, right, exactDimensions, reportedDimensions). One file, so a rename is a one-file edit.src/domain/process/processComparison.ts. Field after line 86. Guard beside 89-99. Build after 431, where both the exact and post-trace dimensions are in scope. Spread beside 482. The functionsapplyTraceVerdict(294-338) andcompareDimensions(345-398) stay as they are. The helperterminalObservations(104-119) may need a sibling for the stream and final-screen projections.processTraceComparison.tsandprocessTraceDimensionProjection.ts: no edit expected. The builder readsexactDimensions, which sits upstream of both.src/domain/process/processCapture.fixture.ts: two or three shared builders, so the ten tests do not repeat setup..jscpd.jsoncounts test files at a 2.5% threshold.src/application/process/ProcessCli.ts84-108 andsrc/server/registerProcessComparisonTool.ts107-139: no change. Both pass the whole result throughjsonValueSchema.parse, so no key may holdundefined.src/contracts/sessionToolContracts.ts100-104: one optional sentence in the tool description (Q5).docs/process-capture.md"Compare two captures", 179-209: one paragraph after line 191. The capture recipe at the top stays untouched;tests/boundary/process/processCli.test.ts26-75 parses it..cache/mcp-tool-catalog.json,docs/public/product-catalog.json, theCATALOG_IDENTITYdigests (src/catalogIdentity.ts66-75) andskills/. CI regenerates them. Nothing inskill-srcnames this tool.CHANGELOG.mdis release-please owned.Logic
Inputs:
left,right,exactDimensionsfromcompareDimensions(line 420), anddimensionsafterapplyTraceVerdict(425-431).dimensions.exitischanged. Each facet runssameJsonValueover its paths on{ exit, settlement }, the object lines 379-382 compare. Each differing path becomes onedifferencesentry. Theobserved_statusvalue isexactDimensions.exit. When that isunchangedbut the reported status ischanged, the cause isapplyTraceVerdict315-316 (differenttrace withprocessTraceOutcomesDiffer,processTraceComparison.ts54-59). The note then shows every facetunchangedand an empty list. The reported status is not touched.dimensions.terminalischanged,addedorremoved. Thestreamfacet compares the joinedframes[].data. Thefinal_screenfacet compares the last rendered frame on each side, minussequenceandat_ms. Missing rendered frames make itunknown. Therendered_payloadsfacet (review 2) compares every rendered frame, minussequenceandat_ms, index by index. A count mismatch ischanged. This closes the hole from the review. An earlier rendered payload differed with its time. The stream and final screen still matched. Theframe_timingfacet compares theat_mslists and the chunk layout. The four facets now cover the whole merged list thatterminalObservationssorts (104-119). Theunexplainedguard stays as a safety net. Achangeddimension with no changed facet getssummary: "unexplained"and the first divergence path. It is never all-unchanged for an observed change.changedand every other facetunchanged. For terminal that meansstream,final_screenandrendered_payloadsallunchanged(review 2). Anunknownfacet blocks it. When timing changed beside another changed or unknown facet,summaryismixed. Thendifferenceslists the timing entries with the first differing payload path. ThesuperRefineenforces this on the way out.unknown, no note is built for it. The causes sit inclassify(350-362) and line 377. Three things cause that. A residual unknown. A truncated source, perprocessSourceTruncated. Or normalization rules that differ. A note never changesstatus, a dimension, orfirst_divergence(432-477, not edited).context.coveragevalue carries theraw_terminalandrendered_terminalretention counters fromtruncation_details, both sides. A truncated terminal is alreadyunknown(359-361). So on achangeddimension, the counters show that both sides were fully retained.raw_timestamps: "unavailable"is a constant in this slice. The producer stores only bucketedat_ms. Thetiming_basisfield says which facet is raw (settlement.elapsed_ms) and which is bucketed. A later producer change can flip the constant without changing the shape.differencesvalues reuse the boundfirst_divergencealready accepts: one frame'sdataper side. Forstream, the entry is the first differing frame index, its two values, and the two total lengths. Theframe_timinglist is capped (Q4).Tests
Placement follows
docs/testing.md35-56: domain semantics insrc/**/*.test.ts, CLI plus MCP parity intests/boundary/mcp/**. New files neednpm run verify:test-discovery.src/domain/process/processComparison.explanations.test.tsemptyProcessCapture,accountFullyObservedProcessCapture,compareUnverifiedProcessCaptures(processCapture.fixture.ts11, 19, 105)["hel", "lo"]vs["hello"]"bar"vs"foo\rbar"(as inprocessCapture.comparison.test.ts101-108), samerendered_frameson both sideshellovshello worldexit.code0 vs 3,elapsed_ms99 vs 105src/domain/process/processTraceAdapters.test.ts,describeat 71withTerminal(99-120); see the note belowcapture(values(...), { omittedTerminalFrame: true })fromprocessTraceComparison.fixture.ts20-81;residual_unknowns: [{ scope: "exit" }]for the exit sidePROCESS_COMPARISON_EVIDENCE(investigationExamples.ts59-80), a self-comparison with no notetests/boundary/mcp/processComparisonExplanationsMcp.test.tsprocessTraceAdaptersParity.test.ts104-159;settlement.elapsed_ms50 vs 60withRenderedFramebuilder. Two rendered frames per side. Frame 0 differs inlinesandat_ms. Frame 1 and rawdataare the same.Test 6 in full: the spec declares
terminal_rawand bothlifecycleevents in one finite variant. The left capture passes and the right fails on terminal. The verdict isdifferent, soexitflips tochangedwhile its fields stay equal.Tests 2, 3, 7 and 10 need synthetic fixtures. Real captures cannot produce them on demand. There is an acceptance test at
tests/acceptance/tools/localToolContracts.test.ts266-280. It parses a real self-comparison through the output schema. It keeps passing with no edit.Fixtures may vary
settlement.elapsed_msalone. The validator (processCaptureValidation.ts) ordersat_mswithin a collection (18-22). It orders the manifest timestamps (47-50). It needs both checkpoints (114). Nothing tieselapsed_mstoafter_settlement.at_ms.Commands:
The full list runs on Node 24.18.0, the toolchain the repo pins. The domain tests also run on Node 22 and 26 through
test:local.Known risks
The six open questions are at the top of this comment.
Two known risks. The first draft's terminal facets had a hole, which sunxiuguo caught (review 2). A differing earlier rendered payload could pass as timing only. The fourth facet closes it. The
unexplainedguard stays as a safety net. Andjscpdcounts test files at a 2.5% threshold, so fixtures are shared.Size estimate
This is an estimate, not a measurement. About 550-700 lines added and about 10 removed, across 7 files. Give or take 30 percent. Where it comes from:
33bf58518): +739/-50 over 14 files, with producer changes this PR does not make.processCaptureCoverage.ts(105 lines) plus a schema.processCapture.comparison.test.tsis 194 lines for four tests.processComparison.tsitself gains 25-40 lines.🤖 Generated with Claude Code (Fable 5.1) 🦊
@sunxiuguo commented on GitHub (Oct 10, 2026):
One guard detail to tighten in the draft: the current terminal comparison includes every rendered frame. If the joined stream and final screen match, but an earlier rendered payload and its timestamp both differ, the listed facets would allow
timing_only. The acknowledgedunexplainedfallback would not trigger becauseframe_timingalready changed.Before emitting
timing_only, please require equality of all compared rendered payloads after removing the explicitly classified timing fields, or report the narrower observed timing difference with an unexplained remainder. A synthetic regression combining an intermediate-rendered-payload difference with a timestamp difference would cover this gap.This is static review of the proposed logic, not runtime reproduction; the contract decision remains with morluto.
@CassieFox23 commented on GitHub (Oct 10, 2026):
TL;DR: Thanks, sunxiuguo. Your frame guard catch is now in the plan. I updated the scope comment above.
You were right. I checked the source at
de8fc671. Every rendered frame is compared, not just the last one. So a middle frame could differ along with its time, and the old guard would still saytiming_only.What changed in the plan:
rendered_payloads. It compares every rendered frame withat_msandsequenceremoved.timing_onlynow needs the stream, the final screen and every rendered payload to match.mixed. It lists the timing change and the first payload that differs.timing_only.Nothing is built yet. The call on the contract is still morluto's.
🤖 Generated with Claude Code (Opus 5.5) 🦊
@Rinkia commented on GitHub (Oct 30, 2026):
This still reproduces on
rea-agents@6.8.0and onmainat45cc93596(Linux x64 container, MCP). It now also reachescorrelate_static_and_runtime.I ran the same self-written binary twice (
capture_process_scenario,arguments: ["1"],normalization.time_bucket_ms: 1000). The terminal text and theexitobject{code: 0, signal: 0, reason: "exited"}were identical.compare_process_capturesreportedterminal: "unchanged"andexit: "changed", andfirst_divergencepointed toexit, where the only difference wassettlement.elapsed_ms(76 vs 68). With the default 10 ms bucket, the same pair also reportedterminal: "changed"when the single output frame landed atat_ms10 vs 50.In
correlate_static_and_runtime, a mapping of acompare_functionspseudocode change to theexitdimension withexpected_pattern: "static_only"therefore gives a different classification from run to run with the same binaries. It iscontradictionwhenelapsed_msdiffers andhypothesiswhen it happens to match. Two consecutive runs of the same 7-mapping input on main gave 3 and then 2 contradictions. The terminal mappings were stable with the 1000 ms bucket and correct in every case: an internal-only change gavestatic_only, a changed constant gavecochanged, and an unchanged function gaveboth_unchanged.That matches the per-facet explanation proposed here. A caller using the
exitdimension as a stand-in for "exit outcome" currently gets timing noise.