omk-agent-core 0.98.2 → 0.98.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/CHANGELOG.md +644 -0
  2. package/dist/agent.d.ts.map +1 -1
  3. package/dist/agent.js +3 -5
  4. package/dist/agent.js.map +1 -1
  5. package/dist/effects/effect-journal.d.ts +43 -0
  6. package/dist/effects/effect-journal.d.ts.map +1 -0
  7. package/dist/effects/effect-journal.js +186 -0
  8. package/dist/effects/effect-journal.js.map +1 -0
  9. package/dist/effects/effect-recovery.d.ts +70 -0
  10. package/dist/effects/effect-recovery.d.ts.map +1 -0
  11. package/dist/effects/effect-recovery.js +120 -0
  12. package/dist/effects/effect-recovery.js.map +1 -0
  13. package/dist/effects/effect-transitions.d.ts +34 -0
  14. package/dist/effects/effect-transitions.d.ts.map +1 -0
  15. package/dist/effects/effect-transitions.js +148 -0
  16. package/dist/effects/effect-transitions.js.map +1 -0
  17. package/dist/effects/effect-types.d.ts +135 -0
  18. package/dist/effects/effect-types.d.ts.map +1 -0
  19. package/dist/effects/effect-types.js +32 -0
  20. package/dist/effects/effect-types.js.map +1 -0
  21. package/dist/harness/abort-delivery.d.ts +26 -0
  22. package/dist/harness/abort-delivery.d.ts.map +1 -0
  23. package/dist/harness/abort-delivery.js +36 -0
  24. package/dist/harness/abort-delivery.js.map +1 -0
  25. package/dist/harness/agent-harness.d.ts +18 -0
  26. package/dist/harness/agent-harness.d.ts.map +1 -1
  27. package/dist/harness/agent-harness.js +37 -28
  28. package/dist/harness/agent-harness.js.map +1 -1
  29. package/dist/harness/canonical-digest.d.ts +32 -0
  30. package/dist/harness/canonical-digest.d.ts.map +1 -0
  31. package/dist/harness/canonical-digest.js +164 -0
  32. package/dist/harness/canonical-digest.js.map +1 -0
  33. package/dist/harness/deferred-commands.d.ts +53 -0
  34. package/dist/harness/deferred-commands.d.ts.map +1 -0
  35. package/dist/harness/deferred-commands.js +96 -0
  36. package/dist/harness/deferred-commands.js.map +1 -0
  37. package/dist/harness/operation-outcome.d.ts +18 -10
  38. package/dist/harness/operation-outcome.d.ts.map +1 -1
  39. package/dist/harness/operation-outcome.js +71 -35
  40. package/dist/harness/operation-outcome.js.map +1 -1
  41. package/dist/harness/operation-trace-divergence.d.ts +60 -0
  42. package/dist/harness/operation-trace-divergence.d.ts.map +1 -0
  43. package/dist/harness/operation-trace-divergence.js +199 -0
  44. package/dist/harness/operation-trace-divergence.js.map +1 -0
  45. package/dist/harness/operation-trace.d.ts +134 -0
  46. package/dist/harness/operation-trace.d.ts.map +1 -0
  47. package/dist/harness/operation-trace.js +161 -0
  48. package/dist/harness/operation-trace.js.map +1 -0
  49. package/dist/harness/subscriber-fanout.d.ts +14 -1
  50. package/dist/harness/subscriber-fanout.d.ts.map +1 -1
  51. package/dist/harness/subscriber-fanout.js +25 -6
  52. package/dist/harness/subscriber-fanout.js.map +1 -1
  53. package/dist/index.d.ts +2 -0
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/index.js +2 -0
  56. package/dist/index.js.map +1 -1
  57. package/dist/listener-delivery.d.ts +22 -0
  58. package/dist/listener-delivery.d.ts.map +1 -0
  59. package/dist/listener-delivery.js +36 -0
  60. package/dist/listener-delivery.js.map +1 -0
  61. package/package.json +4 -3
