Dashboard › opencode › Session 15FJAASx5CVU
15FJAASx5CVUA6iStIndependent adversarial READ-ONLY correctness review of revised frozen V2 recovery candidate. Do not use lore/recall.
Immutable sources only:
ab9408c81cc9916177641ae8e9f08624fc27b5b9/tmp/opencode/session-recovery-final-v2.patch, SHA-256 b5878a90af5202832954778d14d2902e55ee9e51ff856592519177bafd392d90/home/byk/Code/opencode/packages/opencode/dist/server/opencode-server, SHA-256 9bbe18549e623e6ebe4ed6f75c5403381c35ae860ad2dc77698a469ab8a6c7e6Review all files and lifecycle invariants. Pay special attention to the prior blocker: every input referenced by session_recovery, including quarantined/settled rows, must be excluded from ordinary hasPending and queue promotion; only exact recovery promotion may consume it. Verify anti-join semantics, malformed prompt behavior, later ordinary wake, replay and deletion implications, and regression adequacy.
Also assess leases/fencing, newest assistant, provider/tool/compaction continuation, cancellation, Context Epoch, wake/outbox durability, resume:false and retries, bounded poison scans, migration/fresh schema, public OpenAPI/SDK, no SessionPrompt/Core->Server. Accepted residuals to assess: instruction-level external dispatch race and bounded clock-skew assumption.
Evidence: fail-first quarantine test failed on rejected patch (hasPending true), passes now; Core 1,141/0 with 3,169 assertions; five typechecks, migration/diff checks; rebuilt SEA health/shutdown and two-restart exactly-once recovery pass.
Report findings first, MUST-FIX/CONCERN, exact resulting-file line evidence from frozen patch and concrete sequence. If PASS, list residual risks/test gaps. Empty invalid. End exactly MERGE or DO-NOT-MERGE.
<skill_content name="effect">
# Skill: effect
# Effect
This codebase uses Effect for typed, composable TypeScript services, schemas, and workflows.
## Source Of Truth
Use the current Effect v4 / effect-smol source, not memory or older Effect v2/v3 examples.
1. If `.opencode/references/effect-smol` is missing, clone `https://github.com/Effect-TS/effect-smol` there. Do this in the project, not in the skill folder.
2. Search `.opencode/references/effect-smol` for exact APIs, examples, tests, and naming patterns before answering or implementing Effect-specific code.
3. Also inspect existing repo code for local house style before introducing new patterns.
4. Prefer answers and implementations backed by specific source files or nearby repo examples.
## Guidelines
- Prefer current Effect v4 APIs and project-local patterns over old blog posts, examples, or package-memory guesses.
- Use `Effect.gen(function* () { ... })` for multi-step workflows.
- Use `Effect.fn("Name")` or ...
b5878a90af5202832954778d14d2902e55ee9e51ff856592519177bafd392d90 /tmp/opencode/session-recovery-final-v2.patch 9bbe18549e623e6ebe4ed6f75c5403381c35ae860ad2dc77698a469ab8a6c7e6 /home/byk/Code/opencode/packages/opencode/dist/server/opencode-server commit 312 2 packages/core/schema.json 1 0 packages/core/src/database/migration.gen.ts 50 0 packages/core/src/database/migration/20260914170650_session-recovery.ts 40 0 packages/core/src/database/schema.gen.ts 5 4 packages/core/src/session.ts 31 16 packages/core/src/session/compaction.ts 54 34 packages/core/src/session/context-epoch.ts 66 0 packages/core/src/session/execution/claim.ts 118 19 packages/core/src/session/execution/local.ts 203 28 packages/core/src/session/input.ts 69 0 packages/core/src/session/message-updater.ts 77 1 packages/core/src/session/projector.ts 6 0 packages/core/src/session/recovery-id.ts 305 0 packages/core/src/session/recovery.ts 27 5 packages/core/src/session/run-coordinator.ts 4 0 packages/core/src/session/runner/...
<path>/tmp/opencode/session-recovery-final-v2.patch</path>
<type>file</type>
<content>
1: diff --git a/packages/core/schema.json b/packages/core/schema.json
2: index d0eeeebd5c..dad07b6ce6 100644
3: --- a/packages/core/schema.json
4: +++ b/packages/core/schema.json
5: @@ -1,8 +1,8 @@
6: {
7: "version": "7",
8: "dialect": "sqlite",
9: - "id": "f14a9b18-8207-487e-a3d3-227e629ba9ad",
10: - "prevIds": ["169a0f0f-d58f-479f-b024-fa1c7b9a09db"],
11: + "id": "797e6838-8a41-4793-932a-d200aa8d51fb",
12: + "prevIds": ["0445bfe8-162e-4719-a49b-23f1210dcacd"],
13: "ddl": [
14: {
15: "name": "workspace",
16: @@ -60,6 +60,10 @@
17: "name": "session_context_epoch",
18: "entityType": "tables"
19: },
20: + {
21: + "name": "session_execution",
22: + "entityType": "tables"
23: + },
24: {
25: "name": "session_input",
26: "entityType": "tables"
27: @@ -68,6 +72,10 @@
28: "name": "session_message",
29: ...
<path>/tmp/opencode/session-recovery-final-v2.patch</path>
<type>file</type>
<content>
1387: rows: ReadonlyArray<typeof SessionInputTable.$inferSelect>,
1388: + commit?: (row: typeof SessionInputTable.$inferSelect) => Effect.Effect<void>,
1389: ) {
1390: for (const row of rows) {
1391: const id = SessionMessage.ID.make(row.id)
1392: yield* events
1393: - .publish(SessionEvent.Prompted, {
1394: - sessionID,
1395: - timestamp: DateTime.makeUnsafe(row.time_created),
1396: - messageID: id,
1397: - prompt: decodePrompt(row.prompt),
1398: - delivery: row.delivery,
1399: - })
1400: + .publish(
1401: + SessionEvent.Prompted,
1402: + {
1403: + sessionID,
1404: + timestamp: DateTime.makeUnsafe(row.time_created),
1405: + messageID: id,
1406: + prompt: decodePrompt(row.prompt),
1407: + delivery: row.delivery,
1408: + },
1409: + commit ? ...
<path>/tmp/opencode/session-recovery-final-v2.patch</path>
<type>file</type>
<content>
2537: diff --git a/packages/core/src/session/runner/publish-llm-event.ts b/packages/core/src/session/runner/publish-llm-event.ts
2538: index 33652a618c..518a180e24 100644
2539: --- a/packages/core/src/session/runner/publish-llm-event.ts
2540: +++ b/packages/core/src/session/runner/publish-llm-event.ts
2541: @@ -11,6 +11,10 @@ type Input = {
2542: readonly agent: string
2543: readonly model: ModelV2.Ref
2544: readonly snapshot?: string
2545: + readonly startCommit: (assistantMessageID: SessionMessage.ID) => Effect.Effect<void>
2546: + readonly terminalCommit: (assistantMessageID: SessionMessage.ID) => Effect.Effect<void>
2547: + readonly interruptedCommit: (assistantMessageID: SessionMessage.ID) => Effect.Effect<void>
2548: + readonly eventCommit: () => Effect.Effect<void>
2549: }
2550:
2551: const safe = (value: number | undefined) => Math.max(0, Number.isFinite(value) ? (value ?? ...
<path>/tmp/opencode/session-recovery-final-v2.patch</path>
<type>file</type>
<content>
3812: + prompt: Prompt.make({ text: "recover ordinary input" }),
3813: + delivery: "steer",
3814: + requestExecution: true,
3815: + })
3816: + let wakes = 0
3817: + const execution = SessionExecution.Service.of({
3818: + ...recoveryExecution,
3819: + wake: () => Effect.sync(() => wakes++).pipe(Effect.asVoid),
3820: + })
3821: +
3822: + yield* SessionRecovery.recover().pipe(Effect.provideService(SessionExecution.Service, execution))
3823: + yield* SessionRecovery.recover().pipe(Effect.provideService(SessionExecution.Service, execution))
3824: +
3825: + expect(wakes).toBe(2)
3826: + expect(yield* db.select().from(SessionInputTable).all()).toEqual([
3827: + expect.objectContaining({ wake_pending: true, promoted_seq: null }),
3828: + ])
3829: + }),
3830: + )
3831: +
3832: + it.effect("rejects a recovered ...
<path>/tmp/opencode/session-recovery-final-v2.patch</path>
<type>file</type>
<content>
5188: + const session = yield* SessionV2.Service
5189: + const { db } = yield* Database.Service
5190: + response = fragmentFixture("text", "text-before-compaction", ["Earlier answer"]).completeEvents
5191: + yield* session.prompt({
5192: + sessionID,
5193: + prompt: Prompt.make({ text: "Earlier question ".repeat(180) }),
5194: + resume: false,
5195: + })
5196: + yield* session.resume(sessionID)
5197: +
5198: + currentModel = compactModel
5199: + requests.length = 0
5200: + responseStream = Stream.fromIterable(
5201: + fragmentFixture("text", "text-uncommitted-summary", ["## Objective\n- Interrupted"]).completeEvents,
5202: + ).pipe(
5203: + Stream.ensuring(
5204: + db
5205: + .update(SessionExecutionTable)
5206: + .set({ id: "compaction-successor", owner_id: "successor", expires_at: ...
<path>/tmp/opencode/session-recovery-final-v2.patch</path>
<type>file</type>
<content>
6588: + },
6589: + "requestExecution": {
6590: + "type": "boolean"
6591: }
6592: },
6593: "required": ["timestamp", "sessionID", "messageID", "prompt", "delivery"],
6594: @@ -28281,6 +28607,61 @@
6595: "required": ["id", "type", "data"],
6596: "additionalProperties": false
6597: },
6598: + "SessionNextPromptExecution_requested": {
6599: + "type": "object",
6600: + "properties": {
6601: + "id": {
6602: + "type": "string",
6603: + "pattern": "^evt_"
6604: + },
6605: + "metadata": {
6606: + "type": "object"
6607: + },
6608: + "type": {
6609: + "type": "string",
6610: + "enum": ["session.next.prompt.execution_requested"]
6611: + },
6612: + "durable": {
6613: + ...
/usr/bin/bash: line 1: python: command not found
===== packages/core/src/session/input.ts =====
1: export * as SessionInput from "./input"
2:
3: import { and, asc, eq, isNull, lte } from "drizzle-orm"
4: import { DateTime, Effect, Schema } from "effect"
5: import { Admitted, Delivery } from "@opencode-ai/schema/session-input"
6: import type { Database } from "../database/database"
7: import { EventV2 } from "../event"
8: import { SessionEvent } from "./event"
9: import { SessionMessage } from "./message"
10: import { Prompt } from "./prompt"
11: import { SessionSchema } from "./schema"
12: import { SessionInputTable, SessionMessageTable, SessionRecoveryTable } from "./sql"
13:
14: type DatabaseService = Database.Interface["db"]
15:
16: export { Admitted, Delivery }
17:
18: const decodePrompt = Schema.decodeUnknownSync(Prompt)
19: const encodePrompt = Schema.encodeSync(Prompt)
20:
21: export const decode = (value: unknown) => Schema.decodeUnknownOption(Prompt)(value)
22:
23: const fromRow = (row: typeof ...
===== packages/core/src/session/execution/claim.ts =====
1: export * as SessionExecutionClaim from "./claim"
2:
3: import { and, eq, gt } from "drizzle-orm"
4: import { Clock, Effect } from "effect"
5: import type { Database } from "../../database/database"
6: import type { SessionSchema } from "../schema"
7: import { SESSION_EXECUTION_LEASE_MS, SessionExecutionTable } from "../sql"
8:
9: type DatabaseService = Database.Interface["db"]
10:
11: export type Claim = {
12: readonly sessionID: SessionSchema.ID
13: readonly executionID: string
14: readonly ownerID: string
15: }
16:
17: export type Values = Partial<Pick<typeof SessionExecutionTable.$inferInsert, "assistant_message_id" | "phase">>
18:
19: export class Lost extends Error {
20: constructor(readonly claim: Claim) {
21: super(`Session execution claim lost: ${claim.executionID}`)
22: }
23: }
24:
25: const where = (claim: Claim, now: number) =>
26: and(
27: eq(SessionExecutionTable.session_id, ...
...output truncated... Full output saved to: /home/byk/.local/share/opencode/tool-output/tool_0a763b362001xZortfhWQ29csq specs/v2/session.md:154: specs/v2/session.md:155:Inbox delivery is explicit: specs/v2/session.md:156: specs/v2/session.md:157:- `steer` inputs promote at the next safe provider-turn boundary, including continuation inside the current drain. specs/v2/session.md:158:- `queue` inputs remain in a FIFO while the current drain requires continuation. When the Session would otherwise become idle, the runner promotes exactly one queued input, then reevaluates continuation before promoting another. specs/v2/session.md:159: specs/v2/session.md:160:Execution has two entry points: specs/v2/session.md:161: specs/v2/session.md:162:- `run` is an explicit resume. It joins any active execution or starts a forced drain while idle. A forced drain bypasses the no-eligible-input guard, but preparation may still fail before a provider attempt. specs/v2/session.md:163:- `wake` reports ...
packages/core/src/event/sql.ts
ab9408c81cc9916177641ae8e9f08624fc27b5b9:packages/core/src/event.ts:122: /** Local operational projection committed atomically with a new durable event. Not replayed or serialized. */
ab9408c81cc9916177641ae8e9f08624fc27b5b9:packages/core/src/event.ts:123: readonly commit?: (seq: number) => Effect.Effect<void>
ab9408c81cc9916177641ae8e9f08624fc27b5b9:packages/core/src/event.ts:205: function commitDurableEvent(
ab9408c81cc9916177641ae8e9f08624fc27b5b9:packages/core/src/event.ts:214: commit?: (seq: number) => Effect.Effect<void>,
ab9408c81cc9916177641ae8e9f08624fc27b5b9:packages/core/src/event.ts:239: const committed = yield* db
ab9408c81cc9916177641ae8e9f08624fc27b5b9:packages/core/src/event.ts:316: const committed = {
ab9408c81cc9916177641ae8e9f08624fc27b5b9:packages/core/src/event.ts:321: yield* projector(committed)
ab9408c81cc9916177641ae8e9f08624fc27b5b9:packages/core/src/event.ts:323: ...
===== packages/core/src/event.ts:110-135 =====
110: export class SubscriberOverflowError extends Schema.TaggedErrorClass<SubscriberOverflowError>()(
111: "EventV2.SubscriberOverflow",
112: { capacity: Schema.Int },
113: ) {}
114:
115: export const define = Event.define
116: export const versionedType = Event.versionedType
117:
118: export interface PublishOptions {
119: readonly id?: ID
120: readonly metadata?: Record<string, unknown>
121: readonly location?: Location.Ref
122: /** Local operational projection committed atomically with a new durable event. Not replayed or serialized. */
123: readonly commit?: (seq: number) => Effect.Effect<void>
124: }
125:
126: export interface Interface {
127: readonly publish: <D extends Definition>(
128: definition: D,
129: data: Data<D>,
130: options?: PublishOptions,
131: ) => Effect.Effect<Payload<D>>
132: readonly subscribe: <D extends Definition>(definition: D) => Stream.Stream<Payload<D>>
133: readonly ...
===== packages/core/src/session/runner/llm.ts (556 lines) =====
100: const events = yield* EventV2.Service
101: const llm = yield* LLMClient.Service
102: const agents = yield* AgentV2.Service
103: const tools = yield* ToolRegistry.Service
104: const models = yield* SessionRunnerModel.Service
105: const store = yield* SessionStore.Service
106: const location = yield* Location.Service
107: const systemContext = yield* SystemContextRegistry.Service
108: const skillGuidance = yield* SkillGuidance.Service
109: const referenceGuidance = yield* ReferenceGuidance.Service
110: const config = yield* Config.Service
111: const snapshots = yield* Snapshot.Service
112: const db = (yield* Database.Service).db
113: const compaction = SessionCompaction.make({ events, llm, config: yield* config.entries() })
114: const getSession = Effect.fn("SessionRunner.getSession")(function* (sessionID: SessionSchema.ID) {
115: const session = yield* ...
Found 4 matches
/tmp/opencode/session-recovery-final-v2.patch:
Line 5450: import { CorsConfig, isAllowedCorsOrigin, type CorsOptions } from "@opencode-ai/server/cors"
Line 6916: diff --git a/packages/server/src/routes.ts b/packages/server/src/routes.ts
Line 6918: --- a/packages/server/src/routes.ts
Line 6919: +++ b/packages/server/src/routes.ts
(no output)
ab9408c81cc9916177641ae8e9f08624fc27b5b9:packages/core/schema.json:5: "prevIds": ["169a0f0f-d58f-479f-b024-fa1c7b9a09db"],
ab9408c81cc9916177641ae8e9f08624fc27b5b9:packages/core/script/migration.ts:11:const snapshot = path.join(root, "packages/core/schema.json")
#!/usr/bin/env bun
import { $ } from "bun"
import fs from "fs/promises"
import os from "os"
import path from "path"
import { pathToFileURL } from "url"
import { parseArgs } from "util"
const root = path.resolve(import.meta.dirname, "../../..")
const snapshot = path.join(root, "packages/core/schema.json")
const tsDir = path.join(root, "packages/core/src/database/migration")
const registry = path.join(root, "packages/core/src/database/migration.gen.ts")
const schema = path.join(root, "packages/core/src/database/schema.gen.ts")
const args = parseArgs({
args: process.argv.slice(2),
options: {
check: { type: "boolean" },
name: { type: "string" },
},
})
if (args.values.check) {
await check()
process.exit(0)
}
await generate()
async function generate() {
const temporary = await fs.mkdtemp(path.join(os.tmpdir(), "opencode-core-migration-"))
const incremental = path.join(temporary, "incremental")
const full = path.join(temporary, "full")
try {
await ...
===== packages/core/src/session/context-epoch.ts (194 lines) =====
1: export * as SessionContextEpoch from "./context-epoch"
2:
3: import { eq } from "drizzle-orm"
4: import { DateTime, Effect, Schema } from "effect"
5: import type { Database } from "../database/database"
6: import { EventV2 } from "../event"
7: import { SystemContext } from "../system-context/index"
8: import { ContextSnapshotDecodeError } from "./error"
9: import { SessionEvent } from "./event"
10: import { SessionHistory } from "./history"
11: import { SessionInput } from "./input"
12: import { SessionMessage } from "./message"
13: import { SessionSchema } from "./schema"
14: import { SessionContextEpochTable } from "./sql"
15: import { SessionExecutionClaim } from "./execution/claim"
16:
17: type DatabaseService = Database.Interface["db"]
18:
19: interface Prepared {
20: readonly baseline: string
21: readonly baselineSeq: number
22: }
23:
24: export function initialize(
25: db: DatabaseService,
26: ...
ab9408c81cc9916177641ae8e9f08624fc27b5b9:packages/core/src/session/projector.ts:428: .delete(SessionMessageTable) ab9408c81cc9916177641ae8e9f08624fc27b5b9:packages/core/src/session/projector.ts:435: .delete(SessionInputTable)
===== packages/core/test/session-projector.test.ts:435-535 =====
435: }),
436: ])
437: }),
438: )
439:
440: it.effect("quarantines a malformed recovery row without blocking a valid wake", () =>
441: Effect.gen(function* () {
442: const { db } = yield* Database.Service
443: const events = yield* EventV2.Service
444: const valid = SessionV2.ID.make("ses_recovery_row_valid")
445: yield* db
446: .insert(ProjectTable)
447: .values({ id: Project.ID.global, worktree: AbsolutePath.make("/project"), sandboxes: [] })
448: .run()
449: yield* db
450: .insert(SessionTable)
451: .values(
452: [sessionID, valid].map((id) => ({
453: id,
454: project_id: Project.ID.global,
455: slug: id,
456: directory: "/project",
457: title: "test",
458: version: "test",
459: })),
460: )
461: .run()
462: ...
b5878a90af5202832954778d14d2902e55ee9e51ff856592519177bafd392d90 /tmp/opencode/session-recovery-final-v2.patch 9bbe18549e623e6ebe4ed6f75c5403381c35ae860ad2dc77698a469ab8a6c7e6 /home/byk/Code/opencode/packages/opencode/dist/server/opencode-server commit 32
==== packages/schema/src/session-event.ts
220 export const Recovered = Event.define({
484 export const DurableDefinitions = Event.inventory(
==== packages/core/src/session/recovery-id.ts
6 export const continuation = (executionID: string) => SessionMessage.ID.make(`msg_recovery_continue_${executionID}`)
==== packages/core/src/session/recovery.ts
267 const ordinary = yield* db
==== packages/core/src/session/input.ts
103 const requestExecution = Effect.fn("SessionInput.requestExecution")(function* (
389 export const promoteSteers = Effect.fn("SessionInput.promoteSteers")(function* (
hasPending correctly anti-joins every session_recovery row, regardless of wake_pending or recovery_error (packages/core/src/session/input.ts:304-327). Queue promotion applies the same anti-join (packages/core/src/session/input.ts:413-438).
promoteSteers does not (packages/core/src/session/input.ts:389-410).
Concrete sequence:
continue input linked from session_recovery.steer.packages/core/src/session/recovery.ts:205-233).hasPending(..., "steer") true because the anti-join sees the ordinary input.promoteSteers selects both the ordinary steer and the quarantined recovery input.continue, so publish promotes it through the ordinary path.projectPrompted also settles its recovery wake (packages/core/src/session/input.ts:243-268).This violates the central invariant that only promoteExact may consume a recovery continuation.
The regression at packages/core/test/session-projector.test.ts:698-775 proves hasPending and promoteNextQueued, but never exercises ordinary steer promotion. Add a fail-first test that quarantines a recovery input with valid prompt plus malformed steer delivery, admits a later ordinary steer, and proves only the ordinary input is promoted.
Synthetic continuation IDs are deterministic from the public recovery execution ID (packages/core/src/session/recovery-id.ts:6; packages/schema/src/session-event.ts:220-230).
For a quarantined recovery input whose prompt and delivery remain valid:
SessionV2.prompt retry with resume:true passes equivalence.