{"version":3,"file":"session-controller.d.ts","sourceRoot":"","sources":["../../../src/core/reliability/session-controller.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAC5F,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAI5D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAC5E,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,OAAO,EAAE,KAAK,oBAAoB,EAAU,MAAM,eAAe,CAAC;AAElE,MAAM,WAAW,mCAAmC;IACnD,+DAA+D;IAC/D,GAAG,EAAE,MAAM,CAAC;IACZ,oEAAoE;IACpE,cAAc,EAAE,cAAc,CAAC;IAC/B,kEAAkE;IAClE,oBAAoB,EAAE,oBAAoB,CAAC;IAC3C,+EAA+E;IAC/E,QAAQ,EAAE,MAAM,SAAS,SAAS,EAAE,CAAC;IACrC,uEAAuE;IACvE,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,4FAA4F;IAC5F,yBAAyB,CAAC,EAAE,MAAM,CAAC;CACnC;AAED,MAAM,WAAW,uBAAuB;IACvC,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,wBAAwB;IACxC,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,YAAY,CAAC;CACvB;AAED,qBAAa,4BAA4B;IACxC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAiB;IACjD,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAuB;IAC7D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA6B;IACvD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsB;IAC9C,OAAO,CAAC,QAAQ,CAAC,0BAA0B,CAAS;IAEpD,OAAO,CAAC,QAAQ,CAAC,CAAiB;IAClC,OAAO,CAAC,OAAO,CAAC,CAA2B;IAC3C,OAAO,CAAC,sBAAsB,CAAS;IACvC,OAAO,CAAC,kBAAkB,CAAK;IAE/B,YAAY,OAAO,EAAE,mCAAmC,EASvD;IAMD,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED,IAAI,SAAS,IAAI,MAAM,GAAG,SAAS,CAElC;IAED,IAAI,KAAK,IAAI,cAAc,CAAC,OAAO,CAAC,GAAG,SAAS,CAE/C;IAED,IAAI,OAAO,IAAI,cAAc,GAAG,SAAS,CAExC;IAED,IAAI,qBAAqB,IAAI,OAAO,CAEnC;IAED,aAAa,IAAI,UAAU,CAAC,cAAc,CAAC,eAAe,CAAC,CAAC,CAE3D;IAED,iBAAiB,IAAI,MAAM,GAAG,SAAS,CAEtC;IAMD,4EAA4E;IAC5E,YAAY,CAAC,UAAU,EAAE,sBAAsB,GAAG,IAAI,CAOrD;IAED,wEAAwE;IACxE,QAAQ,IAAI,IAAI,CAEf;IAED,OAAO,CAAC,QAAQ;IAiBhB,OAAO,CAAC,OAAO;IAKf,OAAO,CAAC,YAAY;IA0CpB,yEAAyE;IACzE,cAAc,CAAC,QAAQ,EAAE,aAAa,GAAG,uBAAuB,CAuB/D;IAED,4DAA4D;IAC5D,aAAa,CAAC,QAAQ,EAAE,aAAa,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAK/E;IAED,OAAO,CAAC,iBAAiB;IASzB,OAAO,CAAC,OAAO;IAcf;;;;;OAKG;IACG,SAAS,IAAI,OAAO,CAAC,wBAAwB,CAAC,CA6CnD;IAED,4FAA4F;IAC5F,OAAO,CAAC,UAAU;YAQJ,8BAA8B;IAiB5C,OAAO,CAAC,QAAQ;CAIhB","sourcesContent":["/**\n * Reliability Session Controller — production wiring between a live AgentSession\n * and the Reliability Kernel.\n *\n * The controller owns the MissionRuntime, ReliabilitySessionBridge, and the\n * deterministic verification executor. AgentSession delegates its\n * beforeToolCall / afterToolCall / onTurnEnd hooks here so that:\n *\n *   - every real tool call is validated against mission policy (boundary,\n *     forbidden, permission) before execution — schema validation remains the\n *     agent loop's responsibility and is not duplicated here;\n *   - every real tool outcome is recorded as authoritative evidence;\n *   - a model that \"thinks it is done\" is routed through the Completion Gate;\n *   - mission state persists with the session and restores on resume.\n *\n * Tool governance (policy + evidence) is active for every session. The\n * completion gate only applies while a mission is active; plain chat sessions\n * without a mission stop normally.\n */\n\nimport { isAbsolute, relative, resolve } from \"node:path\";\nimport type { AgentMessage, AgentTool, AgentToolCall } from \"@apholdings/jensen-agent-core\";\nimport type { SessionManager } from \"../session-manager.js\";\nimport type { ActionPolicyAdapter } from \"./action-validator.js\";\nimport { finalizationRejectedMessage } from \"./failure-events.js\";\nimport { createToolSchemaValidator, ReliabilitySessionBridge } from \"./integration.js\";\nimport type { MissionDefinitionInput } from \"./mission-contract-factory.js\";\nimport { MissionRuntime } from \"./mission-runtime.js\";\nimport type { ActionValidationFailureCategory, ToolCallAction } from \"./types.js\";\nimport { type VerificationExecutor, verify } from \"./verifier.js\";\n\nexport interface ReliabilitySessionControllerOptions {\n\t/** Session working directory (the workspace boundary root). */\n\tcwd: string;\n\t/** Session manager used to persist/restore the mission document. */\n\tsessionManager: SessionManager;\n\t/** Deterministic verification executor bound to the workspace. */\n\tverificationExecutor: VerificationExecutor;\n\t/** Returns the currently active tools (used for mission schema validation). */\n\tgetTools: () => readonly AgentTool[];\n\t/** Tool names always forbidden for reliability-governed tool calls. */\n\tforbiddenTools?: readonly string[];\n\t/** Maximum consecutive finalization rejections before the run ends (bounded loop guard). */\n\tmaxFinalizationRejections?: number;\n}\n\nexport interface ReliabilityBeforeResult {\n\tblock: boolean;\n\treason?: string;\n}\n\nexport interface ReliabilityTurnEndResult {\n\tcontinue: boolean;\n\tmessage?: AgentMessage;\n}\n\nexport class ReliabilitySessionController {\n\tprivate readonly _cwd: string;\n\tprivate readonly _sessionManager: SessionManager;\n\tprivate readonly _verificationExecutor: VerificationExecutor;\n\tprivate readonly _getTools: () => readonly AgentTool[];\n\tprivate readonly _policy: ActionPolicyAdapter;\n\tprivate readonly _maxFinalizationRejections: number;\n\n\tprivate _runtime?: MissionRuntime;\n\tprivate _bridge?: ReliabilitySessionBridge;\n\tprivate _corruptPersistedState = false;\n\tprivate _rejectionsThisRun = 0;\n\n\tconstructor(options: ReliabilitySessionControllerOptions) {\n\t\tthis._cwd = options.cwd;\n\t\tthis._sessionManager = options.sessionManager;\n\t\tthis._verificationExecutor = options.verificationExecutor;\n\t\tthis._getTools = options.getTools;\n\t\tthis._maxFinalizationRejections = options.maxFinalizationRejections ?? 3;\n\t\tthis._policy = this._buildPolicy(options.forbiddenTools ?? []);\n\n\t\tthis._restore();\n\t}\n\n\t// =========================================================================\n\t// State accessors\n\t// =========================================================================\n\n\tget isActive(): boolean {\n\t\treturn this._runtime !== undefined;\n\t}\n\n\tget missionId(): string | undefined {\n\t\treturn this._runtime?.missionId;\n\t}\n\n\tget phase(): MissionRuntime[\"phase\"] | undefined {\n\t\treturn this._runtime?.phase;\n\t}\n\n\tget runtime(): MissionRuntime | undefined {\n\t\treturn this._runtime;\n\t}\n\n\tget corruptPersistedState(): boolean {\n\t\treturn this._corruptPersistedState;\n\t}\n\n\tcriterionView(): ReturnType<MissionRuntime[\"criterionView\"]> {\n\t\treturn this._runtime?.criterionView() ?? [];\n\t}\n\n\tsummarizeForModel(): string | undefined {\n\t\treturn this._runtime?.summarizeForModel();\n\t}\n\n\t// =========================================================================\n\t// Mission lifecycle\n\t// =========================================================================\n\n\t/** Create and attach a new governed mission from an explicit definition. */\n\tstartMission(definition: MissionDefinitionInput): void {\n\t\tconst runtime = MissionRuntime.create(definition);\n\t\tthis._attach(runtime);\n\t\tthis._corruptPersistedState = false;\n\t\t// Leave PLANNING so the live loop can act on the mission.\n\t\tthis._advanceTo(\"START_EXECUTION\");\n\t\tthis._persist();\n\t}\n\n\t/** Reset the per-run rejection counter (call when a new run begins). */\n\tresetRun(): void {\n\t\tthis._rejectionsThisRun = 0;\n\t}\n\n\tprivate _restore(): void {\n\t\tconst persisted = this._sessionManager.getLatestReliabilityState();\n\t\tif (!persisted) return;\n\n\t\ttry {\n\t\t\tthis._attach(MissionRuntime.deserialize(persisted.data));\n\t\t\tthis._rejectionsThisRun = 0;\n\t\t} catch {\n\t\t\t// A corrupt/foreign persisted document must never abort session load and\n\t\t\t// must never be silently overwritten: mark it and keep running without\n\t\t\t// a mission until the caller explicitly starts a fresh one.\n\t\t\tthis._corruptPersistedState = true;\n\t\t\tthis._runtime = undefined;\n\t\t\tthis._bridge = undefined;\n\t\t}\n\t}\n\n\tprivate _attach(runtime: MissionRuntime): void {\n\t\tthis._runtime = runtime;\n\t\tthis._bridge = new ReliabilitySessionBridge(runtime, createToolSchemaValidator(this._getTools()), this._policy);\n\t}\n\n\tprivate _buildPolicy(forbiddenTools: readonly string[]): ActionPolicyAdapter {\n\t\tconst root = resolve(this._cwd);\n\t\tconst forbidden = new Set(forbiddenTools);\n\n\t\tconst pathArg = (action: ToolCallAction): string | undefined => {\n\t\t\tconst args = action.arguments as Record<string, unknown>;\n\t\t\tconst candidate = args?.path ?? args?.file ?? args?.filePath ?? args?.target ?? args?.newPath;\n\t\t\treturn typeof candidate === \"string\" ? candidate : undefined;\n\t\t};\n\n\t\treturn {\n\t\t\tforbiddenReason(action) {\n\t\t\t\tif (forbidden.has(action.tool)) {\n\t\t\t\t\treturn `tool '${action.tool}' is forbidden for this mission`;\n\t\t\t\t}\n\t\t\t\treturn undefined;\n\t\t\t},\n\t\t\tboundaryViolationReason(action) {\n\t\t\t\tconst p = pathArg(action);\n\t\t\t\tif (!p) return undefined;\n\t\t\t\tif (p.includes(\"\\u0000\")) return \"path contains NUL byte\";\n\n\t\t\t\t// Resolve lexically against the workspace root and reject only paths\n\t\t\t\t// that escape it (including `..` escapes). This is defense in depth;\n\t\t\t\t// the tool executor enforces the realpath boundary.\n\t\t\t\tconst abs = isAbsolute(p) ? resolve(p) : resolve(root, p);\n\t\t\t\tconst rel = relative(root, abs);\n\t\t\t\tif (rel === \"\" || rel.startsWith(\"..\") || isAbsolute(rel)) {\n\t\t\t\t\treturn \"path resolves outside the workspace boundary\";\n\t\t\t\t}\n\t\t\t\treturn undefined;\n\t\t\t},\n\t\t\tpermissionViolationReason() {\n\t\t\t\treturn undefined;\n\t\t\t},\n\t\t};\n\t}\n\n\t// =========================================================================\n\t// Tool governance hooks\n\t// =========================================================================\n\n\t/** Validate a real tool call against mission policy before execution. */\n\tbeforeToolCall(toolCall: AgentToolCall): ReliabilityBeforeResult {\n\t\tconst action = this._toToolCallAction(toolCall);\n\n\t\tconst forbidden = this._policy.forbiddenReason(action);\n\t\tif (forbidden) {\n\t\t\treturn this._reject(action, `Tool ${action.tool} is forbidden: ${forbidden}`, \"FORBIDDEN_ACTION\");\n\t\t}\n\n\t\tconst boundary = this._policy.boundaryViolationReason(action);\n\t\tif (boundary) {\n\t\t\treturn this._reject(\n\t\t\t\taction,\n\t\t\t\t`Tool ${action.tool} violates workspace boundary: ${boundary}`,\n\t\t\t\t\"BOUNDARY_VIOLATION\",\n\t\t\t);\n\t\t}\n\n\t\tconst permission = this._policy.permissionViolationReason(action);\n\t\tif (permission) {\n\t\t\treturn this._reject(action, `Tool ${action.tool} is not permitted: ${permission}`, \"PERMISSION_VIOLATION\");\n\t\t}\n\n\t\treturn { block: false };\n\t}\n\n\t/** Record a real tool outcome as authoritative evidence. */\n\tafterToolCall(toolCall: AgentToolCall, isError: boolean, summary?: string): void {\n\t\tif (!this._bridge) return;\n\t\tthis._bridge.afterToolCall(this._toToolCallAction(toolCall), isError, summary);\n\t\t// Evidence is persisted at the next turn checkpoint to avoid write churn\n\t\t// on every individual tool call while still surviving a crash between turns.\n\t}\n\n\tprivate _toToolCallAction(toolCall: AgentToolCall): ToolCallAction {\n\t\treturn {\n\t\t\ttype: \"tool_call\",\n\t\t\ttool: toolCall.name,\n\t\t\ttoolCallId: toolCall.id,\n\t\t\targuments: toolCall.arguments as Record<string, unknown>,\n\t\t};\n\t}\n\n\tprivate _reject(\n\t\taction: ToolCallAction,\n\t\treason: string,\n\t\tcategory: ActionValidationFailureCategory,\n\t): ReliabilityBeforeResult {\n\t\tthis._runtime?.recorder.record(\"action_validation_failure\", { tool: action.tool, category });\n\t\tthis._persist();\n\t\treturn { block: true, reason };\n\t}\n\n\t// =========================================================================\n\t// Turn-end policy (Completion Gate authority)\n\t// =========================================================================\n\n\t/**\n\t * Called when the model finished its turn without further tool calls.\n\t *\n\t * Runs automatic deterministic verification for outstanding criteria, then\n\t * routes the model's implicit \"I am done\" through the Completion Gate.\n\t */\n\tasync onTurnEnd(): Promise<ReliabilityTurnEndResult> {\n\t\tif (!this._runtime) return { continue: false };\n\n\t\t// Advance the execution state machine so deterministic verification and\n\t\t// the completion gate have an authoritative place to run.\n\t\tthis._advanceTo(\"START_EXECUTION\");\n\t\tthis._advanceTo(\"REQUEST_VERIFICATION\");\n\n\t\tawait this._runAutomaticFinalVerification();\n\n\t\tthis._advanceTo(\"REQUEST_COMPLETION_REVIEW\");\n\n\t\tconst gate = this._runtime.proposeFinalCandidate();\n\t\tthis._persist();\n\n\t\tif (gate.decision === \"accept\") {\n\t\t\tthis._runtime.approveCompletion();\n\t\t\tthis._persist();\n\t\t\treturn { continue: false };\n\t\t}\n\n\t\t// Rejected (missing criteria or genuine blockers): return control to the\n\t\t// execution state so the mission remains resumable and never loops forever.\n\t\tthis._advanceTo(\"RETURN_TO_EXECUTION\");\n\t\tthis._persist();\n\n\t\tif (gate.blockedBy.length > 0) {\n\t\t\treturn { continue: false };\n\t\t}\n\n\t\t// Bounded rejection: give the model structured feedback and another turn,\n\t\t// up to a fixed limit, then stop to avoid an infinite loop.\n\t\tif (this._rejectionsThisRun >= this._maxFinalizationRejections) {\n\t\t\treturn { continue: false };\n\t\t}\n\t\tthis._rejectionsThisRun += 1;\n\n\t\treturn {\n\t\t\tcontinue: true,\n\t\t\tmessage: {\n\t\t\t\trole: \"user\",\n\t\t\t\tcontent: [{ type: \"text\", text: finalizationRejectedMessage(gate.missingCriterionIds) }],\n\t\t\t\ttimestamp: Date.now(),\n\t\t\t},\n\t\t};\n\t}\n\n\t/** Apply an execution transition, ignoring transitions the state machine already passed. */\n\tprivate _advanceTo(kind: Parameters<MissionRuntime[\"transition\"]>[0]): void {\n\t\tif (!this._runtime) return;\n\t\tconst result = this._runtime.transition(kind);\n\t\tif (result.ok) {\n\t\t\tthis._persist();\n\t\t}\n\t}\n\n\tprivate async _runAutomaticFinalVerification(): Promise<void> {\n\t\tif (!this._runtime) return;\n\t\tfor (const criterion of this._runtime.criterionView()) {\n\t\t\tif (criterion.status === \"passed\") continue;\n\t\t\tif (!criterion.verification) continue;\n\t\t\tconst result = await verify(criterion.verification, this._verificationExecutor, {\n\t\t\t\tcriterionId: criterion.id,\n\t\t\t\tcwd: this._cwd,\n\t\t\t});\n\t\t\tthis._runtime.recordVerification(criterion.id, result);\n\t\t}\n\t}\n\n\t// =========================================================================\n\t// Persistence\n\t// =========================================================================\n\n\tprivate _persist(): void {\n\t\tif (!this._runtime) return;\n\t\tthis._sessionManager.appendReliabilityState(this._runtime.serialize());\n\t}\n}\n"]}