@@ -1 +1 @@
1
- {"version":3,"file":"operation-outcome.js","sourceRoot":"","sources":["../../src/harness/operation-outcome.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Pure outcome classification and error aggregation for harness operations.\n *\n * Everything here is a total function over already-observed results: it never\n * touches lifecycle state, sessions, providers, or clocks. Keeping the rules in\n * one leaf module makes the precedence auditable in isolation and keeps\n * `agent-harness.ts` free of the branch-heavy classification tables.\n */\n\nimport { type AssistantMessage, isContextOverflow } from \"omk-ai\";\nimport type { HarnessAttemptOutcome, HarnessOperationOutcome } from \"./operation-lifecycle-types.ts\";\nimport type { NavigateTreeResult } from \"./types.ts\";\nimport { AgentHarnessError, BranchSummaryError, CompactionError, SessionError, toError } from \"./types.ts\";\n\n/**\n * True only for an error that *is* an abort, not merely an error raised while\n * an abort signal happened to be up. `AgentHarnessError` has no \"aborted\" code,\n * so an explicit abort reaches us as a subsystem error carrying code \"aborted\"\n * or as a DOM-style `AbortError`.\n */\nexport function isExplicitAbortError(error: unknown): boolean {\n\tconst cause = toError(error);\n\tif (cause.name === \"AbortError\") return true;\n\treturn (cause instanceof CompactionError || cause instanceof BranchSummaryError) && cause.code === \"aborted\";\n}\n\n/** Map a subsystem failure onto the harness' stable top-level classification. */\nexport function normalizeHarnessError(error: unknown, fallbackCode: AgentHarnessError[\"code\"]): AgentHarnessError {\n\tif (error instanceof AgentHarnessError) return error;\n\tconst cause = toError(error);\n\tif (cause instanceof SessionError) return new AgentHarnessError(\"session\", cause.message, cause);\n\tif (cause instanceof CompactionError) return new AgentHarnessError(\"compaction\", cause.message, cause);\n\tif (cause instanceof BranchSummaryError) return new AgentHarnessError(\"branch_summary\", cause.message, cause);\n\treturn new AgentHarnessError(fallbackCode, cause.message, cause);\n}\n\n/** Result-based outcome for prompt-family operations that resolve with a failure/abort assistant message. */\nexport function classifyAssistantOutcome(message: AssistantMessage): HarnessOperationOutcome | undefined {\n\tif (message.stopReason === \"aborted\") return { status: \"aborted\" };\n\tif (message.stopReason === \"error\") {\n\t\treturn { status: \"failed\", code: \"provider\", message: message.errorMessage ?? \"Provider error\" };\n\t}\n\treturn undefined;\n}\n\n/** Structural cancellation is a distinct, non-failure terminal outcome. */\nexport function classifyNavigateTreeOutcome(result: NavigateTreeResult): HarnessOperationOutcome {\n\treturn result.cancelled ? { status: \"cancelled\", reason: \"tree_navigation_cancelled\" } : { status: \"completed\" };\n}\n\n/** A thrown attempt body is an aborted attempt only when the error itself is an abort. */\nexport function classifyAttemptFailure(error: unknown): HarnessAttemptOutcome {\n\treturn isExplicitAbortError(error) ? \"aborted\" : \"failed\";\n}\n\n/** Context overflow is a recoverable attempt outcome, not an attempt failure. */\nexport function classifyAttemptOutcome(\n\tmessage: AssistantMessage,\n\tcontextWindow: number | undefined,\n): HarnessAttemptOutcome {\n\tif (message.stopReason === \"aborted\") return \"aborted\";\n\tif (isContextOverflow(message, contextWindow)) return \"overflow\";\n\tif (message.stopReason === \"error\") return \"failed\";\n\treturn \"completed\";\n}\n\n/**\n * Single outcome-precedence rule for every public operation:\n *\n * session persistence failure > non-abort body/hook failure >\n * explicit abort > result-classified outcome > completed\n *\n * A raised abort signal alone never downgrades another failure to \"aborted\":\n * only an error that *is* an abort does. Otherwise a flush failure during an\n * aborted turn would settle as \"aborted\" while the public promise rejected\n * with \"session\".\n */\nexport function resolveOperationOutcome<T>(input: {\n\treadonly signalAborted: boolean;\n\treadonly result: T | undefined;\n\treadonly bodyError: unknown;\n\treadonly flushError: unknown;\n\treadonly classifyResult: ((result: T) => HarnessOperationOutcome | undefined) | undefined;\n\treadonly fallbackCode: AgentHarnessError[\"code\"];\n}): HarnessOperationOutcome {\n\tif (input.flushError !== undefined) {\n\t\t// Mirror `resolveOperationFailure`: a flush error that already carries a\n\t\t// harness classification (e.g. an `invalid_state` coordinator reentry)\n\t\t// keeps it, so the recorded outcome and the rejection never disagree.\n\t\tconst error = normalizeHarnessError(input.flushError, \"session\");\n\t\treturn { status: \"failed\", code: error.code, message: error.message };\n\t}\n\tif (input.bodyError !== undefined) {\n\t\tif (isExplicitAbortError(input.bodyError)) return { status: \"aborted\" };\n\t\tconst error = normalizeHarnessError(input.bodyError, input.fallbackCode);\n\t\treturn { status: \"failed\", code: error.code, message: error.message };\n\t}\n\treturn (\n\t\tinput.classifyResult?.(input.result as T) ??\n\t\t(input.signalAborted ? { status: \"aborted\" } : { status: \"completed\" })\n\t);\n}\n\n/**\n * The error a boundary should throw after several steps may have failed, or\n * `undefined` when none did. A single failure is returned untouched so its\n * own classification survives; several are kept reachable through one\n * `AggregateError`, classified by the first (primary) failure. This is what\n * lets a failing boundary flush report *alongside* the body or listener error\n * it followed instead of erasing it.\n */\nexport function combineBoundaryErrors(\n\terrors: readonly unknown[],\n\tmessage: string,\n\tfallbackCode: AgentHarnessError[\"code\"],\n): unknown {\n\tconst present = errors.filter((error) => error !== undefined);\n\tif (present.length <= 1) return present[0];\n\tconst cause = new AggregateError(present.map(toError), message);\n\treturn new AgentHarnessError(normalizeHarnessError(present[0], fallbackCode).code, cause.message, cause);\n}\n\n/**\n * Which error a public operation rejects with, or `undefined` on success.\n *\n * Mirrors the outcome precedence, but every concurrent cause is preserved in an\n * `AggregateError` so an audit can still see that, say, the body and the final\n * flush failed together.\n */\nexport function resolveOperationFailure(input: {\n\treadonly bodyError: unknown;\n\treadonly flushError: unknown;\n\treadonly settleError: unknown;\n\treadonly fallbackCode: AgentHarnessError[\"code\"];\n}): AgentHarnessError | undefined {\n\tconst primaryError = input.bodyError ?? input.flushError;\n\tif (primaryError !== undefined && input.settleError !== undefined) {\n\t\tconst cause = new AggregateError(\n\t\t\t[toError(primaryError), toError(input.settleError)],\n\t\t\t\"Operation failed and settlement failed\",\n\t\t);\n\t\treturn new AgentHarnessError(normalizeHarnessError(primaryError, input.fallbackCode).code, cause.message, cause);\n\t}\n\tif (input.settleError !== undefined) return normalizeHarnessError(input.settleError, \"hook\");\n\tif (input.flushError !== undefined) {\n\t\tif (input.bodyError === undefined) return normalizeHarnessError(input.flushError, \"session\");\n\t\tconst cause = new AggregateError(\n\t\t\t[toError(input.bodyError), toError(input.flushError)],\n\t\t\t\"Operation failed and the final flush failed\",\n\t\t);\n\t\treturn new AgentHarnessError(\"session\", cause.message, cause);\n\t}\n\tif (input.bodyError !== undefined) return normalizeHarnessError(input.bodyError, input.fallbackCode);\n\treturn undefined;\n}\n"]}
1
+ {"version":3,"file":"operation-outcome.js","sourceRoot":"","sources":["../../src/harness/operation-outcome.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAyB,iBAAiB,EAAE,MAAM,QAAQ,CAAC;AAGlE,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,eAAe,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE3G;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAAC,KAAc,EAAW;IAC7D,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IAC7B,IAAI,KAAK,CAAC,IAAI,KAAK,YAAY;QAAE,OAAO,IAAI,CAAC;IAC7C,OAAO,CAAC,KAAK,YAAY,eAAe,IAAI,KAAK,YAAY,kBAAkB,CAAC,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC;AAAA,CAC7G;AAED,iFAAiF;AACjF,MAAM,UAAU,qBAAqB,CAAC,KAAc,EAAE,YAAuC,EAAqB;IACjH,IAAI,KAAK,YAAY,iBAAiB;QAAE,OAAO,KAAK,CAAC;IACrD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IAC7B,IAAI,KAAK,YAAY,YAAY;QAAE,OAAO,IAAI,iBAAiB,CAAC,SAAS,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IACjG,IAAI,KAAK,YAAY,eAAe;QAAE,OAAO,IAAI,iBAAiB,CAAC,YAAY,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IACvG,IAAI,KAAK,YAAY,kBAAkB;QAAE,OAAO,IAAI,iBAAiB,CAAC,gBAAgB,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IAC9G,OAAO,IAAI,iBAAiB,CAAC,YAAY,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;AAAA,CACjE;AAED,6GAA6G;AAC7G,MAAM,UAAU,wBAAwB,CAAC,OAAyB,EAAuC;IACxG,IAAI,OAAO,CAAC,UAAU,KAAK,SAAS;QAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;IACnE,IAAI,OAAO,CAAC,UAAU,KAAK,OAAO,EAAE,CAAC;QACpC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,CAAC,YAAY,IAAI,gBAAgB,EAAE,CAAC;IAClG,CAAC;IACD,OAAO,SAAS,CAAC;AAAA,CACjB;AAED,2EAA2E;AAC3E,MAAM,UAAU,2BAA2B,CAAC,MAA0B,EAA2B;IAChG,OAAO,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,2BAA2B,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;AAAA,CACjH;AAED,0FAA0F;AAC1F,MAAM,UAAU,sBAAsB,CAAC,KAAc,EAAyB;IAC7E,OAAO,oBAAoB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC;AAAA,CAC1D;AAED,iFAAiF;AACjF,MAAM,UAAU,sBAAsB,CACrC,OAAyB,EACzB,aAAiC,EACT;IACxB,IAAI,OAAO,CAAC,UAAU,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IACvD,IAAI,iBAAiB,CAAC,OAAO,EAAE,aAAa,CAAC;QAAE,OAAO,UAAU,CAAC;IACjE,IAAI,OAAO,CAAC,UAAU,KAAK,OAAO;QAAE,OAAO,QAAQ,CAAC;IACpD,OAAO,WAAW,CAAC;AAAA,CACnB;AAED;;;;;;;;;;GAUG;AACH,SAAS,oBAAoB,CAAC,KAI7B,EAEY;IACZ,IAAI,KAAK,CAAC,UAAU,KAAK,SAAS;QAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,UAAU,EAAE,YAAY,EAAE,SAAS,EAAE,CAAC;IAChH,IAAI,KAAK,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;QACnC,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,SAAS,EAAE,YAAY,EAAE,KAAK,CAAC,YAAY,EAAE,CAAC;IACpF,CAAC;IACD,OAAO,SAAS,CAAC;AAAA,CACjB;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,uBAAuB,CAAI,KAO1C,EAA2B;IAC3B,MAAM,MAAM,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAC;IAC3C,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QAC1B,8EAA8E;QAC9E,+CAA+C;QAC/C,IAAI,MAAM,CAAC,KAAK,KAAK,MAAM,IAAI,oBAAoB,CAAC,MAAM,CAAC,KAAK,CAAC;YAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;QAChG,MAAM,KAAK,GAAG,qBAAqB,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,YAAY,CAAC,CAAC;QACvE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC;IACvE,CAAC;IACD,OAAO,CACN,KAAK,CAAC,cAAc,EAAE,CAAC,KAAK,CAAC,MAAW,CAAC;QACzC,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,CACvE,CAAC;AAAA,CACF;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,KAAgD,EAAoB;IAC3G,MAAM,MAAM,GAAY,EAAE,CAAC;IAC3B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QAC1B,IAAI,CAAC;YACJ,MAAM,IAAI,EAAE,CAAC;QACd,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;QAC7B,CAAC;IACF,CAAC;IACD,OAAO,MAAM,CAAC;AAAA,CACd;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,uBAAuB,CAAC,KAKvC,EAAiC;IACjC,MAAM,MAAM,GAAG,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC;IAC7G,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAC1C,MAAM,MAAM,GAAG,oBAAoB,CAAC,KAAK,CAAC,IAAI;QAC7C,KAAK,EAAE,QAAiB;QACxB,KAAK,EAAE,KAAK,CAAC,WAAW;QACxB,YAAY,EAAE,MAAe;KAC7B,CAAC;IACF,MAAM,IAAI,GAAG,qBAAqB,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC;IAC3E,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,qBAAqB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,YAAY,CAAC,CAAC;IACtF,MAAM,MAAM,GAAG;QACd,KAAK,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS;QAClD,KAAK,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS;QAC1D,KAAK,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS;KAC1D,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC;IACzC,MAAM,KAAK,GAAG,IAAI,cAAc,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,qBAAqB,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACjG,OAAO,IAAI,iBAAiB,CAAC,IAAI,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;AAAA,CACzD;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,qBAAqB,CACpC,MAA0B,EAC1B,OAAe,EACf,YAAuC,EAC7B;IACV,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC;IAC9D,IAAI,OAAO,CAAC,MAAM,IAAI,CAAC;QAAE,OAAO,OAAO,CAAC,CAAC,CAAC,CAAC;IAC3C,MAAM,KAAK,GAAG,IAAI,cAAc,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,CAAC;IAChE,OAAO,IAAI,iBAAiB,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;AAAA,CACzG","sourcesContent":["/**\n * Pure outcome classification and error aggregation for harness operations.\n *\n * Everything here is a total function over already-observed results: it never\n * touches lifecycle state, sessions, providers, or clocks. Keeping the rules in\n * one leaf module makes the precedence auditable in isolation and keeps\n * `agent-harness.ts` free of the branch-heavy classification tables.\n */\n\nimport { type AssistantMessage, isContextOverflow } from \"omk-ai\";\nimport type { HarnessAttemptOutcome, HarnessOperationOutcome } from \"./operation-lifecycle-types.ts\";\nimport type { NavigateTreeResult } from \"./types.ts\";\nimport { AgentHarnessError, BranchSummaryError, CompactionError, SessionError, toError } from \"./types.ts\";\n\n/**\n * True only for an error that *is* an abort, not merely an error raised while\n * an abort signal happened to be up. `AgentHarnessError` has no \"aborted\" code,\n * so an explicit abort reaches us as a subsystem error carrying code \"aborted\"\n * or as a DOM-style `AbortError`.\n */\nexport function isExplicitAbortError(error: unknown): boolean {\n\tconst cause = toError(error);\n\tif (cause.name === \"AbortError\") return true;\n\treturn (cause instanceof CompactionError || cause instanceof BranchSummaryError) && cause.code === \"aborted\";\n}\n\n/** Map a subsystem failure onto the harness' stable top-level classification. */\nexport function normalizeHarnessError(error: unknown, fallbackCode: AgentHarnessError[\"code\"]): AgentHarnessError {\n\tif (error instanceof AgentHarnessError) return error;\n\tconst cause = toError(error);\n\tif (cause instanceof SessionError) return new AgentHarnessError(\"session\", cause.message, cause);\n\tif (cause instanceof CompactionError) return new AgentHarnessError(\"compaction\", cause.message, cause);\n\tif (cause instanceof BranchSummaryError) return new AgentHarnessError(\"branch_summary\", cause.message, cause);\n\treturn new AgentHarnessError(fallbackCode, cause.message, cause);\n}\n\n/** Result-based outcome for prompt-family operations that resolve with a failure/abort assistant message. */\nexport function classifyAssistantOutcome(message: AssistantMessage): HarnessOperationOutcome | undefined {\n\tif (message.stopReason === \"aborted\") return { status: \"aborted\" };\n\tif (message.stopReason === \"error\") {\n\t\treturn { status: \"failed\", code: \"provider\", message: message.errorMessage ?? \"Provider error\" };\n\t}\n\treturn undefined;\n}\n\n/** Structural cancellation is a distinct, non-failure terminal outcome. */\nexport function classifyNavigateTreeOutcome(result: NavigateTreeResult): HarnessOperationOutcome {\n\treturn result.cancelled ? { status: \"cancelled\", reason: \"tree_navigation_cancelled\" } : { status: \"completed\" };\n}\n\n/** A thrown attempt body is an aborted attempt only when the error itself is an abort. */\nexport function classifyAttemptFailure(error: unknown): HarnessAttemptOutcome {\n\treturn isExplicitAbortError(error) ? \"aborted\" : \"failed\";\n}\n\n/** Context overflow is a recoverable attempt outcome, not an attempt failure. */\nexport function classifyAttemptOutcome(\n\tmessage: AssistantMessage,\n\tcontextWindow: number | undefined,\n): HarnessAttemptOutcome {\n\tif (message.stopReason === \"aborted\") return \"aborted\";\n\tif (isContextOverflow(message, contextWindow)) return \"overflow\";\n\tif (message.stopReason === \"error\") return \"failed\";\n\treturn \"completed\";\n}\n\n/**\n * The single classification source for a failed operation: `flush > body`.\n *\n * Session persistence outranks the body because a flush failure after a provider\n * success must never record or report a completed operation, and a flush error\n * that already carries a harness classification (e.g. an `invalid_state`\n * coordinator reentry) keeps it. Both the recorded outcome and the public\n * rejection read their top-level code from here, so the two can never disagree.\n * Settlement is not a source: it runs after the outcome is recorded, so it can\n * only add a cause and a rejection.\n */\nfunction classificationSource(input: {\n\treadonly bodyError: unknown;\n\treadonly flushError: unknown;\n\treadonly fallbackCode: AgentHarnessError[\"code\"];\n}):\n\t| { readonly stage: \"body\" | \"flush\"; readonly error: unknown; readonly fallbackCode: AgentHarnessError[\"code\"] }\n\t| undefined {\n\tif (input.flushError !== undefined) return { stage: \"flush\", error: input.flushError, fallbackCode: \"session\" };\n\tif (input.bodyError !== undefined) {\n\t\treturn { stage: \"body\", error: input.bodyError, fallbackCode: input.fallbackCode };\n\t}\n\treturn undefined;\n}\n\n/**\n * Single outcome-precedence rule for every public operation:\n *\n * session persistence failure > non-abort body/hook failure >\n * explicit abort > result-classified outcome > completed\n *\n * A raised abort signal alone never downgrades another failure to \"aborted\":\n * only an error that *is* an abort does. Otherwise a flush failure during an\n * aborted turn would settle as \"aborted\" while the public promise rejected\n * with \"session\".\n */\nexport function resolveOperationOutcome<T>(input: {\n\treadonly signalAborted: boolean;\n\treadonly result: T | undefined;\n\treadonly bodyError: unknown;\n\treadonly flushError: unknown;\n\treadonly classifyResult: ((result: T) => HarnessOperationOutcome | undefined) | undefined;\n\treadonly fallbackCode: AgentHarnessError[\"code\"];\n}): HarnessOperationOutcome {\n\tconst source = classificationSource(input);\n\tif (source !== undefined) {\n\t\t// Only a body failure can be an abort; a flush or settle failure is a failure\n\t\t// even when the abort signal happens to be up.\n\t\tif (source.stage === \"body\" && isExplicitAbortError(source.error)) return { status: \"aborted\" };\n\t\tconst error = normalizeHarnessError(source.error, source.fallbackCode);\n\t\treturn { status: \"failed\", code: error.code, message: error.message };\n\t}\n\treturn (\n\t\tinput.classifyResult?.(input.result as T) ??\n\t\t(input.signalAborted ? { status: \"aborted\" } : { status: \"completed\" })\n\t);\n}\n\n/**\n * Run boundary steps in order and collect their errors instead of stopping at\n * the first. A boundary that must still report, flush, or settle after one step\n * fails uses this so one failure cannot strand the rest.\n */\nexport async function collectStepErrors(steps: ReadonlyArray<() => Promise<void> | void>): Promise<Error[]> {\n\tconst errors: Error[] = [];\n\tfor (const step of steps) {\n\t\ttry {\n\t\t\tawait step();\n\t\t} catch (error) {\n\t\t\terrors.push(toError(error));\n\t\t}\n\t}\n\treturn errors;\n}\n\n/**\n * Which error a public operation rejects with, or `undefined` on success.\n *\n * The top-level code comes from the same `flush > body` source the recorded\n * outcome uses, so `outcome.code === rejection.code` for every failed outcome;\n * settlement only ever contributes a cause. Every concurrent cause stays\n * reachable through one `AggregateError` in body, flush, settle order, so an\n * audit can still see that, say, the body and the final flush failed together.\n */\nexport function resolveOperationFailure(input: {\n\treadonly bodyError: unknown;\n\treadonly flushError: unknown;\n\treadonly settleError: unknown;\n\treadonly fallbackCode: AgentHarnessError[\"code\"];\n}): AgentHarnessError | undefined {\n\tconst causes = [input.bodyError, input.flushError, input.settleError].filter((error) => error !== undefined);\n\tif (causes.length === 0) return undefined;\n\tconst source = classificationSource(input) ?? {\n\t\tstage: \"settle\" as const,\n\t\terror: input.settleError,\n\t\tfallbackCode: \"hook\" as const,\n\t};\n\tconst code = normalizeHarnessError(source.error, source.fallbackCode).code;\n\tif (causes.length === 1) return normalizeHarnessError(causes[0], source.fallbackCode);\n\tconst stages = [\n\t\tinput.bodyError !== undefined ? \"body\" : undefined,\n\t\tinput.flushError !== undefined ? \"final flush\" : undefined,\n\t\tinput.settleError !== undefined ? \"settlement\" : undefined,\n\t].filter((stage) => stage !== undefined);\n\tconst cause = new AggregateError(causes.map(toError), `Operation failed (${stages.join(\", \")})`);\n\treturn new AgentHarnessError(code, cause.message, cause);\n}\n\n/**\n * The error a boundary should throw after several steps may have failed, or\n * `undefined` when none did. A single failure is returned untouched so its\n * own classification survives; several are kept reachable through one\n * `AggregateError`, classified by the first (primary) failure. This is what\n * lets a failing boundary flush report *alongside* the body or listener error\n * it followed instead of erasing it.\n */\nexport function combineBoundaryErrors(\n\terrors: readonly unknown[],\n\tmessage: string,\n\tfallbackCode: AgentHarnessError[\"code\"],\n): unknown {\n\tconst present = errors.filter((error) => error !== undefined);\n\tif (present.length <= 1) return present[0];\n\tconst cause = new AggregateError(present.map(toError), message);\n\treturn new AgentHarnessError(normalizeHarnessError(present[0], fallbackCode).code, cause.message, cause);\n}\n"]}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Trace comparison for shadow-mode runtime convergence (FND-003, Stage C).
3
+ *
4
+ * While `AgentHarness` runs as a shadow reducer beside the authoritative
5
+ * `AgentSession`, every scenario yields two traces. This module reduces each
6
+ * trace to a per-operation summary and classifies every difference as a
7
+ * `blocker` (the two runtimes disagree about lifecycle semantics) or a
8
+ * `non_blocker` (they took a different path to the same committed state).
9
+ *
10
+ * Blocker dimensions follow the plan verbatim: operation count and identity,
11
+ * the attempt set and how each attempt closed, terminal status and public
12
+ * error code, accepted deferred-command IDs in order, session-write order,
13
+ * and effect uncertainty left open at settlement. Stage paths and abort
14
+ * signalling without an outcome difference are informational only.
15
+ */
16
+ import { type OperationTraceEvent, type TraceOutcome } from "./operation-trace.ts";
17
+ export type TraceDivergenceClass = "blocker" | "non_blocker";
18
+ export type TraceDivergenceDimension = "operation_count" | "operation_identity" | "attempt_set" | "terminal_status" | "public_error_code" | "accepted_commands" | "session_write_order" | "effect_uncertainty" | "stage_path" | "abort_signal";
19
+ export interface TraceDivergence {
20
+ readonly dimension: TraceDivergenceDimension;
21
+ readonly class: TraceDivergenceClass;
22
+ readonly operationIndex?: number;
23
+ readonly left: string;
24
+ readonly right: string;
25
+ }
26
+ export interface TraceComparison {
27
+ readonly equal: boolean;
28
+ readonly leftDigest: string;
29
+ readonly rightDigest: string;
30
+ readonly blockers: readonly TraceDivergence[];
31
+ readonly nonBlockers: readonly TraceDivergence[];
32
+ }
33
+ export interface TraceAttemptSummary {
34
+ readonly attemptId: string;
35
+ readonly index: number;
36
+ readonly reason: string;
37
+ readonly outcome?: string;
38
+ }
39
+ /** Everything the comparison reads about one operation, in source order. */
40
+ export interface OperationTraceSummary {
41
+ readonly operationId: string;
42
+ readonly kind: string;
43
+ readonly sequence: number;
44
+ readonly stages: readonly string[];
45
+ readonly attempts: readonly TraceAttemptSummary[];
46
+ readonly abortRequested: boolean;
47
+ readonly settlement?: TraceOutcome;
48
+ readonly settled?: TraceOutcome;
49
+ /** Effects marked uncertain and not reconciled before the operation settled. */
50
+ readonly unresolvedEffectIds: readonly string[];
51
+ }
52
+ export interface TraceSummary {
53
+ readonly operations: readonly OperationTraceSummary[];
54
+ readonly acceptedCommandIds: readonly string[];
55
+ readonly sessionWrites: readonly string[];
56
+ }
57
+ export declare function summarizeTrace(events: readonly OperationTraceEvent[]): TraceSummary;
58
+ /** Compare two traces of the same scenario. Equal digests short-circuit to an empty report. */
59
+ export declare function compareOperationTraces(left: readonly OperationTraceEvent[], right: readonly OperationTraceEvent[]): TraceComparison;
60
+ //# sourceMappingURL=operation-trace-divergence.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"operation-trace-divergence.d.ts","sourceRoot":"","sources":["../../src/harness/operation-trace-divergence.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAsB,KAAK,mBAAmB,EAAE,KAAK,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEvG,MAAM,MAAM,oBAAoB,GAAG,SAAS,GAAG,aAAa,CAAC;AAE7D,MAAM,MAAM,wBAAwB,GACjC,iBAAiB,GACjB,oBAAoB,GACpB,aAAa,GACb,iBAAiB,GACjB,mBAAmB,GACnB,mBAAmB,GACnB,qBAAqB,GACrB,oBAAoB,GACpB,YAAY,GACZ,cAAc,CAAC;AAalB,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,SAAS,EAAE,wBAAwB,CAAC;IAC7C,QAAQ,CAAC,KAAK,EAAE,oBAAoB,CAAC;IACrC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,SAAS,eAAe,EAAE,CAAC;IAC9C,QAAQ,CAAC,WAAW,EAAE,SAAS,eAAe,EAAE,CAAC;CACjD;AAED,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,4EAA4E;AAC5E,MAAM,WAAW,qBAAqB;IACrC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,QAAQ,CAAC,QAAQ,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAClD,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,UAAU,CAAC,EAAE,YAAY,CAAC;IACnC,QAAQ,CAAC,OAAO,CAAC,EAAE,YAAY,CAAC;IAChC,gFAAgF;IAChF,QAAQ,CAAC,mBAAmB,EAAE,SAAS,MAAM,EAAE,CAAC;CAChD;AAED,MAAM,WAAW,YAAY;IAC5B,QAAQ,CAAC,UAAU,EAAE,SAAS,qBAAqB,EAAE,CAAC;IACtD,QAAQ,CAAC,kBAAkB,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/C,QAAQ,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1C;AAcD,wBAAgB,cAAc,CAAC,MAAM,EAAE,SAAS,mBAAmB,EAAE,GAAG,YAAY,CAuFnF;AA6CD,+FAA+F;AAC/F,wBAAgB,sBAAsB,CACrC,IAAI,EAAE,SAAS,mBAAmB,EAAE,EACpC,KAAK,EAAE,SAAS,mBAAmB,EAAE,GACnC,eAAe,CAoCjB","sourcesContent":["/**\n * Trace comparison for shadow-mode runtime convergence (FND-003, Stage C).\n *\n * While `AgentHarness` runs as a shadow reducer beside the authoritative\n * `AgentSession`, every scenario yields two traces. This module reduces each\n * trace to a per-operation summary and classifies every difference as a\n * `blocker` (the two runtimes disagree about lifecycle semantics) or a\n * `non_blocker` (they took a different path to the same committed state).\n *\n * Blocker dimensions follow the plan verbatim: operation count and identity,\n * the attempt set and how each attempt closed, terminal status and public\n * error code, accepted deferred-command IDs in order, session-write order,\n * and effect uncertainty left open at settlement. Stage paths and abort\n * signalling without an outcome difference are informational only.\n */\n\nimport { computeTraceDigest, type OperationTraceEvent, type TraceOutcome } from \"./operation-trace.ts\";\n\nexport type TraceDivergenceClass = \"blocker\" | \"non_blocker\";\n\nexport type TraceDivergenceDimension =\n\t| \"operation_count\"\n\t| \"operation_identity\"\n\t| \"attempt_set\"\n\t| \"terminal_status\"\n\t| \"public_error_code\"\n\t| \"accepted_commands\"\n\t| \"session_write_order\"\n\t| \"effect_uncertainty\"\n\t| \"stage_path\"\n\t| \"abort_signal\";\n\nconst BLOCKER_DIMENSIONS: ReadonlySet<TraceDivergenceDimension> = new Set([\n\t\"operation_count\",\n\t\"operation_identity\",\n\t\"attempt_set\",\n\t\"terminal_status\",\n\t\"public_error_code\",\n\t\"accepted_commands\",\n\t\"session_write_order\",\n\t\"effect_uncertainty\",\n]);\n\nexport interface TraceDivergence {\n\treadonly dimension: TraceDivergenceDimension;\n\treadonly class: TraceDivergenceClass;\n\treadonly operationIndex?: number;\n\treadonly left: string;\n\treadonly right: string;\n}\n\nexport interface TraceComparison {\n\treadonly equal: boolean;\n\treadonly leftDigest: string;\n\treadonly rightDigest: string;\n\treadonly blockers: readonly TraceDivergence[];\n\treadonly nonBlockers: readonly TraceDivergence[];\n}\n\nexport interface TraceAttemptSummary {\n\treadonly attemptId: string;\n\treadonly index: number;\n\treadonly reason: string;\n\treadonly outcome?: string;\n}\n\n/** Everything the comparison reads about one operation, in source order. */\nexport interface OperationTraceSummary {\n\treadonly operationId: string;\n\treadonly kind: string;\n\treadonly sequence: number;\n\treadonly stages: readonly string[];\n\treadonly attempts: readonly TraceAttemptSummary[];\n\treadonly abortRequested: boolean;\n\treadonly settlement?: TraceOutcome;\n\treadonly settled?: TraceOutcome;\n\t/** Effects marked uncertain and not reconciled before the operation settled. */\n\treadonly unresolvedEffectIds: readonly string[];\n}\n\nexport interface TraceSummary {\n\treadonly operations: readonly OperationTraceSummary[];\n\treadonly acceptedCommandIds: readonly string[];\n\treadonly sessionWrites: readonly string[];\n}\n\ninterface MutableOperation {\n\toperationId: string;\n\tkind: string;\n\tsequence: number;\n\tstages: string[];\n\tattempts: TraceAttemptSummary[];\n\tabortRequested: boolean;\n\tsettlement?: TraceOutcome;\n\tsettled?: TraceOutcome;\n\tunresolved: Set<string>;\n}\n\nexport function summarizeTrace(events: readonly OperationTraceEvent[]): TraceSummary {\n\tconst operations: MutableOperation[] = [];\n\tconst byId = new Map<string, MutableOperation>();\n\tconst acceptedCommandIds: string[] = [];\n\tconst sessionWrites: string[] = [];\n\tfor (const event of events) {\n\t\tswitch (event.type) {\n\t\t\tcase \"operation_started\": {\n\t\t\t\tconst operation: MutableOperation = {\n\t\t\t\t\toperationId: event.operationId,\n\t\t\t\t\tkind: event.kind,\n\t\t\t\t\tsequence: event.sequence,\n\t\t\t\t\tstages: [],\n\t\t\t\t\tattempts: [],\n\t\t\t\t\tabortRequested: false,\n\t\t\t\t\tunresolved: new Set(),\n\t\t\t\t};\n\t\t\t\toperations.push(operation);\n\t\t\t\tbyId.set(event.operationId, operation);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"stage_changed\":\n\t\t\t\tbyId.get(event.operationId)?.stages.push(event.stage);\n\t\t\t\tbreak;\n\t\t\tcase \"attempt_started\":\n\t\t\t\tbyId\n\t\t\t\t\t.get(event.operationId)\n\t\t\t\t\t?.attempts.push({ attemptId: event.attemptId, index: event.index, reason: event.reason });\n\t\t\t\tbreak;\n\t\t\tcase \"attempt_finished\": {\n\t\t\t\tconst operation = byId.get(event.operationId);\n\t\t\t\tif (operation === undefined) break;\n\t\t\t\toperation.attempts = operation.attempts.map((attempt) =>\n\t\t\t\t\tattempt.attemptId === event.attemptId ? { ...attempt, outcome: event.outcome } : attempt,\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"abort_requested\": {\n\t\t\t\tconst operation = byId.get(event.operationId);\n\t\t\t\tif (operation !== undefined) operation.abortRequested = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"effect_uncertain\":\n\t\t\t\tbyId.get(event.operationId)?.unresolved.add(event.effectId);\n\t\t\t\tbreak;\n\t\t\tcase \"effect_reconciled\":\n\t\t\t\tbyId.get(event.operationId)?.unresolved.delete(event.effectId);\n\t\t\t\tbreak;\n\t\t\tcase \"effect_prepared\":\n\t\t\t\tbreak;\n\t\t\tcase \"deferred_command_accepted\":\n\t\t\t\tacceptedCommandIds.push(event.commandId);\n\t\t\t\tbreak;\n\t\t\tcase \"session_write_accepted\":\n\t\t\t\tsessionWrites.push(`${event.writeSequence}:${event.writeType}`);\n\t\t\t\tbreak;\n\t\t\tcase \"settlement_started\": {\n\t\t\t\tconst operation = byId.get(event.operationId);\n\t\t\t\tif (operation !== undefined) operation.settlement = event.outcome;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"operation_settled\": {\n\t\t\t\tconst operation = byId.get(event.operationId);\n\t\t\t\tif (operation !== undefined) operation.settled = event.outcome;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tdefault: {\n\t\t\t\tconst unknownEvent: never = event;\n\t\t\t\tthrow new TypeError(`Unknown trace event ${String((unknownEvent as { type?: unknown }).type)}`);\n\t\t\t}\n\t\t}\n\t}\n\treturn {\n\t\toperations: operations.map((operation) => ({\n\t\t\toperationId: operation.operationId,\n\t\t\tkind: operation.kind,\n\t\t\tsequence: operation.sequence,\n\t\t\tstages: operation.stages,\n\t\t\tattempts: operation.attempts,\n\t\t\tabortRequested: operation.abortRequested,\n\t\t\t...(operation.settlement === undefined ? {} : { settlement: operation.settlement }),\n\t\t\t...(operation.settled === undefined ? {} : { settled: operation.settled }),\n\t\t\tunresolvedEffectIds: [...operation.unresolved].sort(compareCodeUnits),\n\t\t})),\n\t\tacceptedCommandIds,\n\t\tsessionWrites,\n\t};\n}\n\n/** Code-unit order, so the report does not depend on the host locale. */\nfunction compareCodeUnits(left: string, right: string): number {\n\tif (left === right) return 0;\n\treturn left < right ? -1 : 1;\n}\n\nfunction describeOutcome(outcome: TraceOutcome | undefined): string {\n\tif (outcome === undefined) return \"<none>\";\n\treturn outcome.status === \"failed\" ? `failed:${outcome.code}` : outcome.status;\n}\n\nfunction describeAttempts(attempts: readonly TraceAttemptSummary[]): string {\n\treturn attempts.map((attempt) => `${attempt.attemptId}/${attempt.reason}=${attempt.outcome ?? \"<open>\"}`).join(\",\");\n}\n\nfunction compareOperation(index: number, left: OperationTraceSummary, right: OperationTraceSummary): TraceDivergence[] {\n\tconst out: TraceDivergence[] = [];\n\tconst push = (dimension: TraceDivergenceDimension, leftValue: string, rightValue: string): void => {\n\t\tif (leftValue === rightValue) return;\n\t\tconst divergenceClass: TraceDivergenceClass = BLOCKER_DIMENSIONS.has(dimension) ? \"blocker\" : \"non_blocker\";\n\t\tout.push({ dimension, class: divergenceClass, operationIndex: index, left: leftValue, right: rightValue });\n\t};\n\tpush(\"operation_identity\", `${left.kind}#${left.sequence}`, `${right.kind}#${right.sequence}`);\n\tpush(\"attempt_set\", describeAttempts(left.attempts), describeAttempts(right.attempts));\n\tconst leftSettled = left.settled ?? left.settlement;\n\tconst rightSettled = right.settled ?? right.settlement;\n\tif ((leftSettled?.status ?? \"<none>\") !== (rightSettled?.status ?? \"<none>\")) {\n\t\tout.push({\n\t\t\tdimension: \"terminal_status\",\n\t\t\tclass: \"blocker\",\n\t\t\toperationIndex: index,\n\t\t\tleft: describeOutcome(leftSettled),\n\t\t\tright: describeOutcome(rightSettled),\n\t\t});\n\t} else if (leftSettled?.status === \"failed\" && rightSettled?.status === \"failed\") {\n\t\tpush(\"public_error_code\", leftSettled.code, rightSettled.code);\n\t}\n\tpush(\"effect_uncertainty\", left.unresolvedEffectIds.join(\",\"), right.unresolvedEffectIds.join(\",\"));\n\tpush(\"stage_path\", left.stages.join(\">\"), right.stages.join(\">\"));\n\tpush(\"abort_signal\", String(left.abortRequested), String(right.abortRequested));\n\treturn out;\n}\n\n/** Compare two traces of the same scenario. Equal digests short-circuit to an empty report. */\nexport function compareOperationTraces(\n\tleft: readonly OperationTraceEvent[],\n\tright: readonly OperationTraceEvent[],\n): TraceComparison {\n\tconst leftDigest = computeTraceDigest(left);\n\tconst rightDigest = computeTraceDigest(right);\n\tif (leftDigest === rightDigest) return { equal: true, leftDigest, rightDigest, blockers: [], nonBlockers: [] };\n\tconst leftSummary = summarizeTrace(left);\n\tconst rightSummary = summarizeTrace(right);\n\tconst divergences: TraceDivergence[] = [];\n\tif (leftSummary.operations.length !== rightSummary.operations.length) {\n\t\tdivergences.push({\n\t\t\tdimension: \"operation_count\",\n\t\t\tclass: \"blocker\",\n\t\t\tleft: String(leftSummary.operations.length),\n\t\t\tright: String(rightSummary.operations.length),\n\t\t});\n\t}\n\tconst shared = Math.min(leftSummary.operations.length, rightSummary.operations.length);\n\tfor (let index = 0; index < shared; index++) {\n\t\tdivergences.push(...compareOperation(index, leftSummary.operations[index], rightSummary.operations[index]));\n\t}\n\tconst leftCommands = leftSummary.acceptedCommandIds.join(\",\");\n\tconst rightCommands = rightSummary.acceptedCommandIds.join(\",\");\n\tif (leftCommands !== rightCommands) {\n\t\tdivergences.push({ dimension: \"accepted_commands\", class: \"blocker\", left: leftCommands, right: rightCommands });\n\t}\n\tconst leftWrites = leftSummary.sessionWrites.join(\",\");\n\tconst rightWrites = rightSummary.sessionWrites.join(\",\");\n\tif (leftWrites !== rightWrites) {\n\t\tdivergences.push({ dimension: \"session_write_order\", class: \"blocker\", left: leftWrites, right: rightWrites });\n\t}\n\treturn {\n\t\tequal: false,\n\t\tleftDigest,\n\t\trightDigest,\n\t\tblockers: divergences.filter((item) => item.class === \"blocker\"),\n\t\tnonBlockers: divergences.filter((item) => item.class === \"non_blocker\"),\n\t};\n}\n"]}
@@ -0,0 +1,199 @@
1
+ /**
2
+ * Trace comparison for shadow-mode runtime convergence (FND-003, Stage C).
3
+ *
4
+ * While `AgentHarness` runs as a shadow reducer beside the authoritative
5
+ * `AgentSession`, every scenario yields two traces. This module reduces each
6
+ * trace to a per-operation summary and classifies every difference as a
7
+ * `blocker` (the two runtimes disagree about lifecycle semantics) or a
8
+ * `non_blocker` (they took a different path to the same committed state).
9
+ *
10
+ * Blocker dimensions follow the plan verbatim: operation count and identity,
11
+ * the attempt set and how each attempt closed, terminal status and public
12
+ * error code, accepted deferred-command IDs in order, session-write order,
13
+ * and effect uncertainty left open at settlement. Stage paths and abort
14
+ * signalling without an outcome difference are informational only.
15
+ */
16
+ import { computeTraceDigest } from "./operation-trace.js";
17
+ const BLOCKER_DIMENSIONS = new Set([
18
+ "operation_count",
19
+ "operation_identity",
20
+ "attempt_set",
21
+ "terminal_status",
22
+ "public_error_code",
23
+ "accepted_commands",
24
+ "session_write_order",
25
+ "effect_uncertainty",
26
+ ]);
27
+ export function summarizeTrace(events) {
28
+ const operations = [];
29
+ const byId = new Map();
30
+ const acceptedCommandIds = [];
31
+ const sessionWrites = [];
32
+ for (const event of events) {
33
+ switch (event.type) {
34
+ case "operation_started": {
35
+ const operation = {
36
+ operationId: event.operationId,
37
+ kind: event.kind,
38
+ sequence: event.sequence,
39
+ stages: [],
40
+ attempts: [],
41
+ abortRequested: false,
42
+ unresolved: new Set(),
43
+ };
44
+ operations.push(operation);
45
+ byId.set(event.operationId, operation);
46
+ break;
47
+ }
48
+ case "stage_changed":
49
+ byId.get(event.operationId)?.stages.push(event.stage);
50
+ break;
51
+ case "attempt_started":
52
+ byId
53
+ .get(event.operationId)
54
+ ?.attempts.push({ attemptId: event.attemptId, index: event.index, reason: event.reason });
55
+ break;
56
+ case "attempt_finished": {
57
+ const operation = byId.get(event.operationId);
58
+ if (operation === undefined)
59
+ break;
60
+ operation.attempts = operation.attempts.map((attempt) => attempt.attemptId === event.attemptId ? { ...attempt, outcome: event.outcome } : attempt);
61
+ break;
62
+ }
63
+ case "abort_requested": {
64
+ const operation = byId.get(event.operationId);
65
+ if (operation !== undefined)
66
+ operation.abortRequested = true;
67
+ break;
68
+ }
69
+ case "effect_uncertain":
70
+ byId.get(event.operationId)?.unresolved.add(event.effectId);
71
+ break;
72
+ case "effect_reconciled":
73
+ byId.get(event.operationId)?.unresolved.delete(event.effectId);
74
+ break;
75
+ case "effect_prepared":
76
+ break;
77
+ case "deferred_command_accepted":
78
+ acceptedCommandIds.push(event.commandId);
79
+ break;
80
+ case "session_write_accepted":
81
+ sessionWrites.push(`${event.writeSequence}:${event.writeType}`);
82
+ break;
83
+ case "settlement_started": {
84
+ const operation = byId.get(event.operationId);
85
+ if (operation !== undefined)
86
+ operation.settlement = event.outcome;
87
+ break;
88
+ }
89
+ case "operation_settled": {
90
+ const operation = byId.get(event.operationId);
91
+ if (operation !== undefined)
92
+ operation.settled = event.outcome;
93
+ break;
94
+ }
95
+ default: {
96
+ const unknownEvent = event;
97
+ throw new TypeError(`Unknown trace event ${String(unknownEvent.type)}`);
98
+ }
99
+ }
100
+ }
101
+ return {
102
+ operations: operations.map((operation) => ({
103
+ operationId: operation.operationId,
104
+ kind: operation.kind,
105
+ sequence: operation.sequence,
106
+ stages: operation.stages,
107
+ attempts: operation.attempts,
108
+ abortRequested: operation.abortRequested,
109
+ ...(operation.settlement === undefined ? {} : { settlement: operation.settlement }),
110
+ ...(operation.settled === undefined ? {} : { settled: operation.settled }),
111
+ unresolvedEffectIds: [...operation.unresolved].sort(compareCodeUnits),
112
+ })),
113
+ acceptedCommandIds,
114
+ sessionWrites,
115
+ };
116
+ }
117
+ /** Code-unit order, so the report does not depend on the host locale. */
118
+ function compareCodeUnits(left, right) {
119
+ if (left === right)
120
+ return 0;
121
+ return left < right ? -1 : 1;
122
+ }
123
+ function describeOutcome(outcome) {
124
+ if (outcome === undefined)
125
+ return "<none>";
126
+ return outcome.status === "failed" ? `failed:${outcome.code}` : outcome.status;
127
+ }
128
+ function describeAttempts(attempts) {
129
+ return attempts.map((attempt) => `${attempt.attemptId}/${attempt.reason}=${attempt.outcome ?? "<open>"}`).join(",");
130
+ }
131
+ function compareOperation(index, left, right) {
132
+ const out = [];
133
+ const push = (dimension, leftValue, rightValue) => {
134
+ if (leftValue === rightValue)
135
+ return;
136
+ const divergenceClass = BLOCKER_DIMENSIONS.has(dimension) ? "blocker" : "non_blocker";
137
+ out.push({ dimension, class: divergenceClass, operationIndex: index, left: leftValue, right: rightValue });
138
+ };
139
+ push("operation_identity", `${left.kind}#${left.sequence}`, `${right.kind}#${right.sequence}`);
140
+ push("attempt_set", describeAttempts(left.attempts), describeAttempts(right.attempts));
141
+ const leftSettled = left.settled ?? left.settlement;
142
+ const rightSettled = right.settled ?? right.settlement;
143
+ if ((leftSettled?.status ?? "<none>") !== (rightSettled?.status ?? "<none>")) {
144
+ out.push({
145
+ dimension: "terminal_status",
146
+ class: "blocker",
147
+ operationIndex: index,
148
+ left: describeOutcome(leftSettled),
149
+ right: describeOutcome(rightSettled),
150
+ });
151
+ }
152
+ else if (leftSettled?.status === "failed" && rightSettled?.status === "failed") {
153
+ push("public_error_code", leftSettled.code, rightSettled.code);
154
+ }
155
+ push("effect_uncertainty", left.unresolvedEffectIds.join(","), right.unresolvedEffectIds.join(","));
156
+ push("stage_path", left.stages.join(">"), right.stages.join(">"));
157
+ push("abort_signal", String(left.abortRequested), String(right.abortRequested));
158
+ return out;
159
+ }
160
+ /** Compare two traces of the same scenario. Equal digests short-circuit to an empty report. */
161
+ export function compareOperationTraces(left, right) {
162
+ const leftDigest = computeTraceDigest(left);
163
+ const rightDigest = computeTraceDigest(right);
164
+ if (leftDigest === rightDigest)
165
+ return { equal: true, leftDigest, rightDigest, blockers: [], nonBlockers: [] };
166
+ const leftSummary = summarizeTrace(left);
167
+ const rightSummary = summarizeTrace(right);
168
+ const divergences = [];
169
+ if (leftSummary.operations.length !== rightSummary.operations.length) {
170
+ divergences.push({
171
+ dimension: "operation_count",
172
+ class: "blocker",
173
+ left: String(leftSummary.operations.length),
174
+ right: String(rightSummary.operations.length),
175
+ });
176
+ }
177
+ const shared = Math.min(leftSummary.operations.length, rightSummary.operations.length);
178
+ for (let index = 0; index < shared; index++) {
179
+ divergences.push(...compareOperation(index, leftSummary.operations[index], rightSummary.operations[index]));
180
+ }
181
+ const leftCommands = leftSummary.acceptedCommandIds.join(",");
182
+ const rightCommands = rightSummary.acceptedCommandIds.join(",");
183
+ if (leftCommands !== rightCommands) {
184
+ divergences.push({ dimension: "accepted_commands", class: "blocker", left: leftCommands, right: rightCommands });
185
+ }
186
+ const leftWrites = leftSummary.sessionWrites.join(",");
187
+ const rightWrites = rightSummary.sessionWrites.join(",");
188
+ if (leftWrites !== rightWrites) {
189
+ divergences.push({ dimension: "session_write_order", class: "blocker", left: leftWrites, right: rightWrites });
190
+ }
191
+ return {
192
+ equal: false,
193
+ leftDigest,
194
+ rightDigest,
195
+ blockers: divergences.filter((item) => item.class === "blocker"),
196
+ nonBlockers: divergences.filter((item) => item.class === "non_blocker"),
197
+ };
198
+ }
199
+ //# sourceMappingURL=operation-trace-divergence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"operation-trace-divergence.js","sourceRoot":"","sources":["../../src/harness/operation-trace-divergence.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Trace comparison for shadow-mode runtime convergence (FND-003, Stage C).\n *\n * While `AgentHarness` runs as a shadow reducer beside the authoritative\n * `AgentSession`, every scenario yields two traces. This module reduces each\n * trace to a per-operation summary and classifies every difference as a\n * `blocker` (the two runtimes disagree about lifecycle semantics) or a\n * `non_blocker` (they took a different path to the same committed state).\n *\n * Blocker dimensions follow the plan verbatim: operation count and identity,\n * the attempt set and how each attempt closed, terminal status and public\n * error code, accepted deferred-command IDs in order, session-write order,\n * and effect uncertainty left open at settlement. Stage paths and abort\n * signalling without an outcome difference are informational only.\n */\n\nimport { computeTraceDigest, type OperationTraceEvent, type TraceOutcome } from \"./operation-trace.ts\";\n\nexport type TraceDivergenceClass = \"blocker\" | \"non_blocker\";\n\nexport type TraceDivergenceDimension =\n\t| \"operation_count\"\n\t| \"operation_identity\"\n\t| \"attempt_set\"\n\t| \"terminal_status\"\n\t| \"public_error_code\"\n\t| \"accepted_commands\"\n\t| \"session_write_order\"\n\t| \"effect_uncertainty\"\n\t| \"stage_path\"\n\t| \"abort_signal\";\n\nconst BLOCKER_DIMENSIONS: ReadonlySet<TraceDivergenceDimension> = new Set([\n\t\"operation_count\",\n\t\"operation_identity\",\n\t\"attempt_set\",\n\t\"terminal_status\",\n\t\"public_error_code\",\n\t\"accepted_commands\",\n\t\"session_write_order\",\n\t\"effect_uncertainty\",\n]);\n\nexport interface TraceDivergence {\n\treadonly dimension: TraceDivergenceDimension;\n\treadonly class: TraceDivergenceClass;\n\treadonly operationIndex?: number;\n\treadonly left: string;\n\treadonly right: string;\n}\n\nexport interface TraceComparison {\n\treadonly equal: boolean;\n\treadonly leftDigest: string;\n\treadonly rightDigest: string;\n\treadonly blockers: readonly TraceDivergence[];\n\treadonly nonBlockers: readonly TraceDivergence[];\n}\n\nexport interface TraceAttemptSummary {\n\treadonly attemptId: string;\n\treadonly index: number;\n\treadonly reason: string;\n\treadonly outcome?: string;\n}\n\n/** Everything the comparison reads about one operation, in source order. */\nexport interface OperationTraceSummary {\n\treadonly operationId: string;\n\treadonly kind: string;\n\treadonly sequence: number;\n\treadonly stages: readonly string[];\n\treadonly attempts: readonly TraceAttemptSummary[];\n\treadonly abortRequested: boolean;\n\treadonly settlement?: TraceOutcome;\n\treadonly settled?: TraceOutcome;\n\t/** Effects marked uncertain and not reconciled before the operation settled. */\n\treadonly unresolvedEffectIds: readonly string[];\n}\n\nexport interface TraceSummary {\n\treadonly operations: readonly OperationTraceSummary[];\n\treadonly acceptedCommandIds: readonly string[];\n\treadonly sessionWrites: readonly string[];\n}\n\ninterface MutableOperation {\n\toperationId: string;\n\tkind: string;\n\tsequence: number;\n\tstages: string[];\n\tattempts: TraceAttemptSummary[];\n\tabortRequested: boolean;\n\tsettlement?: TraceOutcome;\n\tsettled?: TraceOutcome;\n\tunresolved: Set<string>;\n}\n\nexport function summarizeTrace(events: readonly OperationTraceEvent[]): TraceSummary {\n\tconst operations: MutableOperation[] = [];\n\tconst byId = new Map<string, MutableOperation>();\n\tconst acceptedCommandIds: string[] = [];\n\tconst sessionWrites: string[] = [];\n\tfor (const event of events) {\n\t\tswitch (event.type) {\n\t\t\tcase \"operation_started\": {\n\t\t\t\tconst operation: MutableOperation = {\n\t\t\t\t\toperationId: event.operationId,\n\t\t\t\t\tkind: event.kind,\n\t\t\t\t\tsequence: event.sequence,\n\t\t\t\t\tstages: [],\n\t\t\t\t\tattempts: [],\n\t\t\t\t\tabortRequested: false,\n\t\t\t\t\tunresolved: new Set(),\n\t\t\t\t};\n\t\t\t\toperations.push(operation);\n\t\t\t\tbyId.set(event.operationId, operation);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"stage_changed\":\n\t\t\t\tbyId.get(event.operationId)?.stages.push(event.stage);\n\t\t\t\tbreak;\n\t\t\tcase \"attempt_started\":\n\t\t\t\tbyId\n\t\t\t\t\t.get(event.operationId)\n\t\t\t\t\t?.attempts.push({ attemptId: event.attemptId, index: event.index, reason: event.reason });\n\t\t\t\tbreak;\n\t\t\tcase \"attempt_finished\": {\n\t\t\t\tconst operation = byId.get(event.operationId);\n\t\t\t\tif (operation === undefined) break;\n\t\t\t\toperation.attempts = operation.attempts.map((attempt) =>\n\t\t\t\t\tattempt.attemptId === event.attemptId ? { ...attempt, outcome: event.outcome } : attempt,\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"abort_requested\": {\n\t\t\t\tconst operation = byId.get(event.operationId);\n\t\t\t\tif (operation !== undefined) operation.abortRequested = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"effect_uncertain\":\n\t\t\t\tbyId.get(event.operationId)?.unresolved.add(event.effectId);\n\t\t\t\tbreak;\n\t\t\tcase \"effect_reconciled\":\n\t\t\t\tbyId.get(event.operationId)?.unresolved.delete(event.effectId);\n\t\t\t\tbreak;\n\t\t\tcase \"effect_prepared\":\n\t\t\t\tbreak;\n\t\t\tcase \"deferred_command_accepted\":\n\t\t\t\tacceptedCommandIds.push(event.commandId);\n\t\t\t\tbreak;\n\t\t\tcase \"session_write_accepted\":\n\t\t\t\tsessionWrites.push(`${event.writeSequence}:${event.writeType}`);\n\t\t\t\tbreak;\n\t\t\tcase \"settlement_started\": {\n\t\t\t\tconst operation = byId.get(event.operationId);\n\t\t\t\tif (operation !== undefined) operation.settlement = event.outcome;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"operation_settled\": {\n\t\t\t\tconst operation = byId.get(event.operationId);\n\t\t\t\tif (operation !== undefined) operation.settled = event.outcome;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tdefault: {\n\t\t\t\tconst unknownEvent: never = event;\n\t\t\t\tthrow new TypeError(`Unknown trace event ${String((unknownEvent as { type?: unknown }).type)}`);\n\t\t\t}\n\t\t}\n\t}\n\treturn {\n\t\toperations: operations.map((operation) => ({\n\t\t\toperationId: operation.operationId,\n\t\t\tkind: operation.kind,\n\t\t\tsequence: operation.sequence,\n\t\t\tstages: operation.stages,\n\t\t\tattempts: operation.attempts,\n\t\t\tabortRequested: operation.abortRequested,\n\t\t\t...(operation.settlement === undefined ? {} : { settlement: operation.settlement }),\n\t\t\t...(operation.settled === undefined ? {} : { settled: operation.settled }),\n\t\t\tunresolvedEffectIds: [...operation.unresolved].sort(compareCodeUnits),\n\t\t})),\n\t\tacceptedCommandIds,\n\t\tsessionWrites,\n\t};\n}\n\n/** Code-unit order, so the report does not depend on the host locale. */\nfunction compareCodeUnits(left: string, right: string): number {\n\tif (left === right) return 0;\n\treturn left < right ? -1 : 1;\n}\n\nfunction describeOutcome(outcome: TraceOutcome | undefined): string {\n\tif (outcome === undefined) return \"<none>\";\n\treturn outcome.status === \"failed\" ? `failed:${outcome.code}` : outcome.status;\n}\n\nfunction describeAttempts(attempts: readonly TraceAttemptSummary[]): string {\n\treturn attempts.map((attempt) => `${attempt.attemptId}/${attempt.reason}=${attempt.outcome ?? \"<open>\"}`).join(\",\");\n}\n\nfunction compareOperation(index: number, left: OperationTraceSummary, right: OperationTraceSummary): TraceDivergence[] {\n\tconst out: TraceDivergence[] = [];\n\tconst push = (dimension: TraceDivergenceDimension, leftValue: string, rightValue: string): void => {\n\t\tif (leftValue === rightValue) return;\n\t\tconst divergenceClass: TraceDivergenceClass = BLOCKER_DIMENSIONS.has(dimension) ? \"blocker\" : \"non_blocker\";\n\t\tout.push({ dimension, class: divergenceClass, operationIndex: index, left: leftValue, right: rightValue });\n\t};\n\tpush(\"operation_identity\", `${left.kind}#${left.sequence}`, `${right.kind}#${right.sequence}`);\n\tpush(\"attempt_set\", describeAttempts(left.attempts), describeAttempts(right.attempts));\n\tconst leftSettled = left.settled ?? left.settlement;\n\tconst rightSettled = right.settled ?? right.settlement;\n\tif ((leftSettled?.status ?? \"<none>\") !== (rightSettled?.status ?? \"<none>\")) {\n\t\tout.push({\n\t\t\tdimension: \"terminal_status\",\n\t\t\tclass: \"blocker\",\n\t\t\toperationIndex: index,\n\t\t\tleft: describeOutcome(leftSettled),\n\t\t\tright: describeOutcome(rightSettled),\n\t\t});\n\t} else if (leftSettled?.status === \"failed\" && rightSettled?.status === \"failed\") {\n\t\tpush(\"public_error_code\", leftSettled.code, rightSettled.code);\n\t}\n\tpush(\"effect_uncertainty\", left.unresolvedEffectIds.join(\",\"), right.unresolvedEffectIds.join(\",\"));\n\tpush(\"stage_path\", left.stages.join(\">\"), right.stages.join(\">\"));\n\tpush(\"abort_signal\", String(left.abortRequested), String(right.abortRequested));\n\treturn out;\n}\n\n/** Compare two traces of the same scenario. Equal digests short-circuit to an empty report. */\nexport function compareOperationTraces(\n\tleft: readonly OperationTraceEvent[],\n\tright: readonly OperationTraceEvent[],\n): TraceComparison {\n\tconst leftDigest = computeTraceDigest(left);\n\tconst rightDigest = computeTraceDigest(right);\n\tif (leftDigest === rightDigest) return { equal: true, leftDigest, rightDigest, blockers: [], nonBlockers: [] };\n\tconst leftSummary = summarizeTrace(left);\n\tconst rightSummary = summarizeTrace(right);\n\tconst divergences: TraceDivergence[] = [];\n\tif (leftSummary.operations.length !== rightSummary.operations.length) {\n\t\tdivergences.push({\n\t\t\tdimension: \"operation_count\",\n\t\t\tclass: \"blocker\",\n\t\t\tleft: String(leftSummary.operations.length),\n\t\t\tright: String(rightSummary.operations.length),\n\t\t});\n\t}\n\tconst shared = Math.min(leftSummary.operations.length, rightSummary.operations.length);\n\tfor (let index = 0; index < shared; index++) {\n\t\tdivergences.push(...compareOperation(index, leftSummary.operations[index], rightSummary.operations[index]));\n\t}\n\tconst leftCommands = leftSummary.acceptedCommandIds.join(\",\");\n\tconst rightCommands = rightSummary.acceptedCommandIds.join(\",\");\n\tif (leftCommands !== rightCommands) {\n\t\tdivergences.push({ dimension: \"accepted_commands\", class: \"blocker\", left: leftCommands, right: rightCommands });\n\t}\n\tconst leftWrites = leftSummary.sessionWrites.join(\",\");\n\tconst rightWrites = rightSummary.sessionWrites.join(\",\");\n\tif (leftWrites !== rightWrites) {\n\t\tdivergences.push({ dimension: \"session_write_order\", class: \"blocker\", left: leftWrites, right: rightWrites });\n\t}\n\treturn {\n\t\tequal: false,\n\t\tleftDigest,\n\t\trightDigest,\n\t\tblockers: divergences.filter((item) => item.class === \"blocker\"),\n\t\tnonBlockers: divergences.filter((item) => item.class === \"non_blocker\"),\n\t};\n}\n"]}
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Operation trace vocabulary: the normalized event stream two runtimes must
3
+ * agree on before either can be called the operation authority.
4
+ *
5
+ * The CLI `AgentSession` and the core `AgentHarness` both drive an operation
6
+ * lifecycle today. Converging them (FND-003, Stage A) needs a common, timing-
7
+ * free record of what each one did: which operations started, which attempts
8
+ * ran and how they closed, which effects were left uncertain, which deferred
9
+ * commands and session writes were accepted, and how the operation settled.
10
+ * This module fixes that vocabulary and derives it mechanically from the pure
11
+ * lifecycle reducer, so a trace is a function of the command stream and not of
12
+ * whichever runtime happened to emit it.
13
+ *
14
+ * Traces carry no wall-clock data and no human wording: `TraceOutcome` keeps
15
+ * the public failure `code` (a blocker dimension) and drops the message (a
16
+ * non-blocker). Two runtimes that produce the same trace digest for the same
17
+ * scenario are behaviourally indistinguishable at the lifecycle boundary.
18
+ */
19
+ import type { HarnessAttemptOutcome, HarnessAttemptReason, HarnessLifecycleCommand, HarnessLifecycleResult, HarnessLifecycleState, HarnessOperationKind, HarnessOperationOutcome, HarnessOperationStage } from "./operation-lifecycle-types.ts";
20
+ export declare const OPERATION_TRACE_SCHEMA_VERSION: "omk.operation-trace.v1";
21
+ /** Public outcome projection: status and failure code only, never wording. */
22
+ export type TraceOutcome = {
23
+ readonly status: "completed";
24
+ } | {
25
+ readonly status: "failed";
26
+ readonly code: string;
27
+ } | {
28
+ readonly status: "aborted";
29
+ } | {
30
+ readonly status: "cancelled";
31
+ };
32
+ /** Events derived from lifecycle commands. Emitted only by `OperationTraceRecorder.apply`. */
33
+ export type LifecycleTraceEventBody = {
34
+ readonly type: "operation_started";
35
+ readonly operationId: string;
36
+ readonly kind: HarnessOperationKind;
37
+ readonly sequence: number;
38
+ } | {
39
+ readonly type: "stage_changed";
40
+ readonly operationId: string;
41
+ readonly stage: HarnessOperationStage;
42
+ } | {
43
+ readonly type: "attempt_started";
44
+ readonly operationId: string;
45
+ readonly attemptId: string;
46
+ readonly index: number;
47
+ readonly reason: HarnessAttemptReason;
48
+ } | {
49
+ readonly type: "attempt_finished";
50
+ readonly operationId: string;
51
+ readonly attemptId: string;
52
+ readonly outcome: HarnessAttemptOutcome;
53
+ } | {
54
+ readonly type: "abort_requested";
55
+ readonly operationId: string;
56
+ } | {
57
+ readonly type: "settlement_started";
58
+ readonly operationId: string;
59
+ readonly outcome: TraceOutcome;
60
+ } | {
61
+ readonly type: "operation_settled";
62
+ readonly operationId: string;
63
+ readonly outcome: TraceOutcome;
64
+ };
65
+ /** Events reported by subsystems around the lifecycle (effects, deferred commands, session writes). */
66
+ export type ExternalTraceEventBody = {
67
+ readonly type: "effect_prepared";
68
+ readonly operationId: string;
69
+ readonly effectId: string;
70
+ } | {
71
+ readonly type: "effect_uncertain";
72
+ readonly operationId: string;
73
+ readonly effectId: string;
74
+ } | {
75
+ readonly type: "effect_reconciled";
76
+ readonly operationId: string;
77
+ readonly effectId: string;
78
+ } | {
79
+ readonly type: "deferred_command_accepted";
80
+ readonly commandId: string;
81
+ readonly name: string;
82
+ readonly operationId?: string;
83
+ } | {
84
+ readonly type: "session_write_accepted";
85
+ readonly writeSequence: number;
86
+ readonly writeType: string;
87
+ readonly operationId?: string;
88
+ };
89
+ export type OperationTraceEventBody = LifecycleTraceEventBody | ExternalTraceEventBody;
90
+ /** One trace entry. `seq` is 1-based and strictly increasing within a trace. */
91
+ export type OperationTraceEvent = OperationTraceEventBody & {
92
+ readonly seq: number;
93
+ };
94
+ export interface OperationTraceDocument {
95
+ readonly schemaVersion: typeof OPERATION_TRACE_SCHEMA_VERSION;
96
+ readonly events: readonly OperationTraceEvent[];
97
+ }
98
+ export declare function projectTraceOutcome(outcome: HarnessOperationOutcome): TraceOutcome;
99
+ /**
100
+ * The trace events one accepted lifecycle command produces. Pure: it reads the
101
+ * pre-transition state only to recover identities the command omits (the
102
+ * operation of an `attempt_end`, the outcome recorded at `settle_begin`).
103
+ */
104
+ export declare function traceEventsForCommand(before: HarnessLifecycleState, command: HarnessLifecycleCommand): readonly LifecycleTraceEventBody[];
105
+ export type TraceRecordResult = {
106
+ readonly ok: true;
107
+ readonly event: OperationTraceEvent;
108
+ } | {
109
+ readonly ok: false;
110
+ readonly code: "stale_operation" | "no_active_operation";
111
+ readonly message: string;
112
+ };
113
+ /**
114
+ * Drives the pure lifecycle reducer and appends the derived trace. A rejected
115
+ * command records nothing: the trace only ever describes accepted transitions,
116
+ * so two recorders fed the same accepted command stream produce byte-identical
117
+ * traces regardless of how many illegal commands were refused in between.
118
+ */
119
+ export declare class OperationTraceRecorder {
120
+ private readonly events;
121
+ private state;
122
+ constructor(initial?: HarnessLifecycleState);
123
+ get lifecycleState(): HarnessLifecycleState;
124
+ get size(): number;
125
+ apply(command: HarnessLifecycleCommand): HarnessLifecycleResult<HarnessLifecycleState>;
126
+ /** Record a subsystem event. Effect events must name the current operation. */
127
+ record(body: ExternalTraceEventBody): TraceRecordResult;
128
+ snapshot(): OperationTraceDocument;
129
+ digest(): string;
130
+ private push;
131
+ }
132
+ /** Canonical SHA-256 over the schema version and the ordered event list. */
133
+ export declare function computeTraceDigest(events: readonly OperationTraceEvent[]): string;
134
+ //# sourceMappingURL=operation-trace.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"operation-trace.d.ts","sourceRoot":"","sources":["../../src/harness/operation-trace.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Operation trace vocabulary: the normalized event stream two runtimes must\n * agree on before either can be called the operation authority.\n *\n * The CLI `AgentSession` and the core `AgentHarness` both drive an operation\n * lifecycle today. Converging them (FND-003, Stage A) needs a common, timing-\n * free record of what each one did: which operations started, which attempts\n * ran and how they closed, which effects were left uncertain, which deferred\n * commands and session writes were accepted, and how the operation settled.\n * This module fixes that vocabulary and derives it mechanically from the pure\n * lifecycle reducer, so a trace is a function of the command stream and not of\n * whichever runtime happened to emit it.\n *\n * Traces carry no wall-clock data and no human wording: `TraceOutcome` keeps\n * the public failure `code` (a blocker dimension) and drops the message (a\n * non-blocker). Two runtimes that produce the same trace digest for the same\n * scenario are behaviourally indistinguishable at the lifecycle boundary.\n */\n\nimport { canonicalDigest } from \"./canonical-digest.ts\";\nimport { initialHarnessLifecycleState, reduceHarnessLifecycle } from \"./operation-lifecycle-reducer.ts\";\nimport type {\n\tHarnessAttemptOutcome,\n\tHarnessAttemptReason,\n\tHarnessLifecycleCommand,\n\tHarnessLifecycleResult,\n\tHarnessLifecycleState,\n\tHarnessOperationKind,\n\tHarnessOperationOutcome,\n\tHarnessOperationStage,\n} from \"./operation-lifecycle-types.ts\";\n\nexport const OPERATION_TRACE_SCHEMA_VERSION = \"omk.operation-trace.v1\" as const;\n\n/** Public outcome projection: status and failure code only, never wording. */\nexport type TraceOutcome =\n\t| { readonly status: \"completed\" }\n\t| { readonly status: \"failed\"; readonly code: string }\n\t| { readonly status: \"aborted\" }\n\t| { readonly status: \"cancelled\" };\n\n/** Events derived from lifecycle commands. Emitted only by `OperationTraceRecorder.apply`. */\nexport type LifecycleTraceEventBody =\n\t| {\n\t\t\treadonly type: \"operation_started\";\n\t\t\treadonly operationId: string;\n\t\t\treadonly kind: HarnessOperationKind;\n\t\t\treadonly sequence: number;\n\t }\n\t| { readonly type: \"stage_changed\"; readonly operationId: string; readonly stage: HarnessOperationStage }\n\t| {\n\t\t\treadonly type: \"attempt_started\";\n\t\t\treadonly operationId: string;\n\t\t\treadonly attemptId: string;\n\t\t\treadonly index: number;\n\t\t\treadonly reason: HarnessAttemptReason;\n\t }\n\t| {\n\t\t\treadonly type: \"attempt_finished\";\n\t\t\treadonly operationId: string;\n\t\t\treadonly attemptId: string;\n\t\t\treadonly outcome: HarnessAttemptOutcome;\n\t }\n\t| { readonly type: \"abort_requested\"; readonly operationId: string }\n\t| { readonly type: \"settlement_started\"; readonly operationId: string; readonly outcome: TraceOutcome }\n\t| { readonly type: \"operation_settled\"; readonly operationId: string; readonly outcome: TraceOutcome };\n\n/** Events reported by subsystems around the lifecycle (effects, deferred commands, session writes). */\nexport type ExternalTraceEventBody =\n\t| { readonly type: \"effect_prepared\"; readonly operationId: string; readonly effectId: string }\n\t| { readonly type: \"effect_uncertain\"; readonly operationId: string; readonly effectId: string }\n\t| { readonly type: \"effect_reconciled\"; readonly operationId: string; readonly effectId: string }\n\t| {\n\t\t\treadonly type: \"deferred_command_accepted\";\n\t\t\treadonly commandId: string;\n\t\t\treadonly name: string;\n\t\t\treadonly operationId?: string;\n\t }\n\t| {\n\t\t\treadonly type: \"session_write_accepted\";\n\t\t\treadonly writeSequence: number;\n\t\t\treadonly writeType: string;\n\t\t\treadonly operationId?: string;\n\t };\n\nexport type OperationTraceEventBody = LifecycleTraceEventBody | ExternalTraceEventBody;\n\n/** One trace entry. `seq` is 1-based and strictly increasing within a trace. */\nexport type OperationTraceEvent = OperationTraceEventBody & { readonly seq: number };\n\nexport interface OperationTraceDocument {\n\treadonly schemaVersion: typeof OPERATION_TRACE_SCHEMA_VERSION;\n\treadonly events: readonly OperationTraceEvent[];\n}\n\nexport function projectTraceOutcome(outcome: HarnessOperationOutcome): TraceOutcome {\n\tswitch (outcome.status) {\n\t\tcase \"completed\":\n\t\t\treturn { status: \"completed\" };\n\t\tcase \"failed\":\n\t\t\treturn { status: \"failed\", code: outcome.code };\n\t\tcase \"aborted\":\n\t\t\treturn { status: \"aborted\" };\n\t\tcase \"cancelled\":\n\t\t\treturn { status: \"cancelled\" };\n\t}\n}\n\n/**\n * The trace events one accepted lifecycle command produces. Pure: it reads the\n * pre-transition state only to recover identities the command omits (the\n * operation of an `attempt_end`, the outcome recorded at `settle_begin`).\n */\nexport function traceEventsForCommand(\n\tbefore: HarnessLifecycleState,\n\tcommand: HarnessLifecycleCommand,\n): readonly LifecycleTraceEventBody[] {\n\tswitch (command.type) {\n\t\tcase \"begin\":\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: \"operation_started\",\n\t\t\t\t\toperationId: command.operation.operationId,\n\t\t\t\t\tkind: command.operation.kind,\n\t\t\t\t\tsequence: command.operation.sequence,\n\t\t\t\t},\n\t\t\t];\n\t\tcase \"stage\":\n\t\t\treturn [{ type: \"stage_changed\", operationId: command.operationId, stage: command.stage }];\n\t\tcase \"attempt_begin\":\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: \"attempt_started\",\n\t\t\t\t\toperationId: command.attempt.operationId,\n\t\t\t\t\tattemptId: command.attempt.attemptId,\n\t\t\t\t\tindex: command.attempt.index,\n\t\t\t\t\treason: command.attempt.reason,\n\t\t\t\t},\n\t\t\t];\n\t\tcase \"attempt_end\":\n\t\t\tif (before.tag === \"idle\") return [];\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: \"attempt_finished\",\n\t\t\t\t\toperationId: before.operation.operationId,\n\t\t\t\t\tattemptId: command.attemptId,\n\t\t\t\t\toutcome: command.outcome,\n\t\t\t\t},\n\t\t\t];\n\t\tcase \"abort_request\":\n\t\t\treturn [{ type: \"abort_requested\", operationId: command.operationId }];\n\t\tcase \"settle_begin\":\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: \"settlement_started\",\n\t\t\t\t\toperationId: command.operationId,\n\t\t\t\t\toutcome: projectTraceOutcome(command.outcome),\n\t\t\t\t},\n\t\t\t];\n\t\tcase \"settle_finish\":\n\t\t\tif (before.tag !== \"settling\") return [];\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: \"operation_settled\",\n\t\t\t\t\toperationId: command.operationId,\n\t\t\t\t\toutcome: projectTraceOutcome(before.outcome),\n\t\t\t\t},\n\t\t\t];\n\t}\n}\n\nexport type TraceRecordResult =\n\t| { readonly ok: true; readonly event: OperationTraceEvent }\n\t| { readonly ok: false; readonly code: \"stale_operation\" | \"no_active_operation\"; readonly message: string };\n\n/**\n * Drives the pure lifecycle reducer and appends the derived trace. A rejected\n * command records nothing: the trace only ever describes accepted transitions,\n * so two recorders fed the same accepted command stream produce byte-identical\n * traces regardless of how many illegal commands were refused in between.\n */\nexport class OperationTraceRecorder {\n\tprivate readonly events: OperationTraceEvent[] = [];\n\tprivate state: HarnessLifecycleState;\n\n\tconstructor(initial: HarnessLifecycleState = initialHarnessLifecycleState()) {\n\t\tthis.state = initial;\n\t}\n\n\tget lifecycleState(): HarnessLifecycleState {\n\t\treturn this.state;\n\t}\n\n\tget size(): number {\n\t\treturn this.events.length;\n\t}\n\n\tapply(command: HarnessLifecycleCommand): HarnessLifecycleResult<HarnessLifecycleState> {\n\t\tconst result = reduceHarnessLifecycle(this.state, command);\n\t\tif (!result.ok) return result;\n\t\tfor (const body of traceEventsForCommand(this.state, command)) this.push(body);\n\t\tthis.state = result.value;\n\t\treturn result;\n\t}\n\n\t/** Record a subsystem event. Effect events must name the current operation. */\n\trecord(body: ExternalTraceEventBody): TraceRecordResult {\n\t\tif (body.operationId !== undefined) {\n\t\t\tif (this.state.tag === \"idle\") {\n\t\t\t\treturn {\n\t\t\t\t\tok: false,\n\t\t\t\t\tcode: \"no_active_operation\",\n\t\t\t\t\tmessage: `${body.type} names ${body.operationId} while idle`,\n\t\t\t\t};\n\t\t\t}\n\t\t\tif (this.state.operation.operationId !== body.operationId) {\n\t\t\t\treturn {\n\t\t\t\t\tok: false,\n\t\t\t\t\tcode: \"stale_operation\",\n\t\t\t\t\tmessage: `${body.type} names ${body.operationId} but current operation is ${this.state.operation.operationId}`,\n\t\t\t\t};\n\t\t\t}\n\t\t} else if (body.type.startsWith(\"effect_\")) {\n\t\t\treturn { ok: false, code: \"no_active_operation\", message: `${body.type} requires an operationId` };\n\t\t}\n\t\treturn { ok: true, event: this.push(body) };\n\t}\n\n\tsnapshot(): OperationTraceDocument {\n\t\treturn { schemaVersion: OPERATION_TRACE_SCHEMA_VERSION, events: [...this.events] };\n\t}\n\n\tdigest(): string {\n\t\treturn computeTraceDigest(this.events);\n\t}\n\n\tprivate push(body: OperationTraceEventBody): OperationTraceEvent {\n\t\tconst event: OperationTraceEvent = Object.freeze({ ...body, seq: this.events.length + 1 });\n\t\tthis.events.push(event);\n\t\treturn event;\n\t}\n}\n\n/** Canonical SHA-256 over the schema version and the ordered event list. */\nexport function computeTraceDigest(events: readonly OperationTraceEvent[]): string {\n\treturn canonicalDigest({ schemaVersion: OPERATION_TRACE_SCHEMA_VERSION, events });\n}\n"]}